diff --git a/.eslintrc.js b/.eslintrc.js index 3ced4092..93dc441d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,16 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint', - ], extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", ], + env: { + node: true, + es2020: true, + }, + rules: { + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + }, + ignorePatterns: ["dist/", "build/", "node_modules/"], }; diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e0933e65..16a01013 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,12 +6,12 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Use Node.js 14.15.1 - uses: actions/setup-node@v1 - with: - node-version: 14.15.1 - - name: npm install - run: npm install - - name: lint - run: npm run lint + - uses: actions/checkout@v1 + - name: Use Node.js 20.11.1 + uses: actions/setup-node@v1 + with: + node-version: 20.11.1 + - name: npm install + run: npm install + - name: lint + run: npm run lint diff --git a/.gitignore b/.gitignore index e1d17e70..da60a62a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build docs .DS_Store dist +.vscode \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..849bd664 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + printWidth: 80, + printWidth: 100, + tabWidth: 2, + trailingComma: "es5", +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index cbfe31c4..3f31e80e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,8 +1,8 @@ module.exports = { clearMocks: true, - coverageDirectory: '../coverage', + coverageDirectory: "../coverage", resetMocks: true, restoreMocks: true, - rootDir: './src', - preset: 'ts-jest' + rootDir: "./src", + preset: "ts-jest", }; diff --git a/package-lock.json b/package-lock.json index 017492e3..b8592764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@json-schema-tools/meta-schema": "^1.7.5", "@json-schema-tools/reference-resolver": "^1.2.6", "@open-rpc/meta-schema": "^1.14.9", + "@open-rpc/specification-extension-spec": "^1.0.2", "ajv": "^6.10.0", "detect-node": "^2.0.4", "fast-safe-stringify": "^2.0.7", @@ -30,11 +31,13 @@ "@types/node-fetch": "^2.1.6", "@types/rimraf": "^3.0.0", "@types/webpack-env": "^1.13.9", - "@typescript-eslint/eslint-plugin": "^4.11.1", - "@typescript-eslint/parser": "^4.11.1", - "eslint": "^7.17.0", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.0", + "eslint-config-prettier": "^8.0.0", + "eslint-plugin-prettier": "^4.0.0", "jest": "^29.7.0", "json-schema": "^0.4.0", + "prettier": "^2.0.0", "rimraf": "^3.0.0", "ts-jest": "^29.1.2", "typedoc": "^0.25.13", @@ -623,31 +626,72 @@ "node": ">=10.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -658,13 +702,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "engines": { - "node": ">= 4" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { @@ -672,6 +720,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -679,25 +728,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1104,12 +1181,13 @@ "integrity": "sha512-9e42zjhLIxzBONroNC4SGsTqdB877tzwH2S6lqgTav9K24kWJR9vNieeMVSuyqnY8FlclH21D8wsm/tuD9WA9Q==" }, "node_modules/@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.4", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" }, "engines": { @@ -1117,21 +1195,23 @@ } }, "node_modules/@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" }, "engines": { @@ -1143,6 +1223,15 @@ "resolved": "https://registry.npmjs.org/@open-rpc/meta-schema/-/meta-schema-1.14.9.tgz", "integrity": "sha512-2/CbDzOVpcaSnMs28TsRv8MKJwJi0TTYFlQ6q6qobAH26oIuhYgcZooKf4l71emgntU6MMcFQCA0h4mJ4dBCdA==" }, + "node_modules/@open-rpc/specification-extension-spec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@open-rpc/specification-extension-spec/-/specification-extension-spec-1.0.2.tgz", + "integrity": "sha512-EPVI1Mgp5vDSN2Xn+eWjd2t30WU+R0nFeNoP/dp3ox5/qUhNdqlNcwGJDNoJlqK56rk09FYXWJXKLHae4eBE8w==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1352,6 +1441,13 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1380,30 +1476,33 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1412,13 +1511,11 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1426,83 +1523,74 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "eslint": "*" }, "peerDependenciesMeta": { "typescript": { @@ -1510,30 +1598,14 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1541,21 +1613,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1567,59 +1640,52 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8.6.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1627,38 +1693,31 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "dev": true, + "license": "ISC" + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -1887,6 +1946,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1915,15 +1975,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1996,6 +2047,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2458,10 +2510,11 @@ } }, "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "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", @@ -2500,6 +2553,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -2556,18 +2610,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/envinfo": { "version": "7.11.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", @@ -2614,116 +2656,129 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" }, - "engines": { - "node": ">=8.0.0" + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, "engines": { - "node": ">=4" + "node": ">=8.0.0" } }, "node_modules/eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } + "license": "Python-2.0" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", @@ -2737,94 +2792,138 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "type-fest": "^0.20.2" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 4" + "node": ">=4.0" } }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "argparse": "^2.0.1" }, "bin": { - "semver": "bin/semver.js" + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/type-fest": { @@ -2832,6 +2931,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -2840,38 +2940,21 @@ } }, "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -2888,10 +2971,11 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -2904,6 +2988,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3018,6 +3103,30 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3026,8 +3135,9 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", @@ -3170,12 +3280,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3240,6 +3344,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -3262,11 +3367,39 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3304,10 +3437,11 @@ } }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3317,6 +3451,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3333,6 +3468,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3424,10 +3560,11 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "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" }, @@ -3444,6 +3581,16 @@ "node": ">=0.12.0" } }, + "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", @@ -4385,6 +4532,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4424,24 +4585,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -4513,17 +4656,19 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4561,10 +4706,11 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4584,6 +4730,13 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -4685,6 +4838,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4726,6 +4897,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -4789,6 +4961,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4832,6 +5005,45 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -4858,15 +5070,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4904,6 +5107,27 @@ } ] }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4919,18 +5143,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, - "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4940,15 +5152,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5022,9 +5225,9 @@ } }, "node_modules/run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -5039,7 +5242,11 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -5149,32 +5356,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5314,44 +5495,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/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 - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -5552,7 +5695,37 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } }, "node_modules/type-detect": { "version": "4.0.8", @@ -5679,12 +5852,6 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -5904,10 +6071,11 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5957,12 +6125,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 8ee820d2..883f7998 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:code": "tsc", "test": "npm run test:unit && npm run test:web", "test:unit": "jest --coverage", + "test-debug:unit": "node inspect node_modules/jest/bin/jest.js --coverage", "test:web": "npm run build:code && webpack && rm -rf dist", "watch:test": "jest --watch", "lint": "eslint . --ext .ts" @@ -34,6 +35,7 @@ "@json-schema-tools/meta-schema": "^1.7.5", "@json-schema-tools/reference-resolver": "^1.2.6", "@open-rpc/meta-schema": "^1.14.9", + "@open-rpc/specification-extension-spec": "^1.0.2", "ajv": "^6.10.0", "detect-node": "^2.0.4", "fast-safe-stringify": "^2.0.7", @@ -51,11 +53,13 @@ "@types/node-fetch": "^2.1.6", "@types/rimraf": "^3.0.0", "@types/webpack-env": "^1.13.9", - "@typescript-eslint/eslint-plugin": "^4.11.1", - "@typescript-eslint/parser": "^4.11.1", - "eslint": "^7.17.0", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.0", + "eslint-config-prettier": "^8.0.0", + "eslint-plugin-prettier": "^4.0.0", "jest": "^29.7.0", "json-schema": "^0.4.0", + "prettier": "^2.0.0", "rimraf": "^3.0.0", "ts-jest": "^29.1.2", "typedoc": "^0.25.13", diff --git a/src/apply-extension-spec.test.ts b/src/apply-extension-spec.test.ts new file mode 100644 index 00000000..b8900ce7 --- /dev/null +++ b/src/apply-extension-spec.test.ts @@ -0,0 +1,85 @@ +import applyExtensionSpec from "./apply-extension-spec"; +import metaSchema, { OpenrpcDocument } from "@open-rpc/meta-schema"; +import goodSchema from "./extension-good-schema.json"; +import getExtendedMetaSchema from "./get-extended-metaschema"; + +describe("applyExtensionSpec", () => { + it("should successfully apply extension spec to meta schema", () => { + const result = applyExtensionSpec(goodSchema as OpenrpcDocument, getExtendedMetaSchema()); + + // Check if extension was applied to methodObject + const methodObjectDef = result.definitions.methodObject; + expect(methodObjectDef.properties["x-notification"]).toBeDefined(); + expect(methodObjectDef.properties["x-notification"].type).toBe("boolean"); + expect(methodObjectDef.properties["x-notification"].description).toBe( + "Whether or not this method is a notification or not" + ); + expect(methodObjectDef.properties["x-notification"].summary).toBe("OpenRPC Notification"); + }); + + it("should handle multiple extensions", () => { + const multiExtensionDoc = { + ...goodSchema, + "x-extensions": [ + ...goodSchema["x-extensions"], + { + ...goodSchema["x-extensions"][0], + name: "x-another-extension", + schema: { type: "string" }, + }, + ], + }; + + const result = applyExtensionSpec(multiExtensionDoc as OpenrpcDocument, metaSchema); + const methodObjectDef = result.definitions.methodObject; + + expect(methodObjectDef.properties["x-notification"]).toBeDefined(); + expect(methodObjectDef.properties["x-another-extension"]).toBeDefined(); + expect(methodObjectDef.properties["x-another-extension"].type).toBe("string"); + }); + + it("should return unmodified schema when x-extensions is empty", () => { + const emptyExtensionsDoc = { + ...goodSchema, + "x-extensions": [], + }; + + const result = applyExtensionSpec(emptyExtensionsDoc as OpenrpcDocument, metaSchema); + expect(result).toEqual(metaSchema); + }); + + it("should throw error when restricted schema definition doesn't exist", () => { + const badDoc = { + ...goodSchema, + "x-extensions": [ + { + ...goodSchema["x-extensions"][0], + restricted: ["nonExistentDefinition"], + }, + ], + }; + + expect(() => { + applyExtensionSpec(badDoc as OpenrpcDocument, metaSchema); + }).toThrow("nonExistentDefinition does not exist, cannot apply extension x-notification"); + }); + + it("should throw error when extension property already exists", () => { + const modifiedSchema = { + ...metaSchema, + definitions: { + methodObject: { + properties: { + "x-notification": {}, // Already exists + }, + }, + }, + }; + + expect(() => { + applyExtensionSpec(goodSchema as OpenrpcDocument, modifiedSchema); + }).toThrow( + "x-notification already exists in methodObject, cannot apply extension x-notification" + ); + }); +}); diff --git a/src/apply-extension-spec.ts b/src/apply-extension-spec.ts new file mode 100644 index 00000000..6a9aaecf --- /dev/null +++ b/src/apply-extension-spec.ts @@ -0,0 +1,42 @@ +import { OpenrpcDocument as OpenRPC } from "@open-rpc/meta-schema"; + +/** + * Applies extension specifications to an OpenRPC document by modifying the meta schema + * @param document - The OpenRPC document containing x-extensions + * @param metaSchema - The meta schema to extend + * @returns The modified meta schema with extensions applied + * @throws {Error} If the schema definition doesn't exist or if extension name conflicts + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +function applyExtensionSpec(document: OpenRPC, metaSchema: any): any { + const extendedMetaSchema = metaSchema; + + if (!document["x-extensions"]) return extendedMetaSchema; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + document["x-extensions"].forEach((extension: any) => { + const { name, schema, summary, description, restricted } = extension; + restricted.forEach((schemaDefinition: string) => { + const def = extendedMetaSchema.definitions[schemaDefinition]; + + if (!def) + throw new Error(`${schemaDefinition} does not exist, cannot apply extension ${name}`); + + if (def.properties[name]) + throw new Error( + `${name} already exists in ${schemaDefinition}, cannot apply extension ${name}` + ); + + def.properties[name] = { + title: name, + description, + summary, + ...schema, + }; + }); + }); + + return extendedMetaSchema; +} + +export default applyExtensionSpec; diff --git a/src/dereference-document.test.ts b/src/dereference-document.test.ts index d7894a04..51961ce9 100644 --- a/src/dereference-document.test.ts +++ b/src/dereference-document.test.ts @@ -1,8 +1,5 @@ -import * as _fs from "fs-extra"; -import dereferenceDocument, { - OpenRPCDocumentDereferencingError, -} from "./dereference-document"; -import defaultResolver from "@json-schema-tools/reference-resolver"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import dereferenceDocument, { OpenRPCDocumentDereferencingError } from "./dereference-document"; import { OpenrpcDocument, ContentDescriptorObject, @@ -10,7 +7,6 @@ import { MethodObject, } from "@open-rpc/meta-schema"; import { JSONSchemaObject } from "@json-schema-tools/meta-schema"; -import { JSONSchema7 } from "json-schema"; const workingDocument: OpenrpcDocument = { info: { @@ -48,9 +44,75 @@ describe("dereferenceDocument", () => { }); it("derefs simple stuff", async () => { - expect.assertions(7); + expect.assertions(28); + const extendedDoc = { + methods: workingDocument.methods.concat([ + { + name: "extendedFoobar", + "x-test-extension": { + $ref: "#/components/schemas/testExtValue", + }, + "x-test-extension-3": [ + { $ref: "#/components/schemas/testExtValue" }, + { $ref: "#/components/schemas/testExtValue" }, + ], + params: [], + result: { + name: "extededabcfoo", + schema: { type: "number" }, + }, + }, + ]), + }; + const testDoc = { - ...workingDocument, + ...{ + ...workingDocument, + info: { + title: "foo", + version: "1", + "x-test-extension": { + $ref: "#/components/x-test-extension", + }, + }, + }, + "x-extensions": [] as any, + "x-test-extensions": { + testExt: { + name: "x-test-extension", + version: "1.0.0", + description: "test extension", + openrpcExtension: "1.0.0", + externalDocumentation: { + url: "https://example.com", + description: "test extension", + }, + restricted: ["methodObject", "contentDescriptorObject", "serverObject", "infoObject"], + schema: { + type: "boolean", + description: "test extension", + }, + }, + testExt2: { + name: "x-test-extension-2", + version: "1.0.0", + description: "test extension 2", + restricted: ["methodObject"], + schema: { $ref: "#/components/schemas/bigOlExt" }, + }, + testExt3: { + name: "x-test-extension-3", + version: "1.0.0", + description: "test extension 3", + restricted: ["methodObject"], + schema: { + type: "array", + items: { + $ref: "#/components/schemas/testExtValue", + }, + }, + }, + }, "x-methods": { foobar: { name: "foobar", @@ -62,9 +124,12 @@ describe("dereferenceDocument", () => { }, }, components: { + "x-test-extension": true, schemas: { bigOlBaz: { $ref: "#/components/schemas/bigOlFoo" }, bigOlFoo: { title: "bigOlFoo", type: "string" }, + bigOlExt: { type: "string", description: "test extension value" }, + testExtValue: { type: "boolean", description: "test extension value" }, }, contentDescriptors: { bazerino: { @@ -73,6 +138,7 @@ describe("dereferenceDocument", () => { }, barf: { name: "barf", + "x-test-extension": true, schema: { $ref: "#/components/schemas/bigOlFoo" }, }, }, @@ -97,12 +163,29 @@ describe("dereferenceDocument", () => { }; testDoc.methods.push({ tags: [{ $ref: "#/components/tags/foobydooby" }], + "x-test-extension": { + $ref: "#/components/x-test-extension", + }, errors: [{ $ref: "#/components/errors/bigBadError" }], examples: [{ $ref: "#/components/examplePairingObjects/testy" }], + links: [ + { + name: "fooLink", + url: "https://example.com", + description: "fooLink", + server: { + url: "https://example.com", + "x-test-extension": { + $ref: "#/components/x-test-extension", + }, + }, + }, + ], name: "foo", params: [ { $ref: "#/components/contentDescriptors/bazerino" }, { + "x-test-extension": true, name: "blah blah", schema: { $ref: "#/components/schemas/bigOlBaz" }, }, @@ -114,27 +197,51 @@ describe("dereferenceDocument", () => { }, }); testDoc.methods.push({ $ref: "#/x-methods/foobar" }); - + testDoc.methods.push(...extendedDoc.methods); + testDoc["x-extensions"].push({ $ref: "#/x-test-extensions/testExt" }); + testDoc["x-extensions"].push({ $ref: "#/x-test-extensions/testExt2" }); + testDoc["x-extensions"].push({ $ref: "#/x-test-extensions/testExt3" }); const document = await dereferenceDocument(testDoc); const docMethods = document.methods as MethodObject[]; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const docExtensions = document["x-extensions"] as any[]; + expect(document.info["x-test-extension"]).toBe(true); + expect(docExtensions).toBeDefined(); + expect(docExtensions[0]).toBeDefined(); + expect(docExtensions[0].name).toBe("x-test-extension"); + expect(docExtensions[0].description).toBe("test extension"); + expect(docExtensions[0].openrpcExtension).toBe("1.0.0"); + expect(docExtensions[0].externalDocumentation.url).toBe("https://example.com"); + expect(docExtensions[0].externalDocumentation.description).toBe("test extension"); + expect(docExtensions[0].schema.type).toBe("boolean"); + expect(docExtensions[0].schema.description).toBe("test extension"); + + expect(docExtensions[1].name).toBe("x-test-extension-2"); + expect(docExtensions[1].description).toBe("test extension 2"); + expect(docExtensions[1].restricted).toEqual(["methodObject"]); + expect(docExtensions[1].schema.type).toBe("string"); + expect(docExtensions[1].schema.description).toBe("test extension value"); + + expect(docMethods[2]["x-test-extension"]).toBe(testDoc.components.schemas.testExtValue); + expect(docMethods[2]["x-test-extension-3"][0]).toBe(testDoc.components.schemas.testExtValue); + expect(docMethods[2]["x-test-extension-3"][1]).toBe(testDoc.components.schemas.testExtValue); + expect(docMethods).toBeDefined(); expect(docMethods[0]).toBeDefined(); expect(docMethods[0].params[0]).toBeDefined(); - expect((docMethods[0].params[0] as ContentDescriptorObject).name).toBe( - "bazerino" - ); + expect((docMethods[0].params[0] as ContentDescriptorObject).name).toBe("bazerino"); + expect((docMethods[0].params[1] as any)["x-test-extension"]).toBe(true); + expect((docMethods[0].params[2] as any)["x-test-extension"]).toBe(true); + expect((docMethods[0].links as any)[0]["server"]["x-test-extension"]).toBe(true); + //expect((docMethods[0].links?[1] as any)["server"]["x-test-extension"]).toBe(true); + // eslint-disable-next-line @typescript-eslint/no-explicit-any expect(docMethods[0].result).toBeDefined(); expect( - ( - (docMethods[0].result as ContentDescriptorObject) - .schema as JSONSchemaObject - ).title + ((docMethods[0].result as ContentDescriptorObject).schema as JSONSchemaObject).title ).toBe("bigOlFoo"); expect( - ( - (docMethods[0].params as ContentDescriptorObject[])[1] - .schema as JSONSchemaObject - ).title + ((docMethods[0].params as ContentDescriptorObject[])[1].schema as JSONSchemaObject).title ).toBe("bigOlFoo"); }); @@ -150,8 +257,7 @@ describe("dereferenceDocument", () => { { name: "wallet_createSession", paramStructure: "by-name", - params: [ - ], + params: [], result: { name: "wallet_createSessionResult", schema: { @@ -163,9 +269,8 @@ describe("dereferenceDocument", () => { }, }, }, - examples: [ - ], - errors: [] + examples: [], + errors: [], }, ], components: { @@ -237,11 +342,9 @@ describe("dereferenceDocument", () => { const docMethods = document.methods as MethodObject[]; expect(docMethods).toBeDefined(); expect(docMethods[0]).toBeDefined(); - expect( - () => { - JSON.stringify(document) - } - ).not.toThrow(); + expect(() => { + JSON.stringify(document); + }).not.toThrow(); }); it("interdependent refs", async () => { @@ -291,10 +394,8 @@ describe("dereferenceDocument", () => { expect(document.methods).toBeDefined(); expect(document.methods[0]).toBeDefined(); - const params = (document.methods[0] as MethodObject) - .params as ContentDescriptorObject[]; - const result = (document.methods[0] as MethodObject) - .result as ContentDescriptorObject; + const params = (document.methods[0] as MethodObject).params as ContentDescriptorObject[]; + const result = (document.methods[0] as MethodObject).result as ContentDescriptorObject; expect(params).toBeDefined(); expect(result).toBeDefined(); @@ -435,9 +536,7 @@ describe("dereferenceDocument", () => { }, }; - const result = (await dereferenceDocument( - testDoc as OpenrpcDocument - )) as any; + const result = (await dereferenceDocument(testDoc as OpenrpcDocument)) as any; expect(result.methods[0].links[0]).toBe(testDoc.components.links.fooLink); }); @@ -463,9 +562,7 @@ describe("dereferenceDocument", () => { ], }; - const result = (await dereferenceDocument( - testDoc as OpenrpcDocument - )) as any; + const result = (await dereferenceDocument(testDoc as OpenrpcDocument)) as any; expect(result.methods[0].result.schema.type).toBe("string"); }); @@ -501,13 +598,9 @@ describe("dereferenceDocument", () => { }, }; - const result = (await dereferenceDocument( - testDoc as OpenrpcDocument - )) as any; + const result = (await dereferenceDocument(testDoc as OpenrpcDocument)) as any; - expect(result.methods[0].result.schema.properties.foo).toBe( - result.components.schemas.foo - ); + expect(result.methods[0].result.schema.properties.foo).toBe(result.components.schemas.foo); }); it("throws when a schema cannot be resolved from components", async () => { diff --git a/src/dereference-document.ts b/src/dereference-document.ts index 521e932a..7faf45da 100644 --- a/src/dereference-document.ts +++ b/src/dereference-document.ts @@ -1,9 +1,20 @@ import Dereferencer from "@json-schema-tools/dereferencer"; -import { OpenrpcDocument as OpenRPC, ReferenceObject, ExamplePairingObject, JSONSchema, SchemaComponents, ContentDescriptorComponents, ContentDescriptorObject, OpenrpcDocument, MethodObject, MethodOrReference } from "@open-rpc/meta-schema"; +import metaSchema, { + OpenrpcDocument as OpenRPC, + ReferenceObject, + ExamplePairingObject, + JSONSchema, + SchemaComponents, + ContentDescriptorComponents, + ContentDescriptorObject, + OpenrpcDocument, + MethodObject, + MethodOrReference, +} from "@open-rpc/meta-schema"; import referenceResolver from "@json-schema-tools/reference-resolver"; import safeStringify from "fast-safe-stringify"; -export type ReferenceResolver = typeof referenceResolver +export type ReferenceResolver = typeof referenceResolver; /** * Provides an error interface for OpenRPC Document dereferencing problems * @@ -23,38 +34,58 @@ export class OpenRPCDocumentDereferencingError implements Error { const derefItem = async (item: ReferenceObject, doc: OpenRPC, resolver: ReferenceResolver) => { const { $ref } = item; - if ($ref === undefined) { return item; } + if ($ref === undefined) { + return item; + } try { // returns resolved value of the reference - return (await resolver.resolve($ref, doc) as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (await resolver.resolve($ref, doc)) as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { - throw new OpenRPCDocumentDereferencingError([ - `unable to eval pointer against OpenRPC Document.`, - `error type: ${err.name}`, - `instance: ${err.instance}`, - `token: ${err.token}`, - `pointer: ${$ref}`, - `reference object: ${safeStringify(item)}` - ].join("\n")); + throw new OpenRPCDocumentDereferencingError( + [ + `unable to eval pointer against OpenRPC Document.`, + `error type: ${err.name}`, + `instance: ${err.instance}`, + `token: ${err.token}`, + `pointer: ${$ref}`, + `reference object: ${safeStringify(item)}`, + ].join("\n") + ); } }; const derefItems = async (items: ReferenceObject[], doc: OpenRPC, resolver: ReferenceResolver) => { const dereffed = []; for (const i of items) { - dereffed.push(await derefItem(i, doc, resolver)) + dereffed.push(await derefItem(i, doc, resolver)); } return dereffed; }; -const handleSchemaWithSchemaComponents = async (s: JSONSchema, schemaComponents: SchemaComponents | undefined) => { +const matchDerefItems = async ( + items: ReferenceObject[] | ReferenceObject, + doc: OpenRPC, + resolver: ReferenceResolver +) => { + if (Array.isArray(items)) { + return derefItems(items, doc, resolver); + } + return derefItem(items, doc, resolver); +}; + +const handleSchemaWithSchemaComponents = async ( + s: JSONSchema, + schemaComponents: SchemaComponents | undefined +) => { if (s === true || s === false) { return Promise.resolve(s); } if (schemaComponents !== undefined) { - s.components = { schemas: schemaComponents } + s.components = { schemas: schemaComponents }; } const dereffer = new Dereferencer(s); @@ -65,13 +96,16 @@ const handleSchemaWithSchemaComponents = async (s: JSONSchema, schemaComponents: delete s.components; } return dereffed; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - throw new OpenRPCDocumentDereferencingError([ - "Unable to parse reference inside of JSONSchema", - s.title ? `Schema Title: ${s.title}` : "", - `error message: ${e.message}`, - `schema in question: ${safeStringify(s)}` - ].join("\n")); + throw new OpenRPCDocumentDereferencingError( + [ + "Unable to parse reference inside of JSONSchema", + s.title ? `Schema Title: ${s.title}` : "", + `error message: ${e.message}`, + `schema in question: ${safeStringify(s)}`, + ].join("\n") + ); } }; @@ -93,7 +127,9 @@ const handleSchemaComponents = async (doc: OpenrpcDocument): Promise => { +const handleSchemasInsideContentDescriptorComponents = async ( + doc: OpenrpcDocument +): Promise => { if (doc.components === undefined) { return Promise.resolve(doc); } @@ -116,11 +152,248 @@ const handleSchemasInsideContentDescriptorComponents = async (doc: OpenrpcDocume return doc; }; -const handleMethod = async (methodOrRef: MethodOrReference, doc: OpenrpcDocument, resolver: ReferenceResolver): Promise => { +type DefinitionsMap = { [key: string]: string[] }; + +// remap the definitions map to remove the definitions. prefix and replace it with the parent object type +const remap = (definitionsMap: DefinitionsMap): DefinitionsMap => { + const remappedDefinitions: DefinitionsMap = {}; + const graph = new Map>(); + const resolved = new Set(); + + // Build dependency graph + for (const [key, paths] of Object.entries(definitionsMap)) { + graph.set(key, new Set()); + for (const path of paths) { + const parts = path.split("."); + if (parts.length === 1) { + graph.get(key)?.add(path); + } else if (path.startsWith("definitions.")) { + parts.shift(); // Remove 'definitions' + const parentType = parts[0]; + if (parentType && parentType !== key) { + graph.get(key)?.add(parentType); + } + } + } + } + + // Helper to resolve a definition and its dependencies + const resolveDef = (key: string) => { + if (resolved.has(key)) return; + + // Resolve dependencies first + graph.get(key)?.forEach((dep) => resolveDef(dep)); + if (!definitionsMap[key]) { + return key; + } + + const accumulatedPaths: string[] = []; + definitionsMap[key].forEach((path) => { + if (!path.startsWith("definitions.")) { + accumulatedPaths.push(path); + return; + } + + const parts = path.split("."); + parts.shift(); // Remove 'definitions' + const parentType = parts.shift(); + const remainingPath = parts.join("."); + + if (!parentType || !remappedDefinitions[parentType]) { + accumulatedPaths.push(remainingPath); + return; + } + + remappedDefinitions[parentType].forEach((basePath: string) => { + const newPath = basePath ? `${basePath}.${remainingPath}` : remainingPath; + accumulatedPaths.push(newPath); + }); + }); + remappedDefinitions[key] = accumulatedPaths; + + resolved.add(key); + }; + + // Resolve all definitions + Object.keys(definitionsMap).forEach(resolveDef); + + return remappedDefinitions; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function createDefinitionsMap(schema: any, path = ""): DefinitionsMap { + const definitionsMap: DefinitionsMap = {}; + + const simplifyPath = (path: string): string => { + // Remove .items, .patternProperties, and anything after them + return path.split(/\.(items|patternProperties)/)[0]; + }; + + const addToMap = (definitionName: string, currentPath: string) => { + if (definitionName && definitionName !== "referenceObject") { + if (!definitionsMap[definitionName]) { + definitionsMap[definitionName] = []; + } + const simplifiedPath = simplifyPath(currentPath); + if (simplifiedPath && !definitionsMap[definitionName].includes(simplifiedPath)) { + definitionsMap[definitionName].push(simplifiedPath); + } + } + }; + + // Handle object properties recursively + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const traverseObject = (obj: any, currentPath: string) => { + if (!obj || typeof obj !== "object") return; + + // Handle direct $ref + if ("$ref" in obj) { + const definitionName = obj["$ref"].split("/").pop(); + addToMap(definitionName, currentPath); + } + + // Handle arrays with items + if ("items" in obj) { + // Direct $ref in items + if (obj.items.$ref) { + const definitionName = obj.items.$ref.split("/").pop(); + addToMap(definitionName, `${currentPath}.items`); + } + // oneOf in items + if (obj.items.oneOf) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + obj.items.oneOf.forEach((item: any) => { + if (item.$ref) { + const definitionName = item.$ref.split("/").pop(); + addToMap(definitionName, `${currentPath}.items`); + } + }); + } + } + + // Handle properties + if ("properties" in obj) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Object.entries(obj.properties).forEach(([key, value]: [string, any]) => { + const newPath = currentPath ? `${currentPath}.${key}` : key; + traverseObject(value, newPath); + }); + } + + // Handle oneOf at current level + if ("oneOf" in obj) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + obj.oneOf.forEach((item: any) => { + if (item.$ref) { + const definitionName = item.$ref.split("/").pop(); + addToMap(definitionName, currentPath); + } + traverseObject(item, currentPath); + }); + } + + // Recursively traverse all other properties + Object.entries(obj).forEach(([key, value]) => { + if ( + value && + typeof value === "object" && + key !== "properties" && + key !== "items" && + key !== "oneOf" + ) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + traverseObject(value, newPath); + } + }); + }; + + traverseObject(schema, path); + + return remap(definitionsMap); +} + +function resolveDefinition(definitionsMap: DefinitionsMap, definitionKey: string): string[] { + return definitionsMap[definitionKey] || []; +} + +interface DocResult { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + items: any[]; // Single array of all matching objects +} + +// Traverses an object based on a dot-separated path and returns all matching objects at that path +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const getDoc = (docName: string, derefDoc: any): DocResult => { + const docNames = docName.split("."); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const traverseObject = (obj: any, pathParts: string[]): any[] => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const results: any[] = []; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const traverse = (current: any, depth: number) => { + if (!current) return; + + // If we've reached our target depth, collect this object + if (depth === pathParts.length) { + results.push(current); + return; + } + + const part = pathParts[depth]; + const next = current[part]; + + // Handle both arrays and objects + if (Array.isArray(next)) { + next.forEach((item) => traverse(item, depth + 1)); + } else if (next && typeof next === "object") { + traverse(next, depth + 1); + } + }; + + traverse(obj, 0); + return results; + }; + + return { items: traverseObject(derefDoc, docNames) }; +}; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +const handleExtension = async ( + extensionOrRef: any, + doc: OpenrpcDocument, + resolver: ReferenceResolver +): Promise => { + if (extensionOrRef.$ref !== undefined) { + extensionOrRef = await derefItem({ $ref: extensionOrRef.$ref }, doc, resolver); + } + + let componentSchemas: SchemaComponents = {}; + if (doc.components && doc.components.schemas) { + componentSchemas = doc.components.schemas as SchemaComponents; + } + + if (extensionOrRef.schema !== undefined) { + extensionOrRef.schema = await handleSchemaWithSchemaComponents( + extensionOrRef.schema, + componentSchemas + ); + } + + return extensionOrRef; +}; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +const handleMethod = async ( + methodOrRef: MethodOrReference, + doc: OpenrpcDocument, + resolver: ReferenceResolver +): Promise => { let method = methodOrRef as MethodObject; if (methodOrRef.$ref !== undefined) { - method = await derefItem({ $ref: methodOrRef.$ref }, doc, resolver) + method = await derefItem({ $ref: methodOrRef.$ref }, doc, resolver); } if (method.tags !== undefined) { @@ -150,7 +423,6 @@ const handleMethod = async (methodOrRef: MethodOrReference, doc: OpenrpcDocument method.result = await derefItem(method.result as ReferenceObject, doc, resolver); } - let componentSchemas: SchemaComponents = {}; if (doc.components && doc.components.schemas) { componentSchemas = doc.components.schemas as SchemaComponents; @@ -194,16 +466,58 @@ const handleMethod = async (methodOrRef: MethodOrReference, doc: OpenrpcDocument * ``` * */ -export default async function dereferenceDocument(openrpcDocument: OpenRPC, resolver: ReferenceResolver = referenceResolver): Promise { +export default async function dereferenceDocument( + openrpcDocument: OpenRPC, + resolver: ReferenceResolver = referenceResolver +): Promise { let derefDoc = { ...openrpcDocument }; derefDoc = await handleSchemaComponents(derefDoc); derefDoc = await handleSchemasInsideContentDescriptorComponents(derefDoc); + + const definitionsMap = createDefinitionsMap(metaSchema); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extensions = [] as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extensionDerefs = [] as any; + + if (derefDoc["x-extensions"]) { + for (const extension of derefDoc["x-extensions"]) { + const derefedExtension = await handleExtension(extension, derefDoc, resolver); + extensions.push(derefedExtension); + for (const def of derefedExtension.restricted) { + extensionDerefs.push({ + extensionName: derefedExtension.name, + docNames: resolveDefinition(definitionsMap, def), + }); + } + } + derefDoc["x-extensions"] = extensions; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any const methods = [] as any; for (const method of derefDoc.methods) { methods.push(await handleMethod(method, derefDoc, resolver)); } + for (const extension of extensionDerefs) { + for (const docName of extension.docNames) { + const { items } = getDoc(docName, derefDoc); + + // Process all matching items that have the extension + for (const item of items) { + if (item && item[extension.extensionName]) { + item[extension.extensionName] = await matchDerefItems( + item[extension.extensionName], + derefDoc, + resolver + ); + } + } + } + } + derefDoc.methods = methods; return derefDoc; diff --git a/src/extension-bad-schema.json b/src/extension-bad-schema.json new file mode 100644 index 00000000..03c4bb33 --- /dev/null +++ b/src/extension-bad-schema.json @@ -0,0 +1,65 @@ +{ + "openrpc": "1.2.6", + "info": { + "title": "Minimal OpenRPC Example", + "version": "1.0.0" + }, + "methods": [ + { + "name": "getExampleData", + "summary": "Retrieves example data from the API.", + "x-notification": "string", + "params": [ + { + "name": "dataId", + "schema": { + "type": "integer", + "description": "The ID of the data to retrieve." + }, + "required": true + } + ], + "result": { + "name": "exampleData", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the data." + }, + "data": { + "type": "string", + "description": "The content of the data." + } + }, + "required": [ + "id", + "data" + ] + }, + "description": "The result object containing the requested data." + } + } + ], + "x-extensions": [ + { + "openrpcExtension": "0.0.0-development", + "name": "x-notification", + "version": "0.0.1", + "description": "Describe a notification for OpenRPC methods", + "summary": "OpenRPC Notification", + "externalDocumentation": { + "description": "github", + "url": "https://github.com/open-rpc/specification-extensions-spec/examples/x-notification-openrpc-ext.json" + }, + "restricted": [ + "methodObject" + ], + "schema": { + "type": "boolean", + "description": "wether or not this method is a notification or not" + } + } + ] +} \ No newline at end of file diff --git a/src/extension-good-schema.json b/src/extension-good-schema.json new file mode 100644 index 00000000..727105d9 --- /dev/null +++ b/src/extension-good-schema.json @@ -0,0 +1,60 @@ +{ + "openrpc": "1.2.6", + "info": { + "title": "Minimal OpenRPC Example", + "version": "1.0.0" + }, + "methods": [ + { + "name": "getExampleData", + "summary": "Retrieves example data from the API.", + "x-notification": false, + "params": [ + { + "name": "dataId", + "schema": { + "type": "integer", + "description": "The ID of the data to retrieve." + }, + "required": true + } + ], + "result": { + "name": "exampleData", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the data." + }, + "data": { + "type": "string", + "description": "The content of the data." + } + }, + "required": ["id", "data"] + }, + "description": "The result object containing the requested data." + } + } + ], + "x-extensions": [ + { + "openrpcExtension": "0.0.0-development", + "name": "x-notification", + "version": "0.0.1", + "description": "Describe a notification for OpenRPC methods", + "summary": "OpenRPC Notification", + "externalDocumentation": { + "description": "github", + "url": "https://github.com/open-rpc/specification-extensions-spec/examples/x-notification-openrpc-ext.json" + }, + "restricted": ["methodObject"], + "schema": { + "type": "boolean", + "description": "Whether or not this method is a notification or not" + } + } + ] +} diff --git a/src/generate-method-id.test.ts b/src/generate-method-id.test.ts index 7b69cbba..01210160 100644 --- a/src/generate-method-id.test.ts +++ b/src/generate-method-id.test.ts @@ -33,8 +33,9 @@ describe("methodParamId", () => { result: { name: "baz", schema: {} }, } as MethodObject; - expect(() => generateMethodParamId(method, { name: "123", schema: {} })) - .toThrow("Content Descriptor not found in method."); + expect(() => generateMethodParamId(method, { name: "123", schema: {} })).toThrow( + "Content Descriptor not found in method." + ); }); }); @@ -82,7 +83,8 @@ describe("methodResultId", () => { result: { name: "baz", schema: {} }, }; - expect(() => generateMethodResultId(method, { name: "peepee", schema: {} })) - .toThrow("Content Descriptor not found in method."); + expect(() => generateMethodResultId(method, { name: "peepee", schema: {} })).toThrow( + "Content Descriptor not found in method." + ); }); }); diff --git a/src/generate-method-id.ts b/src/generate-method-id.ts index 6940f4d8..10ff114e 100644 --- a/src/generate-method-id.ts +++ b/src/generate-method-id.ts @@ -1,4 +1,3 @@ - import { MethodObject, ContentDescriptorObject } from "@open-rpc/meta-schema"; import { findIndex } from "./helper-functions"; @@ -63,9 +62,12 @@ export class ContentDescriptorNotFoundInMethodError implements Error { */ export function generateMethodParamId( method: MethodObject, - contentDescriptor: ContentDescriptorObject, + contentDescriptor: ContentDescriptorObject ): string { - const pos = findIndex(method.params, (o: any) => { return o.name == contentDescriptor.name }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pos = findIndex(method.params, (o: any) => { + return o.name == contentDescriptor.name; + }); if (pos === -1) { throw new ContentDescriptorNotFoundInMethodError(method, contentDescriptor); @@ -120,7 +122,7 @@ export function generateMethodParamId( */ export function generateMethodResultId( method: MethodObject, - contentDescriptor: ContentDescriptorObject, + contentDescriptor: ContentDescriptorObject ): string { const result = method.result as ContentDescriptorObject; if (result.name !== contentDescriptor.name) { diff --git a/src/get-extended-metaschema.ts b/src/get-extended-metaschema.ts new file mode 100644 index 00000000..40b209ca --- /dev/null +++ b/src/get-extended-metaschema.ts @@ -0,0 +1,29 @@ +import metaSchema from "@open-rpc/meta-schema"; +import { metaSchema as extensionMetaSchema } from "@open-rpc/specification-extension-spec"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getExtendedMetaSchema(): any { + // NOTE: this is to make sure we don't mutate the original meta schema + const metaSchemaCopy = JSON.parse(JSON.stringify(metaSchema)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extensionMetaSchemaCopy = { ...extensionMetaSchema } as any; + + // Process the extension and meta schema to remove the $id and $schema properties + delete extensionMetaSchemaCopy.$schema; + delete extensionMetaSchemaCopy.$id; + delete metaSchemaCopy.definitions.JSONSchema.$id; + delete metaSchemaCopy.definitions.JSONSchema.$schema; + delete metaSchemaCopy.$schema; + delete metaSchemaCopy.$id; + metaSchemaCopy.properties["x-extensions"] = { + $ref: "#/definitions/x-extensions", + }; + metaSchemaCopy.definitions["x-extensions"] = { + type: "array", + items: extensionMetaSchemaCopy, + }; + //extensionMetaSchemaCopy.properties["x-extensions"]; + return metaSchemaCopy; +} + +export default getExtendedMetaSchema; diff --git a/src/get-open-rpc-document-from-file.ts b/src/get-open-rpc-document-from-file.ts index 10e67c2b..3adf1fe5 100644 --- a/src/get-open-rpc-document-from-file.ts +++ b/src/get-open-rpc-document-from-file.ts @@ -4,7 +4,8 @@ import { TGetOpenRPCDocument } from "./get-open-rpc-document"; const readSchemaFromFile: TGetOpenRPCDocument = async (filePath: string) => { try { - return await readJson(filePath) as OpenRPC; + return (await readJson(filePath)) as OpenRPC; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { if (e.message.includes("SyntaxError")) { throw new Error(`Failed to parse json in file ${filePath}`); diff --git a/src/get-open-rpc-document-from-url.ts b/src/get-open-rpc-document-from-url.ts index f3b93abf..2d3b588c 100644 --- a/src/get-open-rpc-document-from-url.ts +++ b/src/get-open-rpc-document-from-url.ts @@ -5,7 +5,7 @@ import { TGetOpenRPCDocument } from "./get-open-rpc-document"; const fetchUrlSchema: TGetOpenRPCDocument = async (schema) => { try { const response = await fetch(schema); - return await response.json() as OpenRPC; + return (await response.json()) as OpenRPC; } catch (e) { throw new Error(`Unable to download openrpc.json file located at the url: ${schema}`); } diff --git a/src/helper-functions.test.ts b/src/helper-functions.test.ts index d50359aa..ce2adc66 100644 --- a/src/helper-functions.test.ts +++ b/src/helper-functions.test.ts @@ -1,93 +1,102 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import * as help from "./helper-functions"; -import { OpenrpcDocument as OpenRPC, MethodObject } from "@open-rpc/meta-schema"; +import { OpenrpcDocument as OpenRPC } from "@open-rpc/meta-schema"; describe("helper functions", () => { describe("find", () => { it("array empty fail", () => { const array: any[] = []; - expect(help.find(array, (o: any[]) => {return o === null})).toBe(undefined); + expect( + help.find(array, (o: any[]) => { + return o === null; + }) + ).toBe(undefined); }); }); describe("findIndex", () => { it("array empty fail", () => { const array: any[] = []; - expect(help.findIndex(array, (o: any[]) => {return o === null})).toBe(-1); - }) + expect( + help.findIndex(array, (o: any[]) => { + return o === null; + }) + ).toBe(-1); + }); }); - - describe("rpcDocIsEqual", () => { - it("missing key", () => { - const doc1: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", - version: "1.0.0", - }, - methods: [], - openrpc: "1.0.0", - components: [], - }; - const doc2: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", - version: "1.0.0", - }, - methods: [], - openrpc: "1.0.0", - servers: [], - }; - expect(help.rpcDocIsEqual(doc1, doc2)).toBe(false); - }); - it("length mismatch", () => { - const doc1: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", - version: "1.0.0", - }, - methods: [], - openrpc: "1.0.0", - servers: [], - }; - const doc2: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", - version: "1.0.0", - }, - methods: [], - openrpc: "1.0.0", - }; - expect(help.rpcDocIsEqual(doc1, doc2)).toBe(false); - }); - it("same key different value in recursive step", () => { - const doc1: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", + + describe("rpcDocIsEqual", () => { + it("missing key", () => { + const doc1: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", + version: "1.0.0", + }, + methods: [], + openrpc: "1.0.0", + components: [], + }; + const doc2: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", + version: "1.0.0", + }, + methods: [], + openrpc: "1.0.0", + servers: [], + }; + expect(help.rpcDocIsEqual(doc1, doc2)).toBe(false); + }); + it("length mismatch", () => { + const doc1: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", + version: "1.0.0", + }, + methods: [], + openrpc: "1.0.0", + servers: [], + }; + const doc2: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", + version: "1.0.0", + }, + methods: [], + openrpc: "1.0.0", + }; + expect(help.rpcDocIsEqual(doc1, doc2)).toBe(false); + }); + it("same key different value in recursive step", () => { + const doc1: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", version: "1.0.0", license: { name: "MIT", }, - }, - methods: [], - openrpc: "1.0.0", - }; - const doc2: OpenRPC = { - info: { - description: "test-doc", - title: "testDoc", + }, + methods: [], + openrpc: "1.0.0", + }; + const doc2: OpenRPC = { + info: { + description: "test-doc", + title: "testDoc", version: "1.0.0", license: { name: "APACHE2", }, - }, - methods: [], - openrpc: "1.0.0", + }, + methods: [], + openrpc: "1.0.0", }; expect(help.rpcDocIsEqual(doc1, doc2)).toBe(false); - }); + }); }); -}); \ No newline at end of file +}); diff --git a/src/helper-functions.ts b/src/helper-functions.ts index d27226ce..eac34299 100644 --- a/src/helper-functions.ts +++ b/src/helper-functions.ts @@ -1,10 +1,12 @@ -type TPredicate = (value: any) => boolean +/* eslint-disable @typescript-eslint/no-explicit-any */ +type TPredicate = (value: any) => boolean; /** * finds array index of array object which matches predicate * @param array {Array} * @param predicate {Function} * @returns {number} || {undefined} -*/ + */ + export const findIndex = (array: any[], predicate: TPredicate): number => { const length = array == null ? 0 : array.length; if (!length) { @@ -24,7 +26,7 @@ export const findIndex = (array: any[], predicate: TPredicate): number => { * @param array {Array} * @returns {Array} */ -export const compact = (array: any[]) => { +export const compact = (array: any[]): any[] => { let index = 0; const result: any[] = []; @@ -41,10 +43,10 @@ export const compact = (array: any[]) => { * @param array {Array} * @param predicate {Function} * @returns {any} || {undefined} -*/ -export const find = (array: any[], predicate: TPredicate) => { + */ +export const find = (array: any[], predicate: TPredicate): any => { const length = array == null ? 0 : array.length; - if(!length) { + if (!length) { return undefined; } let index = -1; @@ -62,14 +64,14 @@ export const find = (array: any[], predicate: TPredicate) => { * @param doc1 {OpenrpcDocument} * @param doc2 {OpenrpcDocument} * @returns {boolean} -*/ + */ export const rpcDocIsEqual = (doc1: any, doc2: any) => { const doc1Keys = Object.keys(doc1); const doc2Keys = Object.keys(doc2); const doc1Len = doc1Keys.length; const doc2Len = doc2Keys.length; - if(doc1Len != doc2Len) { + if (doc1Len != doc2Len) { return false; } @@ -79,13 +81,11 @@ export const rpcDocIsEqual = (doc1: any, doc2: any) => { key = doc1Keys[index]; if (!(key in doc2)) { return false; - } - else if (typeof doc1[key] === 'object' && typeof doc2[key] === 'object') { + } else if (typeof doc1[key] === "object" && typeof doc2[key] === "object") { if (!rpcDocIsEqual(doc1[key], doc2[key])) { return false; } - } - else if (doc1[key] != doc2[key]) { + } else if (doc1[key] != doc2[key]) { return false; } } diff --git a/src/index-web.ts b/src/index-web.ts index 1e70f78e..02ac1eac 100644 --- a/src/index-web.ts +++ b/src/index-web.ts @@ -1,11 +1,17 @@ import makeParseOpenRPCDocument from "./parse-open-rpc-document"; -import validateOpenRPCDocument, { OpenRPCDocumentValidationError } from "./validate-open-rpc-document"; +import validateOpenRPCDocument, { + OpenRPCDocumentValidationError, +} from "./validate-open-rpc-document"; import { generateMethodParamId, generateMethodResultId, ContentDescriptorNotFoundInMethodError, } from "./generate-method-id"; -import { MethodCallValidator, ParameterValidationError, MethodNotFoundError } from "./method-call-validator"; +import { + MethodCallValidator, + ParameterValidationError, + MethodNotFoundError, +} from "./method-call-validator"; import fetchUrlSchema from "./get-open-rpc-document-from-url"; import { TGetOpenRPCDocument } from "./get-open-rpc-document"; diff --git a/src/index.test.ts b/src/index.test.ts index e262b0d7..8d6345e0 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -12,7 +12,9 @@ export const mockServer = (file: string): Promise => { return new Promise((resolve: (value: http.Server) => void) => { const testServer = http.createServer((req, res) => { const rs = fs.createReadStream(file); - if (!req.url) { throw new Error("Request missing url"); } + if (!req.url) { + throw new Error("Request missing url"); + } if (req.url.search("download") > 0) { res.writeHead(200, { "Content-Type": "application/json" }); rs.pipe(res); @@ -22,7 +24,9 @@ export const mockServer = (file: string): Promise => { return; } }); - testServer.listen(0, () => { resolve(testServer); }); + testServer.listen(0, () => { + resolve(testServer); + }); }); }; @@ -67,5 +71,4 @@ describe("parseOpenRPCDocument", () => { const doc = await parseOpenRPCDocument(`http://localhost:${port}/download/openrpc.json`); expect(rpcDocIsEqual(doc, testDoc)).toBe(true); }); - }); diff --git a/src/index.ts b/src/index.ts index a0a0bf51..1c26b20b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,18 @@ import makeParseOpenRPCDocument from "./parse-open-rpc-document"; import dereferenceDocument, { OpenRPCDocumentDereferencingError } from "./dereference-document"; -import validateOpenRPCDocument, { OpenRPCDocumentValidationError } from "./validate-open-rpc-document"; +import validateOpenRPCDocument, { + OpenRPCDocumentValidationError, +} from "./validate-open-rpc-document"; import { generateMethodParamId, generateMethodResultId, ContentDescriptorNotFoundInMethodError, } from "./generate-method-id"; -import { MethodCallValidator, ParameterValidationError, MethodNotFoundError } from "./method-call-validator"; +import { + MethodCallValidator, + ParameterValidationError, + MethodNotFoundError, +} from "./method-call-validator"; import readSchemaFromFile from "./get-open-rpc-document-from-file"; import fetchUrlSchema from "./get-open-rpc-document-from-url"; diff --git a/src/method-call-validator/method-call-validator.test.ts b/src/method-call-validator/method-call-validator.test.ts index 01935476..e9415178 100644 --- a/src/method-call-validator/method-call-validator.test.ts +++ b/src/method-call-validator/method-call-validator.test.ts @@ -5,17 +5,18 @@ import MethodCallMethodNotFoundError from "./method-not-found-error"; import MethodNotFoundError from "./method-not-found-error"; import MethodRefUnexpectedError from "./method-ref-unexpected-error"; -const getExampleSchema = (): OpenRPC => ({ - info: { title: "123", version: "1" }, - methods: [ - { - name: "foo", - params: [{ name: "foofoo", required: true, schema: { type: "string" } }], - result: { name: "foofoo", schema: { type: "integer" } }, - }, - ], - openrpc: "1.0.0-rc1", -}) as OpenRPC; +const getExampleSchema = (): OpenRPC => + ({ + info: { title: "123", version: "1" }, + methods: [ + { + name: "foo", + params: [{ name: "foofoo", required: true, schema: { type: "string" } }], + result: { name: "foofoo", schema: { type: "integer" } }, + }, + ], + openrpc: "1.0.0-rc1", + } as OpenRPC); describe("MethodCallValidator", () => { it("can be instantiated", () => { @@ -39,14 +40,18 @@ describe("MethodCallValidator", () => { }); it("returns array of errors if invalid", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; const methodCallValidator = new MethodCallValidator(example); - const result = methodCallValidator.validate("foo", [123]) as MethodCallParameterValidationError[]; + const result = methodCallValidator.validate("foo", [ + 123, + ]) as MethodCallParameterValidationError[]; expect(result.length).toBe(1); expect(result[0]).toBeInstanceOf(MethodCallParameterValidationError); }); it("can not error if param is optional", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; example.methods[0].params[0].required = false; const methodCallValidator = new MethodCallValidator(example); @@ -55,6 +60,7 @@ describe("MethodCallValidator", () => { }); it("rpc.discover is allowed", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; const methodCallValidator = new MethodCallValidator(example); const result = methodCallValidator.validate("rpc.discover", []); @@ -62,6 +68,7 @@ describe("MethodCallValidator", () => { }); it("returns method not found error when the method name is invalid", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; const methodCallValidator = new MethodCallValidator(example); const result = methodCallValidator.validate("boo", ["123"]); @@ -77,7 +84,7 @@ describe("MethodCallValidator", () => { paramStructure: "by-name", params: [ { name: "foofoo", required: true, schema: { type: "string" } }, - { name: "barbar", required: true } + { name: "barbar", required: true }, ], result: { name: "foofoo", schema: { type: "integer" } }, }, @@ -91,11 +98,11 @@ describe("MethodCallValidator", () => { const result1 = methodCallValidator.validate("foo", { foofoo: 123, barbar: "abc" }); expect(result1).toBeInstanceOf(Array); expect(result1).toHaveLength(1); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const resAsArr = result1 as any[]; expect(resAsArr[0]).toBeInstanceOf(MethodCallParameterValidationError); }); - it("validates methods that use by-position", () => { const example = { info: { title: "123", version: "1" }, @@ -116,6 +123,7 @@ describe("MethodCallValidator", () => { const result1 = methodCallValidator.validate("foo", [123]); expect(result1).toBeInstanceOf(Array); expect(result1).toHaveLength(1); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const resAsArr = result1 as any[]; expect(resAsArr[0]).toBeInstanceOf(MethodCallParameterValidationError); }); @@ -140,6 +148,7 @@ describe("MethodCallValidator", () => { }); it("method not found errors work when the document has params passed by-name", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; const methodCallValidator = new MethodCallValidator(example); const result0 = methodCallValidator.validate("rawr", { barbar: "123" }); @@ -147,11 +156,16 @@ describe("MethodCallValidator", () => { }); it("unexpected reference error when the document has unresolved method reference passed", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const example = getExampleSchema() as any; - example['x-methods']={ foobar: { name: "foobar", params: [], - result: { name: "abcfoo", schema: { type: "number" } } } } - example.methods.push({"$ref":"#/x-methods/foobar"}) - expect(()=>new MethodCallValidator(example)).toThrowError(MethodRefUnexpectedError); + example["x-methods"] = { + foobar: { + name: "foobar", + params: [], + result: { name: "abcfoo", schema: { type: "number" } }, + }, + }; + example.methods.push({ $ref: "#/x-methods/foobar" }); + expect(() => new MethodCallValidator(example)).toThrowError(MethodRefUnexpectedError); }); - }); diff --git a/src/method-call-validator/method-call-validator.ts b/src/method-call-validator/method-call-validator.ts index ed45858b..47769deb 100644 --- a/src/method-call-validator/method-call-validator.ts +++ b/src/method-call-validator/method-call-validator.ts @@ -1,7 +1,12 @@ import Ajv, { ErrorObject, Ajv as IAjv } from "ajv"; import { generateMethodParamId } from "../generate-method-id"; import ParameterValidationError from "./parameter-validation-error"; -import { OpenrpcDocument as OpenRPC, MethodObject, ContentDescriptorObject, MethodOrReference } from "@open-rpc/meta-schema"; +import { + OpenrpcDocument as OpenRPC, + MethodObject, + ContentDescriptorObject, + MethodOrReference, +} from "@open-rpc/meta-schema"; import MethodNotFoundError from "./method-not-found-error"; import { find, compact } from "../helper-functions"; import MethodRefUnexpectedError from "./method-ref-unexpected-error"; @@ -38,8 +43,11 @@ export default class MethodCallValidator { const params = method.params as ContentDescriptorObject[]; params.forEach((param: ContentDescriptorObject) => { - if (param.schema === undefined) { return; } + if (param.schema === undefined) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.ajvValidator.addSchema(param.schema as any, generateMethodParamId(method, param)); }); }); @@ -66,42 +74,53 @@ export default class MethodCallValidator { */ public validate( methodName: string, - params: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + params: any ): ParameterValidationError[] | MethodNotFoundError { - if (methodName === "rpc.discover") { return []; } - const method = find(this.document.methods, (o: MethodObject) => { return o.name == methodName }) as MethodObject; + if (methodName === "rpc.discover") { + return []; + } + const method = find(this.document.methods, (o: MethodObject) => { + return o.name == methodName; + }) as MethodObject; if (!method) { return new MethodNotFoundError(methodName, this.document, params); } - const paramMap = (method.params as ContentDescriptorObject[]); - return compact(paramMap.map((param: ContentDescriptorObject, index: number): ParameterValidationError | undefined => { - let id: string | number; - if (method.paramStructure === "by-position") { - id = index; - } else if (method.paramStructure === "by-name") { - id = param.name; - } else { - if (params[index] !== undefined) { - id = index; - } else { - id = param.name; - } - } - const input = params[id]; + const paramMap = method.params as ContentDescriptorObject[]; + return compact( + paramMap.map( + (param: ContentDescriptorObject, index: number): ParameterValidationError | undefined => { + let id: string | number; + if (method.paramStructure === "by-position") { + id = index; + } else if (method.paramStructure === "by-name") { + id = param.name; + } else { + if (params[index] !== undefined) { + id = index; + } else { + id = param.name; + } + } + const input = params[id]; - if (input === undefined && !param.required) { return; } + if (input === undefined && !param.required) { + return; + } - if (param.schema !== undefined) { - const idForMethod = generateMethodParamId(method, param); - const isValid = this.ajvValidator.validate(idForMethod, input); - const errors = this.ajvValidator.errors as ErrorObject[]; + if (param.schema !== undefined) { + const idForMethod = generateMethodParamId(method, param); + const isValid = this.ajvValidator.validate(idForMethod, input); + const errors = this.ajvValidator.errors as ErrorObject[]; - if (!isValid) { - return new ParameterValidationError(id, param.schema, input, errors); + if (!isValid) { + return new ParameterValidationError(id, param.schema, input, errors); + } + } } - } - })) as ParameterValidationError[]; + ) + ) as ParameterValidationError[]; } } diff --git a/src/method-call-validator/method-not-found-error.test.ts b/src/method-call-validator/method-not-found-error.test.ts index a77be953..0f23f65f 100644 --- a/src/method-call-validator/method-not-found-error.test.ts +++ b/src/method-call-validator/method-not-found-error.test.ts @@ -21,6 +21,22 @@ describe("MethodNotFoundError", () => { expect(error).toBeInstanceOf(MethodNotFoundError); }); + it("works when params are not an array of valid values", () => { + const obj = { + name: "Example", + circular: {}, + }; + obj.circular = obj; // Circular reference + + const error = new MethodNotFoundError("floobar", exampleDoc, [obj]); + expect(error).toBeInstanceOf(MethodNotFoundError); + }); + + it("works when params are an object", () => { + const error = new MethodNotFoundError("floobar", exampleDoc, { test: "param" }); + expect(error).toBeInstanceOf(MethodNotFoundError); + }); + it("properly parses params in to a string", () => { const error = new MethodNotFoundError("floobar", exampleDoc, ["abc", { abc: 123 }, 123]); expect(error).toBeInstanceOf(MethodNotFoundError); diff --git a/src/method-call-validator/method-not-found-error.ts b/src/method-call-validator/method-not-found-error.ts index 351e208f..9be7c299 100644 --- a/src/method-call-validator/method-not-found-error.ts +++ b/src/method-call-validator/method-not-found-error.ts @@ -15,17 +15,19 @@ export default class MethodNotFoundError implements Error { constructor( public methodName: string, public openrpcDocument: OpenRPC, - public receievedParams: any[] | Record = [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public receievedParams: any[] | Record = [] ) { const msg = [ `Method Not Found Error for OpenRPC API named "${openrpcDocument.info.title}"`, `The requested method: "${methodName}" not a valid method.`, - ]; if (openrpcDocument.methods.length > 0) { msg.push( - `Valid method names are as follows: ${(openrpcDocument.methods as MethodObject[]).map(({ name }) => name).join(", ")}`, + `Valid method names are as follows: ${(openrpcDocument.methods as MethodObject[]) + .map(({ name }) => name) + .join(", ")}` ); } @@ -33,7 +35,13 @@ export default class MethodNotFoundError implements Error { if (receievedParams instanceof Array) { if (receievedParams.length > 0) { stringedParams = receievedParams - .map((p) => { try { return JSON.stringify(p); } catch (e) { return p; } }) + .map((p) => { + try { + return JSON.stringify(p); + } catch (e) { + return p; + } + }) .join("\n"); } } else { diff --git a/src/method-call-validator/method-ref-unexpected-error.test.ts b/src/method-call-validator/method-ref-unexpected-error.test.ts index 1084332f..b1c08af5 100644 --- a/src/method-call-validator/method-ref-unexpected-error.test.ts +++ b/src/method-call-validator/method-ref-unexpected-error.test.ts @@ -16,4 +16,3 @@ describe("MethodRefUnexpectedError", () => { expect(error).toBeInstanceOf(MethodRefUnexpectedError); }); }); - diff --git a/src/method-call-validator/method-ref-unexpected-error.ts b/src/method-call-validator/method-ref-unexpected-error.ts index 5af36dfa..aaf71d3f 100644 --- a/src/method-call-validator/method-ref-unexpected-error.ts +++ b/src/method-call-validator/method-ref-unexpected-error.ts @@ -11,14 +11,10 @@ export default class MethodRefUnexpectedError implements Error { * @param methodRef The method reference that was discovered. * @param openrpcDocument The OpenRPC document that the method was used against. */ - constructor( - public methodRef: string, - public openrpcDocument: OpenRPC, - ) { + constructor(public methodRef: string, public openrpcDocument: OpenRPC) { const msg = [ `Method Ref Unexpected Error for OpenRPC API named "${openrpcDocument.info.title}"`, `The requested ref has not been resolved: "${methodRef}" not a valid dereferenced method.`, - ]; this.message = msg.join("\n"); diff --git a/src/method-call-validator/parameter-validation-error.ts b/src/method-call-validator/parameter-validation-error.ts index cbeaf62d..0c5ed288 100644 --- a/src/method-call-validator/parameter-validation-error.ts +++ b/src/method-call-validator/parameter-validation-error.ts @@ -18,8 +18,9 @@ export default class ParameterValidationError implements Error { constructor( public paramIndex: number | string, public expectedSchema: JSONSchema, + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types public receievedParam: any, - errors: ErrorObject[], + errors: ErrorObject[] ) { this.message = [ `Expected param at ${typeof paramIndex === "string" ? "key" : "position"}: `, @@ -30,7 +31,7 @@ export default class ParameterValidationError implements Error { receievedParam, ".", "The Validation errors: \n", - JSON.stringify(errors) + JSON.stringify(errors), ].join(""); } } diff --git a/src/open-rpc-extensions-schema.json b/src/open-rpc-extensions-schema.json new file mode 100644 index 00000000..bd09b8e6 --- /dev/null +++ b/src/open-rpc-extensions-schema.json @@ -0,0 +1,85 @@ +{ + "$id": "https://extensions.meta.open-rpc.org/", + "title": "OpenRPC Reserved Extension Property", + "type": "object", + "properties": { + "x-extensions": { + "type": "array", + "items": { + "title": "OpenRPC Specification Extension", + "description": "A standard way to define OpenRPC Specification Extensions. This makes it easier to provide support for Specification Extensions in other tools.", + "type": "object", + "required": ["name", "schema", "openrpcExtension", "version"], + "properties": { + "openrpcExtension": { + "title": "openrpcExtensionVersion", + "description": "**REQUIRED**. This string MUST be the semantic version number of the Specification that the document uses.", + "type": "string", + "enum": ["0.0.0-development"] + }, + "name": { + "title": "specificationExtensionName", + "description": "**REQUIRED** Name of the Specification Extension. MUST start with `x-`", + "type": "string", + "pattern": "^x-", + "examples": ["x-foobarbaz"] + }, + "version": { + "title": "specificationExtensionVersion", + "description": "**REQUIRED**. The version of the Extension (which is distinct from the `openrpcExtension` version and implementation version).", + "type": "string", + "examples": ["0.0.1"] + }, + "required": { + "title": "specificationExtensionRequired", + "type": "boolean", + "description": "wether or not this specification extension is required or not" + }, + "restricted": { + "title": "restrictedObjects", + "description": "A list of object names to restrict the usage of the specification extension to.", + "type": "array", + "items": { + "title": "restrictedObject", + "description": "restricted object", + "type": "string" + } + }, + "description": { + "title": "specificationExtensionDescription", + "type": "string", + "description": "Markdown description describing the specification extension." + }, + "summary": { + "title": "specificationExtensionSummary", + "type": "string", + "description": "A short summary of what the specification extension is." + }, + "schema": { + "$ref": "https://meta.json-schema.tools" + }, + "externalDocumentation": { + "title": "specificationExtensionExternalDocumentationObject", + "type": "object", + "additionalProperties": false, + "description": "Information about specification extension external documentation.", + "required": ["url"], + "properties": { + "description": { + "title": "specificationExtensionexternalDocumentationObjectDescription", + "description": "external documentation description.", + "type": "string" + }, + "url": { + "title": "specificationExtensionexternalDocumentationObjectUrl", + "description": "external documentation description.", + "type": "string", + "format": "uri" + } + } + } + } + } + } + } +} diff --git a/src/parse-open-rpc-document.test.ts b/src/parse-open-rpc-document.test.ts index 720a1812..2ddba200 100644 --- a/src/parse-open-rpc-document.test.ts +++ b/src/parse-open-rpc-document.test.ts @@ -13,6 +13,7 @@ import { OpenRPCDocumentDereferencingError } from "./dereference-document"; import { JSONSchema } from "@json-schema-tools/meta-schema"; const parseOpenRPCDocument = makeParseOpenRPCDocument(fetchUrlSchema, readSchemaFromFile); +// eslint-disable-next-line @typescript-eslint/no-explicit-any const fs: any = _fs; const workingDocument: OpenRPC = { @@ -32,7 +33,7 @@ const notificationDocument: OpenRPC = { params: [ { name: "bar", - schema: { "type": "boolean" }, + schema: { type: "boolean" }, }, ], examples: [ @@ -45,7 +46,7 @@ const notificationDocument: OpenRPC = { }, ], }, - ] + ], }, ], }; @@ -67,6 +68,7 @@ const badRefDocument: OpenRPC = { }, ], }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const invalidDocument: any = { ...workingDocument, methods: [ @@ -88,7 +90,6 @@ const invalidDocument: any = { }; describe("parseOpenRPCDocument", () => { - beforeEach(() => { fs.readJson.mockResolvedValue(workingDocument); }); @@ -102,14 +103,15 @@ describe("parseOpenRPCDocument", () => { it("handles custom file path", async () => { expect.assertions(1); const document = await parseOpenRPCDocument( - "./node_modules/@open-rpc/examples/service-descriptions/petstore.json", + "./node_modules/@open-rpc/examples/service-descriptions/petstore.json" ); expect(document.methods).toBeDefined(); }); it("handles urls", async () => { expect.assertions(1); - const url = "https://raw.githubusercontent.com/open-rpc/examples/master/service-descriptions/petstore-openrpc.json"; + const url = + "https://raw.githubusercontent.com/open-rpc/examples/master/service-descriptions/petstore-openrpc.json"; const document = await parseOpenRPCDocument(url); expect(document.methods).toBeDefined(); }); @@ -145,7 +147,6 @@ describe("parseOpenRPCDocument", () => { }); describe("errors", () => { - it("rejects when unable to find file via default", async () => { expect.assertions(1); fs.readJson.mockClear(); @@ -162,7 +163,7 @@ describe("parseOpenRPCDocument", () => { fs.readJson.mockClear(); fs.readJson.mockRejectedValue(new Error("cannot compute error")); try { - await parseOpenRPCDocument("./not/a/real/path.json") + await parseOpenRPCDocument("./not/a/real/path.json"); } catch (e) { expect(e).toBeDefined(); } @@ -173,7 +174,7 @@ describe("parseOpenRPCDocument", () => { fs.readJson.mockClear(); fs.readJson.mockRejectedValue(new Error("cannot compute error")); try { - await parseOpenRPCDocument("https://google.com") + await parseOpenRPCDocument("https://google.com"); } catch (e) { expect(e).toBeDefined(); } @@ -183,7 +184,7 @@ describe("parseOpenRPCDocument", () => { expect.assertions(1); fs.readJson.mockClear(); try { - await parseOpenRPCDocument(badRefDocument) + await parseOpenRPCDocument(badRefDocument); } catch (e) { expect(e).toBeInstanceOf(OpenRPCDocumentDereferencingError); } @@ -193,7 +194,7 @@ describe("parseOpenRPCDocument", () => { expect.assertions(1); fs.readJson.mockClear(); try { - await parseOpenRPCDocument(invalidDocument) + await parseOpenRPCDocument(invalidDocument); } catch (e) { expect(e).toBeInstanceOf(OpenRPCDocumentValidationError); } @@ -211,9 +212,7 @@ describe("parseOpenRPCDocument", () => { methods: [ { name: "buildHelicopter", - params: [ - { $ref: "#/components/contentDescriptors/NumBlades" }, - ], + params: [{ $ref: "#/components/contentDescriptors/NumBlades" }], result: { name: "helicopterrr", schema: { $ref: `${__dirname}/bad-schema.json` }, @@ -253,9 +252,7 @@ describe("parseOpenRPCDocument", () => { methods: [ { name: "foo", - params: [ - { $ref: "#/components/contentDescriptors/LeFoo" }, - ], + params: [{ $ref: "#/components/contentDescriptors/LeFoo" }], result: { name: "bar", schema: { $ref: "#/components/contentDescriptors/LeFoo" }, @@ -280,33 +277,32 @@ describe("parseOpenRPCDocument", () => { } }); - it("should make a reference resolver", ()=> { - const resolver = makeCustomResolver({"file": - async (): Promise => { - return {} - } + it("should make a reference resolver", () => { + const resolver = makeCustomResolver({ + file: async (): Promise => { + return {}; + }, }); - expect(resolver).toBeDefined() + expect(resolver).toBeDefined(); }); - it("should handle dereference option true", async ()=> { - const document = await parseOpenRPCDocument(workingDocument,{ + it("should handle dereference option true", async () => { + const document = await parseOpenRPCDocument(workingDocument, { dereference: true, }); expect(document.methods).toBeDefined(); }); - it("should handle custom resolver option", async ()=> { - const resolver = makeCustomResolver({"handler": - async (uri: string): Promise => { - return {} - } + it("should handle custom resolver option", async () => { + const resolver = makeCustomResolver({ + handler: async (_uri: string): Promise => { + return {}; + }, }); - const document = await parseOpenRPCDocument(workingDocument,{ - resolver + const document = await parseOpenRPCDocument(workingDocument, { + resolver, }); expect(document.methods).toBeDefined(); - }); it("rejects when the json provided is invalid from file", async () => { @@ -315,7 +311,7 @@ describe("parseOpenRPCDocument", () => { fs.readJson.mockRejectedValue(new Error("SyntaxError: super duper bad one")); const file = "./node_modules/@open-rpc/examples/service-descriptions/petstore-openrpc.json"; try { - await parseOpenRPCDocument(file) + await parseOpenRPCDocument(file); } catch (e) { expect(e).toBeDefined(); } diff --git a/src/parse-open-rpc-document.ts b/src/parse-open-rpc-document.ts index 4264206c..d65ffbf7 100644 --- a/src/parse-open-rpc-document.ts +++ b/src/parse-open-rpc-document.ts @@ -1,17 +1,26 @@ import dereferenceDocument from "./dereference-document"; -import validateOpenRPCDocument, { OpenRPCDocumentValidationError } from "./validate-open-rpc-document"; -import defaultResolver from "@json-schema-tools/reference-resolver" +import validateOpenRPCDocument, { + OpenRPCDocumentValidationError, +} from "./validate-open-rpc-document"; +import defaultResolver from "@json-schema-tools/reference-resolver"; import isUrl = require("is-url"); import { OpenrpcDocument } from "@open-rpc/meta-schema"; import { TGetOpenRPCDocument } from "./get-open-rpc-document"; -import ReferenceResolver, { ProtocolHandlerMap } from "@json-schema-tools/reference-resolver/build/reference-resolver"; +import ReferenceResolver, { + ProtocolHandlerMap, +} from "@json-schema-tools/reference-resolver/build/reference-resolver"; export { JSONSchema } from "@json-schema-tools/meta-schema"; /** * @ignore */ const isJson = (jsonString: string): boolean => { - try { JSON.parse(jsonString); return true; } catch (e) { return false; } + try { + JSON.parse(jsonString); + return true; + } catch (e) { + return false; + } }; /** @@ -50,7 +59,10 @@ const defaultParseOpenRPCDocumentOptions = { validate: true, }; -const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchemaFromFile: TGetOpenRPCDocument) => { +const makeParseOpenRPCDocument = ( + fetchUrlSchema: TGetOpenRPCDocument, + readSchemaFromFile: TGetOpenRPCDocument +) => { /** * Resolves an OpenRPC document from a variety of input types. The resolved OpenRPC document * will be dereferenced and validated against the [meta-schema](https://github.com/open-rpc/meta-schema). @@ -89,11 +101,14 @@ const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchem */ return async function parseOpenRPCDocument( schema: string | OpenrpcDocument = "./openrpc.json", - options: ParseOpenRPCDocumentOptions = defaultParseOpenRPCDocumentOptions, + options: ParseOpenRPCDocumentOptions = defaultParseOpenRPCDocumentOptions ): Promise { let parsedSchema: OpenrpcDocument; - const parseOptions = { ...defaultParseOpenRPCDocumentOptions, ...options } as ParseOpenRPCDocumentOptions; + const parseOptions = { + ...defaultParseOpenRPCDocumentOptions, + ...options, + } as ParseOpenRPCDocumentOptions; if (typeof schema !== "string") { parsedSchema = schema; @@ -105,7 +120,6 @@ const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchem parsedSchema = await readSchemaFromFile(schema as string); } - if (parseOptions.validate) { const isValid = validateOpenRPCDocument(parsedSchema); if (isValid instanceof OpenRPCDocumentValidationError) { @@ -133,7 +147,7 @@ const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchem }; }; -export function makeCustomResolver(protocolMapHandler: ProtocolHandlerMap): ReferenceResolver{ +export function makeCustomResolver(protocolMapHandler: ProtocolHandlerMap): ReferenceResolver { return new ReferenceResolver(protocolMapHandler); } diff --git a/src/validate-open-rpc-document.test.ts b/src/validate-open-rpc-document.test.ts index 03feade8..5dadf758 100644 --- a/src/validate-open-rpc-document.test.ts +++ b/src/validate-open-rpc-document.test.ts @@ -2,6 +2,8 @@ import validateOpenRPCDocument, { OpenRPCDocumentValidationError, } from "./validate-open-rpc-document"; import { OpenrpcDocument } from "@open-rpc/meta-schema"; +import goodExtensionSchema from "./extension-good-schema.json"; +import badExtensionSchema from "./extension-bad-schema.json"; describe("validateOpenRPCDocument", () => { it("errors when passed an incorrect document", () => { @@ -20,6 +22,12 @@ describe("validateOpenRPCDocument", () => { expect(result).toBeInstanceOf(OpenRPCDocumentValidationError); }); + it("errors when a document is corrupted", () => { + expect(() => validateOpenRPCDocument(undefined as any)).toThrow( + "schema-utils-js: Internal Error" + ); + }); + it("errors when passed an incorrect doc that is deep", () => { expect.assertions(2); const testSchema = { @@ -72,4 +80,16 @@ describe("validateOpenRPCDocument", () => { expect(result).toBe(true); expect(result).not.toBeInstanceOf(OpenRPCDocumentValidationError); }); + + it("supports extensions", () => { + const result = validateOpenRPCDocument(goodExtensionSchema as OpenrpcDocument); + expect(result).toBe(true); + expect(result).not.toBeInstanceOf(OpenRPCDocumentValidationError); + }); + + it("errors when extensions are not valid", () => { + const result = validateOpenRPCDocument(badExtensionSchema as OpenrpcDocument); + expect(result).not.toBe(null); + expect(result).toBeInstanceOf(OpenRPCDocumentValidationError); + }); }); diff --git a/src/validate-open-rpc-document.ts b/src/validate-open-rpc-document.ts index 1774e60a..e40760cc 100644 --- a/src/validate-open-rpc-document.ts +++ b/src/validate-open-rpc-document.ts @@ -1,6 +1,8 @@ -import metaSchema, { OpenrpcDocument as OpenRPC } from "@open-rpc/meta-schema"; +import { OpenrpcDocument as OpenRPC } from "@open-rpc/meta-schema"; import Ajv, { ErrorObject } from "ajv"; import JsonSchemaMetaSchema from "@json-schema-tools/meta-schema"; +import applyExtensionSpec from "./apply-extension-spec"; +import getExtendedMetaSchema from "./get-extended-metaschema"; /** * @ignore @@ -51,21 +53,20 @@ export default function validateOpenRPCDocument( ): OpenRPCDocumentValidationError | true { const ajv = new Ajv(); ajv.addSchema(JsonSchemaMetaSchema, "https://meta.json-schema.tools"); - const metaSchemaCopy = { ...metaSchema } as any; - delete metaSchemaCopy.definitions.JSONSchema.$id; - delete metaSchemaCopy.definitions.JSONSchema.$schema; - delete metaSchemaCopy.$schema; - delete metaSchemaCopy.$id; + let extMetaSchema = getExtendedMetaSchema(); try { - ajv.validate(metaSchemaCopy, document); + extMetaSchema = applyExtensionSpec(document, extMetaSchema); + ajv.validate(extMetaSchema, document); } catch (e) { - throw new Error([ - 'schema-utils-js: Internal Error', - '-----', - e, - '-----', - 'If you see this report it: https://github.com/open-rpc/schema-utils-js/issues', - ].join('\n')); + throw new Error( + [ + "schema-utils-js: Internal Error", + "-----", + e, + "-----", + "If you see this report it: https://github.com/open-rpc/schema-utils-js/issues", + ].join("\n") + ); } if (ajv.errors) { diff --git a/webpack.config.js b/webpack.config.js index d957f18b..5a4da2c0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,9 +1,10 @@ -const path = require('path'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const path = require("path"); module.exports = { - entry: './build/index-web.js', + entry: "./build/index-web.js", output: { - path: path.resolve(__dirname, 'dist'), - filename: 'bundle.js' - } + path: path.resolve(__dirname, "dist"), + filename: "bundle.js", + }, };