From 40a93e9d17dd3bea1afaa9e21c89689fe1219588 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Fri, 4 Oct 2024 14:38:58 +0200 Subject: [PATCH 1/8] chore: stream large lists diff --- src/models/stream.ts | 27 +++++ src/stream/emitter.ts | 27 +++++ src/stream/stream-list-diff.ts | 202 +++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 src/models/stream.ts create mode 100644 src/stream/emitter.ts create mode 100644 src/stream/stream-list-diff.ts diff --git a/src/models/stream.ts b/src/models/stream.ts new file mode 100644 index 0000000..5b874f6 --- /dev/null +++ b/src/models/stream.ts @@ -0,0 +1,27 @@ +import { LIST_STATUS } from "./list"; + +export type StreamListsDiff<T extends Record<string, unknown>> = { + currentValue: T | null; + previousValue: T | null; + prevIndex: number | null; + newIndex: number | null; + indexDiff: number | null; + status: LIST_STATUS; +}; + +export type ReferenceProperty<T extends Record<string, unknown>> = keyof T; + +export type StreamReferences<T extends Record<string, unknown>> = Map< + ReferenceProperty<T>, + { prevIndex: number; nextIndex?: number } +>; + +export type ListStreamOptions = { + chunksSize?: number; // 0 by default. If 0, stream will be live + showOnly?: `${LIST_STATUS}`[]; + considerMoveAsUpdate?: boolean; +}; + +export const DEFAULT_LIST_STREAM_OPTIONS: ListStreamOptions = { + chunksSize: 0, +}; diff --git a/src/stream/emitter.ts b/src/stream/emitter.ts new file mode 100644 index 0000000..4554c32 --- /dev/null +++ b/src/stream/emitter.ts @@ -0,0 +1,27 @@ +type Listener<T extends unknown[]> = (...args: T) => void; + +export enum StreamEvent { + Data = "data", + Finish = "finish", + Error = "error", +} +export class EventEmitter { + private events: Record<string, Listener<unknown[]>[]> = {}; + + on<T extends unknown[]>( + event: `${StreamEvent}`, + listener: Listener<T>, + ): this { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(listener as Listener<unknown[]>); + return this; + } + + emit<T extends unknown[]>(event: `${StreamEvent}`, ...args: T): void { + if (this.events[event]) { + this.events[event].forEach((listener) => listener(...args)); + } + } +} diff --git a/src/stream/stream-list-diff.ts b/src/stream/stream-list-diff.ts new file mode 100644 index 0000000..46eac36 --- /dev/null +++ b/src/stream/stream-list-diff.ts @@ -0,0 +1,202 @@ +import { + DEFAULT_LIST_STREAM_OPTIONS, + ListStreamOptions, + ReferenceProperty, + StreamListsDiff, + StreamReferences, +} from "../models/stream"; +import { LIST_STATUS } from "../models/list"; +import { isEqual } from "../utils"; +import { EventEmitter, StreamEvent } from "./emitter"; + +function outputDiffChunk<T extends Record<string, unknown>>( + emitter: EventEmitter, +) { + let chunks: StreamListsDiff<T>[] = []; + + return function handleDiffChunk( + chunk: StreamListsDiff<T>, + options: ListStreamOptions, + ): void { + const showChunk = options?.showOnly + ? options?.showOnly.includes(chunk.status) + : true; + if (!showChunk) { + return; + } + if ((options.chunksSize as number) > 0) { + chunks.push(chunk); + if (chunks.length >= (options.chunksSize as number)) { + const output = chunks; + chunks = []; + return emitter.emit(StreamEvent.Data, output); + } + } + return emitter.emit(StreamEvent.Data, [chunk]); + }; +} + +function formatSingleListStreamDiff<T extends Record<string, unknown>>( + list: T[], + isPrevious: boolean, + status: LIST_STATUS, + options: ListStreamOptions, +): StreamListsDiff<T>[] { + const diff: StreamListsDiff<T>[] = list.map((data, i) => ({ + previousValue: isPrevious ? data : null, + currentValue: isPrevious ? null : data, + prevIndex: status === LIST_STATUS.ADDED ? null : i, + newIndex: status === LIST_STATUS.ADDED ? i : null, + indexDiff: null, + status, + })); + if (options.showOnly && options.showOnly.length > 0) { + return diff.filter((value) => options.showOnly?.includes(value.status)); + } + return diff; +} + +function getDiffChunks<T extends Record<string, unknown>>( + prevList: T[], + nextList: T[], + referenceProperty: ReferenceProperty<T>, + emitter: EventEmitter, + options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, +) { + if (!prevList && !nextList) { + return []; + } + if (!prevList) { + const nextDiff = formatSingleListStreamDiff( + nextList as T[], + false, + LIST_STATUS.ADDED, + options, + ); + return nextDiff.forEach((data) => handleDiffChunk(data, options)); + } + if (!nextList) { + const prevDiff = formatSingleListStreamDiff( + prevList as T[], + true, + LIST_STATUS.DELETED, + options, + ); + return prevDiff.forEach((data) => handleDiffChunk(data, options)); + } + const listsReferences: StreamReferences<T> = new Map(); + const handleDiffChunk = outputDiffChunk<T>(emitter); + prevList.forEach((data, i) => { + if (data) { + listsReferences.set(String(data[referenceProperty]), { + prevIndex: i, + nextIndex: undefined, + }); + } + }); + + nextList.forEach((data, i) => { + if (data) { + const listReference = listsReferences.get( + String(data[referenceProperty]), + ); + if (listReference) { + listReference.nextIndex = i; + } else { + handleDiffChunk( + { + previousValue: null, + currentValue: data, + prevIndex: null, + newIndex: i, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + options, + ); + } + } + }); + + for (const data of listsReferences.values()) { + if (!data.nextIndex) { + handleDiffChunk( + { + previousValue: prevList[data.prevIndex], + currentValue: null, + prevIndex: data.prevIndex, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + options, + ); + } else { + const prevData = prevList[data.prevIndex]; + const nextData = nextList[data.nextIndex]; + const isDataEqual = isEqual(prevData, nextData); + const indexDiff = data.prevIndex - data.nextIndex; + if (isDataEqual) { + if (indexDiff === 0) { + handleDiffChunk( + { + previousValue: prevList[data.prevIndex], + currentValue: nextList[data.nextIndex], + prevIndex: null, + newIndex: data.nextIndex, + indexDiff: null, + status: LIST_STATUS.EQUAL, + }, + options, + ); + } else { + handleDiffChunk( + { + previousValue: prevList[data.prevIndex], + currentValue: nextList[data.nextIndex], + prevIndex: data.prevIndex, + newIndex: data.nextIndex, + indexDiff, + status: options.considerMoveAsUpdate + ? LIST_STATUS.UPDATED + : LIST_STATUS.MOVED, + }, + options, + ); + } + } else { + handleDiffChunk( + { + previousValue: prevList[data.prevIndex], + currentValue: nextList[data.nextIndex], + prevIndex: data.prevIndex, + newIndex: data.nextIndex, + indexDiff, + status: LIST_STATUS.UPDATED, + }, + options, + ); + } + } + } + emitter.emit(StreamEvent.Finish); +} + +export function streamListsDiff<T extends Record<string, unknown>>( + prevList: T[], + nextList: T[], + referenceProperty: ReferenceProperty<T>, + options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, +) { + const emitter = new EventEmitter(); + try { + setTimeout( + () => + getDiffChunks(prevList, nextList, referenceProperty, emitter, options), + 0, + ); + return emitter; + } catch (err) { + emitter.emit(StreamEvent.Error, err); + } +} From 43b3c9e72ad03ead105b21beb77781126f6209ca Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sat, 5 Oct 2024 10:41:34 +0200 Subject: [PATCH 2/8] chore: remove useless dev dependencies + folder structure --- jest.config.js | 20 +- package-lock.json | 2589 ++++++----------- package.json | 23 +- src/index.ts | 8 +- src/{list-diff.ts => lib/list-diff/index.ts} | 4 +- {test => src/lib/list-diff}/list-diff.test.ts | 4 +- .../object-diff/index.ts} | 4 +- .../lib/object-diff}/object-diff.test.ts | 4 +- .../stream-list-diff}/emitter.ts | 0 .../stream-list-diff/index.ts} | 6 +- src/{utils.ts => lib/utils/index.ts} | 2 +- {test => src/lib/utils}/utils.test.ts | 2 +- src/models/{list.ts => list/index.ts} | 0 src/models/{object.ts => object/index.ts} | 0 src/models/{stream.ts => stream/index.ts} | 2 +- src/models/{utils.ts => utils/index.ts} | 0 tsconfig.json | 11 +- 17 files changed, 899 insertions(+), 1780 deletions(-) rename src/{list-diff.ts => lib/list-diff/index.ts} (98%) rename {test => src/lib/list-diff}/list-diff.test.ts (99%) rename src/{object-diff.ts => lib/object-diff/index.ts} (99%) rename {test => src/lib/object-diff}/object-diff.test.ts (99%) rename src/{stream => lib/stream-list-diff}/emitter.ts (100%) rename src/{stream/stream-list-diff.ts => lib/stream-list-diff/index.ts} (98%) rename src/{utils.ts => lib/utils/index.ts} (95%) rename {test => src/lib/utils}/utils.test.ts (98%) rename src/models/{list.ts => list/index.ts} (100%) rename src/models/{object.ts => object/index.ts} (100%) rename src/models/{stream.ts => stream/index.ts} (94%) rename src/models/{utils.ts => utils/index.ts} (100%) diff --git a/jest.config.js b/jest.config.js index 1da90a9..0dbaf11 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,23 @@ module.exports = { transform: { - "^.+\\.(ts|js)$": "ts-jest", + "^.+\\.(ts|js)$": [ + "@swc/jest", + { + jsc: { + baseUrl: ".", + parser: { + syntax: "typescript", + tsx: true, + dynamicImport: true, + }, + paths: { + "@models/*": ["./src/models/*"], + "@lib/*": ["./src/lib/*"], + + }, + target: "esnext", + }, + }, + ], }, }; diff --git a/package-lock.json b/package-lock.json index e72d858..e2eae4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,52 +1,32 @@ { "name": "@donedeal0/superdiff", - "version": "1.1.3", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@donedeal0/superdiff", - "version": "1.1.3", + "version": "2.0.0", "license": "ISC", "devDependencies": { - "@actions/core": "^1.10.1", - "@babel/preset-env": "^7.25.4", "@eslint/js": "^9.11.1", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.1", + "@swc/core": "^1.7.26", + "@swc/jest": "^0.2.36", "@types/jest": "^29.5.13", "eslint": "^9.11.1", "husky": "^9.1.6", "jest": "^29.7.0", "prettier": "^3.3.3", - "ts-jest": "^29.2.5", + "swc-loader": "^0.2.6", "tsup": "^8.3.0", "typescript": "^5.6.2", "typescript-eslint": "^8.7.0" } }, - "node_modules/@actions/core": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", - "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", - "dev": true, - "dependencies": { - "@actions/http-client": "^2.0.1", - "uuid": "^8.3.2" - } - }, - "node_modules/@actions/http-client": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", - "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", - "dev": true, - "dependencies": { - "tunnel": "^0.0.6", - "undici": "^5.25.4" - } - }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -127,31 +107,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", @@ -168,73 +123,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -266,18 +154,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", @@ -287,40 +163,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/helper-simple-access": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", @@ -334,19 +176,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", @@ -374,20 +203,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { "version": "7.23.8", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", @@ -432,97 +247,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -559,75 +283,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", - "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", - "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -739,941 +394,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", - "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", - "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", - "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.4", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", - "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", - "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", - "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.25.4", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.25.4", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1682,36 +409,19 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", - "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, "dependencies": { - "regenerator-runtime": "^0.14.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/template": { @@ -2303,15 +1013,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2663,6 +1364,18 @@ "node": ">=8" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -3168,6 +1881,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -4119,6 +2843,236 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@swc/core": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.26.tgz", + "integrity": "sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.12" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.7.26", + "@swc/core-darwin-x64": "1.7.26", + "@swc/core-linux-arm-gnueabihf": "1.7.26", + "@swc/core-linux-arm64-gnu": "1.7.26", + "@swc/core-linux-arm64-musl": "1.7.26", + "@swc/core-linux-x64-gnu": "1.7.26", + "@swc/core-linux-x64-musl": "1.7.26", + "@swc/core-win32-arm64-msvc": "1.7.26", + "@swc/core-win32-ia32-msvc": "1.7.26", + "@swc/core-win32-x64-msvc": "1.7.26" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.26.tgz", + "integrity": "sha512-FF3CRYTg6a7ZVW4yT9mesxoVVZTrcSWtmZhxKCYJX9brH4CS/7PRPjAKNk6kzWgWuRoglP7hkjQcd6EpMcZEAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.26.tgz", + "integrity": "sha512-az3cibZdsay2HNKmc4bjf62QVukuiMRh5sfM5kHR/JMTrLyS6vSw7Ihs3UTkZjUxkLTT8ro54LI6sV6sUQUbLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.26.tgz", + "integrity": "sha512-VYPFVJDO5zT5U3RpCdHE5v1gz4mmR8BfHecUZTmD2v1JeFY6fv9KArJUpjrHEEsjK/ucXkQFmJ0jaiWXmpOV9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.26.tgz", + "integrity": "sha512-YKevOV7abpjcAzXrhsl+W48Z9mZvgoVs2eP5nY+uoMAdP2b3GxC0Df1Co0I90o2lkzO4jYBpTMcZlmUXLdXn+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.26.tgz", + "integrity": "sha512-3w8iZICMkQQON0uIcvz7+Q1MPOW6hJ4O5ETjA0LSP/tuKqx30hIniCGOgPDnv3UTMruLUnQbtBwVCZTBKR3Rkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.26.tgz", + "integrity": "sha512-c+pp9Zkk2lqb06bNGkR2Looxrs7FtGDMA4/aHjZcCqATgp348hOKH5WPvNLBl+yPrISuWjbKDVn3NgAvfvpH4w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.26.tgz", + "integrity": "sha512-PgtyfHBF6xG87dUSSdTJHwZ3/8vWZfNIXQV2GlwEpslrOkGqy+WaiiyE7Of7z9AvDILfBBBcJvJ/r8u980wAfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.26.tgz", + "integrity": "sha512-9TNXPIJqFynlAOrRD6tUQjMq7KApSklK3R/tXgIxc7Qx+lWu8hlDQ/kVPLpU7PWvMMwC/3hKBW+p5f+Tms1hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.26.tgz", + "integrity": "sha512-9YngxNcG3177GYdsTum4V98Re+TlCeJEP4kEwEg9EagT5s3YejYdKwVAkAsJszzkXuyRDdnHUpYbTrPG6FiXrQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.26.tgz", + "integrity": "sha512-VR+hzg9XqucgLjXxA13MtV5O3C0bK0ywtLIBw/+a+O+Oc6mxFWHtdUeXDbIi5AiPbn0fjgVJMqYnyjGyyX8u0w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/jest": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.36.tgz", + "integrity": "sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==", + "dev": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", + "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4485,6 +3439,181 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -4497,6 +3626,16 @@ "node": ">=0.4.0" } }, + "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, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4547,6 +3686,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -4625,12 +3774,6 @@ "dev": true, "peer": true }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -4753,45 +3896,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -4872,12 +3976,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4915,18 +4019,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -5063,6 +4155,16 @@ "node": ">= 6" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", @@ -5423,19 +4525,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", - "dev": true, - "dependencies": { - "browserslist": "^4.23.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -5736,21 +4825,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.29", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", @@ -5782,6 +4856,20 @@ "dev": true, "peer": true }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/env-ci": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", @@ -5972,6 +5060,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "peer": true + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -6319,6 +5414,16 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6458,40 +5563,10 @@ "node": ">=16.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6743,6 +5818,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -7359,94 +6441,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/java-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", @@ -9117,6 +8111,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9236,6 +8236,16 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9266,12 +8276,6 @@ "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -9290,12 +8294,6 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9371,12 +8369,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -9478,12 +8470,12 @@ } }, "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, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -9505,6 +8497,29 @@ "node": ">=16" } }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -12811,6 +11826,16 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -12939,63 +11964,13 @@ "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=4" + "node": ">=8.10.0" } }, "node_modules/registry-auth-token": { @@ -13010,27 +11985,6 @@ "node": ">=14" } }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13162,6 +12116,25 @@ "dev": true, "peer": true }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/semantic-release": { "version": "24.1.2", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.1.2.tgz", @@ -13521,6 +12494,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -13964,6 +12947,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swc-loader": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", + "integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -14015,6 +13021,119 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -14198,66 +13317,6 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", - "dev": true, - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.6.3", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tsup": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.3.0.tgz", @@ -14321,15 +13380,6 @@ "node": ">= 8" } }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14413,27 +13463,6 @@ "node": ">=0.8.0" } }, - "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", @@ -14444,37 +13473,6 @@ "node": ">=4" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/unicorn-magic": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", @@ -14572,15 +13570,6 @@ "dev": true, "peer": true }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -14614,12 +13603,114 @@ "makeerror": "1.0.12" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, + "node_modules/webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "dev": true, + "peer": true, + "dependencies": { + "@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-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "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.11", + "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.1", + "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/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/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==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "peer": true + }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", diff --git a/package.json b/package.json index d1be0f4..61b0065 100644 --- a/package.json +++ b/package.json @@ -61,28 +61,29 @@ ], "scripts": { "build": "tsup", - "test": "jest", - "lint": "eslint --cache --max-warnings=0 --fix", - "tsc": "tsc --noEmit --incremental", "format": "npx prettier . --write", - "prepare": "husky" + "lint:dead-code": "npx -p typescript@latest -p knip knip", + "lint": "eslint --cache --max-warnings=0 --fix", + "prepare": "husky", + "test": "jest", + "tsc": "tsc --noEmit --incremental" }, "devDependencies": { "@eslint/js": "^9.11.1", - "eslint": "^9.11.1", - "prettier": "^3.3.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.1", - "@actions/core": "^1.10.1", - "@babel/preset-env": "^7.25.4", + "@swc/core": "^1.7.26", + "@swc/jest": "^0.2.36", "@types/jest": "^29.5.13", + "eslint": "^9.11.1", "husky": "^9.1.6", "jest": "^29.7.0", - "ts-jest": "^29.2.5", + "prettier": "^3.3.3", + "swc-loader": "^0.2.6", "tsup": "^8.3.0", - "typescript-eslint": "^8.7.0", - "typescript": "^5.6.2" + "typescript": "^5.6.2", + "typescript-eslint": "^8.7.0" } } diff --git a/src/index.ts b/src/index.ts index 8eba074..ca0a6d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ -export { getObjectDiff } from "./object-diff"; -export { getListDiff } from "./list-diff"; -export { isEqual, isObject } from "./utils"; +export { getObjectDiff } from "./lib/object-diff"; +export { getListDiff } from "./lib/list-diff"; +export { isEqual, isObject } from "./lib/utils"; +export { streamListsDiff } from "./lib/stream-list-diff"; export * from "./models/list"; export * from "./models/object"; +export * from "./models/stream"; diff --git a/src/list-diff.ts b/src/lib/list-diff/index.ts similarity index 98% rename from src/list-diff.ts rename to src/lib/list-diff/index.ts index 0675938..85b2453 100644 --- a/src/list-diff.ts +++ b/src/lib/list-diff/index.ts @@ -3,8 +3,8 @@ import { LIST_STATUS, ListDiff, ListDiffOptions, -} from "./models/list"; -import { isEqual, isObject } from "./utils"; +} from "@models/list"; +import { isEqual, isObject } from "@lib/utils"; function getLeanDiff( diff: ListDiff["diff"], diff --git a/test/list-diff.test.ts b/src/lib/list-diff/list-diff.test.ts similarity index 99% rename from test/list-diff.test.ts rename to src/lib/list-diff/list-diff.test.ts index 6826d9a..2089b0d 100644 --- a/test/list-diff.test.ts +++ b/src/lib/list-diff/list-diff.test.ts @@ -1,5 +1,5 @@ -import { getListDiff } from "../src/list-diff"; -import { LIST_STATUS } from "../src/models/list"; +import { getListDiff } from "."; +import { LIST_STATUS } from "@models/list"; describe("getListDiff", () => { it("returns an empty diff if no lists are provided", () => { diff --git a/src/object-diff.ts b/src/lib/object-diff/index.ts similarity index 99% rename from src/object-diff.ts rename to src/lib/object-diff/index.ts index 1eb02ec..b5068d8 100644 --- a/src/object-diff.ts +++ b/src/lib/object-diff/index.ts @@ -6,8 +6,8 @@ import { ObjectDiffOptions, Diff, DEFAULT_OBJECT_DIFF_OPTIONS, -} from "./models/object"; -import { isEqual, isObject } from "./utils"; +} from "@models/object"; +import { isEqual, isObject } from "@lib/utils"; function getLeanDiff( diff: ObjectDiff["diff"], diff --git a/test/object-diff.test.ts b/src/lib/object-diff/object-diff.test.ts similarity index 99% rename from test/object-diff.test.ts rename to src/lib/object-diff/object-diff.test.ts index bb3c0c7..ab38818 100644 --- a/test/object-diff.test.ts +++ b/src/lib/object-diff/object-diff.test.ts @@ -1,5 +1,5 @@ -import { GRANULARITY, OBJECT_STATUS } from "../src/models/object"; -import { getObjectDiff } from "../src/object-diff"; +import { GRANULARITY, OBJECT_STATUS } from "../../models/object"; +import { getObjectDiff } from "."; describe("getObjectDiff", () => { it("returns an empty diff if no objects are provided", () => { diff --git a/src/stream/emitter.ts b/src/lib/stream-list-diff/emitter.ts similarity index 100% rename from src/stream/emitter.ts rename to src/lib/stream-list-diff/emitter.ts diff --git a/src/stream/stream-list-diff.ts b/src/lib/stream-list-diff/index.ts similarity index 98% rename from src/stream/stream-list-diff.ts rename to src/lib/stream-list-diff/index.ts index 46eac36..ed009c9 100644 --- a/src/stream/stream-list-diff.ts +++ b/src/lib/stream-list-diff/index.ts @@ -4,9 +4,9 @@ import { ReferenceProperty, StreamListsDiff, StreamReferences, -} from "../models/stream"; -import { LIST_STATUS } from "../models/list"; -import { isEqual } from "../utils"; +} from "@models/stream"; +import { LIST_STATUS } from "@models/list"; +import { isEqual } from "@lib/utils"; import { EventEmitter, StreamEvent } from "./emitter"; function outputDiffChunk<T extends Record<string, unknown>>( diff --git a/src/utils.ts b/src/lib/utils/index.ts similarity index 95% rename from src/utils.ts rename to src/lib/utils/index.ts index b0662bd..1e26b91 100644 --- a/src/utils.ts +++ b/src/lib/utils/index.ts @@ -1,4 +1,4 @@ -import { isEqualOptions } from "./models/utils"; +import { isEqualOptions } from "@models/utils"; /** * Returns true if two data are equal diff --git a/test/utils.test.ts b/src/lib/utils/utils.test.ts similarity index 98% rename from test/utils.test.ts rename to src/lib/utils/utils.test.ts index d1f5e29..f719456 100644 --- a/test/utils.test.ts +++ b/src/lib/utils/utils.test.ts @@ -1,4 +1,4 @@ -import { isEqual, isObject } from "../src/utils"; +import { isEqual, isObject } from "."; describe("isEqual", () => { it("return true if data are the same", () => { diff --git a/src/models/list.ts b/src/models/list/index.ts similarity index 100% rename from src/models/list.ts rename to src/models/list/index.ts diff --git a/src/models/object.ts b/src/models/object/index.ts similarity index 100% rename from src/models/object.ts rename to src/models/object/index.ts diff --git a/src/models/stream.ts b/src/models/stream/index.ts similarity index 94% rename from src/models/stream.ts rename to src/models/stream/index.ts index 5b874f6..0bda847 100644 --- a/src/models/stream.ts +++ b/src/models/stream/index.ts @@ -1,4 +1,4 @@ -import { LIST_STATUS } from "./list"; +import { LIST_STATUS } from "@models/list"; export type StreamListsDiff<T extends Record<string, unknown>> = { currentValue: T | null; diff --git a/src/models/utils.ts b/src/models/utils/index.ts similarity index 100% rename from src/models/utils.ts rename to src/models/utils/index.ts diff --git a/tsconfig.json b/tsconfig.json index 7c6e9b7..9fb7612 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "exclude": ["node_modules", "dist"], - "include": ["src/*.ts"], + "include": ["src"], "compilerOptions": { "declaration": true, "declarationDir": "./dist", @@ -15,6 +15,13 @@ "strict": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "skipLibCheck": true , + "baseUrl": ".", + "paths": { + "@models/*": ["./src/models/*"], + "@lib/*": ["./src/lib/*"], + + + } }, } From 896186c7cf35e7f0ab408fc1ce821bf10d033903 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sat, 5 Oct 2024 12:01:04 +0200 Subject: [PATCH 3/8] chore: handle edgecases streamlistsdiff --- src/lib/object-diff/index.ts | 2 +- src/lib/stream-list-diff/index.ts | 68 +++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/lib/object-diff/index.ts b/src/lib/object-diff/index.ts index b5068d8..524e9cd 100644 --- a/src/lib/object-diff/index.ts +++ b/src/lib/object-diff/index.ts @@ -209,7 +209,7 @@ function getSubPropertiesDiff( * Returns the diff between two objects * @param {ObjectData} prevData - The original object. * @param {ObjectData} nextData - The new object. - * * @param {ObjectOptions} options - Options to refine your output. + * @param {ObjectOptions} options - Options to refine your output. - `showOnly`: returns only the values whose status you are interested in. It takes two parameters: `statuses` and `granularity` `statuses` are the status you want to see in the output (e.g. `["added", "equal"]`) `granularity` can be either `basic` (to return only the main properties whose status matches your query) or `deep` (to return the main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly). diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts index ed009c9..1b27896 100644 --- a/src/lib/stream-list-diff/index.ts +++ b/src/lib/stream-list-diff/index.ts @@ -16,6 +16,7 @@ function outputDiffChunk<T extends Record<string, unknown>>( return function handleDiffChunk( chunk: StreamListsDiff<T>, + isLastChunk: boolean, options: ListStreamOptions, ): void { const showChunk = options?.showOnly @@ -26,7 +27,7 @@ function outputDiffChunk<T extends Record<string, unknown>>( } if ((options.chunksSize as number) > 0) { chunks.push(chunk); - if (chunks.length >= (options.chunksSize as number)) { + if (chunks.length >= (options.chunksSize as number) || isLastChunk) { const output = chunks; chunks = []; return emitter.emit(StreamEvent.Data, output); @@ -56,6 +57,14 @@ function formatSingleListStreamDiff<T extends Record<string, unknown>>( return diff; } +function isValidChunkSize( + chunksSize: ListStreamOptions["chunksSize"], +): boolean { + if (!chunksSize) return true; + const x = String(Math.sign(chunksSize)); + return x !== "-1" && x !== "NaN"; +} + function getDiffChunks<T extends Record<string, unknown>>( prevList: T[], nextList: T[], @@ -63,6 +72,12 @@ function getDiffChunks<T extends Record<string, unknown>>( emitter: EventEmitter, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, ) { + if (!isValidChunkSize(options?.chunksSize)) { + return emitter.emit( + StreamEvent.Error, + `The chunk size can't be negative. You entered the value ${options.chunksSize}`, + ); + } if (!prevList && !nextList) { return []; } @@ -73,7 +88,9 @@ function getDiffChunks<T extends Record<string, unknown>>( LIST_STATUS.ADDED, options, ); - return nextDiff.forEach((data) => handleDiffChunk(data, options)); + return nextDiff.forEach((data, i) => + handleDiffChunk(data, i === nextDiff.length - 1, options), + ); } if (!nextList) { const prevDiff = formatSingleListStreamDiff( @@ -82,7 +99,9 @@ function getDiffChunks<T extends Record<string, unknown>>( LIST_STATUS.DELETED, options, ); - return prevDiff.forEach((data) => handleDiffChunk(data, options)); + return prevDiff.forEach((data, i) => + handleDiffChunk(data, i === prevDiff.length - 1, options), + ); } const listsReferences: StreamReferences<T> = new Map(); const handleDiffChunk = outputDiffChunk<T>(emitter); @@ -112,13 +131,17 @@ function getDiffChunks<T extends Record<string, unknown>>( indexDiff: null, status: LIST_STATUS.ADDED, }, + i === nextList.length - 1, options, ); } } }); - + let streamedChunks = 0; + const totalChunks = listsReferences.size; for (const data of listsReferences.values()) { + streamedChunks++; + const isLastChunk = totalChunks === streamedChunks; if (!data.nextIndex) { handleDiffChunk( { @@ -129,6 +152,7 @@ function getDiffChunks<T extends Record<string, unknown>>( indexDiff: null, status: LIST_STATUS.DELETED, }, + isLastChunk, options, ); } else { @@ -147,6 +171,7 @@ function getDiffChunks<T extends Record<string, unknown>>( indexDiff: null, status: LIST_STATUS.EQUAL, }, + isLastChunk, options, ); } else { @@ -161,6 +186,7 @@ function getDiffChunks<T extends Record<string, unknown>>( ? LIST_STATUS.UPDATED : LIST_STATUS.MOVED, }, + isLastChunk, options, ); } @@ -174,29 +200,39 @@ function getDiffChunks<T extends Record<string, unknown>>( indexDiff, status: LIST_STATUS.UPDATED, }, + isLastChunk, options, ); } } } - emitter.emit(StreamEvent.Finish); + return emitter.emit(StreamEvent.Finish); } +/** + * Streams the diff of two object lists + * @param {Record<string, unknown>[]} prevList - The original object list. + * @param {Record<string, unknown>[]} nextList - The new object list. + * @param {ReferenceProperty<T>} referenceProperty - A common property in all the objects of your lists (e.g. `id`) + * @param {ListStreamOptions} options - Options to refine your output. + - `chunksSize`: the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs. (default is `0`) + - `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`) + - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated` + * @returns EventEmitter + */ export function streamListsDiff<T extends Record<string, unknown>>( prevList: T[], nextList: T[], referenceProperty: ReferenceProperty<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, -) { +): EventEmitter { const emitter = new EventEmitter(); - try { - setTimeout( - () => - getDiffChunks(prevList, nextList, referenceProperty, emitter, options), - 0, - ); - return emitter; - } catch (err) { - emitter.emit(StreamEvent.Error, err); - } + setTimeout(() => { + try { + getDiffChunks(prevList, nextList, referenceProperty, emitter, options); + } catch (err) { + return emitter.emit(StreamEvent.Error, err); + } + }, 0); + return emitter; } From aaf5f3fa13abdb7f369d8e3813e9828d12cc77ea Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sat, 5 Oct 2024 12:52:50 +0200 Subject: [PATCH 4/8] chore: handle errors --- src/lib/stream-list-diff/emitter.ts | 28 ++++--- src/lib/stream-list-diff/index.ts | 118 ++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 34 deletions(-) diff --git a/src/lib/stream-list-diff/emitter.ts b/src/lib/stream-list-diff/emitter.ts index 4554c32..91dff58 100644 --- a/src/lib/stream-list-diff/emitter.ts +++ b/src/lib/stream-list-diff/emitter.ts @@ -1,3 +1,5 @@ +import { StreamListsDiff } from "@models/stream"; + type Listener<T extends unknown[]> = (...args: T) => void; export enum StreamEvent { @@ -5,23 +7,27 @@ export enum StreamEvent { Finish = "finish", Error = "error", } -export class EventEmitter { + +export type Emitter<T extends Record<string, unknown>> = EventEmitter<{ + data: [StreamListsDiff<T>[]]; + error: [Error]; + finish: []; +}>; + +export class EventEmitter<Events extends Record<string, unknown[]>> { private events: Record<string, Listener<unknown[]>[]> = {}; - on<T extends unknown[]>( - event: `${StreamEvent}`, - listener: Listener<T>, - ): this { - if (!this.events[event]) { - this.events[event] = []; + on<E extends keyof Events>(event: E, listener: Listener<Events[E]>): this { + if (!this.events[event as string]) { + this.events[event as string] = []; } - this.events[event].push(listener as Listener<unknown[]>); + this.events[event as string].push(listener as Listener<unknown[]>); return this; } - emit<T extends unknown[]>(event: `${StreamEvent}`, ...args: T): void { - if (this.events[event]) { - this.events[event].forEach((listener) => listener(...args)); + emit<E extends keyof Events>(event: E, ...args: Events[E]): void { + if (this.events[event as string]) { + this.events[event as string].forEach((listener) => listener(...args)); } } } diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts index 1b27896..fe1db67 100644 --- a/src/lib/stream-list-diff/index.ts +++ b/src/lib/stream-list-diff/index.ts @@ -6,11 +6,11 @@ import { StreamReferences, } from "@models/stream"; import { LIST_STATUS } from "@models/list"; -import { isEqual } from "@lib/utils"; -import { EventEmitter, StreamEvent } from "./emitter"; +import { isEqual, isObject } from "@lib/utils"; +import { Emitter, EventEmitter, StreamEvent } from "./emitter"; function outputDiffChunk<T extends Record<string, unknown>>( - emitter: EventEmitter, + emitter: Emitter<T>, ) { let chunks: StreamListsDiff<T>[] = []; @@ -42,15 +42,27 @@ function formatSingleListStreamDiff<T extends Record<string, unknown>>( isPrevious: boolean, status: LIST_STATUS, options: ListStreamOptions, -): StreamListsDiff<T>[] { - const diff: StreamListsDiff<T>[] = list.map((data, i) => ({ - previousValue: isPrevious ? data : null, - currentValue: isPrevious ? null : data, - prevIndex: status === LIST_STATUS.ADDED ? null : i, - newIndex: status === LIST_STATUS.ADDED ? i : null, - indexDiff: null, - status, - })); +): StreamListsDiff<T>[] | null { + let isValid = true; + const diff: StreamListsDiff<T>[] = []; + for (let i = 0; i < list.length; i++) { + const data = list[i]; + if (!isObject(data)) { + isValid = false; + break; + } + diff.push({ + previousValue: isPrevious ? data : null, + currentValue: isPrevious ? null : data, + prevIndex: status === LIST_STATUS.ADDED ? null : i, + newIndex: status === LIST_STATUS.ADDED ? i : null, + indexDiff: null, + status, + }); + } + if (!isValid) { + return null; + } if (options.showOnly && options.showOnly.length > 0) { return diff.filter((value) => options.showOnly?.includes(value.status)); } @@ -65,17 +77,46 @@ function isValidChunkSize( return x !== "-1" && x !== "NaN"; } +function isDataValid<T extends Record<string, unknown>>( + data: T, + referenceProperty: ReferenceProperty<T>, + emitter: Emitter<T>, + listType: "prevList" | "nextList", +): boolean { + if (!isObject(data)) { + emitter.emit( + StreamEvent.Error, + new Error( + `Your ${listType} must only contain valid objects. Found ${data}`, + ), + ); + return false; + } + if (!Object.hasOwn(data, referenceProperty)) { + emitter.emit( + StreamEvent.Error, + new Error( + `The reference property ${String(referenceProperty)} is not available in all the objects of your ${listType}.`, + ), + ); + return false; + } + return true; +} + function getDiffChunks<T extends Record<string, unknown>>( prevList: T[], nextList: T[], referenceProperty: ReferenceProperty<T>, - emitter: EventEmitter, + emitter: Emitter<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, ) { if (!isValidChunkSize(options?.chunksSize)) { return emitter.emit( StreamEvent.Error, - `The chunk size can't be negative. You entered the value ${options.chunksSize}`, + new Error( + `The chunk size can't be negative. You entered the value ${options.chunksSize}`, + ), ); } if (!prevList && !nextList) { @@ -88,7 +129,14 @@ function getDiffChunks<T extends Record<string, unknown>>( LIST_STATUS.ADDED, options, ); - return nextDiff.forEach((data, i) => + if (!nextDiff) { + emitter.emit( + StreamEvent.Error, + new Error("Your nextList must only contain valid objects."), + ); + emitter.emit(StreamEvent.Finish); + } + return nextDiff?.forEach((data, i) => handleDiffChunk(data, i === nextDiff.length - 1, options), ); } @@ -99,23 +147,42 @@ function getDiffChunks<T extends Record<string, unknown>>( LIST_STATUS.DELETED, options, ); - return prevDiff.forEach((data, i) => + if (!prevDiff) { + emitter.emit( + StreamEvent.Error, + new Error("Your prevList must only contain valid objects."), + ); + emitter.emit(StreamEvent.Finish); + } + return prevDiff?.forEach((data, i) => handleDiffChunk(data, i === prevDiff.length - 1, options), ); } const listsReferences: StreamReferences<T> = new Map(); const handleDiffChunk = outputDiffChunk<T>(emitter); - prevList.forEach((data, i) => { + for (let i = 0; i < prevList.length; i++) { + const data = prevList[i]; if (data) { + const isValid = isDataValid(data, referenceProperty, emitter, "prevList"); + if (!isValid) { + emitter.emit(StreamEvent.Finish); + break; + } listsReferences.set(String(data[referenceProperty]), { prevIndex: i, nextIndex: undefined, }); } - }); + } - nextList.forEach((data, i) => { + for (let i = 0; i < nextList.length; i++) { + const data = prevList[i]; if (data) { + const isValid = isDataValid(data, referenceProperty, emitter, "nextList"); + if (!isValid) { + emitter.emit(StreamEvent.Finish); + break; + } const listReference = listsReferences.get( String(data[referenceProperty]), ); @@ -136,7 +203,8 @@ function getDiffChunks<T extends Record<string, unknown>>( ); } } - }); + } + let streamedChunks = 0; const totalChunks = listsReferences.size; for (const data of listsReferences.values()) { @@ -225,13 +293,17 @@ export function streamListsDiff<T extends Record<string, unknown>>( nextList: T[], referenceProperty: ReferenceProperty<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, -): EventEmitter { - const emitter = new EventEmitter(); +): Emitter<T> { + const emitter = new EventEmitter<{ + data: [StreamListsDiff<T>[]]; + error: [Error]; + finish: []; + }>(); setTimeout(() => { try { getDiffChunks(prevList, nextList, referenceProperty, emitter, options); } catch (err) { - return emitter.emit(StreamEvent.Error, err); + return emitter.emit(StreamEvent.Error, err as Error); } }, 0); return emitter; From 7fe7a840824a77429b4b8c2ac40844ef1d5d27e9 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sat, 5 Oct 2024 14:42:32 +0200 Subject: [PATCH 5/8] chore: test stream-list-diff --- src/lib/stream-list-diff/index.ts | 90 +++-- .../stream-list-diff/stream-list-diff.test.ts | 374 ++++++++++++++++++ 2 files changed, 425 insertions(+), 39 deletions(-) create mode 100644 src/lib/stream-list-diff/stream-list-diff.test.ts diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts index fe1db67..90e1d65 100644 --- a/src/lib/stream-list-diff/index.ts +++ b/src/lib/stream-list-diff/index.ts @@ -6,7 +6,7 @@ import { StreamReferences, } from "@models/stream"; import { LIST_STATUS } from "@models/list"; -import { isEqual, isObject } from "@lib/utils"; +import { isObject } from "@lib/utils"; import { Emitter, EventEmitter, StreamEvent } from "./emitter"; function outputDiffChunk<T extends Record<string, unknown>>( @@ -31,6 +31,8 @@ function outputDiffChunk<T extends Record<string, unknown>>( const output = chunks; chunks = []; return emitter.emit(StreamEvent.Data, output); + } else { + return; } } return emitter.emit(StreamEvent.Data, [chunk]); @@ -73,56 +75,53 @@ function isValidChunkSize( chunksSize: ListStreamOptions["chunksSize"], ): boolean { if (!chunksSize) return true; - const x = String(Math.sign(chunksSize)); - return x !== "-1" && x !== "NaN"; + const sign = String(Math.sign(chunksSize)); + return sign !== "-1" && sign !== "NaN"; } function isDataValid<T extends Record<string, unknown>>( data: T, referenceProperty: ReferenceProperty<T>, - emitter: Emitter<T>, listType: "prevList" | "nextList", -): boolean { +): { isValid: boolean; message?: string } { if (!isObject(data)) { - emitter.emit( - StreamEvent.Error, - new Error( - `Your ${listType} must only contain valid objects. Found ${data}`, - ), - ); - return false; + return { + isValid: false, + message: `Your ${listType} must only contain valid objects. Found '${data}'`, + }; } if (!Object.hasOwn(data, referenceProperty)) { - emitter.emit( - StreamEvent.Error, - new Error( - `The reference property ${String(referenceProperty)} is not available in all the objects of your ${listType}.`, - ), - ); - return false; + return { + isValid: false, + message: `The reference property '${String(referenceProperty)}' is not available in all the objects of your ${listType}.`, + }; } - return true; + return { + isValid: true, + message: "", + }; } function getDiffChunks<T extends Record<string, unknown>>( - prevList: T[], - nextList: T[], + prevList: T[] = [], + nextList: T[] = [], referenceProperty: ReferenceProperty<T>, emitter: Emitter<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, -) { +): void { if (!isValidChunkSize(options?.chunksSize)) { return emitter.emit( StreamEvent.Error, new Error( - `The chunk size can't be negative. You entered the value ${options.chunksSize}`, + `The chunk size can't be negative. You entered the value '${options.chunksSize}'`, ), ); } - if (!prevList && !nextList) { - return []; + if (prevList.length === 0 && nextList.length === 0) { + return emitter.emit(StreamEvent.Finish); } - if (!prevList) { + const handleDiffChunk = outputDiffChunk<T>(emitter); + if (prevList.length === 0) { const nextDiff = formatSingleListStreamDiff( nextList as T[], false, @@ -136,11 +135,12 @@ function getDiffChunks<T extends Record<string, unknown>>( ); emitter.emit(StreamEvent.Finish); } - return nextDiff?.forEach((data, i) => + nextDiff?.forEach((data, i) => handleDiffChunk(data, i === nextDiff.length - 1, options), ); + return emitter.emit(StreamEvent.Finish); } - if (!nextList) { + if (nextList.length === 0) { const prevDiff = formatSingleListStreamDiff( prevList as T[], true, @@ -154,17 +154,22 @@ function getDiffChunks<T extends Record<string, unknown>>( ); emitter.emit(StreamEvent.Finish); } - return prevDiff?.forEach((data, i) => + prevDiff?.forEach((data, i) => handleDiffChunk(data, i === prevDiff.length - 1, options), ); + return emitter.emit(StreamEvent.Finish); } const listsReferences: StreamReferences<T> = new Map(); - const handleDiffChunk = outputDiffChunk<T>(emitter); for (let i = 0; i < prevList.length; i++) { const data = prevList[i]; if (data) { - const isValid = isDataValid(data, referenceProperty, emitter, "prevList"); + const { isValid, message } = isDataValid( + data, + referenceProperty, + "prevList", + ); if (!isValid) { + emitter.emit(StreamEvent.Error, new Error(message)); emitter.emit(StreamEvent.Finish); break; } @@ -176,10 +181,15 @@ function getDiffChunks<T extends Record<string, unknown>>( } for (let i = 0; i < nextList.length; i++) { - const data = prevList[i]; + const data = nextList[i]; if (data) { - const isValid = isDataValid(data, referenceProperty, emitter, "nextList"); + const { isValid, message } = isDataValid( + data, + referenceProperty, + "nextList", + ); if (!isValid) { + emitter.emit(StreamEvent.Error, new Error(message)); emitter.emit(StreamEvent.Finish); break; } @@ -207,10 +217,11 @@ function getDiffChunks<T extends Record<string, unknown>>( let streamedChunks = 0; const totalChunks = listsReferences.size; + for (const data of listsReferences.values()) { streamedChunks++; const isLastChunk = totalChunks === streamedChunks; - if (!data.nextIndex) { + if (typeof data.nextIndex === "undefined") { handleDiffChunk( { previousValue: prevList[data.prevIndex], @@ -226,17 +237,17 @@ function getDiffChunks<T extends Record<string, unknown>>( } else { const prevData = prevList[data.prevIndex]; const nextData = nextList[data.nextIndex]; - const isDataEqual = isEqual(prevData, nextData); - const indexDiff = data.prevIndex - data.nextIndex; + const isDataEqual = JSON.stringify(prevData) === JSON.stringify(nextData); + const indexDiff = data.nextIndex - data.prevIndex; if (isDataEqual) { if (indexDiff === 0) { handleDiffChunk( { previousValue: prevList[data.prevIndex], currentValue: nextList[data.nextIndex], - prevIndex: null, + prevIndex: data.prevIndex, newIndex: data.nextIndex, - indexDiff: null, + indexDiff: 0, status: LIST_STATUS.EQUAL, }, isLastChunk, @@ -274,6 +285,7 @@ function getDiffChunks<T extends Record<string, unknown>>( } } } + return emitter.emit(StreamEvent.Finish); } diff --git a/src/lib/stream-list-diff/stream-list-diff.test.ts b/src/lib/stream-list-diff/stream-list-diff.test.ts new file mode 100644 index 0000000..1a47732 --- /dev/null +++ b/src/lib/stream-list-diff/stream-list-diff.test.ts @@ -0,0 +1,374 @@ +import { LIST_STATUS } from "@models/list"; +import { streamListsDiff } from "."; + +describe("streamListsDiff data", () => { + it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => { + const nextList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const diff = streamListsDiff([], nextList, "id", { chunksSize: 2 }); + + const expectedChunks = [ + { + previousValue: null, + currentValue: { id: 1, name: "Item 1" }, + prevIndex: null, + newIndex: 0, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: null, + currentValue: { id: 2, name: "Item 2" }, + prevIndex: null, + newIndex: 1, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + ]; + diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks)); + diff.on("finish", () => done()); + }); + it("emits 'data' event and consider the all the prevList deleted if no nextList is provided", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const diff = streamListsDiff(prevList, [], "id", { chunksSize: 2 }); + + const expectedChunks = [ + { + previousValue: { id: 1, name: "Item 1" }, + currentValue: null, + prevIndex: 0, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: null, + prevIndex: 1, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ]; + diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks)); + diff.on("finish", () => done()); + }); + it("emits 'data' event with one object diff by chunk if chunkSize is 0 or undefined", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const nextList = [ + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id"); + + const expectedChunks = [ + [ + { + previousValue: null, + currentValue: { id: 3, name: "Item 3" }, + prevIndex: null, + newIndex: 1, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + ], + [ + { + previousValue: { id: 1, name: "Item 1" }, + currentValue: null, + prevIndex: 0, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ], + [ + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: { id: 2, name: "Item 2" }, + prevIndex: 1, + newIndex: 0, + indexDiff: -1, + status: LIST_STATUS.MOVED, + }, + ], + ]; + + let chunkCount = 0; + + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks[chunkCount]); + chunkCount++; + }); + diff.on("finish", () => done()); + }); + it("emits 'data' event with 5 object diff by chunk", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + { id: 4, name: "Item 4" }, + { id: 5, name: "Item 5" }, + { id: 6, name: "Item 6" }, + { id: 7, name: "Item 7" }, + { id: 8, name: "Item 8" }, + { id: 9, name: "Item 9" }, + { id: 10, name: "Item 10" }, + ]; + const nextList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item Two" }, + { id: 3, name: "Item 3" }, + { id: 5, name: "Item 5" }, + { id: 6, name: "Item Six" }, + { id: 7, name: "Item 7" }, + { id: 10, name: "Item 10" }, + { id: 11, name: "Item 11" }, + { id: 9, name: "Item 9" }, + { id: 8, name: "Item 8" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); + + const expectedChunks = [ + [ + { + previousValue: null, + currentValue: { id: 11, name: "Item 11" }, + prevIndex: null, + newIndex: 7, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: { id: 1, name: "Item 1" }, + currentValue: { id: 1, name: "Item 1" }, + prevIndex: 0, + newIndex: 0, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: { id: 2, name: "Item Two" }, + prevIndex: 1, + newIndex: 1, + indexDiff: 0, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 3, name: "Item 3" }, + currentValue: { id: 3, name: "Item 3" }, + prevIndex: 2, + newIndex: 2, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 4, name: "Item 4" }, + currentValue: null, + prevIndex: 3, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ], + [ + { + previousValue: { id: 5, name: "Item 5" }, + currentValue: { id: 5, name: "Item 5" }, + prevIndex: 4, + newIndex: 3, + indexDiff: -1, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { id: 6, name: "Item 6" }, + currentValue: { id: 6, name: "Item Six" }, + prevIndex: 5, + newIndex: 4, + indexDiff: -1, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 7, name: "Item 7" }, + currentValue: { id: 7, name: "Item 7" }, + prevIndex: 6, + newIndex: 5, + indexDiff: -1, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { id: 8, name: "Item 8" }, + currentValue: { id: 8, name: "Item 8" }, + prevIndex: 7, + newIndex: 9, + indexDiff: 2, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { id: 9, name: "Item 9" }, + currentValue: { id: 9, name: "Item 9" }, + prevIndex: 8, + newIndex: 8, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 10, name: "Item 10" }, + currentValue: { id: 10, name: "Item 10" }, + prevIndex: 9, + newIndex: 6, + indexDiff: -3, + status: LIST_STATUS.MOVED, + }, + ], + [ + { + previousValue: { id: 10, name: "Item 10" }, + currentValue: { id: 10, name: "Item 10" }, + prevIndex: 9, + newIndex: 6, + indexDiff: -3, + status: LIST_STATUS.MOVED, + }, + ], + ]; + + let chunkCount = 0; + + diff.on("data", (chunk) => { + // console.log("chunks received", chunk); + // console.log("expected chunk", expectedChunks[chunkCount]); + //expect(chunk).toStrictEqual(expectedChunks[chunkCount]); + chunkCount++; + }); + diff.on("finish", () => { + expect(chunkCount).toBe(3); + done(); + }); + }); +}); + +describe("streamListsDiff finish", () => { + it("emits 'finish' event if no prevList nor nextList is provided", (done) => { + const diff = streamListsDiff([], [], "id"); + diff.on("finish", () => done()); + }); + it("emits 'finish' event when all the chunks have been processed", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const nextList = [ + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id"); + diff.on("finish", () => done()); + }); +}); + +describe("streamListsDiff error", () => { + test("emits 'error' event when prevList has invalid data", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + "hello", + { id: 2, name: "Item 2" }, + ]; + const nextList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + + // @ts-expect-error prevList is invalid by design for the test + const diff = streamListsDiff(prevList, nextList, "id"); + + diff.on("error", (err) => { + expect(err["message"]).toEqual( + `Your prevList must only contain valid objects. Found 'hello'`, + ); + done(); + }); + }); + + test("emits 'error' event when nextList has invalid data", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const nextList = [ + { id: 1, name: "Item 1" }, + "hello", + { id: 2, name: "Item 2" }, + ]; + + // @ts-expect-error nextList is invalid by design for the test + const diff = streamListsDiff(prevList, nextList, "id"); + + diff.on("error", (err) => { + expect(err["message"]).toEqual( + `Your nextList must only contain valid objects. Found 'hello'`, + ); + done(); + }); + }); + + test("emits 'error' event when all prevList ojects don't have the requested reference property", (done) => { + const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; + const nextList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + + const diff = streamListsDiff(prevList, nextList, "id"); + + diff.on("error", (err) => { + expect(err["message"]).toEqual( + `The reference property 'id' is not available in all the objects of your prevList.`, + ); + done(); + }); + }); + + test("emits 'error' event when all nextList ojects don't have the requested reference property", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; + + const diff = streamListsDiff(prevList, nextList, "id"); + + diff.on("error", (err) => { + expect(err["message"]).toEqual( + `The reference property 'id' is not available in all the objects of your nextList.`, + ); + done(); + }); + }); + + test("emits 'error' event when the chunkSize option is negative", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + ]; + const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; + + const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: -3 }); + + diff.on("error", (err) => { + expect(err["message"]).toEqual( + "The chunk size can't be negative. You entered the value '-3'", + ); + done(); + }); + }); +}); From 2b5e2b38b766a0a07cf4548121174a53802044d9 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sun, 6 Oct 2024 12:43:13 +0200 Subject: [PATCH 6/8] chore: improve memory usage + add tests --- src/lib/stream-list-diff/emitter.ts | 13 + src/lib/stream-list-diff/index.ts | 31 +- .../stream-list-diff/stream-list-diff.test.ts | 557 +++++++++++++++++- 3 files changed, 577 insertions(+), 24 deletions(-) diff --git a/src/lib/stream-list-diff/emitter.ts b/src/lib/stream-list-diff/emitter.ts index 91dff58..4d27b30 100644 --- a/src/lib/stream-list-diff/emitter.ts +++ b/src/lib/stream-list-diff/emitter.ts @@ -31,3 +31,16 @@ export class EventEmitter<Events extends Record<string, unknown[]>> { } } } + +export type EmitterEvents<T extends Record<string, unknown>> = { + data: [StreamListsDiff<T>[]]; + error: [Error]; + finish: []; +}; + +export interface ReadOnlyEmitter<T extends Record<string, unknown>> { + on<E extends keyof EmitterEvents<T>>( + event: E, + listener: Listener<EmitterEvents<T>[E]>, + ): this; +} diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts index 90e1d65..d47c1bc 100644 --- a/src/lib/stream-list-diff/index.ts +++ b/src/lib/stream-list-diff/index.ts @@ -7,7 +7,13 @@ import { } from "@models/stream"; import { LIST_STATUS } from "@models/list"; import { isObject } from "@lib/utils"; -import { Emitter, EventEmitter, StreamEvent } from "./emitter"; +import { + Emitter, + EmitterEvents, + EventEmitter, + ReadOnlyEmitter, + StreamEvent, +} from "./emitter"; function outputDiffChunk<T extends Record<string, unknown>>( emitter: Emitter<T>, @@ -133,7 +139,7 @@ function getDiffChunks<T extends Record<string, unknown>>( StreamEvent.Error, new Error("Your nextList must only contain valid objects."), ); - emitter.emit(StreamEvent.Finish); + return emitter.emit(StreamEvent.Finish); } nextDiff?.forEach((data, i) => handleDiffChunk(data, i === nextDiff.length - 1, options), @@ -152,7 +158,7 @@ function getDiffChunks<T extends Record<string, unknown>>( StreamEvent.Error, new Error("Your prevList must only contain valid objects."), ); - emitter.emit(StreamEvent.Finish); + return emitter.emit(StreamEvent.Finish); } prevDiff?.forEach((data, i) => handleDiffChunk(data, i === prevDiff.length - 1, options), @@ -180,6 +186,8 @@ function getDiffChunks<T extends Record<string, unknown>>( } } + const totalChunks = listsReferences.size; + for (let i = 0; i < nextList.length; i++) { const data = nextList[i]; if (data) { @@ -208,7 +216,7 @@ function getDiffChunks<T extends Record<string, unknown>>( indexDiff: null, status: LIST_STATUS.ADDED, }, - i === nextList.length - 1, + totalChunks > 0 ? false : i === nextList.length - 1, options, ); } @@ -216,11 +224,11 @@ function getDiffChunks<T extends Record<string, unknown>>( } let streamedChunks = 0; - const totalChunks = listsReferences.size; - for (const data of listsReferences.values()) { + for (const [key, data] of listsReferences.entries()) { streamedChunks++; const isLastChunk = totalChunks === streamedChunks; + if (typeof data.nextIndex === "undefined") { handleDiffChunk( { @@ -284,6 +292,7 @@ function getDiffChunks<T extends Record<string, unknown>>( ); } } + listsReferences.delete(key); // to free up memory } return emitter.emit(StreamEvent.Finish); @@ -305,12 +314,8 @@ export function streamListsDiff<T extends Record<string, unknown>>( nextList: T[], referenceProperty: ReferenceProperty<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, -): Emitter<T> { - const emitter = new EventEmitter<{ - data: [StreamListsDiff<T>[]]; - error: [Error]; - finish: []; - }>(); +): ReadOnlyEmitter<T> { + const emitter = new EventEmitter<EmitterEvents<T>>(); setTimeout(() => { try { getDiffChunks(prevList, nextList, referenceProperty, emitter, options); @@ -318,5 +323,5 @@ export function streamListsDiff<T extends Record<string, unknown>>( return emitter.emit(StreamEvent.Error, err as Error); } }, 0); - return emitter; + return emitter as ReadOnlyEmitter<T>; } diff --git a/src/lib/stream-list-diff/stream-list-diff.test.ts b/src/lib/stream-list-diff/stream-list-diff.test.ts index 1a47732..2a171a7 100644 --- a/src/lib/stream-list-diff/stream-list-diff.test.ts +++ b/src/lib/stream-list-diff/stream-list-diff.test.ts @@ -1,5 +1,6 @@ import { LIST_STATUS } from "@models/list"; import { streamListsDiff } from "."; +import { StreamListsDiff } from "@models/stream"; describe("streamListsDiff data", () => { it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => { @@ -27,8 +28,15 @@ describe("streamListsDiff data", () => { status: LIST_STATUS.ADDED, }, ]; - diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks)); - diff.on("finish", () => done()); + let chunkCount = 0; + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks); + chunkCount++; + }); + diff.on("finish", () => { + expect(chunkCount).toBe(1); + done(); + }); }); it("emits 'data' event and consider the all the prevList deleted if no nextList is provided", (done) => { const prevList = [ @@ -55,8 +63,15 @@ describe("streamListsDiff data", () => { status: LIST_STATUS.DELETED, }, ]; - diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks)); - diff.on("finish", () => done()); + let chunkCount = 0; + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks); + chunkCount++; + }); + diff.on("finish", () => { + expect(chunkCount).toBe(1); + done(); + }); }); it("emits 'data' event with one object diff by chunk if chunkSize is 0 or undefined", (done) => { const prevList = [ @@ -108,9 +123,12 @@ describe("streamListsDiff data", () => { expect(chunk).toStrictEqual(expectedChunks[chunkCount]); chunkCount++; }); - diff.on("finish", () => done()); + diff.on("finish", () => { + expect(chunkCount).toBe(3); + done(); + }); }); - it("emits 'data' event with 5 object diff by chunk", (done) => { + it("emits 'data' event with 5 object diff by chunk and return the last object diff in a one entry chunk at the end", (done) => { const prevList = [ { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, @@ -221,6 +239,8 @@ describe("streamListsDiff data", () => { indexDiff: 0, status: LIST_STATUS.EQUAL, }, + ], + [ { previousValue: { id: 10, name: "Item 10" }, currentValue: { id: 10, name: "Item 10" }, @@ -230,10 +250,397 @@ describe("streamListsDiff data", () => { status: LIST_STATUS.MOVED, }, ], + ]; + + let chunkCount = 0; + + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks[chunkCount]); + chunkCount++; + }); + + diff.on("finish", () => { + expect(chunkCount).toBe(3); + done(); + }); + }); + it("emits 'data' event with all the objects diff in a single chunk if the chunkSize is bigger than the provided lists ", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + { id: 4, name: "Item 4" }, + ]; + const nextList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item Two" }, + { id: 3, name: "Item 3" }, + { id: 5, name: "Item 5" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 150 }); + + const expectedChunks = [ + { + previousValue: null, + currentValue: { id: 5, name: "Item 5" }, + prevIndex: null, + newIndex: 3, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: { id: 1, name: "Item 1" }, + currentValue: { id: 1, name: "Item 1" }, + prevIndex: 0, + newIndex: 0, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: { id: 2, name: "Item Two" }, + prevIndex: 1, + newIndex: 1, + indexDiff: 0, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 3, name: "Item 3" }, + currentValue: { id: 3, name: "Item 3" }, + prevIndex: 2, + newIndex: 2, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 4, name: "Item 4" }, + currentValue: null, + prevIndex: 3, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ]; + + let chunkCount = 0; + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks); + chunkCount++; + }); + + diff.on("finish", () => { + expect(chunkCount).toBe(1); + done(); + }); + }); + it("emits 'data' event with moved objects considered as updated if considerMoveAsUpdate is true", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + { id: 4, name: "Item 4" }, + ]; + const nextList = [ + { id: 2, name: "Item Two" }, + { id: 1, name: "Item 1" }, + { id: 3, name: "Item 3" }, + { id: 5, name: "Item 5" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id", { + chunksSize: 5, + considerMoveAsUpdate: true, + }); + + const expectedChunks = [ + { + previousValue: null, + currentValue: { id: 5, name: "Item 5" }, + prevIndex: null, + newIndex: 3, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: { id: 1, name: "Item 1" }, + currentValue: { id: 1, name: "Item 1" }, + prevIndex: 0, + newIndex: 1, + indexDiff: 1, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: { id: 2, name: "Item Two" }, + prevIndex: 1, + newIndex: 0, + indexDiff: -1, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 3, name: "Item 3" }, + currentValue: { id: 3, name: "Item 3" }, + prevIndex: 2, + newIndex: 2, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 4, name: "Item 4" }, + currentValue: null, + prevIndex: 3, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ]; + + let chunkCount = 0; + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks); + chunkCount++; + }); + + diff.on("finish", () => { + expect(chunkCount).toBe(1); + done(); + }); + }); + it("emits 'data' event only with objects diff whose status match with showOnly's", (done) => { + const prevList = [ + { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" }, + { id: 4, name: "Item 4" }, + ]; + const nextList = [ + { id: 2, name: "Item Two" }, + { id: 1, name: "Item 1" }, + { id: 3, name: "Item 3" }, + { id: 5, name: "Item 5" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id", { + chunksSize: 5, + showOnly: ["added", "deleted"], + }); + + const expectedChunks = [ + { + previousValue: null, + currentValue: { id: 5, name: "Item 5" }, + prevIndex: null, + newIndex: 3, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: { id: 4, name: "Item 4" }, + currentValue: null, + prevIndex: 3, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ]; + + let chunkCount = 0; + diff.on("data", (chunk) => { + expect(chunk).toStrictEqual(expectedChunks); + chunkCount++; + }); + + diff.on("finish", () => { + expect(chunkCount).toBe(1); + done(); + }); + }); + it("emits 'data' event with deep nested objects diff", (done) => { + const prevList = [ + { + id: 1, + name: "Item 1", + user: { role: "admin", hobbies: ["golf", "football"] }, + }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3", user: { role: "admin", hobbies: ["rugby"] } }, + { + id: 4, + name: "Item 4", + user: { role: "reader", hobbies: ["video games", "fishing"] }, + }, + { id: 5, name: "Item 5" }, + { id: 6, name: "Item 6", user: { role: "root", hobbies: ["coding"] } }, + { id: 7, name: "Item 7" }, + { id: 8, name: "Item 8" }, + { id: 9, name: "Item 9" }, + { + id: 10, + name: "Item 10", + user: { + role: "root", + hobbies: ["coding"], + skills: { driving: true, diving: false }, + }, + }, + ]; + const nextList = [ + { + id: 1, + name: "Item 1", + user: { role: "admin", hobbies: ["golf", "football"] }, + }, + { id: 2, name: "Item Two" }, + { id: 3, name: "Item 3", user: { role: "admin", hobbies: ["rugby"] } }, + { id: 5, name: "Item 5" }, + { id: 6, name: "Item 6", user: { role: "root", hobbies: ["farming"] } }, + { id: 7, name: "Item 7" }, + { + id: 10, + name: "Item 10", + user: { + role: "root", + hobbies: ["coding"], + skills: { driving: true, diving: false }, + }, + }, + { id: 11, name: "Item 11" }, + { id: 9, name: "Item 9" }, + { id: 8, name: "Item 8" }, + ]; + const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); + + const expectedChunks = [ + [ + { + previousValue: null, + currentValue: { id: 11, name: "Item 11" }, + prevIndex: null, + newIndex: 7, + indexDiff: null, + status: LIST_STATUS.ADDED, + }, + { + previousValue: { + id: 1, + name: "Item 1", + user: { role: "admin", hobbies: ["golf", "football"] }, + }, + currentValue: { + id: 1, + name: "Item 1", + user: { role: "admin", hobbies: ["golf", "football"] }, + }, + prevIndex: 0, + newIndex: 0, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { id: 2, name: "Item 2" }, + currentValue: { id: 2, name: "Item Two" }, + prevIndex: 1, + newIndex: 1, + indexDiff: 0, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { + id: 3, + name: "Item 3", + user: { role: "admin", hobbies: ["rugby"] }, + }, + currentValue: { + id: 3, + name: "Item 3", + user: { role: "admin", hobbies: ["rugby"] }, + }, + prevIndex: 2, + newIndex: 2, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + { + previousValue: { + id: 4, + name: "Item 4", + user: { role: "reader", hobbies: ["video games", "fishing"] }, + }, + currentValue: null, + prevIndex: 3, + newIndex: null, + indexDiff: null, + status: LIST_STATUS.DELETED, + }, + ], + [ + { + previousValue: { id: 5, name: "Item 5" }, + currentValue: { id: 5, name: "Item 5" }, + prevIndex: 4, + newIndex: 3, + indexDiff: -1, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { + id: 6, + name: "Item 6", + user: { role: "root", hobbies: ["coding"] }, + }, + currentValue: { + id: 6, + name: "Item 6", + user: { role: "root", hobbies: ["farming"] }, + }, + prevIndex: 5, + newIndex: 4, + indexDiff: -1, + status: LIST_STATUS.UPDATED, + }, + { + previousValue: { id: 7, name: "Item 7" }, + currentValue: { id: 7, name: "Item 7" }, + prevIndex: 6, + newIndex: 5, + indexDiff: -1, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { id: 8, name: "Item 8" }, + currentValue: { id: 8, name: "Item 8" }, + prevIndex: 7, + newIndex: 9, + indexDiff: 2, + status: LIST_STATUS.MOVED, + }, + { + previousValue: { id: 9, name: "Item 9" }, + currentValue: { id: 9, name: "Item 9" }, + prevIndex: 8, + newIndex: 8, + indexDiff: 0, + status: LIST_STATUS.EQUAL, + }, + ], [ { - previousValue: { id: 10, name: "Item 10" }, - currentValue: { id: 10, name: "Item 10" }, + previousValue: { + id: 10, + name: "Item 10", + user: { + role: "root", + hobbies: ["coding"], + skills: { driving: true, diving: false }, + }, + }, + currentValue: { + id: 10, + name: "Item 10", + user: { + role: "root", + hobbies: ["coding"], + skills: { driving: true, diving: false }, + }, + }, prevIndex: 9, newIndex: 6, indexDiff: -3, @@ -245,11 +652,10 @@ describe("streamListsDiff data", () => { let chunkCount = 0; diff.on("data", (chunk) => { - // console.log("chunks received", chunk); - // console.log("expected chunk", expectedChunks[chunkCount]); - //expect(chunk).toStrictEqual(expectedChunks[chunkCount]); + expect(chunk).toStrictEqual(expectedChunks[chunkCount]); chunkCount++; }); + diff.on("finish", () => { expect(chunkCount).toBe(3); done(); @@ -372,3 +778,132 @@ describe("streamListsDiff error", () => { }); }); }); + +describe("Performance", () => { + it("should correctly stream diff for 10.000 entries", (done) => { + const generateLargeList = (size: number, idPrefix: string) => { + return Array.from({ length: size }, (_, i) => ({ + id: `${idPrefix}-${i}`, + value: i, + })); + }; + const prevList = generateLargeList(10_000, "prev"); + const nextList = [ + ...generateLargeList(5000, "prev"), + ...generateLargeList(5000, "next"), + ]; + + const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; + let chunkCount = 0; + const diffStream = streamListsDiff(prevList, nextList, "id", { + chunksSize: 1000, + }); + + diffStream.on("data", (chunk) => { + receivedChunks.push(...chunk); + chunkCount++; + }); + + diffStream.on("finish", () => { + const deletions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.DELETED, + ); + const additions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.ADDED, + ); + const updates = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.EQUAL, + ); + expect(receivedChunks.length).toBe(15_000); // 5000 deletions + 5000 equal + 5000 additions + expect(chunkCount).toBe(15); + expect(deletions.length).toBe(5000); + expect(additions.length).toBe(5000); + expect(updates.length).toBe(5000); + done(); + }); + }); + it("should correctly stream diff for 100.000 entries", (done) => { + const generateLargeList = (size: number, idPrefix: string) => { + return Array.from({ length: size }, (_, i) => ({ + id: `${idPrefix}-${i}`, + value: i, + })); + }; + const prevList = generateLargeList(100_000, "prev"); + const nextList = [ + ...generateLargeList(50000, "prev"), + ...generateLargeList(50000, "next"), + ]; + + const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; + let chunkCount = 0; + const diffStream = streamListsDiff(prevList, nextList, "id", { + chunksSize: 10_000, + }); + + diffStream.on("data", (chunk) => { + receivedChunks.push(...chunk); + chunkCount++; + }); + + diffStream.on("finish", () => { + const deletions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.DELETED, + ); + const additions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.ADDED, + ); + const updates = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.EQUAL, + ); + expect(receivedChunks.length).toBe(150_000); // 50.000 deletions + 50.000 equal + 50.000 additions + expect(chunkCount).toBe(15); + expect(deletions.length).toBe(50000); + expect(additions.length).toBe(50000); + expect(updates.length).toBe(50000); + done(); + }); + }); + // it("should correctly stream diff for 1.000.000 entries", (done) => { + // const generateLargeList = (size: number, idPrefix: string) => { + // return Array.from({ length: size }, (_, i) => ({ + // id: `${idPrefix}-${i}`, + // value: i, + // })); + // }; + // const prevList = generateLargeList(1_000_000, "prev"); + // const nextList = [ + // ...generateLargeList(500_000, "prev"), + // ...generateLargeList(500_000, "next"), + // ]; + + // const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; + // let chunkCount = 0; + // const diffStream = streamListsDiff(prevList, nextList, "id", { + // chunksSize: 100_000, + // }); + + // diffStream.on("data", (chunk) => { + // receivedChunks.push(...chunk); + // chunkCount++; + // }); + + // diffStream.on("finish", () => { + // const deletions = receivedChunks.filter( + // (diff) => diff.status === LIST_STATUS.DELETED, + // ); + // const additions = receivedChunks.filter( + // (diff) => diff.status === LIST_STATUS.ADDED, + // ); + // const updates = receivedChunks.filter( + // (diff) => diff.status === LIST_STATUS.EQUAL, + // ); + // expect(receivedChunks.length).toBe(1_500_000); // 50.000 deletions + 50.000 equal + 50.000 additions + // expect(chunkCount).toBe(15); + // expect(deletions.length).toBe(500000); + // expect(additions.length).toBe(500000); + // expect(updates.length).toBe(500000); + // done(); + // }); + // }); +}); From dc15ce9be326dd1d7ab1ada06509422f78ee17e7 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sun, 6 Oct 2024 15:33:24 +0200 Subject: [PATCH 7/8] chore: update readme --- README.md | 471 +++++++++++------- src/index.ts | 2 +- src/lib/stream-list-diff/emitter.ts | 8 +- src/lib/stream-list-diff/index.ts | 22 +- .../stream-list-diff/stream-list-diff.test.ts | 90 +--- src/models/stream/index.ts | 2 +- 6 files changed, 338 insertions(+), 257 deletions(-) diff --git a/README.md b/README.md index 255351d..bf84553 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This library compares two arrays or objects and returns a full diff of their dif All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. -**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. đ +**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested, has zero dependencies, and is super fast. Import. Enjoy. đ <hr/> @@ -40,64 +40,159 @@ I am grateful to the generous donors of **Superdiff**! **Superdiff** exports 4 functions: +```ts +// Compares two objects and return a diff for each value and their potential subvalues +getObjectDiff(prevObject, nextObject) +// Compares two arrays and returns a diff for each value +getListDiff(prevList, nextList) +// Streams the diff of two object lists, ideal for large lists and maximum performance +streamListDiff(prevList, nextList, referenceProperty) +// Checks whether two values are equal +isEqual(dataA, dataB) +// Checks whether a value is an object +isObject(data) +``` +<hr/> + ### getObjectDiff() ```js import { getObjectDiff } from "@donedeal0/superdiff"; ``` -Compares two objects and return a diff for each value and their potential subvalues: +Compares two objects and return a diff for each value and their potential subvalues. Supports deeply nested objects with any kind of values. -- property name -- status: `added`, `deleted`, `equal`, `updated` -- previous value, current value -- supports deeply nested objects with any kind of values +**Format** -format: +input + +```ts +prevData: Record<string, unknown>; +nextData: Record<string, unknown>; +options?: { + ignoreArrayOrder?: boolean, // false by default, + showOnly?: { + statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default + granularity?: "basic" | "deep" // "basic" by default + } +} +``` + +- `prevData`: the original object +- `nextData`: the new object +- `options` + - `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. + - `showOnly`: returns only the values whose status you are interested in. It takes two parameters: + + - `statuses`: status you want to see in the output (e.g. `["added", "equal"]`) + - `granularity`: + - `basic` returns only the main properties whose status matches your query. + - `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly. + +output ```ts type ObjectDiff = { type: "object"; status: "added" | "deleted" | "equal" | "updated"; - diff: { - property: string; - previousValue: unknown; - currentValue: unknow; - status: "added" | "deleted" | "equal" | "updated"; - // only appears if some subproperties have been added/deleted/updated - diff?: { - property: string; - previousValue: unknown; - currentValue: unknown; - status: "added" | "deleted" | "equal" | "updated"; - // recursive diff in case of subproperties - diff?: SubDiff[]; - }[]; - }[]; + diff: Diff[]; }; -``` -**Options** +/** recursive diff in case of subproperties */ +type Diff = { + property: string; + previousValue: unknown; + currentValue: unknown; + status: "added" | "deleted" | "equal" | "updated"; + diff?: Diff[]; +}; +``` +**Usage** -You can add a third `options` parameter to `getObjectDiff`. +input -```ts -{ - ignoreArrayOrder?: boolean // false by default, - showOnly?: { - statuses: ("added" | "deleted" | "updated" | "equal")[], // [] by default - granularity?: "basic" | "deep" // "basic" by default +```diff +getObjectDiff( + { + id: 54, + user: { + name: "joe", +- member: true, +- hobbies: ["golf", "football"], + age: 66, + }, + }, + { + id: 54, + user: { + name: "joe", ++ member: false, ++ hobbies: ["golf", "chess"], + age: 66, + }, } -} +); ``` -- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. -- `showOnly`: returns only the values whose status you are interested in. It takes two parameters: +output - - `statuses`: status you want to see in the output (e.g. `["added", "equal"]`) - - `granularity`: - - `basic` returns only the main properties whose status matches your query. - - `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly. +```diff +{ + type: "object", ++ status: "updated", + diff: [ + { + property: "id", + previousValue: 54, + currentValue: 54, + status: "equal", + }, + { + property: "user", + previousValue: { + name: "joe", + member: true, + hobbies: ["golf", "football"], + age: 66, + }, + currentValue: { + name: "joe", + member: false, + hobbies: ["golf", "chess"], + age: 66, + }, ++ status: "updated", + diff: [ + { + property: "name", + previousValue: "joe", + currentValue: "joe", + status: "equal", + }, ++ { ++ property: "member", ++ previousValue: true, ++ currentValue: false, ++ status: "updated", ++ }, ++ { ++ property: "hobbies", ++ previousValue: ["golf", "football"], ++ currentValue: ["golf", "chess"], ++ status: "updated", ++ }, + { + property: "age", + previousValue: 66, + currentValue: 66, + status: "equal", + }, + ], + }, + ], + } +``` +<hr/> ### getListDiff() @@ -105,15 +200,31 @@ You can add a third `options` parameter to `getObjectDiff`. import { getListDiff } from "@donedeal0/superdiff"; ``` -Compares two arrays and returns a diff for each value: +Compares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects. -- index change: `prevIndex`, `newIndex`, `indexDiff` -- status: `added`, `deleted`, `equal`, `moved`, `updated` -- value -- supports arrays of primitive values and objects -- supports arrays with duplicate values +**Format** + +input + +```ts + prevList: T[]; + nextList: T[]; + options?: { + showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default + referenceProperty?: string, // "" by default + ignoreArrayOrder?: boolean, // false by default, + considerMoveAsUpdate?: boolean // false by default + } +``` +- `prevList`: the original list +- `nextList`: the new list +- `options` + - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). + - `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes. + - `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. + - `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`. -format: +output ```ts type ListDiff = { @@ -128,56 +239,7 @@ type ListDiff = { }[]; }; ``` - -**Options** - -You can add a third `options` parameter to `getListDiff`. - -```ts -{ - showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default - referenceProperty?: string; // "" by default - ignoreArrayOrder?: boolean // false by default, -} -``` - -- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). -- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes. -- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - -### isEqual() - -```js -import { isEqual } from "@donedeal0/superdiff"; -``` - -Tests whether two values are equal. - -**Options** - -You can add a third `options` parameter to `isEqual`. - -```ts -{ - ignoreArrayOrder?: boolean // false by default, -} -``` - -- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - -### isObject() - -```js -import { isObject } from "@donedeal0/superdiff"; -``` - -Tests whether a value is an object. - -<hr/> - -## EXAMPLES - -### getListDiff() +**Usage** input @@ -233,96 +295,146 @@ output ], } ``` +<hr/> -### getObjectDiff() +### streamListDiff() + +```js +import { streamListDiff } from "@donedeal0/superdiff"; +``` + +Streams the diff of two object lists, ideal for large lists and maximum performance. + +**Format** input -```diff -getObjectDiff( - { - id: 54, - user: { - name: "joe", -- member: true, -- hobbies: ["golf", "football"], - age: 66, - }, - }, - { - id: 54, - user: { - name: "joe", -+ member: false, -+ hobbies: ["golf", "chess"], - age: 66, - }, - } -); +```ts + prevList: T[], + nextList: T[], + referenceProperty: ReferenceProperty<T>, + options: { + showOnly?: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`), // [] by default + chunksSize?: number, // // 0 by default + considerMoveAsUpdate? boolean; // false by default +} ``` +- `prevList`: the original object list. +- `nextList`: the new object list. +- `referenceProperty`: a common property in all the objects of your lists (e.g. `id`). +- `options` + - `chunksSize` the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs. + - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). + - `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`. + output +```ts +type StreamListDiff<T extends Record<string, unknown>> = { + currentValue: T | null; + previousValue: T | null; + prevIndex: number | null; + newIndex: number | null; + indexDiff: number | null; + status: "added" | "deleted" | "moved" | "updated" | "equal"; +}; +``` + +**Usage** + +input + ```diff -{ - type: "object", -+ status: "updated", - diff: [ - { - property: "id", - previousValue: 54, - currentValue: 54, - status: "equal", - }, +const diff = streamListDiff( + [ +- { id: 1, name: "Item 1" }, + { id: 2, name: "Item 2" }, + { id: 3, name: "Item 3" } + ], + [ ++ { id: 0, name: "Item 0" }, + { id: 2, name: "Item 2" }, ++ { id: 3, name: "Item Three" }, + ], + "id", + { chunksSize: 2 } + ); +``` + +output + +```diff +diff.on("data", (chunk) => { + // first chunk received (2 object diffs) + [ ++ { ++ previousValue: null, ++ currentValue: { id: 0, name: 'Item 0' }, ++ prevIndex: null, ++ newIndex: 0, ++ indexDiff: null, ++ status: 'added' ++ }, +- { +- previousValue: { id: 1, name: 'Item 1' }, +- currentValue: null, +- prevIndex: 0, +- newIndex: null, +- indexDiff: null, +- status: 'deleted' +- } + ] + // second chunk received (2 object diffs) + [ { - property: "user", - previousValue: { - name: "joe", - member: true, - hobbies: ["golf", "football"], - age: 66, - }, - currentValue: { - name: "joe", - member: false, - hobbies: ["golf", "chess"], - age: 66, - }, -+ status: "updated", - diff: [ - { - property: "name", - previousValue: "joe", - currentValue: "joe", - status: "equal", - }, -+ { -+ property: "member", -+ previousValue: true, -+ currentValue: false, -+ status: "updated", -+ }, -+ { -+ property: "hobbies", -+ previousValue: ["golf", "football"], -+ currentValue: ["golf", "chess"], -+ status: "updated", -+ }, - { - property: "age", - previousValue: 66, - currentValue: 66, - status: "equal", - }, - ], + previousValue: { id: 2, name: 'Item 2' }, + currentValue: { id: 2, name: 'Item 2' }, + prevIndex: 1, + newIndex: 1, + indexDiff: 0, + status: 'equal' }, - ], - } ++ { ++ previousValue: { id: 3, name: 'Item 3' }, ++ currentValue: { id: 3, name: 'Item Three' }, ++ prevIndex: 2, ++ newIndex: 2, ++ indexDiff: 0, ++ status: 'updated' ++ }, + ] +}); + +diff.on("finish", () => console.log("The full diff is available")) +diff.on("error", (err)=> console.log(err)) ``` +<hr/> ### isEqual() ```js +import { isEqual } from "@donedeal0/superdiff"; +``` + +Checks whether two values are equal. + +**Options** + +You can add a third `options` parameter to `isEqual`. + +```ts +{ + ignoreArrayOrder?: boolean // false by default, +} +``` + +- `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. + +**Usage** + + +```ts isEqual( [ { name: "joe", age: 99 }, @@ -337,25 +449,36 @@ isEqual( output -```js +```ts false; ``` +<hr/> ### isObject() +```js +import { isObject } from "@donedeal0/superdiff"; +``` + +Tests whether a value is an object. + +**Usage** + input -```js +```ts isObject(["hello", "world"]); ``` output -```js +```ts false; ``` -More examples are available in the source code tests. +<hr/> + +### More examples are available in the source code tests. <hr/> @@ -365,7 +488,7 @@ DoneDeal0 ## SUPPORT -If you or your company uses **Superdiff**, please show your support by becoming a sponsor! Your name and company logo will be displayed on the `README.md`. https://github.com/sponsors/DoneDeal0 +If you or your company uses **Superdiff**, please show your support by becoming a sponsor! Your name and company logo will be displayed on the `README.md`. Premium support is also available. https://github.com/sponsors/DoneDeal0 <br/> <a href="https://github.com/sponsors/DoneDeal0" target="_blank"> diff --git a/src/index.ts b/src/index.ts index ca0a6d8..798227b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export { getObjectDiff } from "./lib/object-diff"; export { getListDiff } from "./lib/list-diff"; export { isEqual, isObject } from "./lib/utils"; -export { streamListsDiff } from "./lib/stream-list-diff"; +export { streamListDiff } from "./lib/stream-list-diff"; export * from "./models/list"; export * from "./models/object"; export * from "./models/stream"; diff --git a/src/lib/stream-list-diff/emitter.ts b/src/lib/stream-list-diff/emitter.ts index 4d27b30..b768784 100644 --- a/src/lib/stream-list-diff/emitter.ts +++ b/src/lib/stream-list-diff/emitter.ts @@ -1,4 +1,4 @@ -import { StreamListsDiff } from "@models/stream"; +import { StreamListDiff } from "@models/stream"; type Listener<T extends unknown[]> = (...args: T) => void; @@ -9,7 +9,7 @@ export enum StreamEvent { } export type Emitter<T extends Record<string, unknown>> = EventEmitter<{ - data: [StreamListsDiff<T>[]]; + data: [StreamListDiff<T>[]]; error: [Error]; finish: []; }>; @@ -33,12 +33,12 @@ export class EventEmitter<Events extends Record<string, unknown[]>> { } export type EmitterEvents<T extends Record<string, unknown>> = { - data: [StreamListsDiff<T>[]]; + data: [StreamListDiff<T>[]]; error: [Error]; finish: []; }; -export interface ReadOnlyEmitter<T extends Record<string, unknown>> { +export interface StreamListener<T extends Record<string, unknown>> { on<E extends keyof EmitterEvents<T>>( event: E, listener: Listener<EmitterEvents<T>[E]>, diff --git a/src/lib/stream-list-diff/index.ts b/src/lib/stream-list-diff/index.ts index d47c1bc..e949619 100644 --- a/src/lib/stream-list-diff/index.ts +++ b/src/lib/stream-list-diff/index.ts @@ -2,7 +2,7 @@ import { DEFAULT_LIST_STREAM_OPTIONS, ListStreamOptions, ReferenceProperty, - StreamListsDiff, + StreamListDiff, StreamReferences, } from "@models/stream"; import { LIST_STATUS } from "@models/list"; @@ -11,17 +11,17 @@ import { Emitter, EmitterEvents, EventEmitter, - ReadOnlyEmitter, + StreamListener, StreamEvent, } from "./emitter"; function outputDiffChunk<T extends Record<string, unknown>>( emitter: Emitter<T>, ) { - let chunks: StreamListsDiff<T>[] = []; + let chunks: StreamListDiff<T>[] = []; return function handleDiffChunk( - chunk: StreamListsDiff<T>, + chunk: StreamListDiff<T>, isLastChunk: boolean, options: ListStreamOptions, ): void { @@ -50,9 +50,9 @@ function formatSingleListStreamDiff<T extends Record<string, unknown>>( isPrevious: boolean, status: LIST_STATUS, options: ListStreamOptions, -): StreamListsDiff<T>[] | null { +): StreamListDiff<T>[] | null { let isValid = true; - const diff: StreamListsDiff<T>[] = []; + const diff: StreamListDiff<T>[] = []; for (let i = 0; i < list.length; i++) { const data = list[i]; if (!isObject(data)) { @@ -292,7 +292,7 @@ function getDiffChunks<T extends Record<string, unknown>>( ); } } - listsReferences.delete(key); // to free up memory + listsReferences.delete(key); } return emitter.emit(StreamEvent.Finish); @@ -304,17 +304,17 @@ function getDiffChunks<T extends Record<string, unknown>>( * @param {Record<string, unknown>[]} nextList - The new object list. * @param {ReferenceProperty<T>} referenceProperty - A common property in all the objects of your lists (e.g. `id`) * @param {ListStreamOptions} options - Options to refine your output. - - `chunksSize`: the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs. (default is `0`) + - `chunksSize`: the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff by chunk, `10` = 10 object diffs by chunk). - `showOnly`: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`) - `considerMoveAsUpdate`: if set to `true` a `moved` object will be considered as `updated` * @returns EventEmitter */ -export function streamListsDiff<T extends Record<string, unknown>>( +export function streamListDiff<T extends Record<string, unknown>>( prevList: T[], nextList: T[], referenceProperty: ReferenceProperty<T>, options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS, -): ReadOnlyEmitter<T> { +): StreamListener<T> { const emitter = new EventEmitter<EmitterEvents<T>>(); setTimeout(() => { try { @@ -323,5 +323,5 @@ export function streamListsDiff<T extends Record<string, unknown>>( return emitter.emit(StreamEvent.Error, err as Error); } }, 0); - return emitter as ReadOnlyEmitter<T>; + return emitter as StreamListener<T>; } diff --git a/src/lib/stream-list-diff/stream-list-diff.test.ts b/src/lib/stream-list-diff/stream-list-diff.test.ts index 2a171a7..5d8795c 100644 --- a/src/lib/stream-list-diff/stream-list-diff.test.ts +++ b/src/lib/stream-list-diff/stream-list-diff.test.ts @@ -1,14 +1,14 @@ import { LIST_STATUS } from "@models/list"; -import { streamListsDiff } from "."; -import { StreamListsDiff } from "@models/stream"; +import { streamListDiff } from "."; +import { StreamListDiff } from "@models/stream"; -describe("streamListsDiff data", () => { +describe("streamListDiff data", () => { it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => { const nextList = [ { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, ]; - const diff = streamListsDiff([], nextList, "id", { chunksSize: 2 }); + const diff = streamListDiff([], nextList, "id", { chunksSize: 2 }); const expectedChunks = [ { @@ -43,7 +43,7 @@ describe("streamListsDiff data", () => { { id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, ]; - const diff = streamListsDiff(prevList, [], "id", { chunksSize: 2 }); + const diff = streamListDiff(prevList, [], "id", { chunksSize: 2 }); const expectedChunks = [ { @@ -82,7 +82,7 @@ describe("streamListsDiff data", () => { { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }, ]; - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); const expectedChunks = [ [ @@ -153,7 +153,7 @@ describe("streamListsDiff data", () => { { id: 9, name: "Item 9" }, { id: 8, name: "Item 8" }, ]; - const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 }); const expectedChunks = [ [ @@ -277,7 +277,7 @@ describe("streamListsDiff data", () => { { id: 3, name: "Item 3" }, { id: 5, name: "Item 5" }, ]; - const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 150 }); + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 150 }); const expectedChunks = [ { @@ -346,7 +346,7 @@ describe("streamListsDiff data", () => { { id: 3, name: "Item 3" }, { id: 5, name: "Item 5" }, ]; - const diff = streamListsDiff(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, considerMoveAsUpdate: true, }); @@ -418,7 +418,7 @@ describe("streamListsDiff data", () => { { id: 3, name: "Item 3" }, { id: 5, name: "Item 5" }, ]; - const diff = streamListsDiff(prevList, nextList, "id", { + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5, showOnly: ["added", "deleted"], }); @@ -506,7 +506,7 @@ describe("streamListsDiff data", () => { { id: 9, name: "Item 9" }, { id: 8, name: "Item 8" }, ]; - const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 }); + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: 5 }); const expectedChunks = [ [ @@ -663,9 +663,9 @@ describe("streamListsDiff data", () => { }); }); -describe("streamListsDiff finish", () => { +describe("streamListDiff finish", () => { it("emits 'finish' event if no prevList nor nextList is provided", (done) => { - const diff = streamListsDiff([], [], "id"); + const diff = streamListDiff([], [], "id"); diff.on("finish", () => done()); }); it("emits 'finish' event when all the chunks have been processed", (done) => { @@ -677,12 +677,12 @@ describe("streamListsDiff finish", () => { { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }, ]; - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("finish", () => done()); }); }); -describe("streamListsDiff error", () => { +describe("streamListDiff error", () => { test("emits 'error' event when prevList has invalid data", (done) => { const prevList = [ { id: 1, name: "Item 1" }, @@ -695,7 +695,7 @@ describe("streamListsDiff error", () => { ]; // @ts-expect-error prevList is invalid by design for the test - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -717,7 +717,7 @@ describe("streamListsDiff error", () => { ]; // @ts-expect-error nextList is invalid by design for the test - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -734,7 +734,7 @@ describe("streamListsDiff error", () => { { id: 2, name: "Item 2" }, ]; - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -751,7 +751,7 @@ describe("streamListsDiff error", () => { ]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; - const diff = streamListsDiff(prevList, nextList, "id"); + const diff = streamListDiff(prevList, nextList, "id"); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -768,7 +768,7 @@ describe("streamListsDiff error", () => { ]; const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }]; - const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: -3 }); + const diff = streamListDiff(prevList, nextList, "id", { chunksSize: -3 }); diff.on("error", (err) => { expect(err["message"]).toEqual( @@ -793,9 +793,9 @@ describe("Performance", () => { ...generateLargeList(5000, "next"), ]; - const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; + const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = []; let chunkCount = 0; - const diffStream = streamListsDiff(prevList, nextList, "id", { + const diffStream = streamListDiff(prevList, nextList, "id", { chunksSize: 1000, }); @@ -835,9 +835,9 @@ describe("Performance", () => { ...generateLargeList(50000, "next"), ]; - const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; + const receivedChunks: StreamListDiff<{ id: string; value: number }>[] = []; let chunkCount = 0; - const diffStream = streamListsDiff(prevList, nextList, "id", { + const diffStream = streamListDiff(prevList, nextList, "id", { chunksSize: 10_000, }); @@ -864,46 +864,4 @@ describe("Performance", () => { done(); }); }); - // it("should correctly stream diff for 1.000.000 entries", (done) => { - // const generateLargeList = (size: number, idPrefix: string) => { - // return Array.from({ length: size }, (_, i) => ({ - // id: `${idPrefix}-${i}`, - // value: i, - // })); - // }; - // const prevList = generateLargeList(1_000_000, "prev"); - // const nextList = [ - // ...generateLargeList(500_000, "prev"), - // ...generateLargeList(500_000, "next"), - // ]; - - // const receivedChunks: StreamListsDiff<{ id: string; value: number }>[] = []; - // let chunkCount = 0; - // const diffStream = streamListsDiff(prevList, nextList, "id", { - // chunksSize: 100_000, - // }); - - // diffStream.on("data", (chunk) => { - // receivedChunks.push(...chunk); - // chunkCount++; - // }); - - // diffStream.on("finish", () => { - // const deletions = receivedChunks.filter( - // (diff) => diff.status === LIST_STATUS.DELETED, - // ); - // const additions = receivedChunks.filter( - // (diff) => diff.status === LIST_STATUS.ADDED, - // ); - // const updates = receivedChunks.filter( - // (diff) => diff.status === LIST_STATUS.EQUAL, - // ); - // expect(receivedChunks.length).toBe(1_500_000); // 50.000 deletions + 50.000 equal + 50.000 additions - // expect(chunkCount).toBe(15); - // expect(deletions.length).toBe(500000); - // expect(additions.length).toBe(500000); - // expect(updates.length).toBe(500000); - // done(); - // }); - // }); }); diff --git a/src/models/stream/index.ts b/src/models/stream/index.ts index 0bda847..d932ebd 100644 --- a/src/models/stream/index.ts +++ b/src/models/stream/index.ts @@ -1,6 +1,6 @@ import { LIST_STATUS } from "@models/list"; -export type StreamListsDiff<T extends Record<string, unknown>> = { +export type StreamListDiff<T extends Record<string, unknown>> = { currentValue: T | null; previousValue: T | null; prevIndex: number | null; From f613071378fd5980a1fe1582c7ae9d675e8a4033 Mon Sep 17 00:00:00 2001 From: Antoine Lanoe <antoine.lanoe@meltwater.com> Date: Sun, 6 Oct 2024 20:15:07 +0200 Subject: [PATCH 8/8] chore: update package.json --- README.md | 135 +++++++++++++++--------- dist/index.d.mts | 107 ------------------- dist/index.d.ts | 107 ------------------- dist/index.js | 11 -- dist/index.mjs | 3 - package.json | 10 +- src/lib/object-diff/object-diff.test.ts | 2 +- src/models/list/index.ts | 4 +- src/models/stream/index.ts | 2 +- 9 files changed, 98 insertions(+), 283 deletions(-) delete mode 100644 dist/index.d.mts delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.js delete mode 100644 dist/index.mjs diff --git a/README.md b/README.md index bf84553..d19bbda 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ This library compares two arrays or objects and returns a full diff of their dif All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. -**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested, has zero dependencies, and is super fast. Import. Enjoy. đ +**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested, has zero dependencies, and is super fast. + +Import. Enjoy. đ <hr/> @@ -38,17 +40,21 @@ I am grateful to the generous donors of **Superdiff**! ## FEATURES -**Superdiff** exports 4 functions: +**Superdiff** exports 5 functions: ```ts -// Compares two objects and return a diff for each value and their potential subvalues +// Returns a complete diff of two objects getObjectDiff(prevObject, nextObject) -// Compares two arrays and returns a diff for each value + +// Returns a complete diff of two arrays getListDiff(prevList, nextList) + // Streams the diff of two object lists, ideal for large lists and maximum performance streamListDiff(prevList, nextList, referenceProperty) + // Checks whether two values are equal isEqual(dataA, dataB) + // Checks whether a value is an object isObject(data) ``` @@ -62,9 +68,9 @@ import { getObjectDiff } from "@donedeal0/superdiff"; Compares two objects and return a diff for each value and their potential subvalues. Supports deeply nested objects with any kind of values. -**Format** +#### FORMAT -input +**Input** ```ts prevData: Record<string, unknown>; @@ -78,8 +84,8 @@ options?: { } ``` -- `prevData`: the original object -- `nextData`: the new object +- `prevData`: the original object. +- `nextData`: the new object. - `options` - `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - `showOnly`: returns only the values whose status you are interested in. It takes two parameters: @@ -89,7 +95,7 @@ options?: { - `basic` returns only the main properties whose status matches your query. - `deep` can return main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly. -output +**Output** ```ts type ObjectDiff = { @@ -107,9 +113,9 @@ type Diff = { diff?: Diff[]; }; ``` -**Usage** +#### USAGE -input +**Input** ```diff getObjectDiff( @@ -134,7 +140,7 @@ getObjectDiff( ); ``` -output +**Output** ```diff { @@ -202,9 +208,9 @@ import { getListDiff } from "@donedeal0/superdiff"; Compares two arrays and returns a diff for each entry. Supports duplicate values, primitive values and objects. -**Format** +#### FORMAT -input +**Input** ```ts prevList: T[]; @@ -216,15 +222,15 @@ input considerMoveAsUpdate?: boolean // false by default } ``` -- `prevList`: the original list -- `nextList`: the new list +- `prevList`: the original list. +- `nextList`: the new list. - `options` - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). - `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes. - `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - - `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`. + - `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`. -output +**Output** ```ts type ListDiff = { @@ -239,9 +245,9 @@ type ListDiff = { }[]; }; ``` -**Usage** +#### USAGE -input +**Input** ```diff getListDiff( @@ -250,7 +256,7 @@ getListDiff( ); ``` -output +**Output** ```diff { @@ -297,7 +303,7 @@ output ``` <hr/> -### streamListDiff() +### streamListDiff() ```js import { streamListDiff } from "@donedeal0/superdiff"; @@ -305,16 +311,16 @@ import { streamListDiff } from "@donedeal0/superdiff"; Streams the diff of two object lists, ideal for large lists and maximum performance. -**Format** +#### FORMAT -input +**Input** ```ts - prevList: T[], - nextList: T[], - referenceProperty: ReferenceProperty<T>, + prevList: Record<string, unknown>[], + nextList: Record<string, unknown>[], + referenceProperty: keyof Record<string, unknown>, options: { - showOnly?: returns only the values whose status you are interested in. (e.g. `["added", "equal"]`), // [] by default + showOnly?: ("added" | "deleted" | "moved" | "updated" | "equal")[], // [] by default chunksSize?: number, // // 0 by default considerMoveAsUpdate? boolean; // false by default } @@ -324,13 +330,32 @@ input - `nextList`: the new object list. - `referenceProperty`: a common property in all the objects of your lists (e.g. `id`). - `options` - - `chunksSize` the number of object diffs returned by each stream chunk. If set to `0`, each stream will return a single object diff. If set to `10` each stream will return 10 object diffs. + - `chunksSize` the number of object diffs returned by each streamed chunk. (e.g. `0` = 1 object diff by chunk, `10` = 10 object diffs by chunk). - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). - - `considerMoveAsUpdate`: if set to `true` the `moved` value will be considered as `updated`. + - `considerMoveAsUpdate`: if set to `true` a `moved` value will be considered as `updated`. + +**Output** -output +The objects diff are grouped in arrays - called `chunks` - and are consumed thanks to an event listener. You have access to 3 events: + - `data`: to be notified when a new chunk of object diffs is available. + - `finish`: to be notified when the stream is complete. + - `error`: to be notified of an error during the stream. ```ts +interface StreamListener<T extends Record<string, unknown>> { + on<E extends keyof EmitterEvents<T>>( + event: E, + listener: Listener<EmitterEvents<T>[E]>, + ): this; +} + +type EmitterEvents<T extends Record<string, unknown>> = { + data: [StreamListDiff<T>[]]; + error: [Error]; + finish: []; +}; + + type StreamListDiff<T extends Record<string, unknown>> = { currentValue: T | null; previousValue: T | null; @@ -341,9 +366,9 @@ type StreamListDiff<T extends Record<string, unknown>> = { }; ``` -**Usage** +#### USAGE -input +**Input** ```diff const diff = streamListDiff( @@ -362,7 +387,7 @@ const diff = streamListDiff( ); ``` -output +**Output** ```diff diff.on("data", (chunk) => { @@ -407,7 +432,7 @@ diff.on("data", (chunk) => { }); diff.on("finish", () => console.log("The full diff is available")) -diff.on("error", (err)=> console.log(err)) +diff.on("error", (err) => console.log(err)) ``` <hr/> @@ -419,19 +444,22 @@ import { isEqual } from "@donedeal0/superdiff"; Checks whether two values are equal. -**Options** +#### FORMAT -You can add a third `options` parameter to `isEqual`. +**Input** ```ts -{ - ignoreArrayOrder?: boolean // false by default, -} +a: unknown, +b: unknown, +options: { + ignoreArrayOrder: boolean; // false by default + }, ``` - +- `a`: the value to compare to the value `b`. +- `b`: the value that will be compared to the value `a`. - `ignoreArrayOrder`: if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. -**Usage** +#### USAGE ```ts @@ -447,7 +475,7 @@ isEqual( ); ``` -output +**Output** ```ts false; @@ -462,15 +490,25 @@ import { isObject } from "@donedeal0/superdiff"; Tests whether a value is an object. -**Usage** +#### FORMAT + +**Input** + +```ts +value: unknown; +``` + +- `value`: the value whose type will be checked. -input +#### USAGE + +**Input** ```ts isObject(["hello", "world"]); ``` -output +**Output** ```ts false; @@ -478,7 +516,8 @@ false; <hr/> -### More examples are available in the source code tests. +### âšī¸ More examples are available in the source code tests. + <hr/> @@ -498,4 +537,4 @@ If you or your company uses **Superdiff**, please show your support by becoming ## CONTRIBUTING -Pull requests are welcome! +Issues and pull requests are welcome! diff --git a/dist/index.d.mts b/dist/index.d.mts deleted file mode 100644 index b2559e6..0000000 --- a/dist/index.d.mts +++ /dev/null @@ -1,107 +0,0 @@ -declare const STATUS: Record<string, ObjectDiffStatus>; -declare const LIST_STATUS: Record<string, ListDiffStatus>; -declare const GRANULARITY: Record<string, "basic" | "deep">; -type ListDiffStatus = "added" | "equal" | "moved" | "deleted" | "updated"; -type ObjectDiffStatus = "added" | "equal" | "deleted" | "updated"; -type ObjectData = Record<string, any> | undefined | null; -type ListData = any; -type ObjectStatusTuple = readonly [ - "added", - "equal", - "deleted", - "updated" -]; -type ListStatusTuple = readonly [ - "added", - "equal", - "deleted", - "moved", - "updated" -]; -type isEqualOptions = { - ignoreArrayOrder?: boolean; -}; -type ObjectOptions = { - ignoreArrayOrder?: boolean; - showOnly?: { - statuses: Array<ObjectStatusTuple[number]>; - granularity?: (typeof GRANULARITY)[keyof typeof GRANULARITY]; - }; -}; -type ListOptions = { - showOnly?: Array<ListStatusTuple[number]>; - referenceProperty?: string; - considerMoveAsUpdate?: boolean; - ignoreArrayOrder?: boolean; -}; -type ListDiff = { - type: "list"; - status: ListDiffStatus; - diff: { - value: ListData; - prevIndex: number | null; - newIndex: number | null; - indexDiff: number | null; - status: ListDiffStatus; - }[]; -}; -type SubProperties = { - property: string; - previousValue: any; - currentValue: any; - status: ObjectDiffStatus; - subPropertiesDiff?: SubProperties[]; -}; -type ObjectDiff = { - type: "object"; - status: ObjectDiffStatus; - diff: { - property: string; - previousValue: any; - currentValue: any; - status: ObjectDiffStatus; - subPropertiesDiff?: SubProperties[]; - }[]; -}; -type DataDiff = ListDiff | ObjectDiff; - -/** - * Returns the diff between two objects - * @param {Record<string, any>} prevData - The original object. - * @param {Record<string, any>} nextData - The new object. - * * @param {ListOptions} options - Options to refine your output. - - `showOnly`: returns only the values whose status you are interested in. It takes two parameters: `statuses` and `granularity` - `statuses` are the status you want to see in the output (e.g. `["added", "equal"]`) - `granularity` can be either `basic` (to return only the main properties whose status matches your query) or `deep` (to return the main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly). - - `ignoreArrayOrder` if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - * @returns ObjectDiff - */ -declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff; - -/** - * Returns the diff between two arrays - * @param {Array<T>} prevList - The original array. - * @param {Array<T>} nextList - The new array. - * @param {ListOptions} options - Options to refine your output. - - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). - - `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes. - * @returns ListDiff - */ -declare const getListDiff: <T>(prevList: T[] | undefined | null, nextList: T[] | undefined | null, options?: ListOptions) => ListDiff; - -/** - * Returns true if two data are equal - * @param {any} a - The original data. - * @param {any} b - The data to compare. - * @param {isEqualOptions} options - The options to compare the data. - * @returns boolean - */ -declare function isEqual(a: any, b: any, options?: isEqualOptions): boolean; -/** - * Returns true if the provided value is an object - * @param {any} value - The data to check. - * @returns value is Record<string, any> - */ -declare function isObject(value: any): value is Record<string, any>; - -export { type DataDiff, GRANULARITY, LIST_STATUS, type ListData, type ListDiff, type ListDiffStatus, type ListOptions, type ListStatusTuple, type ObjectData, type ObjectDiff, type ObjectDiffStatus, type ObjectOptions, type ObjectStatusTuple, STATUS, type SubProperties, getListDiff, getObjectDiff, isEqual, type isEqualOptions, isObject }; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index b2559e6..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,107 +0,0 @@ -declare const STATUS: Record<string, ObjectDiffStatus>; -declare const LIST_STATUS: Record<string, ListDiffStatus>; -declare const GRANULARITY: Record<string, "basic" | "deep">; -type ListDiffStatus = "added" | "equal" | "moved" | "deleted" | "updated"; -type ObjectDiffStatus = "added" | "equal" | "deleted" | "updated"; -type ObjectData = Record<string, any> | undefined | null; -type ListData = any; -type ObjectStatusTuple = readonly [ - "added", - "equal", - "deleted", - "updated" -]; -type ListStatusTuple = readonly [ - "added", - "equal", - "deleted", - "moved", - "updated" -]; -type isEqualOptions = { - ignoreArrayOrder?: boolean; -}; -type ObjectOptions = { - ignoreArrayOrder?: boolean; - showOnly?: { - statuses: Array<ObjectStatusTuple[number]>; - granularity?: (typeof GRANULARITY)[keyof typeof GRANULARITY]; - }; -}; -type ListOptions = { - showOnly?: Array<ListStatusTuple[number]>; - referenceProperty?: string; - considerMoveAsUpdate?: boolean; - ignoreArrayOrder?: boolean; -}; -type ListDiff = { - type: "list"; - status: ListDiffStatus; - diff: { - value: ListData; - prevIndex: number | null; - newIndex: number | null; - indexDiff: number | null; - status: ListDiffStatus; - }[]; -}; -type SubProperties = { - property: string; - previousValue: any; - currentValue: any; - status: ObjectDiffStatus; - subPropertiesDiff?: SubProperties[]; -}; -type ObjectDiff = { - type: "object"; - status: ObjectDiffStatus; - diff: { - property: string; - previousValue: any; - currentValue: any; - status: ObjectDiffStatus; - subPropertiesDiff?: SubProperties[]; - }[]; -}; -type DataDiff = ListDiff | ObjectDiff; - -/** - * Returns the diff between two objects - * @param {Record<string, any>} prevData - The original object. - * @param {Record<string, any>} nextData - The new object. - * * @param {ListOptions} options - Options to refine your output. - - `showOnly`: returns only the values whose status you are interested in. It takes two parameters: `statuses` and `granularity` - `statuses` are the status you want to see in the output (e.g. `["added", "equal"]`) - `granularity` can be either `basic` (to return only the main properties whose status matches your query) or `deep` (to return the main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly). - - `ignoreArrayOrder` if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order. - * @returns ObjectDiff - */ -declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff; - -/** - * Returns the diff between two arrays - * @param {Array<T>} prevList - The original array. - * @param {Array<T>} nextList - The new array. - * @param {ListOptions} options - Options to refine your output. - - `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`). - - `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes. - * @returns ListDiff - */ -declare const getListDiff: <T>(prevList: T[] | undefined | null, nextList: T[] | undefined | null, options?: ListOptions) => ListDiff; - -/** - * Returns true if two data are equal - * @param {any} a - The original data. - * @param {any} b - The data to compare. - * @param {isEqualOptions} options - The options to compare the data. - * @returns boolean - */ -declare function isEqual(a: any, b: any, options?: isEqualOptions): boolean; -/** - * Returns true if the provided value is an object - * @param {any} value - The data to check. - * @returns value is Record<string, any> - */ -declare function isObject(value: any): value is Record<string, any>; - -export { type DataDiff, GRANULARITY, LIST_STATUS, type ListData, type ListDiff, type ListDiffStatus, type ListOptions, type ListStatusTuple, type ObjectData, type ObjectDiff, type ObjectDiffStatus, type ObjectOptions, type ObjectStatusTuple, STATUS, type SubProperties, getListDiff, getObjectDiff, isEqual, type isEqualOptions, isObject }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index b527bb3..0000000 --- a/dist/index.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},c={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function D(e,t,i={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)&&Array.isArray(t)?e.length!==t.length?!1:i.ignoreArrayOrder?e.every(f=>t.some(n=>JSON.stringify(n)===JSON.stringify(f))):e.every((f,n)=>JSON.stringify(f)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function O(e,t={statuses:[],granularity:y.BASIC}){let{statuses:i,granularity:f}=t;return e.reduce((n,r)=>{if(f===y.DEEP&&r.subPropertiesDiff){let s=O(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(f===y.DEEP&&r.subDiff){let s=O(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return i.includes(r.status)?[...n,r]:n},[])}function E(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function A(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let f=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,d])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:d,currentValue:t===u.ADDED?d:void 0,status:t});}),f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:t,diff:O(f,i.showOnly)}:{type:"object",status:t,diff:f}}function P(e,t,i){if(!e)return;let f=Object.entries(e).find(([n])=>D(n,t,i));return f?f[1]:void 0}function j(e,t,i){return D(e,t,i)?u.EQUAL:u.UPDATED}function U(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function g(e,t){if(!e)return;let i=Object.keys(e),f=Object.keys(t),n=i.filter(r=>!f.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function S(e,t,i){let f=[],n,r=g(e,t);return r&&r.forEach(s=>{f.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let d=P(e,s,i);if(!d)return f.push({property:s,previousValue:d,currentValue:o,status:!e||!(s in e)?u.ADDED:d===o?u.EQUAL:u.UPDATED});if(p(o)){let a=S(d,o,i);a&&a.length>0&&(n=a);}d&&f.push({property:s,previousValue:d,currentValue:o,status:j(d,o,i),...!!n&&{subDiff:n}});}),f}function m(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return A(t,u.ADDED,i);if(!t)return A(e,u.DELETED,i);let f=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return f.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let d=S(o,s,i),a=U(d);return f.push({property:r,previousValue:o,currentValue:s,status:a,...a!==u.EQUAL&&{subPropertiesDiff:d}})}return f.push({property:r,previousValue:o,currentValue:s,status:j(o,s,i)})});let n=g(e,t);return n&&n.forEach(r=>{f.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:E(f),diff:O(f,i.showOnly)}:{type:"object",status:E(f),diff:f}}function w(e,t=[]){return e.filter(i=>t?.includes(i.status))}function h(e,t,i={showOnly:[]}){let f=e.map((n,r)=>({value:n,prevIndex:t===c.ADDED?null:r,newIndex:t===c.ADDED?r:null,indexDiff:null,status:t}));return i.showOnly&&i.showOnly.length>0?{type:"list",status:t,diff:f.filter(n=>i.showOnly?.includes(n.status))}:{type:"list",status:t,diff:f}}function L(e){return e.some(t=>t.status!==c.EQUAL)?c.UPDATED:c.EQUAL}function T(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var I=(e,t,i={showOnly:[],referenceProperty:void 0,considerMoveAsUpdate:!1,ignoreArrayOrder:!1})=>{if(!e&&!t)return {type:"list",status:c.EQUAL,diff:[]};if(!e)return h(t,c.ADDED,i);if(!t)return h(e,c.DELETED,i);let f=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((a,b)=>T(a,i.referenceProperty)?p(r)?D(a[i.referenceProperty],r[i.referenceProperty])&&!n.includes(b):!1:D(a,r)&&!n.includes(b));o>-1&&n.push(o);let d=o===-1?null:s-o;if(d===0||i.ignoreArrayOrder){let a=c.EQUAL;return T(r,i.referenceProperty)&&(D(e[o],r)||(a=c.UPDATED)),f.push({value:r,prevIndex:o,newIndex:s,indexDiff:d,status:a})}return o===-1?f.push({value:r,prevIndex:null,newIndex:s,indexDiff:d,status:c.ADDED}):f.push({value:r,prevIndex:o,newIndex:s,indexDiff:d,status:i.considerMoveAsUpdate?c.UPDATED:c.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return f.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:c.DELETED})}),i.showOnly&&i?.showOnly?.length>0?{type:"list",status:L(f),diff:w(f,i.showOnly)}:{type:"list",status:L(f),diff:f}}; - -exports.GRANULARITY = y; -exports.LIST_STATUS = c; -exports.STATUS = u; -exports.getListDiff = I; -exports.getObjectDiff = m; -exports.isEqual = D; -exports.isObject = p; diff --git a/dist/index.mjs b/dist/index.mjs deleted file mode 100644 index 68cdd2c..0000000 --- a/dist/index.mjs +++ /dev/null @@ -1,3 +0,0 @@ -var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},c={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function D(e,t,i={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)&&Array.isArray(t)?e.length!==t.length?!1:i.ignoreArrayOrder?e.every(f=>t.some(n=>JSON.stringify(n)===JSON.stringify(f))):e.every((f,n)=>JSON.stringify(f)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function b(e,t={statuses:[],granularity:y.BASIC}){let{statuses:i,granularity:f}=t;return e.reduce((n,r)=>{if(f===y.DEEP&&r.subPropertiesDiff){let s=b(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(f===y.DEEP&&r.subDiff){let s=b(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return i.includes(r.status)?[...n,r]:n},[])}function A(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function j(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let f=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,d])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:d,currentValue:t===u.ADDED?d:void 0,status:t});}),f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:t,diff:b(f,i.showOnly)}:{type:"object",status:t,diff:f}}function U(e,t,i){if(!e)return;let f=Object.entries(e).find(([n])=>D(n,t,i));return f?f[1]:void 0}function g(e,t,i){return D(e,t,i)?u.EQUAL:u.UPDATED}function m(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function S(e,t){if(!e)return;let i=Object.keys(e),f=Object.keys(t),n=i.filter(r=>!f.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function h(e,t,i){let f=[],n,r=S(e,t);return r&&r.forEach(s=>{f.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let d=U(e,s,i);if(!d)return f.push({property:s,previousValue:d,currentValue:o,status:!e||!(s in e)?u.ADDED:d===o?u.EQUAL:u.UPDATED});if(p(o)){let a=h(d,o,i);a&&a.length>0&&(n=a);}d&&f.push({property:s,previousValue:d,currentValue:o,status:g(d,o,i),...!!n&&{subDiff:n}});}),f}function w(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return j(t,u.ADDED,i);if(!t)return j(e,u.DELETED,i);let f=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return f.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let d=h(o,s,i),a=m(d);return f.push({property:r,previousValue:o,currentValue:s,status:a,...a!==u.EQUAL&&{subPropertiesDiff:d}})}return f.push({property:r,previousValue:o,currentValue:s,status:g(o,s,i)})});let n=S(e,t);return n&&n.forEach(r=>{f.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:A(f),diff:b(f,i.showOnly)}:{type:"object",status:A(f),diff:f}}function I(e,t=[]){return e.filter(i=>t?.includes(i.status))}function L(e,t,i={showOnly:[]}){let f=e.map((n,r)=>({value:n,prevIndex:t===c.ADDED?null:r,newIndex:t===c.ADDED?r:null,indexDiff:null,status:t}));return i.showOnly&&i.showOnly.length>0?{type:"list",status:t,diff:f.filter(n=>i.showOnly?.includes(n.status))}:{type:"list",status:t,diff:f}}function T(e){return e.some(t=>t.status!==c.EQUAL)?c.UPDATED:c.EQUAL}function P(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var R=(e,t,i={showOnly:[],referenceProperty:void 0,considerMoveAsUpdate:!1,ignoreArrayOrder:!1})=>{if(!e&&!t)return {type:"list",status:c.EQUAL,diff:[]};if(!e)return L(t,c.ADDED,i);if(!t)return L(e,c.DELETED,i);let f=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((a,E)=>P(a,i.referenceProperty)?p(r)?D(a[i.referenceProperty],r[i.referenceProperty])&&!n.includes(E):!1:D(a,r)&&!n.includes(E));o>-1&&n.push(o);let d=o===-1?null:s-o;if(d===0||i.ignoreArrayOrder){let a=c.EQUAL;return P(r,i.referenceProperty)&&(D(e[o],r)||(a=c.UPDATED)),f.push({value:r,prevIndex:o,newIndex:s,indexDiff:d,status:a})}return o===-1?f.push({value:r,prevIndex:null,newIndex:s,indexDiff:d,status:c.ADDED}):f.push({value:r,prevIndex:o,newIndex:s,indexDiff:d,status:i.considerMoveAsUpdate?c.UPDATED:c.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return f.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:c.DELETED})}),i.showOnly&&i?.showOnly?.length>0?{type:"list",status:T(f),diff:I(f,i.showOnly)}:{type:"list",status:T(f),diff:f}}; - -export { y as GRANULARITY, c as LIST_STATUS, u as STATUS, R as getListDiff, w as getObjectDiff, D as isEqual, p as isObject }; diff --git a/package.json b/package.json index 61b0065..02202ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@donedeal0/superdiff", - "version": "2.0.0", + "version": "2.1.0", "description": "SuperDiff checks the changes between two objects or arrays. It returns a complete diff with relevant information for each property or piece of data", "main": "dist/index.js", "module": "dist/index.mjs", @@ -18,6 +18,10 @@ "bugs": { "url": "https://github.com/DoneDeal0/superdiff/issues" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/DoneDeal0" + }, "readme": "./README.md", "release": { "branches": [ @@ -57,7 +61,9 @@ "diff", "deep-diff", "comparison", - "compare" + "compare", + "stream", + "streaming" ], "scripts": { "build": "tsup", diff --git a/src/lib/object-diff/object-diff.test.ts b/src/lib/object-diff/object-diff.test.ts index ab38818..62295c3 100644 --- a/src/lib/object-diff/object-diff.test.ts +++ b/src/lib/object-diff/object-diff.test.ts @@ -1,4 +1,4 @@ -import { GRANULARITY, OBJECT_STATUS } from "../../models/object"; +import { GRANULARITY, OBJECT_STATUS } from "@models/object"; import { getObjectDiff } from "."; describe("getObjectDiff", () => { diff --git a/src/models/list/index.ts b/src/models/list/index.ts index 79fc9ba..8616faa 100644 --- a/src/models/list/index.ts +++ b/src/models/list/index.ts @@ -6,8 +6,6 @@ export enum LIST_STATUS { MOVED = "moved", } -export type ListData = unknown; - export type ListDiffOptions = { showOnly?: `${LIST_STATUS}`[]; referenceProperty?: string; @@ -26,7 +24,7 @@ export type ListDiff = { type: "list"; status: LIST_STATUS; diff: { - value: ListData; + value: unknown; prevIndex: number | null; newIndex: number | null; indexDiff: number | null; diff --git a/src/models/stream/index.ts b/src/models/stream/index.ts index d932ebd..d5d0df4 100644 --- a/src/models/stream/index.ts +++ b/src/models/stream/index.ts @@ -17,7 +17,7 @@ export type StreamReferences<T extends Record<string, unknown>> = Map< >; export type ListStreamOptions = { - chunksSize?: number; // 0 by default. If 0, stream will be live + chunksSize?: number; // 0 by default. showOnly?: `${LIST_STATUS}`[]; considerMoveAsUpdate?: boolean; };