diff --git a/.cursor/rules/asyncapi-testing.mdc b/.cursor/rules/asyncapi-testing.mdc new file mode 100644 index 0000000..ce1fdd9 --- /dev/null +++ b/.cursor/rules/asyncapi-testing.mdc @@ -0,0 +1,105 @@ +--- +description: AsyncAPI test writing standards and patterns +globs: test/**/*.test.ts +alwaysApply: false +--- + +# AsyncAPI Test Writing Standards + +## 1. Validate Test Specifications + +**All test specs MUST be valid AsyncAPI specifications** unless intentionally testing error handling. + +### Pattern: Valid Specifications + +```typescript +// ✅ GOOD: Define spec as constant, then validate +const before = { + asyncapi: '3.0.0', + info: { + title: 'Test API', + version: '1.0.0', + }, + channels: { + testChannel: {} + } +} +... + +await parseAsyncApiAndAssertValid(before) +await parseAsyncApiAndAssertValid(after) + +const { merged, diffs } = apiDiff(before, after, COMPARE_OPTIONS) +// ... assertions +``` + +### Pattern: Intentionally Invalid Specifications + +When testing error handling or invalid specs, **clearly document with a comment**: + +```typescript +// ✅ GOOD: Commented validation with explanation +const source = { + asyncapi: '3.0.0', + // Missing required 'info' field +} + +// await parseAsyncApiAndAssertValid(source) // intentionally not valid source + +const result = unify(source, { unify: true }) +// ... test error handling +``` + +## 2. Use Helper Functions for Complex Specs + +Create well-documented helper functions to reduce boilerplate: + +```typescript +/** + * Helper function to create AsyncAPI document with schema in components + * @param schemaDefinition - The schema definition + * @param schemaFormat - Optional schema format: + * - undefined: creates regular schema (default) + * - string value: creates Multi Format Schema with schemaFormat property + */ +const createAsyncAPIWithSchema = ( + schemaDefinition: unknown, + schemaFormat?: string +) => { + return { + asyncapi: '3.0.0', + info: { + title: 'Test API', + version: '1.0.0', + }, + components: { + schemas: { + TestSchema: schemaFormat + ? { schemaFormat, schema: schemaDefinition } + : schemaDefinition, + }, + }, + } +} +``` + +## 3. Required Fields for Valid Specs + +All valid AsyncAPI 3.0.0 specs must include: + +```typescript +{ + asyncapi: '3.0.0', // Required + info: { // Required + title: 'Test API', // Required + version: '1.0.0', // Required + }, + // ... other properties +} +``` + +## 4. Test Organization + +- Define `COMPARE_OPTIONS` constants at the top of describe blocks +- Keep spec definitions close to where they're used +- Use descriptive variable names (`before`, `after`, or domain-specific names) diff --git a/package-lock.json b/package-lock.json index 3ac97da..9c377e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,13 @@ "name": "@netcracker/qubership-apihub-api-diff", "version": "3.0.1", "dependencies": { - "@netcracker/qubership-apihub-api-unifier": "2.5.0", + "@netcracker/qubership-apihub-api-unifier": "dev", "@netcracker/qubership-apihub-json-crawl": "1.2.0", "fast-equals": "6.0.0" }, "devDependencies": { - "@netcracker/qubership-apihub-compatibility-suites": "2.4.0", + "@asyncapi/parser": "3.4.0", + "@netcracker/qubership-apihub-compatibility-suites": "dev", "@netcracker/qubership-apihub-graphapi": "1.0.9", "@netcracker/qubership-apihub-npm-gitflow": "3.1.0", "@types/jest": "30.0.0", @@ -40,6 +41,96 @@ "vite-plugin-singlefile": "2.3.0" } }, + "node_modules/@asyncapi/parser": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-3.4.0.tgz", + "integrity": "sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@asyncapi/specs": "^6.8.0", + "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", + "@stoplight/json": "3.21.0", + "@stoplight/json-ref-readers": "^1.2.2", + "@stoplight/json-ref-resolver": "^3.1.5", + "@stoplight/spectral-core": "^1.18.3", + "@stoplight/spectral-functions": "^1.7.2", + "@stoplight/spectral-parsers": "^1.0.2", + "@stoplight/spectral-ref-resolver": "^1.0.3", + "@stoplight/types": "^13.12.0", + "@types/json-schema": "^7.0.11", + "@types/urijs": "^1.19.19", + "ajv": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "avsc": "^5.7.5", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.0.0", + "node-fetch": "2.6.7" + } + }, + "node_modules/@asyncapi/parser/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@asyncapi/parser/node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/@asyncapi/parser/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@asyncapi/parser/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asyncapi/specs": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-6.11.1.tgz", + "integrity": "sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.11" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1875,6 +1966,45 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/ternary": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/ternary/-/ternary-1.1.4.tgz", + "integrity": "sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -2064,9 +2194,9 @@ } }, "node_modules/@netcracker/qubership-apihub-api-unifier": { - "version": "2.5.0", - "resolved": "https://npm.pkg.github.com/download/@netcracker/qubership-apihub-api-unifier/2.5.0/e34184b7ff40cc40f81c65b179379fbfffa2f5a5", - "integrity": "sha512-ZPhoyBsoHbPRU3bOB1TnC1vs9VMrz3RI831fiGVF9CFdHo3rvSi3hC/aq/p52uON2N771Czq54UdIBqXoBnGTw==", + "version": "2.5.1-dev.20260318081635", + "resolved": "https://npm.pkg.github.com/download/@netcracker/qubership-apihub-api-unifier/2.5.1-dev.20260318081635/9eac01d9e5b573cc4c27299d82e2f2140ec88ac9", + "integrity": "sha512-UIjh0ZW0A64OpFYTGHRKydTJD5UT1xmtqHqMm4Gv8I7Q+WGIyHyPHGtQb9vWd2J18RTNbzHfnF1v2X9SyGJh3A==", "dependencies": { "@netcracker/qubership-apihub-json-crawl": "1.2.0", "fast-equals": "4.0.3", @@ -2081,9 +2211,9 @@ "license": "MIT" }, "node_modules/@netcracker/qubership-apihub-compatibility-suites": { - "version": "2.4.0", - "resolved": "https://npm.pkg.github.com/download/@netcracker/qubership-apihub-compatibility-suites/2.4.0/cc7744d33ada45bc8c904511cda2fd0ce8c68e5c", - "integrity": "sha512-P4Xypdhk3oHvjlb7dwUtDRyRXvQgMtR6gBa4MOPsu4yp/x5j1mOnPPRLMtO2mPwKUHtyJKE2MTTpSXcd6UJKhA==", + "version": "2.4.1-dev.20260220135014", + "resolved": "https://npm.pkg.github.com/download/@netcracker/qubership-apihub-compatibility-suites/2.4.1-dev.20260220135014/f889426c02d15db2d7a9b3ccd63a81ed58bcf49d", + "integrity": "sha512-8e6e/GeiTyhqa1MixvKERjqm0JfNsoDTI548eUxEZATw4C2HwyExlmSZngTX7Brw2j2B61sF61jPHYeCvddr8g==", "dev": true, "license": "Apache-2.0" }, @@ -2167,6 +2297,16 @@ "node": ">= 8" } }, + "node_modules/@openapi-contrib/openapi-schema-to-json-schema": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.2.0.tgz", + "integrity": "sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2789,157 +2929,674 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "node_modules/@stoplight/json": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.21.0.tgz", + "integrity": "sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.3", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^13.6.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + }, + "engines": { + "node": ">=8.3.0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "node_modules/@stoplight/json-ref-readers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-readers/-/json-ref-readers-1.2.2.tgz", + "integrity": "sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "node-fetch": "^2.6.0", + "tslib": "^1.14.1" + }, + "engines": { + "node": ">=8.3.0" + } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@stoplight/json-ref-resolver": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.6.tgz", + "integrity": "sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "@stoplight/json": "^3.21.0", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^12.3.0 || ^13.0.0", + "@types/urijs": "^1.19.19", + "dependency-graph": "~0.11.0", + "fast-memoize": "^2.5.2", + "immer": "^9.0.6", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "urijs": "^1.19.11" + }, + "engines": { + "node": ">=8.3.0" } }, - "node_modules/@tybys/wasm-util/node_modules/tslib": { + "node_modules/@stoplight/json-ref-resolver/node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, - "node_modules/@types/argparse": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", - "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "node_modules/@stoplight/ordered-object-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz", + "integrity": "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@stoplight/spectral-core": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.21.0.tgz", + "integrity": "sha512-oj4e/FrDLUhBRocIW+lRMKlJ/q/rDZw61HkLbTFsdMd+f/FTkli2xHNB1YC6n1mrMKjjvy7XlUuFkC7XxtgbWw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.0.0" + "@stoplight/better-ajv-errors": "1.0.3", + "@stoplight/json": "~3.21.0", + "@stoplight/path": "1.3.2", + "@stoplight/spectral-parsers": "^1.0.0", + "@stoplight/spectral-ref-resolver": "^1.0.4", + "@stoplight/spectral-runtime": "^1.1.2", + "@stoplight/types": "~13.6.0", + "@types/es-aggregate-error": "^1.0.2", + "@types/json-schema": "^7.0.11", + "ajv": "^8.17.1", + "ajv-errors": "~3.0.0", + "ajv-formats": "~2.1.1", + "es-aggregate-error": "^1.0.7", + "jsonpath-plus": "^10.3.0", + "lodash": "~4.17.23", + "lodash.topath": "^4.5.2", + "minimatch": "3.1.2", + "nimma": "0.2.3", + "pony-cause": "^1.1.1", + "simple-eval": "1.0.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "node_modules/@stoplight/spectral-core/node_modules/@stoplight/better-ajv-errors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", + "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": "^12.20 || >= 14.13" + }, + "peerDependencies": { + "ajv": ">=8" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@stoplight/spectral-core/node_modules/@stoplight/types": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", + "integrity": "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@stoplight/spectral-core/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "node_modules/@stoplight/spectral-core/node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "peerDependencies": { + "ajv": "^8.0.1" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/@stoplight/spectral-core/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@stoplight/spectral-core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "node_modules/@stoplight/spectral-core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT" + "license": "0BSD" }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@stoplight/spectral-formats": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-formats/-/spectral-formats-1.8.2.tgz", + "integrity": "sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@stoplight/json": "^3.17.0", + "@stoplight/spectral-core": "^1.19.2", + "@types/json-schema": "^7.0.7", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@stoplight/spectral-formats/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@stoplight/spectral-functions": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-functions/-/spectral-functions-1.10.1.tgz", + "integrity": "sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/better-ajv-errors": "1.0.3", + "@stoplight/json": "^3.17.1", + "@stoplight/spectral-core": "^1.19.4", + "@stoplight/spectral-formats": "^1.8.1", + "@stoplight/spectral-runtime": "^1.1.2", + "ajv": "^8.17.1", + "ajv-draft-04": "~1.0.0", + "ajv-errors": "~3.0.0", + "ajv-formats": "~2.1.1", + "lodash": "~4.17.21", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/@stoplight/better-ajv-errors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", + "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": "^12.20 || >= 14.13" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@stoplight/spectral-functions/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stoplight/spectral-functions/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@stoplight/spectral-parsers": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-parsers/-/spectral-parsers-1.0.5.tgz", + "integrity": "sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/json": "~3.21.0", + "@stoplight/types": "^14.1.1", + "@stoplight/yaml": "~4.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "node_modules/@stoplight/spectral-parsers/node_modules/@stoplight/types": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", + "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/spectral-parsers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@stoplight/spectral-ref-resolver": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-ref-resolver/-/spectral-ref-resolver-1.0.5.tgz", + "integrity": "sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/json-ref-readers": "1.2.2", + "@stoplight/json-ref-resolver": "~3.1.6", + "@stoplight/spectral-runtime": "^1.1.2", + "dependency-graph": "0.11.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "node_modules/@stoplight/spectral-ref-resolver/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@stoplight/spectral-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-runtime/-/spectral-runtime-1.1.4.tgz", + "integrity": "sha512-YHbhX3dqW0do6DhiPSgSGQzr6yQLlWybhKwWx0cqxjMwxej3TqLv3BXMfIUYFKKUqIwH4Q2mV8rrMM8qD2N0rQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/json": "^3.20.1", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^13.6.0", + "abort-controller": "^3.0.0", + "lodash": "^4.17.21", + "node-fetch": "^2.7.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "node_modules/@stoplight/spectral-runtime/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@stoplight/spectral-runtime/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@stoplight/types": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.20.0.tgz", + "integrity": "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.3.0.tgz", + "integrity": "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.5", + "@stoplight/types": "^14.1.1", + "@stoplight/yaml-ast-parser": "0.0.50", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=10.8" + } + }, + "node_modules/@stoplight/yaml-ast-parser": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.50.tgz", + "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@stoplight/yaml/node_modules/@stoplight/types": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", + "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stoplight/yaml/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tybys/wasm-util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/es-aggregate-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.6.tgz", + "integrity": "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2969,8 +3626,7 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/node": { "version": "25.0.3", @@ -2989,6 +3645,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/urijs": { + "version": "1.19.26", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.26.tgz", + "integrity": "sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3829,6 +4492,19 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -4035,6 +4711,23 @@ "node": ">=6" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -4045,6 +4738,74 @@ "node": ">=8" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avsc": { + "version": "5.7.9", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.9.tgz", + "integrity": "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.11" + } + }, "node_modules/babel-jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", @@ -4248,6 +5009,56 @@ "node": ">=0.10.0" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4476,6 +5287,60 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -4516,21 +5381,67 @@ } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6.0" } }, "node_modules/detect-newline": { @@ -4579,6 +5490,21 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4664,6 +5590,118 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-aggregate-error": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.14.tgz", + "integrity": "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "globalthis": "^1.0.4", + "has-property-descriptors": "^1.0.2", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", @@ -4672,6 +5710,53 @@ "license": "MIT", "peer": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", @@ -4948,6 +6033,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5085,6 +6180,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", @@ -5223,6 +6325,22 @@ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -5300,6 +6418,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5320,6 +6479,31 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -5330,6 +6514,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -5343,6 +6541,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5402,6 +6618,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5423,6 +6656,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5469,6 +6715,19 @@ "uglify-js": "^3.1.4" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5479,6 +6738,64 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5529,6 +6846,17 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5605,6 +6933,39 @@ "dev": true, "license": "ISC" }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5612,6 +6973,72 @@ "dev": true, "license": "MIT" }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -5628,6 +7055,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5638,6 +7100,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5648,85 +7126,300 @@ "node": ">=8" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-primitive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", - "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6603,6 +8296,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6657,6 +8360,13 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -6670,6 +8380,35 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6764,9 +8503,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -6791,6 +8530,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.topath": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", + "integrity": "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6857,6 +8603,16 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7064,6 +8820,47 @@ "dev": true, "license": "MIT" }, + "node_modules/nimma": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", + "integrity": "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsep-plugin/regex": "^1.0.1", + "@jsep-plugin/ternary": "^1.0.2", + "astring": "^1.8.1", + "jsep": "^1.2.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + }, + "optionalDependencies": { + "jsonpath-plus": "^6.0.1 || ^10.1.0", + "lodash.topath": "^4.5.2" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7110,6 +8907,50 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7161,6 +9002,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7438,6 +9297,26 @@ "pathe": "^2.0.3" } }, + "node_modules/pony-cause": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-1.1.1.tgz", + "integrity": "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==", + "dev": true, + "license": "0BSD", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -7598,6 +9477,50 @@ "dev": true, "license": "MIT" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7833,13 +9756,75 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT", + "peer": true + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true, + "license": "MIT" }, "node_modules/schema-utils": { "version": "3.3.0", @@ -7882,6 +9867,55 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", @@ -7924,6 +9958,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -7931,6 +10041,19 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-eval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-eval/-/simple-eval-1.0.1.tgz", + "integrity": "sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsep": "^1.3.6" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/simple-git": { "version": "3.27.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", @@ -8018,6 +10141,20 @@ "node": ">=8" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -8073,6 +10210,65 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8470,6 +10666,13 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -8840,6 +11043,84 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -8885,6 +11166,25 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -8978,6 +11278,23 @@ "punycode": "^2.1.0" } }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9189,6 +11506,13 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/webpack": { "version": "5.97.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", @@ -9274,6 +11598,17 @@ "node": ">=4.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9290,6 +11625,95 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 4da304e..9f65881 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,12 @@ "update-lock-file": "update-lock-file @netcracker" }, "dependencies": { - "@netcracker/qubership-apihub-api-unifier": "feature-asyncapi-basic-e2e", + "@netcracker/qubership-apihub-api-unifier": "dev", "@netcracker/qubership-apihub-json-crawl": "1.2.0", "fast-equals": "6.0.0" }, "devDependencies": { + "@asyncapi/parser": "3.4.0", "@netcracker/qubership-apihub-compatibility-suites": "dev", "@netcracker/qubership-apihub-graphapi": "1.0.9", "@netcracker/qubership-apihub-npm-gitflow": "3.1.0", diff --git a/src/asyncapi/asyncapi3.bindings.ts b/src/asyncapi/asyncapi3.bindings.ts new file mode 100644 index 0000000..b4b1fe1 --- /dev/null +++ b/src/asyncapi/asyncapi3.bindings.ts @@ -0,0 +1,11 @@ +import { allUnclassified } from '../core' +import type { CompareRules } from '../types' + +/** + * Bindings rules (protocol-specific: server, channel, operation, message). + * All binding types use the same structure; protocol-specific content is unclassified. + */ +export const bindingsRules: CompareRules = { + $: allUnclassified, + '/**': { $: allUnclassified }, +} diff --git a/src/asyncapi/asyncapi3.compare.ts b/src/asyncapi/asyncapi3.compare.ts index c544048..378d028 100644 --- a/src/asyncapi/asyncapi3.compare.ts +++ b/src/asyncapi/asyncapi3.compare.ts @@ -4,11 +4,17 @@ import { asyncApi3Rules } from './asyncapi3.rules' import { SPEC_TYPE_ASYNCAPI_3 } from '@netcracker/qubership-apihub-api-unifier' export const compareAsyncApi = (version: typeof SPEC_TYPE_ASYNCAPI_3) => (before: unknown, after: unknown, options: StrictCompareOptions): CompareResult => { + const effectiveFirstRefKeyProp = options.firstReferenceKeyProperty ?? Symbol('firstReferenceKey') + const retainFirstReferenceKeyProperty = !!options.firstReferenceKeyProperty + return compare(before, after, { ...options, + firstReferenceKeyProperty: effectiveFirstRefKeyProp, + retainFirstReferenceKeyProperty, rules: asyncApi3Rules({ mode: options.mode, version: version, + firstReferenceKeyProperty: effectiveFirstRefKeyProp, }), }) } diff --git a/src/asyncapi/asyncapi3.const.ts b/src/asyncapi/asyncapi3.const.ts index 76e3414..cf57986 100644 --- a/src/asyncapi/asyncapi3.const.ts +++ b/src/asyncapi/asyncapi3.const.ts @@ -6,10 +6,3 @@ import type { CompareScope } from '../types' export const COMPARE_SCOPE_SEND: CompareScope = 'send' export const COMPARE_SCOPE_RECEIVE: CompareScope = 'receive' export const COMPARE_SCOPE_COMPONENTS: CompareScope = 'components' - -// Re-export action constants from api-unifier (do not duplicate) -export { - ASYNCAPI_ACTION_SEND, - ASYNCAPI_ACTION_RECEIVE, -} from '@netcracker/qubership-apihub-api-unifier' - diff --git a/src/asyncapi/asyncapi3.mapping.ts b/src/asyncapi/asyncapi3.mapping.ts new file mode 100644 index 0000000..5bcf7aa --- /dev/null +++ b/src/asyncapi/asyncapi3.mapping.ts @@ -0,0 +1,65 @@ +import { MapKeysResult, MappingArrayResolver } from '../types' +import { onlyExistedArrayIndexes } from '../utils' + +/** + * Creates an array-mapping resolver that matches items by the value of a given property. + * + * For each non‑sparse index in the `before` and `after` arrays, this resolver: + * + * - Reads `propertyKey` from the array element (only if the element is a non‑null object) + * - Builds a lookup from string property value → index for the `after` array + * - For every `before[i]` whose property value is a string present in the lookup, + * records a mapping `i → j` where `j` is the corresponding index in `after` + * - Treats `before` items whose property is missing or not found in `after` as **removed** + * - Treats remaining `after` items that were never matched as **added** + * + * This is used for AsyncAPI arrays (e.g. messages, servers) where elements are + * conceptually keyed by some identifier property (such as a captured reference key), + * so that reordering the array does not produce spurious add/remove or rename diffs. + * + * @param propertyKey - The object property whose string value is used as the logical key + * to match `before` and `after` array items. + * @returns A {@link MappingArrayResolver} that maps array indices based on `propertyKey`. + */ +export const createPropertyMappingResolver = ( + propertyKey: PropertyKey, +): MappingArrayResolver => (before, after) => { + const result: MapKeysResult = { added: [], removed: [], mapped: {} } + const beforeIndexes = onlyExistedArrayIndexes(before) + const afterIndexes = onlyExistedArrayIndexes(after) + + // Build lookup: firstRefKey string value → after index + const afterKeyToIndex = new Map() + for (const j of afterIndexes) { + const item = after[j] + if (item !== null && typeof item === 'object') { + const key = (item as Record)[propertyKey] + if (typeof key === 'string') { + afterKeyToIndex.set(key, j) + } + } + } + + const unmatchedAfterIndexes = new Set(afterIndexes) + + for (const i of beforeIndexes) { + const item = before[i] + const key = (item !== null && typeof item === 'object') + ? (item as Record)[propertyKey] + : undefined + + if (typeof key === 'string' && afterKeyToIndex.has(key)) { + const j = afterKeyToIndex.get(key)! + result.mapped[i] = j + unmatchedAfterIndexes.delete(j) + } else { + result.removed.push(i) + } + } + + for (const j of unmatchedAfterIndexes) { + result.added.push(j) + } + + return result +} diff --git a/src/asyncapi/asyncapi3.rules.common.ts b/src/asyncapi/asyncapi3.rules.common.ts new file mode 100644 index 0000000..7024d8b --- /dev/null +++ b/src/asyncapi/asyncapi3.rules.common.ts @@ -0,0 +1,14 @@ +import { + allAnnotation, +} from '../core' +import type { CompareRules } from '../types' +import { asyncApiSpecificationExtensionRulesFunction } from './asyncapi3.compare.rules' + + +// TODO: move to asyncapi.jsonschema.common.ts +export const externalDocumentationRules: CompareRules = { + $: allAnnotation, + '/description': { $: allAnnotation }, + '/url': { $: allAnnotation }, + ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), +} diff --git a/src/asyncapi/asyncapi3.rules.ts b/src/asyncapi/asyncapi3.rules.ts index 03eca86..f31f6e3 100644 --- a/src/asyncapi/asyncapi3.rules.ts +++ b/src/asyncapi/asyncapi3.rules.ts @@ -15,62 +15,81 @@ import { START_NEW_COMPARE_SCOPE_RULE, } from '../types' import { AsyncApi3RulesOptions } from './asyncapi3.types' -import { asyncApiSchemaRules } from './asyncapi3.schema' +import { createPropertyMappingResolver } from './asyncapi3.mapping' +import { schemaOrMultiFormatSchemaRules } from './asyncapi3.schema' import { asyncApiSpecificationExtensionRulesFunction } from './asyncapi3.compare.rules' import { COMPARE_SCOPE_COMPONENTS, COMPARE_SCOPE_RECEIVE, COMPARE_SCOPE_SEND, - ASYNCAPI_ACTION_SEND, } from './asyncapi3.const' -import { SPEC_TYPE_ASYNCAPI_3 } from '@netcracker/qubership-apihub-api-unifier' +import { externalDocumentationRules } from './asyncapi3.rules.common' +import { bindingsRules } from './asyncapi3.bindings' +import { ASYNCAPI_ACTION_SEND } from '@netcracker/qubership-apihub-api-unifier' -/*** - * Keep consistent ordering for the rules: - * - classify rule ($) for the node itself first - * - other rules for the node itself in rule-key alphabetical order - * - rules for children - * - for specific child keys (in alphabetical order) - * - prefix rules - * - local rules ('/*') - * - global rules ('/**') - * The only exception is top-level structure of AsyncAPI Object where specific keys are in the natural order from the specification. -***/ +/** + * Keep consisten ordering for the rules + * - Classify rule ($) for the node itself first + * - Other rules for the node itself in the order they listed in specification + * - Children: specific keys, then prefix rules, then '/*', then '/**' * + */ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => { - const sendSchemaRules = asyncApiSchemaRules({ version: SPEC_TYPE_ASYNCAPI_3, send: true }) - const receiveSchemaRules = asyncApiSchemaRules({ version: SPEC_TYPE_ASYNCAPI_3, send: false }) + const firstReferenceKeyMapping = options.firstReferenceKeyProperty + ? createPropertyMappingResolver(options.firstReferenceKeyProperty) + : undefined + + const tagRules: CompareRules = { + $: allAnnotation, + '/name': { $: allAnnotation }, + '/description': { $: allAnnotation }, + '/externalDocs': externalDocumentationRules, + ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), + } - // Common rules for tags (used in multiple places) const tagsRules: CompareRules = { $: allAnnotation, mapping: deepEqualsUniqueItemsArrayMappingResolver, - '/*': { - $: allAnnotation, - '/description': { $: allAnnotation }, - '/name': { $: allAnnotation }, - ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), - }, + '/*': tagRules, } - // External documentation rules - const externalDocsRules: CompareRules = { - $: allAnnotation, + const oAuthFlowRules: CompareRules = { + $: [breaking, nonBreaking, breaking], + ...asyncApiSpecificationExtensionRulesFunction(), + } + + const oAuthFlowsRules: CompareRules = { + $: [breaking, nonBreaking, breaking], + ...asyncApiSpecificationExtensionRulesFunction(), + '/*': oAuthFlowRules, + } + + const securitySchemeRules: CompareRules = { + $: [breaking, nonBreaking, breaking], + '/type': { $: [breaking, nonBreaking, breaking] }, '/description': { $: allAnnotation }, - '/url': { $: allAnnotation }, - ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), + '/name': { $: [breaking, nonBreaking, breaking] }, + '/in': { $: [breaking, nonBreaking, breaking] }, + '/scheme': { $: [breaking, nonBreaking, breaking] }, + '/bearerFormat': { $: allAnnotation }, + '/flows': oAuthFlowsRules, + '/openIdConnectUrl': { $: allAnnotation }, + '/scopes': { + $: [nonBreaking, breaking, breaking], + '/*': { $: [nonBreaking, breaking, breaking] }, + }, + ...asyncApiSpecificationExtensionRulesFunction(), } - // Server variable rules const serverVariableRules: CompareRules = { $: allAnnotation, - '/default': { $: allAnnotation }, - '/description': { $: allAnnotation }, '/enum': { $: allAnnotation, mapping: deepEqualsUniqueItemsArrayMappingResolver, '/*': { $: allAnnotation, ignoreKeyDifference: true }, }, + '/default': { $: allAnnotation }, + '/description': { $: allAnnotation }, '/examples': { $: allAnnotation, '/*': { $: allAnnotation }, @@ -81,16 +100,17 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => // Server rules const serverRules: CompareRules = { $: allAnnotation, - '/bindings': { - $: allUnclassified, - '/*': { $: allUnclassified }, - '/**': { $: allUnclassified }, - }, - '/description': { $: allAnnotation }, '/host': { $: allAnnotation }, - '/pathname': { $: allAnnotation }, '/protocol': { $: allAnnotation }, '/protocolVersion': { $: allAnnotation }, + '/pathname': { $: allAnnotation }, + '/description': { $: allAnnotation }, + '/title': { $: allAnnotation }, + '/summary': { $: allAnnotation }, + '/variables': { + $: allAnnotation, + '/*': serverVariableRules, + }, '/security': { $: allAnnotation, '/*': { @@ -99,158 +119,133 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => }, }, '/tags': tagsRules, - '/title': { $: allAnnotation }, - '/variables': { - $: allAnnotation, - '/*': serverVariableRules, - }, + '/externalDocs': externalDocumentationRules, + '/bindings': bindingsRules, ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), } - // Servers map rules const serversRules: CompareRules = { $: allAnnotation, '/*': serverRules, } - // Bindings rules (protocol-specific, unclassified) - const bindingsRules: CompareRules = { + const correlationIdRules: CompareRules = { $: allUnclassified, - '/*': { - $: allUnclassified, - '/*': { $: allUnclassified }, - '/**': { $: allUnclassified }, - }, + '/description': { $: allUnclassified }, + '/location': { $: allUnclassified }, + ...asyncApiSpecificationExtensionRulesFunction(allUnclassified), } - // Correlation ID rules - const correlationIdRules: CompareRules = { - $: addNonBreaking, - '/description': { $: allAnnotation }, - '/location': { $: addNonBreaking }, - ...asyncApiSpecificationExtensionRulesFunction(), + const messageExampleRules: CompareRules = { + $: allAnnotation, + '/headers': { + $: allAnnotation, + '/**': { $: allAnnotation }, + }, + '/payload': { + $: allAnnotation, + '/**': { $: allAnnotation }, + }, + '/name': { $: allAnnotation }, + '/summary': { $: allAnnotation }, + ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), } - // Message examples rules const messageExamplesRules: CompareRules = { $: allAnnotation, - '/*': { - $: allAnnotation, - '/headers': { - $: allAnnotation, - '/*': { $: allAnnotation }, - '/**': { $: allAnnotation }, - }, - '/name': { $: allAnnotation }, - '/payload': { - $: allAnnotation, - '/**': { $: allAnnotation }, - }, - '/summary': { $: allAnnotation }, - ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), - }, + '/*': messageExampleRules, } - // Message rules factory based on send/receive context - const messageRules = (isSend: boolean): CompareRules => ({ - $: allBreaking, - '/bindings': bindingsRules, - '/contentType': { $: addNonBreaking }, + const messageRules: CompareRules = { + $: allUnclassified, + '/headers': (ctx) => ({ ...schemaOrMultiFormatSchemaRules(ctx), $: allBreaking }), '/correlationId': correlationIdRules, - '/description': { $: allAnnotation }, - '/examples': messageExamplesRules, - '/headers': () => ({ - ...(isSend ? sendSchemaRules : receiveSchemaRules), - $: allBreaking, - }), - '/name': { $: allNonBreaking }, - '/payload': () => ({ - ...(isSend ? sendSchemaRules : receiveSchemaRules), - $: allBreaking, - }), - '/schemaFormat': { $: allBreaking }, + '/contentType': { $: addNonBreaking }, + '/name': { $: allAnnotation }, + '/title': { $: allAnnotation }, '/summary': { $: allAnnotation }, + '/description': { $: allAnnotation }, '/tags': tagsRules, - '/title': { $: allAnnotation }, + '/externalDocs': externalDocumentationRules, + '/bindings': bindingsRules, + '/examples': messageExamplesRules, + '/payload': (ctx) => ({ ...schemaOrMultiFormatSchemaRules(ctx), $: allBreaking }), '/traits': { - $: addNonBreaking, + $: allUnclassified, '/*': { - $: addNonBreaking, + $: allUnclassified, '/*': { $: allUnclassified }, '/**': { $: allUnclassified }, }, }, - ...asyncApiSpecificationExtensionRulesFunction(), - }) + ...asyncApiSpecificationExtensionRulesFunction(allUnclassified), + } - // Channel parameter rules - const channelParameterRules: CompareRules = { - $: addNonBreaking, - '/default': { $: allAnnotation }, - '/description': { $: allAnnotation }, + const parameterRules: CompareRules = { + $: allUnclassified, '/enum': { - $: allBreaking, + $: allUnclassified, mapping: deepEqualsUniqueItemsArrayMappingResolver, - '/*': { $: allBreaking, ignoreKeyDifference: true }, + '/*': { $: allUnclassified, ignoreKeyDifference: true }, }, + '/default': { $: allUnclassified }, + '/description': { $: allAnnotation }, '/examples': { $: allAnnotation, '/*': { $: allAnnotation }, }, - '/location': { $: allBreaking }, + '/location': { $: allUnclassified }, ...asyncApiSpecificationExtensionRulesFunction(), } - // Channel rules const channelRules: CompareRules = { $: addNonBreaking, - '/address': { $: allAnnotation }, - '/bindings': bindingsRules, - '/description': { $: allAnnotation }, + '/address': { $: allUnclassified }, '/messages': { $: addNonBreaking, - '/*': messageRules(true), // Default to send scope for channel-level messages - }, - '/parameters': { - $: allBreaking, - '/*': channelParameterRules, + '/*': messageRules, }, + '/title': { $: allAnnotation }, + '/summary': { $: allAnnotation }, + '/description': { $: allAnnotation }, '/servers': { - $: allAnnotation, - '/*': { $: allAnnotation }, + $: allUnclassified, + mapping: firstReferenceKeyMapping, + '/*': { $: allUnclassified, ignoreKeyDifference: true }, + }, + '/parameters': { + $: allUnclassified, + '/*': parameterRules, }, - '/summary': { $: allAnnotation }, '/tags': tagsRules, - '/title': { $: allAnnotation }, + '/externalDocs': externalDocumentationRules, + '/bindings': bindingsRules, ...asyncApiSpecificationExtensionRulesFunction(), } - // Reply address rules - const replyAddressRules: CompareRules = { - $: addNonBreaking, + const operationReplyAddressRules: CompareRules = { + $: allUnclassified, '/description': { $: allAnnotation }, - '/location': { $: addNonBreaking }, + '/location': { $: allUnclassified }, ...asyncApiSpecificationExtensionRulesFunction(), } - // Reply rules - uses SEND scope since it's about sending messages back - const replyRules: CompareRules = { - $: addNonBreaking, - [START_NEW_COMPARE_SCOPE_RULE]: COMPARE_SCOPE_SEND, - '/address': replyAddressRules, - '/channel': { $: allBreaking }, + const operationReplyRules = (isSendAction: boolean): CompareRules => ({ + $: allUnclassified, + [START_NEW_COMPARE_SCOPE_RULE]: isSendAction ? COMPARE_SCOPE_RECEIVE : COMPARE_SCOPE_SEND, + '/address': operationReplyAddressRules, + '/channel': channelRules, '/messages': { - $: addNonBreaking, - '/*': messageRules(true), // Reply messages use send scope + $: allUnclassified, + '/*': messageRules, }, ...asyncApiSpecificationExtensionRulesFunction(), - } + }) - // Operation traits rules const operationTraitsRules: CompareRules = { - $: addNonBreaking, + $: allUnclassified, '/*': { - $: addNonBreaking, + $: allUnclassified, '/*': { $: allUnclassified }, '/**': { $: allUnclassified }, }, @@ -264,41 +259,28 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => ? [nonBreaking, breaking, unclassified] : [breaking, nonBreaking, unclassified], [START_NEW_COMPARE_SCOPE_RULE]: isSendAction ? COMPARE_SCOPE_SEND : COMPARE_SCOPE_RECEIVE, - '/action': { $: allBreaking }, - '/bindings': bindingsRules, - '/channel': { $: allBreaking }, - '/deprecated': { $: allDeprecated }, + '/title': { $: allAnnotation }, + '/summary': { $: allAnnotation }, '/description': { $: allAnnotation }, - '/externalDocs': externalDocsRules, - '/messages': { - // For send: add message = non-breaking, remove = breaking - // For receive: add message = breaking, remove = non-breaking - $: isSendAction - ? [nonBreaking, breaking, breaking] - : [breaking, nonBreaking, breaking], - '/*': messageRules(isSendAction), - }, - '/reply': replyRules, // Reply always uses send scope '/security': { - // Security changes follow the same pattern as messages - $: isSendAction - ? [nonBreaking, breaking, breaking] - : [breaking, nonBreaking, breaking], - '/*': { - $: isSendAction - ? [nonBreaking, breaking, breaking] - : [breaking, nonBreaking, breaking], - '/*': { $: allBreaking }, - }, + $: allUnclassified, + '/*': securitySchemeRules, }, - '/summary': { $: allAnnotation }, '/tags': tagsRules, - '/title': { $: allAnnotation }, + '/externalDocs': externalDocumentationRules, + '/bindings': bindingsRules, + '/reply': operationReplyRules(isSendAction), + '/action': { $: allBreaking }, + '/channel': channelRules, '/traits': operationTraitsRules, + '/messages': { + $: allUnclassified, + mapping: firstReferenceKeyMapping, + '/*': { ...messageRules, ignoreKeyDifference: true }, + }, ...asyncApiSpecificationExtensionRulesFunction(), }) - // Operations map rules with dynamic operation type detection const operationsRules: CompareRules = { $: addNonBreaking, '/*': ({ value }) => { @@ -309,53 +291,21 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => }, } - // Security scheme rules - const securitySchemeRules: CompareRules = { - $: [breaking, nonBreaking, breaking], - '/bearerFormat': { $: allAnnotation }, - '/description': { $: allAnnotation }, - '/flows': { - $: [breaking, nonBreaking, breaking], - '/*': { - $: [breaking, nonBreaking, breaking], - ...asyncApiSpecificationExtensionRulesFunction(), - }, - }, - '/in': { $: [breaking, nonBreaking, breaking] }, - '/name': { $: [breaking, nonBreaking, breaking] }, - '/openIdConnectUrl': { $: allAnnotation }, - '/scheme': { $: [breaking, nonBreaking, breaking] }, - '/scopes': { - $: [nonBreaking, breaking, breaking], - '/*': { $: [nonBreaking, breaking, breaking] }, - }, - '/type': { $: [breaking, nonBreaking, breaking] }, - ...asyncApiSpecificationExtensionRulesFunction(), - } - // Components rules const componentsRules: CompareRules = { $: allNonBreaking, [START_NEW_COMPARE_SCOPE_RULE]: COMPARE_SCOPE_COMPONENTS, - '/channels': { - $: [nonBreaking, breaking, breaking], - '/*': channelRules, - }, - '/correlationIds': { + '/schemas': { $: [nonBreaking, breaking, breaking], - '/*': correlationIdRules, + '/*': (ctx) => ({ $: allUnclassified, ...schemaOrMultiFormatSchemaRules(ctx) }), }, - '/messages': { + '/servers': { $: [nonBreaking, breaking, breaking], - '/*': messageRules(true), // Component messages default to send scope + '/*': serverRules, }, - '/messageTraits': { + '/channels': { $: [nonBreaking, breaking, breaking], - '/*': { - $: addNonBreaking, - '/*': { $: allUnclassified }, - '/**': { $: allUnclassified }, - }, + '/*': channelRules, }, '/operations': { $: [nonBreaking, breaking, breaking], @@ -365,6 +315,42 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => return operationRules(isSendAction) }, }, + '/messages': { + $: allUnclassified, + '/*': messageRules, + }, + '/securitySchemes': { + $: [breaking, nonBreaking, breaking], + '/*': securitySchemeRules, + }, + '/serverVariables': { + $: [nonBreaking, breaking, breaking], + '/*': serverVariableRules, + }, + '/parameters': { + $: [nonBreaking, breaking, breaking], + '/*': parameterRules, + }, + '/correlationIds': { + $: [nonBreaking, breaking, breaking], + '/*': correlationIdRules, + }, + '/replies': { + $: [nonBreaking, breaking, breaking], + '/*': operationReplyRules(true), + }, + '/replyAddresses': { + $: [nonBreaking, breaking, breaking], + '/*': operationReplyAddressRules, + }, + '/externalDocs': { + $: [nonBreaking, breaking, breaking], + '/*': externalDocumentationRules, + }, + '/tags': { + $: [nonBreaking, breaking, breaking], + '/*': tagRules, + }, '/operationTraits': { $: [nonBreaking, breaking, breaking], '/*': { @@ -373,77 +359,77 @@ export const asyncApi3Rules = (options: AsyncApi3RulesOptions): CompareRules => '/**': { $: allUnclassified }, }, }, - '/parameters': { + '/messageTraits': { $: [nonBreaking, breaking, breaking], - '/*': channelParameterRules, + '/*': { + $: addNonBreaking, + '/*': { $: allUnclassified }, + '/**': { $: allUnclassified }, + }, }, - '/replies': { + '/serverBindings': { $: [nonBreaking, breaking, breaking], - '/*': replyRules, + '/*': bindingsRules, }, - '/replyAddresses': { + '/channelBindings': { $: [nonBreaking, breaking, breaking], - '/*': replyAddressRules, + '/*': bindingsRules, }, - '/schemas': { + '/operationBindings': { $: [nonBreaking, breaking, breaking], - '/*': () => ({ - $: allUnclassified, // For component schemas, use unclassified as default - ...sendSchemaRules, - }), + '/*': bindingsRules, }, - '/securitySchemes': { - $: [breaking, nonBreaking, breaking], - '/*': securitySchemeRules, - }, - '/servers': { + '/messageBindings': { $: [nonBreaking, breaking, breaking], - '/*': serverRules, + '/*': bindingsRules, }, ...asyncApiSpecificationExtensionRulesFunction(), } + // Contact rules (info.contact) + const contactRules: CompareRules = { + $: allAnnotation, + '/name': { $: allAnnotation }, + '/url': { $: allAnnotation }, + '/email': { $: allAnnotation }, + ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), + } + + // License rules (info.license) + const licenseRules: CompareRules = { + $: allAnnotation, + '/name': { $: allAnnotation }, + '/url': { $: allAnnotation }, + ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), + } + // Info rules const infoRules: CompareRules = { $: allAnnotation, - '/contact': { - $: allAnnotation, - '/email': { $: allAnnotation }, - '/name': { $: allAnnotation }, - '/url': { $: allAnnotation }, - ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), - }, - '/description': { $: allAnnotation }, - '/externalDocs': externalDocsRules, - '/license': { - $: allAnnotation, - '/name': { $: allAnnotation }, - '/url': { $: allAnnotation }, - ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), - }, - '/tags': tagsRules, - '/termsOfService': { $: allAnnotation }, '/title': { $: allAnnotation }, '/version': { $: allAnnotation }, + '/description': { $: allAnnotation }, + '/termsOfService': { $: allAnnotation }, + '/contact': contactRules, + '/license': licenseRules, + '/tags': tagsRules, + '/externalDocs': externalDocumentationRules, ...asyncApiSpecificationExtensionRulesFunction(allAnnotation), } - // Root AsyncAPI document rules return { - ...asyncApiSpecificationExtensionRulesFunction(), '/asyncapi': { $: allAnnotation }, '/id': { $: allAnnotation }, - '/defaultContentType': { $: allBreaking }, '/info': infoRules, '/servers': serversRules, + '/defaultContentType': { $: allUnclassified }, '/channels': { - $: addNonBreaking, + $: allUnclassified, '/*': channelRules, }, '/operations': operationsRules, '/components': componentsRules, - '/tags': tagsRules, - '/externalDocs': externalDocsRules, + ...asyncApiSpecificationExtensionRulesFunction(), } } diff --git a/src/asyncapi/asyncapi3.schema.ts b/src/asyncapi/asyncapi3.schema.ts index 91928ce..4fdac87 100644 --- a/src/asyncapi/asyncapi3.schema.ts +++ b/src/asyncapi/asyncapi3.schema.ts @@ -1,3 +1,4 @@ +import { CrawlRulesContext } from '@netcracker/qubership-apihub-json-crawl' import { jsonSchemaAdapter, jsonSchemaRules, @@ -6,24 +7,33 @@ import { } from '../jsonSchema' import { allAnnotation, + allBreaking, + allUnclassified, diffDescription, - reverseClassifyRuleTransformer, + dynamicReclassifyTransformer, + ReversePredicate, transformCompareRules, } from '../core' -import type { AsyncApi3SchemaRulesOptions } from './asyncapi3.types' -import { CompareRules } from '../types' +import { AdapterResolver, CompareRules } from '../types' import { + ASYNCAPI_SCHEMA_FORMAT_DEFAULT, + ASYNCAPI_SCHEMA_FORMATS_ASCYNAPI_30, + ASYNCAPI_SCHEMA_FORMATS_JSON, + ASYNCAPI_SCHEMA_FORMATS_OPENAPI_30, normalize, - OriginsMetaRecord, SPEC_TYPE_JSON_SCHEMA_07, + SPEC_TYPE_OPEN_API_30, } from '@netcracker/qubership-apihub-api-unifier' +import { COMPARE_MODE_DEFAULT } from '../types' +import { COMPARE_SCOPE_RECEIVE } from './asyncapi3.const' import { asyncApiSpecificationExtensionRulesFunction } from './asyncapi3.compare.rules' +import { openApiSchemaRules } from '../openapi/openapi3.schema' +import { isObject } from '../utils' -// AsyncAPI 3.0 uses JSON Schema draft-07 +// AsyncAPI 3.0 uses JSON Schema draft-07 as the base const asyncApiJsonSchemaAnyFactory: NativeAnySchemaFactory = (schema, schemaOrigins, opt) => { return normalize(schema, { ...opt, - // schema is already normalized, resolveRef is disabled and originsAlreadyDefined is true in order to prevent origins override resolveRef: false, originsAlreadyDefined: true, validate: false, @@ -31,15 +41,25 @@ const asyncApiJsonSchemaAnyFactory: NativeAnySchemaFactory = (schema, schemaOrig }) as Record } -export const asyncApiSchemaRules = (options: AsyncApi3SchemaRulesOptions): CompareRules => { - // AsyncAPI 3.0 uses JSON Schema draft-07 - const schemaRules = jsonSchemaRules({ +export function isMultiFormatSchema(value: unknown): boolean { + return isObject(value) && 'schema' in (value as Record) +} + +// Predicate: returns true when the current compare scope is 'receive' +const isReceiveScope: ReversePredicate = (ctx) => ctx.scope === COMPARE_SCOPE_RECEIVE + +// Applies dynamic scope-based reclassification to a set of CompareRules. +// breaking ↔ nonBreaking are swapped when isReceiveScope(ctx) is true at diff time. +const withDynamicScopeReclassification = (rules: CompareRules): CompareRules => + transformCompareRules(rules, dynamicReclassifyTransformer(isReceiveScope)) + +function asyncApiSchemaRulesFactory(): CompareRules { + return jsonSchemaRules({ additionalRules: { adapter: [ jsonSchemaAdapter(asyncApiJsonSchemaAnyFactory), ], description: diffDescription(resolveSchemaDescriptionTemplates()), - // AsyncAPI-specific schema extensions '/example': { $: allAnnotation, description: diffDescription(resolveSchemaDescriptionTemplates('example')), @@ -65,11 +85,110 @@ export const asyncApiSchemaRules = (options: AsyncApi3SchemaRulesOptions): Compa }, version: SPEC_TYPE_JSON_SCHEMA_07, }) +} + +// Cached rule sets with dynamic scope reclassification applied at module load time +const asyncApiSchemaRules: CompareRules = withDynamicScopeReclassification(asyncApiSchemaRulesFactory()) + +const openApi30SchemaRules: CompareRules = withDynamicScopeReclassification( + openApiSchemaRules({ version: SPEC_TYPE_OPEN_API_30, mode: COMPARE_MODE_DEFAULT }), +) + +const jsonDraft7SchemaRules: CompareRules = withDynamicScopeReclassification( + jsonSchemaRules({ + additionalRules: { + description: diffDescription(resolveSchemaDescriptionTemplates()), //TODO: verify description templates + ...asyncApiSpecificationExtensionRulesFunction(), + }, + version: SPEC_TYPE_JSON_SCHEMA_07, + }), +) + +const unknownFormatSchemaRules: CompareRules = { '/**': { $: allUnclassified } } - // For receive operations (send=false or undefined), apply reverse classifier - // This mirrors OpenAPI's behavior where response schemas have reversed classification - return options.send - ? schemaRules - : transformCompareRules(schemaRules, reverseClassifyRuleTransformer) +type NormalizedSchemaFormat = string + +function normalizeSchemaFormat(format: string): NormalizedSchemaFormat { + return format.trim().toLowerCase() +} + +function getSchemaRulesByFormat(schemaFormat: string): CompareRules { + const normalized = normalizeSchemaFormat(schemaFormat) + if (ASYNCAPI_SCHEMA_FORMATS_ASCYNAPI_30.includes(normalized)) { + return asyncApiSchemaRules + } + if (ASYNCAPI_SCHEMA_FORMATS_OPENAPI_30.includes(normalized)) { + return openApi30SchemaRules + } + if (ASYNCAPI_SCHEMA_FORMATS_JSON.includes(normalized)) { + return jsonDraft7SchemaRules + } + return unknownFormatSchemaRules +} + +// Adapts a plain-schema side to multi-format when the reference is multi-format. +// This runs symmetrically (before→after and after→before) so covers both mismatch directions. +const schemaToMultiFormatSchemaAdapter: AdapterResolver = (value, reference, ctx) => { + if (!isObject(value) || !isObject(reference)) return value + const val = value as Record + const ref = reference as Record + if ('schema' in val || !('schema' in ref)) return value + return ctx.transformer(value, 'schema-to-multi-format', (current) => ({ + schema: current, + schemaFormat: ASYNCAPI_SCHEMA_FORMAT_DEFAULT, + })) +} + +// asyncApiSchemaRules augmented with: +// 1. schemaToMultiFormatSchemaAdapter – so it fires when before=plain and after=multi-format. +// 2. /schema + /schemaFormat rules – after the adapter wraps the plain side into +// {schema:..., schemaFormat:...} both sides have the multi-format shape, and these +// rules handle the inner content comparison correctly without falling through to /** +// (allUnclassified). /schema reuses the same dynamically-reclassified asyncApiSchemaRules +// which is correct because plain-schema implies AsyncAPI JSON schema format. +// +// The alternative to adding /schema + /schemaFormat rules is to add a post-adaptation rule hook in compare.ts +// Something like: if the adapter changes the structural shape of a value, re-call getNodeRules +// with the adapted value and update crawlContext.rules. +const asyncApiSchemaRulesWithAdapter: CompareRules = { + ...asyncApiSchemaRules, + '/schema': asyncApiSchemaRules, + '/schemaFormat': { $: allBreaking }, + adapter: [schemaToMultiFormatSchemaAdapter, ...(asyncApiSchemaRules.adapter ?? [])], +} + +// Cache one instance per supported format string to avoid re-computation +const multiFormatSchemaRulesCache = new Map() + +function multiFormatSchemaRulesFactory(schemaFormat: string): CompareRules { + const normalized = normalizeSchemaFormat(schemaFormat) + const cached = multiFormatSchemaRulesCache.get(normalized) + if (cached) return cached + + const contentRules = getSchemaRulesByFormat(normalized) + const rules: CompareRules = { + adapter: [schemaToMultiFormatSchemaAdapter], + '/schemaFormat': { $: allBreaking }, + '/schema': contentRules, + ...asyncApiSpecificationExtensionRulesFunction(), + } + multiFormatSchemaRulesCache.set(normalized, rules) + return rules } +/** + * Entry-point CrawlRulesFunc for AsyncAPI 3.0 schema/multiFormatSchema nodes. + * Dispatches based on the **before** value only (value from CrawlRulesContext). + * - Plain schema object → asyncApiSchemaRules (with dynamic scope reclassification) + * - Multi-format schema object → multiFormatSchemaRulesFactory(schemaFormat) + */ +export const schemaOrMultiFormatSchemaRules = ({ value }: CrawlRulesContext): CompareRules => { + if (!isMultiFormatSchema(value)) { + return asyncApiSchemaRulesWithAdapter + } + const obj = value as Record + const schemaFormat = typeof obj.schemaFormat === 'string' + ? obj.schemaFormat + : ASYNCAPI_SCHEMA_FORMAT_DEFAULT + return multiFormatSchemaRulesFactory(schemaFormat) +} diff --git a/src/asyncapi/asyncapi3.types.ts b/src/asyncapi/asyncapi3.types.ts index 6c3d72e..aefde7b 100644 --- a/src/asyncapi/asyncapi3.types.ts +++ b/src/asyncapi/asyncapi3.types.ts @@ -4,13 +4,9 @@ import { SPEC_TYPE_ASYNCAPI_3 } from '@netcracker/qubership-apihub-api-unifier' export type AsyncApi3RulesOptions = { version: typeof SPEC_TYPE_ASYNCAPI_3 mode: CompareMode + firstReferenceKeyProperty?: symbol } -export type AsyncApi3SchemaRulesOptions = { - version: typeof SPEC_TYPE_ASYNCAPI_3 - // true = send scope (like request), false = receive scope (like response) - send?: boolean -} -export type AsyncApiCompareOptions = StrictCompareOptions & Omit +export type AsyncApiCompareOptions = StrictCompareOptions & Omit diff --git a/src/asyncapi/index.ts b/src/asyncapi/index.ts index 072b147..7a8feee 100644 --- a/src/asyncapi/index.ts +++ b/src/asyncapi/index.ts @@ -1,6 +1,7 @@ export * from './asyncapi3.compare' export * from './asyncapi3.compare.rules' export * from './asyncapi3.const' +export * from './asyncapi3.mapping' export * from './asyncapi3.rules' export * from './asyncapi3.schema' export * from './asyncapi3.types' diff --git a/src/core/compare.ts b/src/core/compare.ts index b084fc0..f29832a 100644 --- a/src/core/compare.ts +++ b/src/core/compare.ts @@ -276,6 +276,10 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): if (!(beforeKey in keyMap)) { //actually we don't make deep copy here and create "way to modify" original source mergedJso[beforeKey] = value + // remove case- cleanup firstReferenceKeyProperty if required + if (!options.retainFirstReferenceKeyProperty && options.firstReferenceKeyProperty && isObject(value)) { + delete (value as Record)[options.firstReferenceKeyProperty] + } return { done: true } } @@ -376,10 +380,27 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter, additionBwc) jsoDiffEntries.push(getOrCreateChildDiffAdd(diffUniquenessCache, childCtx)) mergedJsoValue[keyInMerge] = afterValue[keyInAfter] + // add case- cleanup firstReferenceKeyProperty if required + if (!options.retainFirstReferenceKeyProperty && options.firstReferenceKeyProperty && isObject(afterValue[keyInAfter])) { + delete (afterValue[keyInAfter] as Record)[options.firstReferenceKeyProperty] + } }) jsoDiffEntries.forEach(e => addDiff(e.diff)) addDiffObjectToContainer(mergedJsoValue, metaKey, jsoDiffEntries) + + // merge case- keep firstReferenceKeyProperty if required + if ( + options.retainFirstReferenceKeyProperty && + options.firstReferenceKeyProperty && + !isArray(mergedJsoValue) && + isObject(afterValue) + ) { + const firstRefKey = (afterValue as Record)[options.firstReferenceKeyProperty] + if (firstRefKey !== undefined) { + mergedJsoValue[options.firstReferenceKeyProperty] = firstRefKey + } + } } return { diff --git a/src/core/rules.ts b/src/core/rules.ts index c401f0e..88441f6 100644 --- a/src/core/rules.ts +++ b/src/core/rules.ts @@ -13,6 +13,8 @@ import { import { isFunc, isObject, isString } from '../utils' import { breaking, DiffAction, nonBreaking, risky } from './constants' +export type ReversePredicate = (ctx: CompareContext) => boolean + export const transformCompareRules = (rules: CompareRules, transformer: CompareRulesTransformer): CompareRules => { return syncClone(rules, ({ value, key, state, path }) => { if (key && (!isString(key) || !key.startsWith('/'))) { @@ -82,6 +84,63 @@ export const transformClassifyRule = ([add, remove, replace, reverseAdd, reverse ] } +/** + * Wraps a single classify slot dynamically: when shouldReverse(ctx) is true the + * classification is reversed, otherwise the original value is used. + * + * Reversible statics (breaking/nonBreaking) and classifier functions are wrapped + * in a predicate check. Non-reversible statics (annotation, unclassified, deprecated, + * risky) are returned as-is. + * + * When an explicit reversed value is provided (from a 6-tuple ClassifyRule) it is + * used directly instead of computing the reverse. + */ +const dynamicReverseSlot = ( + originalDiffType: RuleDiffType, + explicitReversedDiffType: RuleDiffType | undefined, + shouldReverse: ReversePredicate, +): RuleDiffType => { + if (explicitReversedDiffType !== undefined) { + return (ctx: CompareContext): DiffType => { + const chosenDiffType = shouldReverse(ctx) ? explicitReversedDiffType : originalDiffType + return isFunc(chosenDiffType) ? chosenDiffType(ctx) : chosenDiffType + } + } + if (isFunc(originalDiffType)) { + return (ctx: CompareContext): DiffType => { + if (shouldReverse(ctx)) { + return reverseDiffType(originalDiffType(ctx)) as DiffType + } + return originalDiffType(ctx) + } + } + if (originalDiffType === breaking || originalDiffType === nonBreaking) { + const reversedDiffType = reverseDiffType(originalDiffType) as DiffType + return (ctx: CompareContext): DiffType => (shouldReverse(ctx) ? reversedDiffType : originalDiffType) + } + return originalDiffType +} + +const dynamicReverseClassifyRule = (rule: ClassifyRule, shouldReverse: ReversePredicate): ClassifyRule => { + const [add, remove, replace, reversedAdd, reversedRemove, reversedReplace] = rule + return [ + dynamicReverseSlot(add, reversedAdd, shouldReverse), + dynamicReverseSlot(remove, reversedRemove, shouldReverse), + dynamicReverseSlot(replace, reversedReplace, shouldReverse), + ] +} + +/** + * Like reverseClassifyRuleTransformer but defers the reversal decision to + * diff-creation time via a predicate that receives CompareContext. + * This allows scope-aware reversal without pre-computing two rule sets. + */ +export const dynamicReclassifyTransformer = (shouldReverse: ReversePredicate): CompareRulesTransformer => + (value) => { + if (!('$' in value) || !Array.isArray(value.$)) return value + return { ...value, $: dynamicReverseClassifyRule(value.$ as ClassifyRule, shouldReverse) } + } + export const breakingIf = (v: boolean): DiffType => (v ? breaking : nonBreaking) export const riskyIf = (v: boolean): DiffType => (v ? risky : nonBreaking) export const breakingIfAfterTrue: DiffTypeClassifier = ({ after }): DiffType => breakingIf(!!after.value) diff --git a/src/types/compare.ts b/src/types/compare.ts index 7dbef1d..e56b5d4 100644 --- a/src/types/compare.ts +++ b/src/types/compare.ts @@ -114,10 +114,15 @@ export interface CompareOptions extends Omit { * For OpenAPI specs: * If a whole PathItem is removed, generate separate diffs for each HTTP operation (get/post/...) * instead of a single diff for the whole PathItem. - * + * * Default: `false` */ openApiPathItemPerOperationDiffs?: boolean + /** + * When true, the symbol value stored under `firstReferenceKeyProperty` will be preserved in the merged result. + * Set automatically by the AsyncAPI engine when `firstReferenceKeyProperty` is user-provided. + */ + retainFirstReferenceKeyProperty?: boolean } export type DiffCallback = (diff: Diff/*, ctx: CompareContext*/) => void diff --git a/test/asyncapi.extensions.test.ts b/test/asyncapi.extensions.test.ts new file mode 100644 index 0000000..b667227 --- /dev/null +++ b/test/asyncapi.extensions.test.ts @@ -0,0 +1,258 @@ +import { apiDiff, CompareOptions, DiffAction, unclassified } from '../src' +import { diffsMatcher } from './helper/matchers' + +const TEST_COMPARE_OPTIONS: CompareOptions = { + unify: true, +} + +const extensionName = 'x-channel-extension' +const CHANNEL_EXTENSION_PATH = ['channels', 'ch', extensionName] + +const makeSpecWithChannelExtension = (extensionValue: unknown) => ({ + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + channels: { + ch: extensionValue === undefined ? {} : { [extensionName]: extensionValue }, + }, +}) + +const prepareSpecsForComparison = (beforeExtensionValue: unknown, afterExtensionValue: unknown) => { + const before = makeSpecWithChannelExtension(beforeExtensionValue) + const after = makeSpecWithChannelExtension(afterExtensionValue) + + return { before, after } +} + +const jsonSchemaSample = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + id: { type: 'string' }, + }, + required: ['id'], +} + +describe('AsyncAPI channel specification extensions - JSON changes', () => { + it('string-to-array: replace extension value from string to array', () => { + const { before, after } = prepareSpecsForComparison('value', ['value']) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [CHANNEL_EXTENSION_PATH], + afterDeclarationPaths: [CHANNEL_EXTENSION_PATH], + action: DiffAction.replace, + type: unclassified, + beforeValue: 'value', + afterValue: ['value'], + }), + ])) + }) + + it('string-to-object: replace extension value from string to object', () => { + const { before, after } = prepareSpecsForComparison('value', { key: 'value' }) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [CHANNEL_EXTENSION_PATH], + afterDeclarationPaths: [CHANNEL_EXTENSION_PATH], + action: DiffAction.replace, + type: unclassified, + beforeValue: 'value', + afterValue: { key: 'value' }, + }), + ])) + }) + + it('string-to-json-schema: replace extension value from string to JSON Schema', () => { + const { before, after } = prepareSpecsForComparison('value', jsonSchemaSample) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [CHANNEL_EXTENSION_PATH], + afterDeclarationPaths: [CHANNEL_EXTENSION_PATH], + action: DiffAction.replace, + type: unclassified, + beforeValue: 'value', + afterValue: jsonSchemaSample, + }), + ])) + }) + + it('array-to-object: replace extension value from array to object', () => { + const { before, after } = prepareSpecsForComparison(['value'], { key: 'value' }) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [CHANNEL_EXTENSION_PATH], + afterDeclarationPaths: [CHANNEL_EXTENSION_PATH], + action: DiffAction.replace, + type: unclassified, + beforeValue: ['value'], + afterValue: { key: 'value' }, + }), + ])) + }) + + it('array-to-json-schema: replace extension value from array to JSON Schema', () => { + const { before, after } = prepareSpecsForComparison(['value'], jsonSchemaSample) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [CHANNEL_EXTENSION_PATH], + afterDeclarationPaths: [CHANNEL_EXTENSION_PATH], + action: DiffAction.replace, + type: unclassified, + beforeValue: ['value'], + afterValue: jsonSchemaSample, + }), + ])) + }) + + it('object-to-json-schema: replace extension value from object to JSON Schema', () => { + const { before, after } = prepareSpecsForComparison({ key: 'value' }, jsonSchemaSample) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + const keyPath = [...CHANNEL_EXTENSION_PATH, 'key'] + const schemaPath = [...CHANNEL_EXTENSION_PATH, '$schema'] + const typePath = [...CHANNEL_EXTENSION_PATH, 'type'] + const propertiesPath = [...CHANNEL_EXTENSION_PATH, 'properties'] + const requiredPath = [...CHANNEL_EXTENSION_PATH, 'required'] + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [keyPath], + action: DiffAction.remove, + type: unclassified, + beforeValue: 'value', + }), + expect.objectContaining({ + afterDeclarationPaths: [schemaPath], + action: DiffAction.add, + type: unclassified, + afterValue: jsonSchemaSample.$schema, + }), + expect.objectContaining({ + afterDeclarationPaths: [typePath], + action: DiffAction.add, + type: unclassified, + afterValue: jsonSchemaSample.type, + }), + expect.objectContaining({ + afterDeclarationPaths: [propertiesPath], + action: DiffAction.add, + type: unclassified, + afterValue: jsonSchemaSample.properties, + }), + expect.objectContaining({ + afterDeclarationPaths: [requiredPath], + action: DiffAction.add, + type: unclassified, + afterValue: jsonSchemaSample.required, + }), + ])) + }) + + it('delete-any-property-from-json: remove nested property inside extension JSON value', () => { + const beforeValue = { nested: { property: 'value' } } + const afterValue = { nested: {} } + const propertyPath = [...CHANNEL_EXTENSION_PATH, 'nested', 'property'] + + const { before, after } = prepareSpecsForComparison(beforeValue, afterValue) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [propertyPath], + action: DiffAction.remove, + type: unclassified, + beforeValue: 'value', + }), + ])) + }) + + it('add-any-property-to-json: add nested property inside extension JSON value', () => { + const beforeValue = { nested: {} } + const afterValue = { nested: { property: 'value' } } + const propertyPath = [...CHANNEL_EXTENSION_PATH, 'nested', 'property'] + + const { before, after } = prepareSpecsForComparison(beforeValue, afterValue) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + afterDeclarationPaths: [propertyPath], + action: DiffAction.add, + type: unclassified, + afterValue: 'value', + }), + ])) + }) + + it('complex-json-to-complex-json-schema-like: mixed adds, removes and replaces inside extension value', () => { + const beforeValue = { + description: true, + minLength: '123', + hello: 'world', + } + + const afterValue = { + type: 'string', + minLength: 42, + description: 'Test', + } + + const descriptionPath = [...CHANNEL_EXTENSION_PATH, 'description'] + const minLengthPath = [...CHANNEL_EXTENSION_PATH, 'minLength'] + const helloPath = [...CHANNEL_EXTENSION_PATH, 'hello'] + const typePath = [...CHANNEL_EXTENSION_PATH, 'type'] + + const { before, after } = prepareSpecsForComparison(beforeValue, afterValue) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + beforeDeclarationPaths: [helloPath], + action: DiffAction.remove, + type: unclassified, + beforeValue: 'world', + }), + expect.objectContaining({ + beforeDeclarationPaths: [descriptionPath], + afterDeclarationPaths: [descriptionPath], + action: DiffAction.replace, + type: unclassified, + beforeValue: true, + afterValue: 'Test', + }), + expect.objectContaining({ + beforeDeclarationPaths: [minLengthPath], + afterDeclarationPaths: [minLengthPath], + action: DiffAction.replace, + type: unclassified, + beforeValue: '123', + afterValue: 42, + }), + expect.objectContaining({ + afterDeclarationPaths: [typePath], + action: DiffAction.add, + type: unclassified, + afterValue: 'string', + }), + ])) + }) +}) + diff --git a/test/asyncapi.firstReferenceKeyMapping.test.ts b/test/asyncapi.firstReferenceKeyMapping.test.ts new file mode 100644 index 0000000..af837f9 --- /dev/null +++ b/test/asyncapi.firstReferenceKeyMapping.test.ts @@ -0,0 +1,796 @@ +import { apiDiff, CompareOptions, DiffAction, } from '../src' +import { COMPARE_SCOPE_ROOT } from '../src/types' +import { createPropertyMappingResolver } from '../src/asyncapi/asyncapi3.mapping' +import { parseAsyncApiAndAssertValid } from './helper/asyncapi' +import { diffsMatcher } from './helper/matchers' +import { COMPARE_SCOPE_SEND } from '../src/asyncapi' +import { COMPARE_SCOPE_COMPONENTS } from '../src/graphapi' + +// ------------------------------------------------------------------ +// Spec factory +// +// Builds a minimal AsyncAPI 3 spec with: +// - Two component servers: serverA, serverB +// - Two component messages: MessageA (payload: string), MessageB (payload: integer) +// Override via messagePayloads: { MessageA: { type: 'integer' } } +// - A channel "myChannel" that references the given serverIds (in order) and +// exposes the given messageIds as channel-level messages +// - An operation "myOp" (send) that references the given messageIds (in order) +// via the channel messages +// ------------------------------------------------------------------ + +interface MakeSpecOptions { + serverIds?: string[] + messageIds?: string[] + messagePayloadsPatch?: Record + serversPatch?: Record + operationIds?: string[] +} + +const DEFAULT_PAYLOADS: Record = { + MessageA: { type: 'string' }, + MessageB: { type: 'integer' }, +} + +const DEFAULT_SERVERS: Record = { + serverA: { host: 'localhost', protocol: 'amqp' }, + serverB: { host: 'remote.host', protocol: 'amqp' }, +} + +function makeSpec({ + serverIds = ['serverA'], + messageIds = ['MessageA'], + messagePayloadsPatch, + serversPatch, + operationIds, +}: MakeSpecOptions = {}): { asyncapi: string } & Record { + const payloads = { ...DEFAULT_PAYLOADS, ...messagePayloadsPatch } + const servers = { ...DEFAULT_SERVERS, ...serversPatch } + + // Channel-level messages map: { MessageA: { $ref: '#/components/messages/MessageA' }, ... } + const channelMessages = Object.fromEntries( + messageIds.map(id => [id, { $ref: `#/components/messages/${id}` }]), + ) + + // Operation messages array: [{ $ref: '#/channels/myChannel/messages/MessageA' }, ...] + const operationMessages = messageIds.map(id => ({ + $ref: `#/channels/myChannel/messages/${id}`, + })) + + // Build operations map + const ops = operationIds ?? ['myOp'] + const operations = Object.fromEntries( + ops.map(opId => [ + opId, + { + action: 'send', + channel: { $ref: '#/channels/myChannel' }, + messages: operationMessages, + }, + ]), + ) + + return { + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + servers, + channels: { + myChannel: { + servers: serverIds.map(id => ({ $ref: `#/servers/${id}` })), + messages: channelMessages, + }, + }, + operations, + components: { + messages: Object.fromEntries( + ['MessageA', 'MessageB'].map(id => [ + id, + { payload: payloads[id] ?? { type: 'string' } }, + ]), + ), + }, + } +} + +describe('createPropertyMappingResolver', () => { + const SYM = Symbol('key') + const resolver = createPropertyMappingResolver(SYM) + const mockCtx = {} as any + + const item = (key: string) => ({ [SYM]: key }) + + it('maps elements in identical order', () => { + const before = [item('A'), item('B')] + const after = [item('A'), item('B')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [], + removed: [], + mapped: { 0: 0, 1: 1 }, + }) + }) + + it('maps elements in reversed order', () => { + const before = [item('A'), item('B')] + const after = [item('B'), item('A')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [], + removed: [], + mapped: { 0: 1, 1: 0 }, + }) + }) + + it('marks new element as added', () => { + const before = [item('A')] + const after = [item('A'), item('B')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [1], + removed: [], + mapped: { 0: 0 }, + }) + }) + + it('marks missing element as removed', () => { + const before = [item('A'), item('B')] + const after = [item('A')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [], + removed: [1], + mapped: { 0: 0 }, + }) + }) + + it('treats replaced key (A→B) as removed+added, not mapped', () => { + const before = [item('A')] + const after = [item('B')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [0], + removed: [0], + mapped: {}, + }) + }) + + it('treats elements without the symbol as unmatched (removed+added)', () => { + const before = [{}] + const after = [{}] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [0], + removed: [0], + mapped: {}, + }) + }) + + it('maps only elements that share the symbol; unmatched go to added/removed', () => { + const before = [item('A'), item('B')] + const after = [item('A'), item('C')] + expect(resolver(before, after, mockCtx)).toEqual({ + added: [1], + removed: [1], + mapped: { 0: 0 }, + }) + }) +}) + +describe('firstReferenceKeyProperty retained in merged document if explicitly passed', () => { + const TEST_FIRST_REF_KEY_PROP = Symbol('test-first-ref-key') + + const COMPARE_OPTIONS: CompareOptions = { + firstReferenceKeyProperty: TEST_FIRST_REF_KEY_PROP, + } + + type Doc = Record + + const getMessages = (merged: unknown): Doc[] => + (((merged as Doc).operations as Doc)?.myOp as Doc)?.messages as Doc[] + + const getServers = (merged: unknown): Doc[] => + (((merged as Doc).channels as Doc)?.myChannel as Doc)?.servers as Doc[] + + + describe('messages', () => { + it('symbol present on matched/modified message merged node', async () => { + const before = makeSpec({ messageIds: ['MessageA'] }) + const after = makeSpec({ + messageIds: ['MessageA'], + messagePayloadsPatch: { MessageA: { type: 'integer' } }, + }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after, COMPARE_OPTIONS) + const messages = getMessages(merged) + expect(messages[0][TEST_FIRST_REF_KEY_PROP]).toBe('MessageA') + }) + + it('symbol present on added message node in merged document', async () => { + const before = makeSpec({ messageIds: ['MessageA'] }) + const after = makeSpec({ messageIds: ['MessageA', 'MessageB'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after, COMPARE_OPTIONS) + const messages = getMessages(merged) + + expect(messages[0][TEST_FIRST_REF_KEY_PROP]).toBe('MessageA') + expect(messages[1][TEST_FIRST_REF_KEY_PROP]).toBe('MessageB') + }) + + it('symbol present on removed message node in merged document', async () => { + const before = makeSpec({ messageIds: ['MessageA', 'MessageB'] }) + const after = makeSpec({ messageIds: ['MessageA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged, diffs } = apiDiff(before, after, COMPARE_OPTIONS) + const messages = getMessages(merged) + + expect(messages[0][TEST_FIRST_REF_KEY_PROP]).toBe('MessageA') + expect(messages[1][TEST_FIRST_REF_KEY_PROP]).toBe('MessageB') + }) + }) + + describe('servers', () => { + + it('symbol present on matched server node in merged document', async () => { + const before = makeSpec({ serverIds: ['serverA'] }) + const after = makeSpec({ serverIds: ['serverA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after, COMPARE_OPTIONS) + const servers = getServers(merged) + expect(servers[0][TEST_FIRST_REF_KEY_PROP]).toBe('serverA') + }) + + it('symbol present on added server node in merged document', async () => { + const before = makeSpec({ serverIds: ['serverA'] }) + const after = makeSpec({ serverIds: ['serverA', 'serverB'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after, COMPARE_OPTIONS) + const servers = getServers(merged) + + expect(servers[0][TEST_FIRST_REF_KEY_PROP]).toBe('serverA') + expect(servers[1][TEST_FIRST_REF_KEY_PROP]).toBe('serverB') + }) + + it('symbol present on removed server node in merged document', async () => { + const before = makeSpec({ serverIds: ['serverA', 'serverB'] }) + const after = makeSpec({ serverIds: ['serverA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after, COMPARE_OPTIONS) + const servers = getServers(merged) + + expect(servers[0][TEST_FIRST_REF_KEY_PROP]).toBe('serverA') + expect(servers[1][TEST_FIRST_REF_KEY_PROP]).toBe('serverB') + }) + }) + + describe('firstReferenceKeyProperty not passed', () => { + it('symbol absent on merged message when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ messageIds: ['MessageA'] }) + const after = makeSpec({ messageIds: ['MessageA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const messages = getMessages(merged) + + expect(Object.getOwnPropertySymbols(messages[0] as object)).toBeEmpty() + }) + + it('symbol absent on added message when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ messageIds: ['MessageA'] }) + const after = makeSpec({ messageIds: ['MessageA', 'MessageB'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const messages = getMessages(merged) + + expect(messages).toHaveLength(2) + for (const msg of messages) { + expect(Object.getOwnPropertySymbols(msg as object)).toBeEmpty() + } + }) + + it('symbol absent on removed message when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ messageIds: ['MessageA', 'MessageB'] }) + const after = makeSpec({ messageIds: ['MessageA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const messages = getMessages(merged) + + expect(messages).toHaveLength(2) + for (const msg of messages) { + expect(Object.getOwnPropertySymbols(msg as object)).toBeEmpty() + } + }) + + it('symbol absent on merged server when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ serverIds: ['serverA'] }) + const after = makeSpec({ serverIds: ['serverA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const servers = getServers(merged) + + expect(Object.getOwnPropertySymbols(servers[0] as object)).toBeEmpty() + }) + + it('symbol absent on added server when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ serverIds: ['serverA'] }) + const after = makeSpec({ serverIds: ['serverA', 'serverB'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const servers = getServers(merged) + + expect(servers).toHaveLength(2) + for (const server of servers) { + expect(Object.getOwnPropertySymbols(server as object)).toBeEmpty() + } + }) + + it('symbol absent on removed server when firstReferenceKeyProperty not passed', async () => { + const before = makeSpec({ serverIds: ['serverA', 'serverB'] }) + const after = makeSpec({ serverIds: ['serverA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { merged } = apiDiff(before, after) + const servers = getServers(merged) + + expect(servers).toHaveLength(2) + for (const server of servers) { + expect(Object.getOwnPropertySymbols(server as object)).toBeEmpty() + } + }) + }) +}) + +describe('messages mapped by firstReferenceKeyProperty', () => { + it('reordered operation messages produce no diffs', async () => { + const before = makeSpec({ messageIds: ['MessageA', 'MessageB'] }) + const after = makeSpec({ messageIds: ['MessageB', 'MessageA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toBeEmpty() + }) + + it('modified message content with order swapped generates only type replace diffs', async () => { + const before = makeSpec({ + messageIds: ['MessageA', 'MessageB'], + }) + const after = makeSpec({ + messageIds: ['MessageB', 'MessageA'], + messagePayloadsPatch: { MessageA: { type: 'integer' } }, + }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + // diffs for MessageA in 3 scopes are reported only + expect(diffs).toHaveLength(3) + const COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH = ['components', 'messages', 'MessageA', 'payload', 'type'] + const TYPE_CHANGE_DIFF = { + beforeDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + afterDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + action: DiffAction.replace, + beforeValue: 'string', + afterValue: 'integer', + } + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_COMPONENTS, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) + + it('add message, modify existing message', async () => { + const before = makeSpec({ + messageIds: ['MessageA'], + }) + const after = makeSpec({ + messageIds: ['MessageB', 'MessageA'], + messagePayloadsPatch: { MessageA: { type: 'integer' } }, + }) + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + + const COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH = ['components', 'messages', 'MessageA', 'payload', 'type'] + const TYPE_CHANGE_DIFF = { + beforeDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + afterDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + action: DiffAction.replace, + beforeValue: 'string', + afterValue: 'integer', + } + const ADD_MESSAGE_DIFF = { + afterDeclarationPaths: [['channels', 'myChannel', 'messages', 'MessageB']], + action: DiffAction.add, + afterValue: { + payload: { + type: "integer", + }, + }, + } + + expect(diffs).toHaveLength(6) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + ...ADD_MESSAGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...ADD_MESSAGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...ADD_MESSAGE_DIFF, + afterDeclarationPaths: [['operations', 'myOp', 'messages', 0]], + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_COMPONENTS, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) + + it('remove message, modify remaining message', async () => { + const before = makeSpec({ + messageIds: ['MessageB', 'MessageA'], + }) + const after = makeSpec({ + messageIds: ['MessageA'], + messagePayloadsPatch: { MessageA: { type: 'integer' } }, + }) + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + const COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH = ['components', 'messages', 'MessageA', 'payload', 'type'] + const TYPE_CHANGE_DIFF = { + beforeDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + afterDeclarationPaths: [COMPONENTS_MESSAGE_A_PAYLOAD_TYPE_PATH], + action: DiffAction.replace, + beforeValue: 'string', + afterValue: 'integer', + } + const REMOVE_MESSAGE_DIFF = { + beforeDeclarationPaths: [['channels', 'myChannel', 'messages', 'MessageB']], + action: DiffAction.remove, + beforeValue: { + payload: { + type: "integer", + }, + }, + } + + expect(diffs).toHaveLength(6) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + ...REMOVE_MESSAGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...REMOVE_MESSAGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...REMOVE_MESSAGE_DIFF, + beforeDeclarationPaths: [['operations', 'myOp', 'messages', 0]], + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_COMPONENTS, + }), + expect.objectContaining({ + ...TYPE_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) +}) + +describe('servers mapped by key', () => { + it('reordered servers produce no diffs', async () => { + const before = makeSpec({ serverIds: ['serverA', 'serverB'] }) + const after = makeSpec({ serverIds: ['serverB', 'serverA'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toBeEmpty() + }) + + it('modified server content with order swapped generates only replace diffs', async () => { + const before = makeSpec({ + serverIds: ['serverA', 'serverB'], + }) + const after = makeSpec({ + serverIds: ['serverB', 'serverA'], + serversPatch: { + serverB: { host: 'remote.changed', protocol: 'amqp' }, + }, + }) + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toHaveLength(2) + const HOST_CHANGE_DIFF = { + beforeValue: 'remote.host', + afterValue: 'remote.changed', + action: DiffAction.replace, + afterDeclarationPaths: [['servers', 'serverB', 'host']], + beforeDeclarationPaths: [['servers', 'serverB', 'host']], + } + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) + + it('add server, modify existing server', async () => { + const before = makeSpec({ + serverIds: ['serverA'], + }) + const after = makeSpec({ + serverIds: ['serverB', 'serverA'], + serversPatch: { + serverA: { host: 'localhost.changed', protocol: 'amqp' }, + }, + }) + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + const HOST_CHANGE_DIFF = { + beforeValue: 'localhost', + afterValue: 'localhost.changed', + action: DiffAction.replace, + afterDeclarationPaths: [['servers', 'serverA', 'host']], + beforeDeclarationPaths: [['servers', 'serverA', 'host']], + } + const ADD_SERVER_DIFF = { + afterDeclarationPaths: [['channels', 'myChannel', 'servers', 0]], + action: DiffAction.add, + afterValue: { + host: 'remote.host', + protocol: 'amqp', + }, + } + + expect(diffs).toHaveLength(4) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + ...ADD_SERVER_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...ADD_SERVER_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) + + it('remove server, modify remaining server', async () => { + const before = makeSpec({ + serverIds: ['serverB', 'serverA'], + }) + const after = makeSpec({ + serverIds: ['serverA'], + serversPatch: { + serverA: { host: 'localhost.changed', protocol: 'amqp' }, + }, + }) + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + const HOST_CHANGE_DIFF = { + beforeValue: 'localhost', + afterValue: 'localhost.changed', + action: DiffAction.replace, + afterDeclarationPaths: [['servers', 'serverA', 'host']], + beforeDeclarationPaths: [['servers', 'serverA', 'host']], + } + const REMOVE_SERVER_DIFF = { + beforeDeclarationPaths: [['channels', 'myChannel', 'servers', 0]], + action: DiffAction.remove, + beforeValue: { + host: 'remote.host', + protocol: 'amqp', + }, + } + + expect(diffs).toHaveLength(4) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + ...REMOVE_SERVER_DIFF, + + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...REMOVE_SERVER_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_ROOT, + }), + expect.objectContaining({ + ...HOST_CHANGE_DIFF, + scope: COMPARE_SCOPE_SEND, + }), + ])) + }) +}) + +describe('operations mapped by key', () => { + it('reordered operations produce no diffs', async () => { + const before = makeSpec({ operationIds: ['opA', 'opB'] }) + const after = makeSpec({ operationIds: ['opA', 'opB'] }) + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toBeEmpty() + }) + + it('modified operation combined with changing operations order generates only replace diffs', async () => { + const before = makeSpec({ operationIds: ['opA', 'opB'] }) + const after = makeSpec({ operationIds: ['opB', 'opA'] }) + + ; (after as any).operations.opB.description = 'changed description' + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toHaveLength(1) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + afterValue: "changed description", + action: "add", + afterDeclarationPaths: [ + [ + "operations", + "opB", + "description", + ], + ], + scope: "send", + }), + ])) + }) + + it('add operation, modify existing operation', async () => { + const before = makeSpec({ operationIds: ['opA'] }) + const after = makeSpec({ operationIds: ['opB', 'opA'] }) + + ; (after as any).operations.opA.description = 'changed description' + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toHaveLength(2) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + afterDeclarationPaths: [['operations', 'opB']], + action: DiffAction.add, + scope: 'root', + }), + expect.objectContaining({ + afterValue: "changed description", + action: "add", + afterDeclarationPaths: [ + [ + "operations", + "opA", + "description", + ], + ], + scope: "send", + }), + ])) + }) + + it('remove operation, modify remaining operation', async () => { + const before = makeSpec({ operationIds: ['opB', 'opA'] }) + const after = makeSpec({ operationIds: ['opA'] }) + + ; (after as any).operations.opA.description = 'changed description' + + await parseAsyncApiAndAssertValid(before) + await parseAsyncApiAndAssertValid(after) + + const { diffs } = apiDiff(before, after) + + expect(diffs).toHaveLength(2) + expect(diffs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + beforeDeclarationPaths: [['operations', 'opB']], + action: DiffAction.remove, + scope: 'root', + }), + expect.objectContaining({ + afterValue: "changed description", + action: "add", + afterDeclarationPaths: [ + [ + "operations", + "opA", + "description", + ], + ], + scope: "send", + }), + ])) + }) +}) diff --git a/test/asyncapi.multiformat-schema.test.ts b/test/asyncapi.multiformat-schema.test.ts new file mode 100644 index 0000000..c2f30ab --- /dev/null +++ b/test/asyncapi.multiformat-schema.test.ts @@ -0,0 +1,392 @@ +import { apiDiff, breaking, CompareOptions, DiffAction, nonBreaking, unclassified } from '../src' +import { COMPARE_SCOPE_RECEIVE, COMPARE_SCOPE_SEND } from '../src/asyncapi/asyncapi3.const' +import { COMPARE_SCOPE_ROOT } from '../src/types' +import { diffsMatcher } from './helper/matchers' + +const TEST_COMPARE_OPTIONS: CompareOptions = { + unify: true, +} + +// --- Minimal spec wrappers --- + +const sendSpec = (payload: unknown) => ({ + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + channels: { + ch: { + messages: { msg: { payload } }, + }, + }, + operations: { + op: { + action: 'send', + channel: { $ref: '#/channels/ch' }, + messages: [{ $ref: '#/channels/ch/messages/msg' }], + }, + }, +}) + +const receiveSpec = (payload: unknown) => ({ + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + channels: { + ch: { + messages: { msg: { payload } }, + }, + }, + operations: { + op: { + action: 'receive', + channel: { $ref: '#/channels/ch' }, + messages: [{ $ref: '#/channels/ch/messages/msg' }], + }, + }, +}) + +const sendSpecWithReply = (payload: unknown, replyPayload: unknown) => ({ + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + channels: { + ch: { messages: { msg: { payload } } }, + replyCh: { messages: { replyMsg: { payload: replyPayload } } }, + }, + operations: { + op: { + action: 'send', + channel: { $ref: '#/channels/ch' }, + messages: [{ $ref: '#/channels/ch/messages/msg' }], + reply: { + channel: { $ref: '#/channels/replyCh' }, + messages: [{ $ref: '#/channels/replyCh/messages/replyMsg' }], + }, + }, + }, +}) + +const receiveSpecWithReply = (payload: unknown, replyPayload: unknown) => ({ + asyncapi: '3.0.0', + info: { title: 'Test', version: '1.0.0' }, + channels: { + ch: { messages: { msg: { payload } } }, + replyCh: { messages: { replyMsg: { payload: replyPayload } } }, + }, + operations: { + op: { + action: 'receive', + channel: { $ref: '#/channels/ch' }, + messages: [{ $ref: '#/channels/ch/messages/msg' }], + reply: { + channel: { $ref: '#/channels/replyCh' }, + messages: [{ $ref: '#/channels/replyCh/messages/replyMsg' }], + }, + }, + }, +}) + +// --- Schema constants for tests --- + +const ASYNCAPI_FORMAT = 'application/vnd.aai.asyncapi+json;version=3.0.0' +const ASYNCAPI_FORMAT_ALT = 'application/vnd.aai.asyncapi;version=3.0.0' +const OPENAPI30_FORMAT = 'application/vnd.oai.openapi+json;version=3.0.0' +const OPENAPI30_FORMAT_ALT = 'application/vnd.oai.openapi;version=3.0.0' +const JSON_DRAFT7_FORMAT = 'application/schema+json;version=draft-07' +const UNKNOWN_FORMAT = 'application/x-custom-format;version=1.0' + +// --- Group 1: Adapter — schema object ↔ multi-format schema object --- + +describe('AsyncAPI multi-format schema - Adapter', () => { + it('schema-to-multiformat-no-content-change: plain schema before → multi-format after (same content)', () => { + const plainSchema = { type: 'string' } + const multiFormatSchema = { schema: { type: 'string' }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(sendSpec(plainSchema), sendSpec(multiFormatSchema), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([])) + }) + + it('multiformat-to-schema-no-content-change: multi-format before → plain schema after (same content)', () => { + const multiFormatSchema = { schema: { type: 'string' }, schemaFormat: ASYNCAPI_FORMAT } + const plainSchema = { type: 'string' } + + const { diffs } = apiDiff(sendSpec(multiFormatSchema), sendSpec(plainSchema), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([])) + }) + + it('schema-to-multiformat-with-type-change: plain string → multi-format integer (send scope)', () => { + const before = { type: 'string' } + const after = { schema: { type: 'integer' }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + action: DiffAction.replace, + scope: COMPARE_SCOPE_SEND, + type: breaking, + }), + expect.objectContaining({ + action: DiffAction.replace, + scope: COMPARE_SCOPE_ROOT, + type: breaking, + }), + ])) + }) + + it('multiformat-to-schema-with-type-change: multi-format integer → plain string (send scope)', () => { + const before = { schema: { type: 'integer' }, schemaFormat: ASYNCAPI_FORMAT } + const after = { type: 'string' } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + action: DiffAction.replace, + scope: COMPARE_SCOPE_SEND, + type: breaking, + }), + expect.objectContaining({ + action: DiffAction.replace, + scope: COMPARE_SCOPE_ROOT, + type: breaking, + }), + ])) + }) +}) + +// --- Group 2: Schema rules selection based on schemaFormat --- + +describe('AsyncAPI multi-format schema - Schema rules selection', () => { + it('asyncapi-format-uses-asyncapi-schema-rules: type change is breaking', () => { + const before = { schema: { type: 'string' }, schemaFormat: ASYNCAPI_FORMAT } + const after = { schema: { type: 'integer' }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: breaking }), + ])) + }) + + it('asyncapi-format-uses-asyncapi-schema-rules: nullable change is unclassified', () => { + const before = { schema: { type: 'string', nullable: false }, schemaFormat: ASYNCAPI_FORMAT } + const after = { schema: { type: 'string', nullable: true }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + // AsyncAPI schema rules does not have a rule for nullable property, hence unclassified + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: unclassified }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: unclassified }), + ])) + }) + + it('asyncapi-format-aliases: alternate asyncapi format string also uses asyncapi schema rules', () => { + const before = { schema: { type: 'string', nullable: false }, schemaFormat: ASYNCAPI_FORMAT_ALT } + const after = { schema: { type: 'string', nullable: true }, schemaFormat: ASYNCAPI_FORMAT_ALT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: unclassified }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: unclassified }), + ])) + }) + + it('openapi30-format-uses-openapi-schema-rules: nullable change is detected', () => { + const before = { schema: { type: 'string', nullable: false }, schemaFormat: OPENAPI30_FORMAT } + const after = { schema: { type: 'string', nullable: true }, schemaFormat: OPENAPI30_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + // OpenAPI schema rules does have a rule for nullable property, hence nonBreaking + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: nonBreaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: nonBreaking }), + ])) + }) + + it('openapi30-format-aliases: alternate openapi format string also selects openapi rules', () => { + const before = { schema: { type: 'string', nullable: false }, schemaFormat: OPENAPI30_FORMAT_ALT } + const after = { schema: { type: 'string', nullable: true }, schemaFormat: OPENAPI30_FORMAT_ALT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: nonBreaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: nonBreaking }), + ])) + }) + + it('json-draft7-format-uses-json-schema-rules: type change is breaking', () => { + const before = { schema: { type: 'string' }, schemaFormat: JSON_DRAFT7_FORMAT } + const after = { schema: { type: 'integer' }, schemaFormat: JSON_DRAFT7_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: breaking }), + ])) + }) + + it('json-draft7-format-uses-json-schema-rules: nullable change is unclassified', () => { + const before = { schema: { type: 'string', nullable: false }, schemaFormat: JSON_DRAFT7_FORMAT } + const after = { schema: { type: 'string', nullable: true }, schemaFormat: JSON_DRAFT7_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.replace, type: unclassified }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.replace, type: unclassified }), + ])) + }) + + it('unknown-format-fallback: changes under unknown format are unclassified', () => { + const before = { schema: { type: 'string' }, schemaFormat: UNKNOWN_FORMAT } + const after = { schema: { type: 'integer' }, schemaFormat: UNKNOWN_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + // All diffs under an unknown format should be unclassified + expect(diffs.length).toBeGreaterThanOrEqual(0) + for (const diff of diffs) { + expect(diff.type).toBe(unclassified) + } + }) +}) + +// --- Group 3: Scope-based dynamic classification (send vs receive) --- + +describe('AsyncAPI: scope-based classification', () => { + describe('plain AsyncAPI schema', () => { + it('add-required-property-send-scope: breaking (producer must send it)', () => { + const before = { type: 'object', properties: { a: { type: 'string' } } } + const after = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.add, type: breaking }), + ])) + }) + + it('add-required-property-receive-scope: nonBreaking (consumer doesn\'t need to produce it)', () => { + const before = { type: 'object', properties: { a: { type: 'string' } } } + const after = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + + const { diffs } = apiDiff(receiveSpec(before), receiveSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_RECEIVE, action: DiffAction.add, type: nonBreaking }), + ])) + }) + + it('remove-required-property-send-scope: nonBreaking', () => { + const before = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + const after = { type: 'object', properties: { a: { type: 'string' } } } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.remove, type: nonBreaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.remove, type: nonBreaking }), + ])) + }) + + it('remove-required-property-receive-scope: breaking', () => { + const before = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + const after = { type: 'object', properties: { a: { type: 'string' } } } + + const { diffs } = apiDiff(receiveSpec(before), receiveSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.remove, type: nonBreaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_RECEIVE, action: DiffAction.remove, type: breaking }), + ])) + }) + }) + + describe('multi-format AsyncAPI schema', () => { + it('multiformat-asyncapi-add-required-send-scope: breaking', () => { + const before = { schema: { type: 'object', properties: { a: { type: 'string' } } }, schemaFormat: ASYNCAPI_FORMAT } + const after = { schema: { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(sendSpec(before), sendSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.add, type: breaking }), + ])) + }) + + it('multiformat-asyncapi-add-required-receive-scope: nonBreaking', () => { + const before = { schema: { type: 'object', properties: { a: { type: 'string' } } }, schemaFormat: ASYNCAPI_FORMAT } + const after = { schema: { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }, schemaFormat: ASYNCAPI_FORMAT } + + const { diffs } = apiDiff(receiveSpec(before), receiveSpec(after), TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_RECEIVE, action: DiffAction.add, type: nonBreaking }), + ])) + }) + }) +}) + +// --- Group 4: Reply scope inversion --- + +describe('AsyncAPI multi-format schema - Reply scope inversion', () => { + it('reply-of-send-operation-uses-receive-scope: add required is nonBreaking', () => { + // send operation → reply scope = receive + // adding required in receive scope is nonBreaking + const sharedPayload = { type: 'object', properties: { a: { type: 'string' } } } + const replyPayloadBefore = { type: 'object', properties: { a: { type: 'string' } } } + const replyPayloadAfter = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + + const before = sendSpecWithReply(sharedPayload, replyPayloadBefore) + const after = sendSpecWithReply(sharedPayload, replyPayloadAfter) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_RECEIVE, action: DiffAction.add, type: nonBreaking }), + ])) + }) + + it('reply-of-receive-operation-uses-send-scope: add required is breaking', () => { + const sharedPayload = { type: 'object', properties: { a: { type: 'string' } } } + const replyPayloadBefore = { type: 'object', properties: { a: { type: 'string' } } } + const replyPayloadAfter = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + + const before = receiveSpecWithReply(sharedPayload, replyPayloadBefore) + const after = receiveSpecWithReply(sharedPayload, replyPayloadAfter) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.add, type: breaking }), + ])) + }) + + it('diffs for each scope are present for a change in shared payload', () => { + const sharedPayloadBefore = { type: 'object', properties: { a: { type: 'string' } } } + const sharedPayloadAfter = { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + + const before = sendSpecWithReply(sharedPayloadBefore, sharedPayloadBefore) + const after = sendSpecWithReply(sharedPayloadAfter, sharedPayloadAfter) + + const { diffs } = apiDiff(before, after, TEST_COMPARE_OPTIONS) + + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ scope: COMPARE_SCOPE_ROOT, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_SEND, action: DiffAction.add, type: breaking }), + expect.objectContaining({ scope: COMPARE_SCOPE_RECEIVE, action: DiffAction.add, type: nonBreaking }), + ])) + }) +}) diff --git a/test/compatibility-suites/asyncapi/operation-receive-message-headers-schema.test.ts b/test/compatibility-suites/asyncapi/operation-receive-message-headers-schema.test.ts index 3dc37d1..95fd86f 100644 --- a/test/compatibility-suites/asyncapi/operation-receive-message-headers-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-receive-message-headers-schema.test.ts @@ -14,6 +14,6 @@ const MESSAGE_HEADERS_PATH = [ 'header1', ] -describe.skip('AsyncAPI Operation Receive Message Headers', () => { +describe('AsyncAPI Operation Receive Message Headers', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, MESSAGE_HEADERS_PATH, DATA_FLOW_DIRECTION_RECEIVE) }) diff --git a/test/compatibility-suites/asyncapi/operation-receive-message-payload-schema.test.ts b/test/compatibility-suites/asyncapi/operation-receive-message-payload-schema.test.ts index e3f33c4..97fbf8d 100644 --- a/test/compatibility-suites/asyncapi/operation-receive-message-payload-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-receive-message-payload-schema.test.ts @@ -12,6 +12,6 @@ const MESSAGE_PAYLOAD_PATH = [ 'payload', ] -describe.skip('AsyncAPI Operation Receive Message Payload', () => { +describe('AsyncAPI Operation Receive Message Payload', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, MESSAGE_PAYLOAD_PATH, DATA_FLOW_DIRECTION_RECEIVE) }) diff --git a/test/compatibility-suites/asyncapi/operation-reply-receive-message-headers-schema.test.ts b/test/compatibility-suites/asyncapi/operation-reply-receive-message-headers-schema.test.ts index 5009015..dfb2928 100644 --- a/test/compatibility-suites/asyncapi/operation-reply-receive-message-headers-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-reply-receive-message-headers-schema.test.ts @@ -14,6 +14,6 @@ const REPLY_MESSAGE_HEADERS_PATH = [ 'header1', ] -describe.skip('AsyncAPI Operation Reply Receive Message Headers', () => { +describe('AsyncAPI Operation Reply Receive Message Headers', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, REPLY_MESSAGE_HEADERS_PATH, DATA_FLOW_DIRECTION_SEND) }) diff --git a/test/compatibility-suites/asyncapi/operation-reply-receive-message-payload-schema.test.ts b/test/compatibility-suites/asyncapi/operation-reply-receive-message-payload-schema.test.ts index ccb60d1..536e1ea 100644 --- a/test/compatibility-suites/asyncapi/operation-reply-receive-message-payload-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-reply-receive-message-payload-schema.test.ts @@ -12,6 +12,6 @@ const REPLY_MESSAGE_PAYLOAD_PATH = [ 'payload', ] -describe.skip('AsyncAPI Operation Reply Receive Message Payload', () => { +describe('AsyncAPI Operation Reply Receive Message Payload', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, REPLY_MESSAGE_PAYLOAD_PATH, DATA_FLOW_DIRECTION_SEND) }) diff --git a/test/compatibility-suites/asyncapi/operation-reply-send-message-headers-schema.test.ts b/test/compatibility-suites/asyncapi/operation-reply-send-message-headers-schema.test.ts index 4b7aa89..f3f715a 100644 --- a/test/compatibility-suites/asyncapi/operation-reply-send-message-headers-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-reply-send-message-headers-schema.test.ts @@ -14,6 +14,6 @@ const REPLY_MESSAGE_HEADERS_PATH = [ 'header1', ] -describe.skip('AsyncAPI Operation Reply Send Message Headers', () => { +describe('AsyncAPI Operation Reply Send Message Headers', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, REPLY_MESSAGE_HEADERS_PATH, DATA_FLOW_DIRECTION_RECEIVE) }) diff --git a/test/compatibility-suites/asyncapi/operation-reply-send-message-payload-schema.test.ts b/test/compatibility-suites/asyncapi/operation-reply-send-message-payload-schema.test.ts index b56a8eb..b8b0430 100644 --- a/test/compatibility-suites/asyncapi/operation-reply-send-message-payload-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-reply-send-message-payload-schema.test.ts @@ -12,6 +12,6 @@ const REPLY_MESSAGE_PAYLOAD_PATH = [ 'payload', ] -describe.skip('AsyncAPI Operation Reply Send Message Payload', () => { +describe('AsyncAPI Operation Reply Send Message Payload', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, REPLY_MESSAGE_PAYLOAD_PATH, DATA_FLOW_DIRECTION_RECEIVE) }) diff --git a/test/compatibility-suites/asyncapi/operation-send-message-headers-schema.test.ts b/test/compatibility-suites/asyncapi/operation-send-message-headers-schema.test.ts index d6d3a06..0644fb4 100644 --- a/test/compatibility-suites/asyncapi/operation-send-message-headers-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-send-message-headers-schema.test.ts @@ -14,6 +14,6 @@ const MESSAGE_HEADERS_PATH = [ 'header1', ] -describe.skip('AsyncAPI Operation Send Message Headers', () => { +describe('AsyncAPI Operation Send Message Headers', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, MESSAGE_HEADERS_PATH, DATA_FLOW_DIRECTION_SEND) }) diff --git a/test/compatibility-suites/asyncapi/operation-send-message-payload-schema.test.ts b/test/compatibility-suites/asyncapi/operation-send-message-payload-schema.test.ts index 72711d3..e02be37 100644 --- a/test/compatibility-suites/asyncapi/operation-send-message-payload-schema.test.ts +++ b/test/compatibility-suites/asyncapi/operation-send-message-payload-schema.test.ts @@ -12,6 +12,6 @@ const MESSAGE_PAYLOAD_PATH = [ 'payload', ] -describe.skip('AsyncAPI Operation Send Message Payload', () => { +describe('AsyncAPI Operation Send Message Payload', () => { runGeneralSchemaTests(TEST_SPEC_TYPE_ASYNC_API, SUITE_ID, MESSAGE_PAYLOAD_PATH, DATA_FLOW_DIRECTION_SEND) }) diff --git a/test/compatibility-suites/schemas/schema-test-runner-general.ts b/test/compatibility-suites/schemas/schema-test-runner-general.ts index ba4d007..677d159 100644 --- a/test/compatibility-suites/schemas/schema-test-runner-general.ts +++ b/test/compatibility-suites/schemas/schema-test-runner-general.ts @@ -11,6 +11,7 @@ import { DataFlowDirection, TEST_DEFAULTS_DECLARATION_PATHS, } from '../utils' +import { COMPARE_SCOPE_ROOT, CompareScope } from '../../../src/types/compare' export function runGeneralSchemaTests( suiteType: TestSpecType, @@ -19,6 +20,7 @@ export function runGeneralSchemaTests( direction: DataFlowDirection, ): void { const expectedType = createExpectedDiffTypeSelector(direction) + const skipScopesRoot = new Set([COMPARE_SCOPE_ROOT]) describe('General', () => { describe('JSON Schema Keywords', () => { @@ -30,7 +32,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'title']], type: annotation, }), - ])) + ], skipScopesRoot)) }) test('update-schema-title', async () => { @@ -42,7 +44,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'title']], type: annotation, }), - ])) + ], skipScopesRoot)) }) test('remove-schema-title', async () => { @@ -53,7 +55,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'title']], type: annotation, }), - ])) + ], skipScopesRoot)) }) test('update-schema-type', async () => { @@ -65,7 +67,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'type']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('update-schema-type-from-specific-type-to-any-type', async () => { @@ -79,10 +81,11 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) - test('update-schema-type-to-an-equivalent-value', async () => { + //TODO: use different set of types for different dialects + test.skip('update-schema-type-to-an-equivalent-value', async () => { const result = await compareFiles(suiteId, currentTestId(), suiteType) expect(result).toEqual([]) }) @@ -95,7 +98,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'enum']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-enum', async () => { @@ -106,7 +109,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'enum']], type: expectedType(nonBreaking, risky), }), - ])) + ], skipScopesRoot)) }) test('add-enum-value', async () => { @@ -117,7 +120,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'enum', 2]], type: expectedType(nonBreaking, risky), }), - ])) + ], skipScopesRoot)) }) test('update-enum-value', async () => { @@ -133,7 +136,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'enum', 1]], type: expectedType(nonBreaking, risky), }), - ])) + ], skipScopesRoot)) }) test('remove-enum-value', async () => { @@ -144,7 +147,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'enum', 2]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('add-format-for-string-property', async () => { @@ -155,7 +158,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'format']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('update-format-for-string-property', async () => { @@ -167,7 +170,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'format']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('remove-format-for-string-property', async () => { @@ -178,7 +181,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'format']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-min-length-for-string-property', async () => { @@ -196,7 +199,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'minLength']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-min-length-for-string-property', async () => { @@ -208,7 +211,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minLength']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-min-length-for-string-property', async () => { @@ -220,7 +223,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minLength']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-min-length-for-string-property', async () => { @@ -238,7 +241,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-max-length-for-string-property', async () => { @@ -249,7 +252,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxLength']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-max-length-for-string-property', async () => { @@ -261,7 +264,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxLength']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-max-length-for-string-property', async () => { @@ -273,7 +276,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxLength']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-max-length-for-string-property', async () => { @@ -284,7 +287,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'maxLength']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-pattern-for-string-property', async () => { @@ -295,7 +298,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'pattern']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('update-pattern-for-string-property', async () => { @@ -307,7 +310,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'pattern']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('remove-pattern-for-string-property', async () => { @@ -318,7 +321,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'pattern']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-format-for-number-property', async () => { @@ -329,7 +332,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'format']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('update-format-for-number-property', async () => { @@ -341,7 +344,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'format']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('remove-format-for-number-property', async () => { @@ -352,7 +355,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'format']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-minimum-for-number-property', async () => { @@ -363,7 +366,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minimum']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-minimum-for-number-property', async () => { @@ -375,7 +378,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minimum']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-minimum-for-number-property', async () => { @@ -387,7 +390,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minimum']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-minimum-for-number-property', async () => { @@ -398,7 +401,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'minimum']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-maximum-for-number-property', async () => { @@ -409,7 +412,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maximum']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-maximum-for-number-property', async () => { @@ -421,7 +424,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maximum']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-maximum-for-number-property', async () => { @@ -433,7 +436,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maximum']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-maximum-for-number-property', async () => { @@ -444,7 +447,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'maximum']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-multiple-of-for-number-property', async () => { @@ -455,7 +458,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'multipleOf']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('update-multiple-of-for-number-property', async () => { @@ -467,7 +470,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'multipleOf']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('remove-multiple-of-for-number-property', async () => { @@ -478,7 +481,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'multipleOf']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-min-items-for-array-property', async () => { @@ -490,7 +493,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minItems']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-min-items-for-array-property', async () => { @@ -502,7 +505,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minItems']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-min-items-for-array-property', async () => { @@ -514,7 +517,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minItems']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-min-items-for-array-property', async () => { @@ -526,7 +529,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-max-items-for-array-property', async () => { @@ -537,7 +540,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxItems']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-max-items-for-array-property', async () => { @@ -549,7 +552,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxItems']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-max-items-for-array-property', async () => { @@ -561,7 +564,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxItems']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-max-items-for-array-property', async () => { @@ -572,7 +575,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'maxItems']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('prohibit-non-unique-items-for-array-property', async () => { @@ -590,7 +593,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'uniqueItems']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('allow-non-unique-items-for-array-property', async () => { @@ -608,7 +611,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'uniqueItems']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-new-property-compliance', async () => { @@ -619,7 +622,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'prop2']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('remove-property-compliance', async () => { @@ -630,7 +633,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'properties', 'prop2']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('add-required-property', async () => { @@ -646,7 +649,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'required', 1]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('add-required-property-with-default', async () => { @@ -662,7 +665,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'required', 1]], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('remove-required-property', async () => { @@ -673,7 +676,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'required', 0]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('update-required-property', async () => { @@ -689,7 +692,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'required', 0]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('mark-object-property-as-read-only', async () => { @@ -707,7 +710,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'readOnly']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('mark-object-property-as-not-read-only', async () => { @@ -725,7 +728,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('mark-object-property-as-write-only', async () => { @@ -743,7 +746,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'writeOnly']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('mark-object-property-as-not-write-only', async () => { @@ -761,7 +764,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'properties', 'option2', 'writeOnly']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('add-min-properties-for-object-property', async () => { @@ -773,7 +776,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minProperties']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-min-properties-for-object-property', async () => { @@ -785,7 +788,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minProperties']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-min-properties-for-object-property', async () => { @@ -797,7 +800,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'minProperties']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-min-properties-for-object-property', async () => { @@ -809,7 +812,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-max-properties-for-object-property', async () => { @@ -820,7 +823,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxProperties']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('increase-max-properties-for-object-property', async () => { @@ -832,7 +835,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxProperties']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('decrease-max-properties-for-object-property', async () => { @@ -844,7 +847,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'maxProperties']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-max-properties-for-object-property', async () => { @@ -855,7 +858,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'maxProperties']], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('update-definition-of-free-form-object', async () => { @@ -878,7 +881,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'additionalProperties', 'type']], type: expectedType(breaking, nonBreaking), }), - ]), + ], skipScopesRoot), ) }) @@ -891,7 +894,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'additionalProperties', 'type']], type: breaking, }), - ])) + ], skipScopesRoot)) }) test('remove-additional-properties', async () => { @@ -906,7 +909,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: TEST_DEFAULTS_DECLARATION_PATHS, type: expectedType(nonBreaking, breaking), }), - ]), + ], skipScopesRoot), ) }) @@ -918,7 +921,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'oneOf', 1]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-one-of-option', async () => { @@ -929,7 +932,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'oneOf', 2]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-one-of-option', async () => { @@ -940,7 +943,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'oneOf', 2]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-one-of', async () => { @@ -951,7 +954,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'oneOf', 1]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('add-any-of', async () => { @@ -962,7 +965,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'anyOf', 1]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('add-any-of-option', async () => { @@ -973,7 +976,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'anyOf', 2]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }) test('remove-any-of-option', async () => { @@ -984,7 +987,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'anyOf', 2]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-any-of', async () => { @@ -995,7 +998,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'anyOf', 1]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('add-all-of', async () => { @@ -1006,7 +1009,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'allOf', 1, 'properties', 'prop2']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('add-all-of-option', async () => { @@ -1017,7 +1020,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'allOf', 2, 'properties', 'prop3']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) test('remove-all-of-option', async () => { @@ -1028,7 +1031,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'allOf', 2, 'properties', 'prop3']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) test('remove-all-of', async () => { @@ -1039,7 +1042,7 @@ export function runGeneralSchemaTests( beforeDeclarationPaths: [[...commonPath, 'allOf', 1, 'properties', 'prop2']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) // TODO: fixme @@ -1052,7 +1055,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'type']], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }) // TODO: fixme @@ -1065,7 +1068,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'allOf']], type: breaking, }), - ])) + ], skipScopesRoot)) }) // TODO: fixme @@ -1078,7 +1081,7 @@ export function runGeneralSchemaTests( afterDeclarationPaths: [[...commonPath, 'type']], type: nonBreaking, }), - ])) + ], skipScopesRoot)) }) // --- General default value tests (no path needed, all expect empty diffs) --- @@ -1119,15 +1122,14 @@ export function runGeneralSchemaTests( suiteType, 'add-union-type', suiteId, - async ({ beforeVersion, afterVersion, diffs }) => { + async ({ diffs }) => { expect(diffs).toEqual(diffsMatcher([ - expectSpecVersionChange(suiteType, beforeVersion, afterVersion), expect.objectContaining({ action: DiffAction.add, afterDeclarationPaths: [[...commonPath, 'type', 1]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }, ) @@ -1135,15 +1137,14 @@ export function runGeneralSchemaTests( suiteType, 'add-null-to-union-type', suiteId, - async ({ beforeVersion, afterVersion, diffs }) => { + async ({ diffs }) => { expect(diffs).toEqual(diffsMatcher([ - expectSpecVersionChange(suiteType, beforeVersion, afterVersion), expect.objectContaining({ action: DiffAction.add, afterDeclarationPaths: [[...commonPath, 'type', 2]], type: expectedType(nonBreaking, breaking), }), - ])) + ], skipScopesRoot)) }, ) @@ -1151,15 +1152,14 @@ export function runGeneralSchemaTests( suiteType, 'remove-union-type', suiteId, - async ({ beforeVersion, afterVersion, diffs }) => { + async ({ diffs }) => { expect(diffs).toEqual(diffsMatcher([ - expectSpecVersionChange(suiteType, beforeVersion, afterVersion), expect.objectContaining({ action: DiffAction.remove, beforeDeclarationPaths: [[...commonPath, 'type', 1]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }, ) @@ -1167,15 +1167,14 @@ export function runGeneralSchemaTests( suiteType, 'remove-null-from-union-type', suiteId, - async ({ beforeVersion, afterVersion, diffs }) => { + async ({ diffs }) => { expect(diffs).toEqual(diffsMatcher([ - expectSpecVersionChange(suiteType, beforeVersion, afterVersion), expect.objectContaining({ action: DiffAction.remove, beforeDeclarationPaths: [[...commonPath, 'type', 2]], type: expectedType(breaking, nonBreaking), }), - ])) + ], skipScopesRoot)) }, ) diff --git a/test/helper/asyncapi.ts b/test/helper/asyncapi.ts new file mode 100644 index 0000000..3d5c75d --- /dev/null +++ b/test/helper/asyncapi.ts @@ -0,0 +1,27 @@ +import { Parser } from '@asyncapi/parser' +import type { Input } from '@asyncapi/parser/esm/types' +import type { v3 } from '@asyncapi/parser/esm/spec-types' +import 'jest-extended' + +const parser = new Parser() + +/** + * Parses an AsyncAPI spec with the AsyncAPI parser, asserts there are no diagnostics, + * and returns the parsed document as JSON. Use in tests to ensure the spec is valid + * and to compare unifier output with parser output. + */ +export async function parseAsyncApiAndAssertValid(spec: Input): Promise { + const { document, diagnostics } = await parser.parse(spec) + const filteredDiagnostics = diagnostics.filter( + diagnostic => !( + diagnostic.code === 'asyncapi-latest-version' && + diagnostic.message.includes('The latest version of AsyncAPi is not used') + ), + ) + expect(filteredDiagnostics).toBeEmpty() + const json = document?.json() + if (json === undefined) { + throw new Error('Expected document when diagnostics are empty') + } + return json as v3.AsyncAPIObject +} diff --git a/test/helper/matchers.ts b/test/helper/matchers.ts index 9fdfeb6..f89bf51 100644 --- a/test/helper/matchers.ts +++ b/test/helper/matchers.ts @@ -1,4 +1,5 @@ import { Diff } from '../../src' +import { CompareScope } from '../../src/types' import 'jest-extended' import { TEST_SPEC_TYPE_ASYNC_API, @@ -41,8 +42,8 @@ export interface ObjectContaining extends AsymmetricMatcher { export type RecursiveMatcher = { [P in keyof T]?: T[P] extends (infer U)[] ? ArrayContaining> : - T[P] extends object[] ? ExpectedRecursive : - T[P]; + T[P] extends object[] ? ExpectedRecursive : + T[P]; } export type DiffMatcher = ArrayContaining & Diff[] @@ -61,11 +62,35 @@ export function diffDescriptionMatcher( export function diffsMatcher( expected: Array | typeof DIFF_MATCHER_SKIP>, + skipScopes: Set = new Set(), ): DiffMatcher { const compactExpected = expected.filter( (value): value is RecursiveMatcher => value !== DIFF_MATCHER_SKIP, ) - return expect.toIncludeSameMembers(compactExpected) + const inner = expect.toIncludeSameMembers(compactExpected) + return { + $$typeof: Symbol.for('jest.asymmetricMatcher'), + asymmetricMatch(actual: Diff[]) { + const filteredActual = actual.filter(diff => !skipScopes.has(diff.scope)) + return inner.asymmetricMatch(filteredActual) + }, + toString() { + return 'DiffsMatcher' + }, + toAsymmetricMatcher() { + const itemStrings = compactExpected.map(item => { + if (item !== null && typeof item === 'object' && 'toAsymmetricMatcher' in item && typeof (item as any).toAsymmetricMatcher === 'function') { + return (item as any).toAsymmetricMatcher() + } + try { + return JSON.stringify(item, null, 2) + } catch { + return String(item) + } + }) + return `DiffsMatcher (skipScopes: [${[...skipScopes].join(', ')}]) [\n ${itemStrings.join(',\n ')}\n]` + }, + } as unknown as DiffMatcher } /**