From 4e928577f42b18fff43c84fafce4bd817e64d794 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Wed, 28 Aug 2024 06:18:57 +0200 Subject: [PATCH 1/8] Added eslint to workspace --- .github/workflows/ngrx-hateoas.yml | 5 + angular.json | 10 + eslint.config.js | 43 + libs/ngrx-hateoas/eslint.config.js | 32 + package-lock.json | 1903 ++++++++++++++++++++++++++-- package.json | 10 +- 6 files changed, 1899 insertions(+), 104 deletions(-) create mode 100644 eslint.config.js create mode 100644 libs/ngrx-hateoas/eslint.config.js diff --git a/.github/workflows/ngrx-hateoas.yml b/.github/workflows/ngrx-hateoas.yml index dca64dc..7f4bd0c 100644 --- a/.github/workflows/ngrx-hateoas.yml +++ b/.github/workflows/ngrx-hateoas.yml @@ -31,3 +31,8 @@ jobs: - name: Build library run: | npm run build + + # Lint library + - name: Lint library + run: | + npm run lint diff --git a/angular.json b/angular.json index a3e9b56..19e5f7a 100644 --- a/angular.json +++ b/angular.json @@ -33,6 +33,16 @@ "zone.js/testing" ] } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "libs/ngrx-hateoas/**/*.ts", + "libs/ngrx-hateoas/**/*.html" + ], + "eslintConfig": "libs/ngrx-hateoas/eslint.config.js" + } } } }, diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..46ea569 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,43 @@ +// @ts-check +const eslint = require("@eslint/js"); +const tseslint = require("typescript-eslint"); +const angular = require("angular-eslint"); + +module.exports = tseslint.config( + { + files: ["**/*.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...tseslint.configs.stylistic, + ...angular.configs.tsRecommended, + ], + processor: angular.processInlineTemplates, + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "lib", + style: "camelCase", + }, + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "lib", + style: "kebab-case", + }, + ], + }, + }, + { + files: ["**/*.html"], + extends: [ + ...angular.configs.templateRecommended, + ...angular.configs.templateAccessibility, + ], + rules: {}, + } +); diff --git a/libs/ngrx-hateoas/eslint.config.js b/libs/ngrx-hateoas/eslint.config.js new file mode 100644 index 0000000..1a56623 --- /dev/null +++ b/libs/ngrx-hateoas/eslint.config.js @@ -0,0 +1,32 @@ +// @ts-check +const tseslint = require("typescript-eslint"); +const rootConfig = require("../../eslint.config.js"); + +module.exports = tseslint.config( + ...rootConfig, + { + files: ["**/*.ts"], + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "hat", + style: "camelCase", + }, + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "hat", + style: "kebab-case", + }, + ], + }, + }, + { + files: ["**/*.html"], + rules: {}, + } +); diff --git a/package-lock.json b/package-lock.json index 5e8f2d7..bd93b17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ "@angular/cli": "^18.1.0", "@angular/compiler-cli": "^18.1.0", "@types/jasmine": "~5.1.0", + "angular-eslint": "18.3.0", + "eslint": "^9.9.0", "jasmine-core": "~5.1.0", "json-server": "0.17.4", "karma": "~6.4.0", @@ -35,7 +37,8 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "ng-packagr": "^18.1.0", - "typescript": "~5.4.2" + "typescript": "~5.4.2", + "typescript-eslint": "8.1.0" } }, "node_modules/@ampproject/remapping": { @@ -259,6 +262,136 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-eslint/builder": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", + "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", + "dev": true, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", + "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.0.tgz", + "integrity": "sha512-Vl7gfPMXxvtHTjYdlzR161aj5xrqW6T57wd8ToQ7Gqzm0qHGfY6kE4SQobUa2LCYckTNSlv+zXe48C4ah/dSjw==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "@angular-eslint/utils": "18.3.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.0.tgz", + "integrity": "sha512-ddR/qwYbUeq9IpyVKrPbfZyRBTy6V8uc5I0JcBKttQ4CZ4joXhqsVgWFsI+JAMi8E66uNj1VC7NuKCOjDINv2Q==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "@angular-eslint/utils": "18.3.0", + "aria-query": "5.3.0", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.0.tgz", + "integrity": "sha512-rQ4DEWwf3f5n096GAK6JvXD0SRzRJ52WRaIyKg8MMkk6qvUDfZI8seOkcbjDtZoIe6Ds7DfqSfJgNVte75qvPQ==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "18.3.0", + "@angular-eslint/eslint-plugin-template": "18.3.0", + "ignore": "5.3.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", + "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/template-parser/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.0.tgz", + "integrity": "sha512-sCrkHkpxBJZLuCikdboZoawCfc2UgbJv+T14tu2uQCv+Vwzeadnu04vkeY2vTkA8GeBdBij/G9/N/nvwmwVw3g==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.3.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular/animations": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.1.0.tgz", @@ -2701,6 +2834,145 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", + "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.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, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@inquirer/checkbox": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.3.10.tgz", @@ -4488,91 +4760,732 @@ "@types/node": "*" } }, - "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.1.0.tgz", + "integrity": "sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==", "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.1.0", + "@typescript-eslint/type-utils": "8.1.0", + "@typescript-eslint/utils": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, "engines": { - "node": ">=14.6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", + "integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", + "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", + "integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz", + "integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==", "dev": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.1.0", + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/typescript-estree": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", + "integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", "dev": true, "dependencies": { - "@xtuc/long": "4.2.2" + "@typescript-eslint/types": "8.1.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.1.0.tgz", + "integrity": "sha512-U7iTAtGgJk6DPX9wIWPPOlt1gO57097G06gIcl0N0EEnNw8RGD62c+2/DiP/zL7KrkqnnqF7gtFGR7YgzPllTA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.1.0", + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/typescript-estree": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", + "integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", + "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", + "integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", + "integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz", + "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.1.0.tgz", + "integrity": "sha512-oLYvTxljVvsMnldfl6jIKxTaU7ok7km0KDrwOt1RHYu6nxlhN3TIx8k5Q52L6wR33nOwDgM7VwW1fT1qMNfFIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.1.0", + "@typescript-eslint/utils": "8.1.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", + "integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", + "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", + "integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz", + "integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.1.0", + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/typescript-estree": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", + "integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz", + "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz", + "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz", + "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/typescript-estree": "8.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz", + "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.3.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { @@ -4707,6 +5620,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -4804,6 +5726,24 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-eslint": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-18.3.0.tgz", + "integrity": "sha512-neBE3BUtxj1EPPNVww3i/e8DKh/gb+fT/WpDEsRZM//8vS+qb0pMC04dn4bqeUriM05Nq/oUESdwkLuyadJE9A==", + "dev": true, + "dependencies": { + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "18.3.0", + "@angular-eslint/eslint-plugin-template": "18.3.0", + "@angular-eslint/schematics": "18.3.0", + "@angular-eslint/template-parser": "18.3.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4892,12 +5832,30 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -4935,6 +5893,15 @@ "postcss": "^8.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-loader": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", @@ -6216,6 +7183,12 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -6324,6 +7297,15 @@ "node": ">=4" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6343,18 +7325,39 @@ "node": ">=8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -6699,44 +7702,400 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", + "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.9.1", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "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": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "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, + "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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.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/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=0.10" + } + }, + "node_modules/esquery/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, + "engines": { + "node": ">=4.0" } }, "node_modules/esrecurse": { @@ -6987,6 +8346,12 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -7008,6 +8373,18 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7094,6 +8471,19 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -7369,6 +8759,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -7687,9 +9083,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -7997,6 +9393,15 @@ "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, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -8311,6 +9716,12 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", @@ -8438,6 +9849,12 @@ "node": ">=8" } }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8739,6 +10156,15 @@ "node": ">=10" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8867,6 +10293,19 @@ "node": ">=0.10.0" } }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -9048,6 +10487,12 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -9851,6 +11296,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", @@ -10889,6 +12340,23 @@ "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, + "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/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -11522,6 +12990,15 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", @@ -12884,6 +14361,18 @@ "node": ">=6" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -13101,6 +14590,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -13186,6 +14681,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -13205,6 +14712,18 @@ "node": "^16.14.0 || >=18.0.0" } }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -13249,6 +14768,179 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.1.0.tgz", + "integrity": "sha512-prB2U3jXPJLpo1iVLN338Lvolh6OrcCZO+9Yv6AR+tvegPPptYCDBIHiEEUdqRi8gAv2bXNKfMUrgAd2ejn/ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.1.0", + "@typescript-eslint/parser": "8.1.0", + "@typescript-eslint/utils": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", + "integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", + "integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", + "integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/visitor-keys": "8.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz", + "integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.1.0", + "@typescript-eslint/types": "8.1.0", + "@typescript-eslint/typescript-estree": "8.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", + "integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.1.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-eslint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ua-parser-js": { "version": "0.7.38", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", @@ -13952,6 +15644,15 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 235fb47..4308a54 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "ng build ngrx-hateoas", "watch": "ng build --watch --configuration development", "test": "ng test --code-coverage", - "test:github": "ng test --browsers=ChromeHeadless --watch=false --code-coverage" + "test:github": "ng test --browsers=ChromeHeadless --watch=false --code-coverage", + "lint": "ng lint" }, "private": true, "dependencies": { @@ -31,7 +32,10 @@ "@angular/cli": "^18.1.0", "@angular/compiler-cli": "^18.1.0", "@types/jasmine": "~5.1.0", + "angular-eslint": "18.3.0", + "eslint": "^9.9.0", "jasmine-core": "~5.1.0", + "json-server": "0.17.4", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", @@ -39,6 +43,6 @@ "karma-jasmine-html-reporter": "~2.1.0", "ng-packagr": "^18.1.0", "typescript": "~5.4.2", - "json-server": "0.17.4" + "typescript-eslint": "8.1.0" } -} +} \ No newline at end of file From f6612ad1174af5a8386942daec5d30c98173edcf Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Wed, 28 Aug 2024 06:36:14 +0200 Subject: [PATCH 2/8] Fixed some linting issues --- .../src/lib/store-features/with-patchable-resource.spec.ts | 1 - libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.spec.ts | 4 ++-- libs/ngrx-hateoas/src/lib/util/when-true.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts index f583d6f..7331904 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts @@ -1,6 +1,5 @@ import { TestBed } from '@angular/core/testing'; import { signalStore, withState } from '@ngrx/signals'; -import { provideHateoas } from '../provide'; import { HypermediaResourceState } from './with-hypermedia-resource'; import { withPatchableResource } from './with-patchable-resource'; diff --git a/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.spec.ts b/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.spec.ts index 08b89ed..51f3306 100644 --- a/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.spec.ts +++ b/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.spec.ts @@ -1,4 +1,4 @@ -import { fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { TestBed } from "@angular/core/testing"; import { patchState, signalStore, withMethods, withState } from "@ngrx/signals"; import { toDeepPatchableSignal } from "./deep-patchable-signal"; import { signal } from "@angular/core"; @@ -7,7 +7,7 @@ type TestModel = { numProp: number, objProp: { stringProp: string - }, + } } const initialTestModel: TestModel = { diff --git a/libs/ngrx-hateoas/src/lib/util/when-true.ts b/libs/ngrx-hateoas/src/lib/util/when-true.ts index 7929dd5..da13fb5 100644 --- a/libs/ngrx-hateoas/src/lib/util/when-true.ts +++ b/libs/ngrx-hateoas/src/lib/util/when-true.ts @@ -1,4 +1,4 @@ -import { effect, Signal } from "@angular/core"; +import { Signal } from "@angular/core"; import { toObservable } from "@angular/core/rxjs-interop"; import { firstValueFrom, filter } from "rxjs"; From 835372a0c967820ae775875a9f6fc4f55616c5e1 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:05:41 +0200 Subject: [PATCH 3/8] Worked on linting issues --- libs/ngrx-hateoas/eslint.config.js | 6 ++ libs/ngrx-hateoas/package.json | 2 +- libs/ngrx-hateoas/src/lib/models.ts | 5 +- .../src/lib/pipes/get-action.pipe.spec.ts | 4 +- .../src/lib/pipes/get-link.pipe.spec.ts | 4 +- .../src/lib/pipes/get-socket.pipe.spec.ts | 4 +- .../src/lib/pipes/has-action.pipe.spec.ts | 4 +- .../src/lib/pipes/has-action.pipe.ts | 2 +- .../src/lib/pipes/has-link.pipe.spec.ts | 4 +- .../src/lib/pipes/has-link.pipe.ts | 2 +- .../src/lib/pipes/has-socket.pipe.spec.ts | 4 +- .../src/lib/pipes/has-socket.pipe.ts | 2 +- libs/ngrx-hateoas/src/lib/provide.spec.ts | 91 +++++++++++++++++++ libs/ngrx-hateoas/src/lib/provide.ts | 26 ++++-- .../src/lib/services/hateoas.service.spec.ts | 9 +- .../src/lib/services/hateoas.service.ts | 15 +-- .../src/lib/services/request.service.spec.ts | 4 +- .../src/lib/services/request.service.ts | 11 +-- .../store-features/with-hypermedia-action.ts | 8 +- .../with-hypermedia-resource.ts | 8 +- .../with-initial-hypermedia-resource.ts | 5 +- .../with-linked-hypermedia-resource.ts | 8 +- .../store-features/with-patchable-resource.ts | 8 +- 23 files changed, 168 insertions(+), 68 deletions(-) create mode 100644 libs/ngrx-hateoas/src/lib/provide.spec.ts diff --git a/libs/ngrx-hateoas/eslint.config.js b/libs/ngrx-hateoas/eslint.config.js index 1a56623..6643cb0 100644 --- a/libs/ngrx-hateoas/eslint.config.js +++ b/libs/ngrx-hateoas/eslint.config.js @@ -23,6 +23,12 @@ module.exports = tseslint.config( style: "kebab-case", }, ], + "@typescript-eslint/consistent-type-definitions": [ + "off" + ], + "@typescript-eslint/no-unsafe-function-type": [ + "off" + ] }, }, { diff --git a/libs/ngrx-hateoas/package.json b/libs/ngrx-hateoas/package.json index 22396e5..474dbb7 100644 --- a/libs/ngrx-hateoas/package.json +++ b/libs/ngrx-hateoas/package.json @@ -1,6 +1,6 @@ { "name": "@angular-architects/ngrx-hateoas", - "version": "18.0.0-rc.1", + "version": "18.0.0-rc.2", "peerDependencies": { "@angular/common": "^18.0.0", "@angular/core": "^18.0.0", diff --git a/libs/ngrx-hateoas/src/lib/models.ts b/libs/ngrx-hateoas/src/lib/models.ts index 6fba420..a317841 100644 --- a/libs/ngrx-hateoas/src/lib/models.ts +++ b/libs/ngrx-hateoas/src/lib/models.ts @@ -20,13 +20,12 @@ export interface ResourceSocket { href: string } -export type Resource = { -} +export type Resource = Record; export type DynamicResourceValue = DynamicResource | Resource | number | string | boolean | DynamicResource[] | Resource[] | number[] | boolean[] | null; export type DynamicResource = Resource & { - [key: string]: DynamicResourceValue; + [key: string]: DynamicResource | number | string | boolean | DynamicResource[] | number[] | boolean[] | null; } export const initialDynamicResource: DynamicResource = {}; diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts index 201edbe..6a2f738 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; import { GetActionPipe } from './get-action.pipe'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; const testModel = { _actions: { @@ -13,7 +13,7 @@ describe('GetActionPipe', () => { let getActionPipe: GetActionPipe; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ GetActionPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ GetActionPipe, HateoasService ]}); getActionPipe = TestBed.inject(GetActionPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts index 0cf35cf..b9128f9 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; import { GetLinkPipe } from './get-link.pipe'; const testModel = { @@ -13,7 +13,7 @@ describe('GetLinkPipe', () => { let getLinkPipe: GetLinkPipe; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ GetLinkPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ GetLinkPipe, HateoasService ]}); getLinkPipe = TestBed.inject(GetLinkPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts index 4c10cb0..b7d132f 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; import { GetSocketPipe } from './get-socket.pipe'; const testModel = { @@ -13,7 +13,7 @@ describe('GetSocketPipe', () => { let getSocketPipe: GetSocketPipe; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ GetSocketPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ GetSocketPipe, HateoasService ]}); getSocketPipe = TestBed.inject(GetSocketPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.spec.ts index d400e56..3114296 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.spec.ts @@ -1,6 +1,6 @@ import { HasActionPipe } from './has-action.pipe'; import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; const testModel = { _actions: { @@ -13,7 +13,7 @@ describe('HasActionPipe', () => { let hasActionPipe: HasActionPipe beforeEach(() => { - TestBed.configureTestingModule({ providers: [ HasActionPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ HasActionPipe, HateoasService ]}); hasActionPipe = TestBed.inject(HasActionPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.ts index 291cd65..e45675d 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-action.pipe.ts @@ -9,7 +9,7 @@ export class HasActionPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: any, actionName: string): boolean { + transform(resource: unknown, actionName: string): boolean { return this.hateoasService.getAction(resource, actionName) !== undefined; } diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.spec.ts index 069174e..4356888 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; import { HasLinkPipe } from './has-link.pipe'; const testModel = { @@ -13,7 +13,7 @@ describe('HasLinkPipe', () => { let hasLinkPipe: HasLinkPipe; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ HasLinkPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ HasLinkPipe, HateoasService ]}); hasLinkPipe = TestBed.inject(HasLinkPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.ts index dfd8086..c6c8dda 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-link.pipe.ts @@ -9,7 +9,7 @@ export class HasLinkPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: any, linkName: string): boolean { + transform(resource: unknown, linkName: string): boolean { return this.hateoasService.getLink(resource, linkName ) !== undefined; } diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.spec.ts index 1dccf5f..de369e3 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from '../services/hateoas.service'; +import { HateoasService } from '../services/hateoas.service'; import { HasSocketPipe } from './has-socket.pipe'; const testModel = { @@ -13,7 +13,7 @@ describe('HasSocketPipe', () => { let hasSocketPipe: HasSocketPipe; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ HasSocketPipe, HateoasService, HateoasConfig ]}); + TestBed.configureTestingModule({ providers: [ HasSocketPipe, HateoasService ]}); hasSocketPipe = TestBed.inject(HasSocketPipe); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.ts index 4cb1299..03c50e2 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/has-socket.pipe.ts @@ -9,7 +9,7 @@ export class HasSocketPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: any, socketName: string): boolean { + transform(resource: unknown, socketName: string): boolean { return this.hateoasService.getSocket(resource, socketName) !== undefined; } diff --git a/libs/ngrx-hateoas/src/lib/provide.spec.ts b/libs/ngrx-hateoas/src/lib/provide.spec.ts new file mode 100644 index 0000000..d47b10f --- /dev/null +++ b/libs/ngrx-hateoas/src/lib/provide.spec.ts @@ -0,0 +1,91 @@ +import { TestBed } from "@angular/core/testing"; +import { AntiForgeryOptions, CustomHeadersOptions, HATEOAS_ANTI_FORGERY, HATEOAS_CUSTOM_HEADERS, HATEOAS_LOGIN_REDIRECT, HATEOAS_METADATA_PROVIDER, LoginRedirectOptions, MetadataProvider, provideHateoas, withAntiForgery, withCustomHeaders, withLoginRedirect, withMetadataProvider } from "./provide"; +import { DynamicResource, DynamicResourceValue, ResourceAction, ResourceLink, ResourceSocket } from "./models"; + +describe('provideHateaos', () => { + + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + describe('withAntiForgery', () => { + + it('registers custom anti forgery options in injection context', () => { + + const dummyAntiForgeryOptions: AntiForgeryOptions = { + cookieName: 'foo', + headerName: 'bar' + }; + + TestBed.configureTestingModule({ providers: [ provideHateoas(withAntiForgery(dummyAntiForgeryOptions)) ]}); + const antiForgeryOptions = TestBed.inject(HATEOAS_ANTI_FORGERY); + + expect(antiForgeryOptions.cookieName).toBe(dummyAntiForgeryOptions.cookieName); + expect(antiForgeryOptions.headerName).toBe(dummyAntiForgeryOptions.headerName); + }); + + }); + + describe('withLoginRedirect', () => { + + it('registers custom login redirect options in injection context', () => { + + const dummyLoginRedirectOptions: LoginRedirectOptions = { + loginUrl: 'foo', + redirectUrlParamName: 'bar' + }; + + TestBed.configureTestingModule({ providers: [ provideHateoas(withLoginRedirect(dummyLoginRedirectOptions)) ]}); + const loginRedirectOptoins = TestBed.inject(HATEOAS_LOGIN_REDIRECT); + + expect(loginRedirectOptoins.loginUrl).toBe(dummyLoginRedirectOptions.loginUrl); + expect(loginRedirectOptoins.redirectUrlParamName).toBe(dummyLoginRedirectOptions.redirectUrlParamName); + }); + + }); + + describe('withCustomHeaders', () => { + + it('registers custom header options in injection context', () => { + + const dummyCustomHeaderOptions: CustomHeadersOptions = { + headers: { + foo: 'bar' + } + }; + + TestBed.configureTestingModule({ providers: [ provideHateoas(withCustomHeaders(dummyCustomHeaderOptions)) ]}); + const customHeaderOptions = TestBed.inject(HATEOAS_CUSTOM_HEADERS); + + expect(customHeaderOptions.headers).toBe(dummyCustomHeaderOptions.headers); + }); + + }); + + describe('withMedatadaProvider', () => { + + it('registers custom metadataprovider in injection context', () => { + + const dummyMetadataProvider: MetadataProvider = { + linkLookup(resource, linkName) { + return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; + }, + actionLookup(resource, actionName) { + return { href: (resource as any)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction; + }, + socketLookup(resource, socketName) { + return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; + } + } + + TestBed.configureTestingModule({ providers: [ provideHateoas(withMetadataProvider(dummyMetadataProvider)) ]}); + const metadataProvider = TestBed.inject(HATEOAS_METADATA_PROVIDER); + + expect(metadataProvider.linkLookup).toBe(dummyMetadataProvider.linkLookup); + expect(metadataProvider.actionLookup).toBe(dummyMetadataProvider.actionLookup); + expect(metadataProvider.socketLookup).toBe(dummyMetadataProvider.socketLookup); + }); + + }); + +}); \ No newline at end of file diff --git a/libs/ngrx-hateoas/src/lib/provide.ts b/libs/ngrx-hateoas/src/lib/provide.ts index edd67e3..316e400 100644 --- a/libs/ngrx-hateoas/src/lib/provide.ts +++ b/libs/ngrx-hateoas/src/lib/provide.ts @@ -1,7 +1,7 @@ import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders, Provider } from "@angular/core"; -import { HateoasConfig, HateoasService } from "./services/hateoas.service"; +import { HateoasService } from "./services/hateoas.service"; import { RequestService } from "./services/request.service"; -import { ResourceAction, ResourceLink, ResourceSocket } from "./models"; +import { DynamicResource, Resource, ResourceAction, ResourceLink, ResourceSocket } from "./models"; export enum HateoasFeatureKind { AntiForgery, @@ -49,9 +49,24 @@ export interface MetadataProvider { socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined; } +function isResource(resource: unknown): resource is DynamicResource { + return typeof resource === 'object' && resource !== null; +} + +function isResourceLinkCollection(resourceLinks: unknown): resourceLinks is Record { + return typeof resourceLinks === 'object' && resourceLinks !== null; +} + +function isResourceLink(resourceLink: unknown): resourceLink is ResourceLink { + return typeof resourceLink === 'object' && resourceLink !== null && 'href' in resourceLink; +} + const defaultMetadataProvider: MetadataProvider = { linkLookup(resource: unknown, linkName: string): ResourceLink | undefined { - return (resource as any)?._links?.[linkName]; + if(isResource(resource) && isResourceLinkCollection(resource['_links'] && isResourceLink(resource['_links'][linkName]))) + return resource['_links'][linkName]; + else + return undefined; }, actionLookup(resource: unknown, actionName: string): ResourceAction | undefined { return (resource as any)?._actions?.[actionName]; @@ -94,7 +109,7 @@ export function withCustomHeaders(options?: CustomHeadersOptions): HateoasFeatur }; } -export const HATEOAS_METADATA_PROVIDER = new InjectionToken('HATEOAS_METADATA_PROVIDER'); +export const HATEOAS_METADATA_PROVIDER = new InjectionToken('HATEOAS_METADATA_PROVIDER', { providedIn: 'root', factory: () => defaultMetadataProvider }); export function withMetadataProvider(provider?: MetadataProvider): HateoasFeature { return { @@ -107,9 +122,6 @@ export function withMetadataProvider(provider?: MetadataProvider): HateoasFeatur export function provideHateoas(...features: HateoasFeature[]): EnvironmentProviders { return makeEnvironmentProviders([{ - provide: HateoasConfig, - useValue: new HateoasConfig() - }, { provide: HateoasService, useClass: HateoasService }, { diff --git a/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts b/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts index 617a953..317da5a 100644 --- a/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts +++ b/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts @@ -1,8 +1,9 @@ import { TestBed } from '@angular/core/testing'; -import { HateoasConfig, HateoasService } from './hateoas.service'; +import { HateoasService } from './hateoas.service'; import { ResourceAction, ResourceLink, ResourceSocket } from '../models'; +import { HATEOAS_METADATA_PROVIDER, MetadataProvider } from '../provide'; -const dummyHateoasConfig: HateoasConfig = { +const dummyHateoasMetadataProvider: MetadataProvider = { linkLookup(resource, linkName) { return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; }, @@ -11,7 +12,7 @@ const dummyHateoasConfig: HateoasConfig = { }, socketLookup(resource, socketName) { return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; - }, + } } const testObj = { @@ -27,7 +28,7 @@ describe('HateoasService', () => { let hateoasService: HateoasService; beforeEach(() => { - TestBed.configureTestingModule({ providers: [ { provide: HateoasConfig, useValue: dummyHateoasConfig }, HateoasService ]}); + TestBed.configureTestingModule({ providers: [ { provide: HATEOAS_METADATA_PROVIDER, useValue: dummyHateoasMetadataProvider }, HateoasService ]}); hateoasService = TestBed.inject(HateoasService); }); diff --git a/libs/ngrx-hateoas/src/lib/services/hateoas.service.ts b/libs/ngrx-hateoas/src/lib/services/hateoas.service.ts index 5e240e1..1dd630c 100644 --- a/libs/ngrx-hateoas/src/lib/services/hateoas.service.ts +++ b/libs/ngrx-hateoas/src/lib/services/hateoas.service.ts @@ -1,21 +1,10 @@ import { Injectable, inject } from "@angular/core"; import { ResourceAction, ResourceLink, ResourceSocket } from "../models"; - -export class HateoasConfig { - linkLookup(resource: unknown, linkName: string): ResourceLink | undefined { - return (resource as any)?._links?.[linkName]; - } - actionLookup(resource: unknown, actionName: string): ResourceAction | undefined { - return (resource as any)?._actions?.[actionName]; - } - socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined { - return (resource as any)?._sockets?.[socketName]; - } -} +import { HATEOAS_METADATA_PROVIDER } from "../provide"; @Injectable() export class HateoasService { - private hateoasConfig = inject(HateoasConfig); + private hateoasConfig = inject(HATEOAS_METADATA_PROVIDER); getLink(resource: unknown, linkName: string): ResourceLink | undefined { return this.hateoasConfig.linkLookup(resource, linkName); diff --git a/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts b/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts index 20ced51..4d32cec 100644 --- a/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts +++ b/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts @@ -141,7 +141,7 @@ describe('RequestService', () => { currentLocation = '/angular/route' - let windowsStub = { + const windowsStub = { location: { set href(value: string) { currentLocation = value; }, get href() { return currentLocation; } @@ -207,7 +207,7 @@ describe('RequestService', () => { let httpTestingController: HttpTestingController; beforeEach(() => { - let customHeaderOptions: CustomHeadersOptions = { + const customHeaderOptions: CustomHeadersOptions = { headers: { 'X-Foo': 'Bar' } diff --git a/libs/ngrx-hateoas/src/lib/services/request.service.ts b/libs/ngrx-hateoas/src/lib/services/request.service.ts index 3f94fc2..f6b1724 100644 --- a/libs/ngrx-hateoas/src/lib/services/request.service.ts +++ b/libs/ngrx-hateoas/src/lib/services/request.service.ts @@ -14,11 +14,11 @@ export class RequestService { private httpClient = inject(HttpClient); - public async request(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: any): Promise { + public async request(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: unknown): Promise { let headers = new HttpHeaders().set('Content-Type', 'application/json'); if(this.customHeadersOptions) { - for(let key in this.customHeadersOptions.headers) { + for(const key in this.customHeadersOptions.headers) { headers = headers.set(key, this.customHeadersOptions.headers[key]); }; } @@ -32,10 +32,9 @@ export class RequestService { } try { - let response = await firstValueFrom(this.httpClient.request(method, url, { body, headers })); - return response; - } catch(errorResponse: any) { - if(errorResponse.status === 401 && this.loginRedirectOptions) { + return await firstValueFrom(this.httpClient.request(method, url, { body, headers })); + } catch(errorResponse) { + if(typeof errorResponse === 'object' && errorResponse !== null && 'status' in errorResponse && errorResponse.status === 401 && this.loginRedirectOptions) { // Redirect to sign in const currentUrl = this.window.location.href; this.window.location.href = `${this.loginRedirectOptions.loginUrl}?${this.loginRedirectOptions.redirectUrlParamName}=` + encodeURIComponent(currentUrl); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts index a521115..18bff31 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts @@ -41,16 +41,16 @@ export type HypermediaActionMethods = ExecuteHypermediaActionMethod & ConnectHypermediaActionMethod type actionRxInput = { - resource: any, + resource: unknown, action: string } export function withHypermediaAction( actionName: ActionName): SignalStoreFeature< - { state: {}; computed: {}; methods: {} }, + { state: object; computed: Record>; methods: Record }, { state: HypermediaActionState; - computed: {}, + computed: Record>; methods: HypermediaActionMethods; } >; @@ -80,7 +80,7 @@ export function withHypermediaAction(actionName: Acti const rxConnectToResource = rxMethod( pipe( - tap(_ => patchState(store, { [stateKey]: { ...store[stateKey](), href: '', method: '', isAvailable: false } })), + tap(() => patchState(store, { [stateKey]: { ...store[stateKey](), href: '', method: '', isAvailable: false } })), map(input => hateoasService.getAction(input.resource, input.action)), filter(action => isValidHref(action?.href) && isValidActionVerb(action?.method)), map(action => action!), diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts index 606603c..28e8e67 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts @@ -1,4 +1,4 @@ -import { inject } from "@angular/core"; +import { inject, Signal } from "@angular/core"; import { SignalStoreFeature, patchState, signalStoreFeature, withMethods, withState } from "@ngrx/signals"; import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchable-signal"; import { HateoasService } from "../services/hateoas.service"; @@ -54,10 +54,10 @@ export type HypermediaResourceMethods = export function withHypermediaResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< - { state: {}; computed: {}; methods: {} }, + { state: object; computed: Record>; methods: Record }, { state: HypermediaResourceState; - computed: {}, + computed: Record>; methods: HypermediaResourceMethods; } >; @@ -85,7 +85,7 @@ export function withHypermediaResource(r const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource); - const loadFromUrlMethod = async (url: string | null, fromCache: boolean = false): Promise => { + const loadFromUrlMethod = async (url: string | null, fromCache = false): Promise => { if(!url) { patchState(store, { [stateKey]: { ...store[stateKey](), url: '', isLoading: false, isLoaded: false, resource: initialValue } }); return Promise.resolve(); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts index 17a15be..01d97d7 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts @@ -1,12 +1,13 @@ import { SignalStoreFeature, signalStoreFeature, withHooks } from "@ngrx/signals"; import { withHypermediaResource, HypermediaResourceState, HypermediaResourceMethods, generateLoadHypermediaResourceFromUrlMethodName } from "./with-hypermedia-resource"; +import { Signal } from "@angular/core"; export function withInitialHypermediaResource( resourceName: ResourceName, initialValue: TResource, url: string | (() => string)): SignalStoreFeature< - { state: {}; computed: {}; methods: {} }, + { state: object; computed: Record>; methods: Record }, { state: HypermediaResourceState; - computed: {}, + computed: Record>; methods: HypermediaResourceMethods; } >; diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts index 6c125ca..881886c 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts @@ -48,16 +48,16 @@ export type LinkedHypermediaResourceMethods; type linkedRxInput = { - resource: any, + resource: unknown, linkName: string } export function withLinkedHypermediaResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< - { state: {}; computed: {}; methods: {} }, + { state: object; computed: Record>; methods: Record }, { state: LinkedHypermediaResourceState; - computed: {}, + computed: Record>; methods: LinkedHypermediaResourceMethods; } >; @@ -78,7 +78,7 @@ export function withLinkedHypermediaResource { + withMethods((store, requestService = inject(RequestService)) => { const hateoasService = inject(HateoasService); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts index 81e7520..8960921 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts @@ -1,6 +1,7 @@ import { SignalStoreFeature, patchState, signalStoreFeature, withMethods } from "@ngrx/signals"; import { HypermediaResourceState } from "./with-hypermedia-resource"; import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchable-signal"; +import { Signal } from "@angular/core"; export type GetPatchableResourceMethod = { [K in ResourceName as `get${Capitalize}AsPatchable`]: () => DeepPatchableSignal @@ -15,13 +16,14 @@ export type PatchableResourceMethods = export function withPatchableResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< - { state: HypermediaResourceState; computed: {}; methods: {} }, + { state: HypermediaResourceState; computed: Record>; methods: Record }, { - state: {}; - computed: {}, + state: object, + computed: Record>, methods: PatchableResourceMethods; } >; +// eslint-disable-next-line @typescript-eslint/no-unused-vars export function withPatchableResource(resourceName: ResourceName, initialValue: TResource) { const stateKey = `${resourceName}`; From 38613bcf04e2694ef99f45623525cf965edf3cb5 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:53:28 +0200 Subject: [PATCH 4/8] Fixed build issues --- libs/ngrx-hateoas/src/lib/provide.spec.ts | 8 ++-- libs/ngrx-hateoas/src/lib/provide.ts | 36 +++++++++++++---- .../src/lib/services/hateoas.service.spec.ts | 6 +-- .../src/lib/services/request.service.spec.ts | 40 +++++++++++++------ .../src/lib/services/request.service.ts | 6 +-- .../with-linked-hypermedia-resource.ts | 40 +++++++++++-------- 6 files changed, 89 insertions(+), 47 deletions(-) diff --git a/libs/ngrx-hateoas/src/lib/provide.spec.ts b/libs/ngrx-hateoas/src/lib/provide.spec.ts index d47b10f..66d6144 100644 --- a/libs/ngrx-hateoas/src/lib/provide.spec.ts +++ b/libs/ngrx-hateoas/src/lib/provide.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from "@angular/core/testing"; import { AntiForgeryOptions, CustomHeadersOptions, HATEOAS_ANTI_FORGERY, HATEOAS_CUSTOM_HEADERS, HATEOAS_LOGIN_REDIRECT, HATEOAS_METADATA_PROVIDER, LoginRedirectOptions, MetadataProvider, provideHateoas, withAntiForgery, withCustomHeaders, withLoginRedirect, withMetadataProvider } from "./provide"; -import { DynamicResource, DynamicResourceValue, ResourceAction, ResourceLink, ResourceSocket } from "./models"; +import { ResourceAction, ResourceLink, ResourceSocket } from "./models"; describe('provideHateaos', () => { @@ -68,13 +68,13 @@ describe('provideHateaos', () => { const dummyMetadataProvider: MetadataProvider = { linkLookup(resource, linkName) { - return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; + return { href: (resource as Record>)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; }, actionLookup(resource, actionName) { - return { href: (resource as any)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction; + return { href: (resource as Record>)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction; }, socketLookup(resource, socketName) { - return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; + return { href: (resource as Record>)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; } } diff --git a/libs/ngrx-hateoas/src/lib/provide.ts b/libs/ngrx-hateoas/src/lib/provide.ts index 316e400..cd5a91d 100644 --- a/libs/ngrx-hateoas/src/lib/provide.ts +++ b/libs/ngrx-hateoas/src/lib/provide.ts @@ -1,7 +1,7 @@ import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders, Provider } from "@angular/core"; import { HateoasService } from "./services/hateoas.service"; import { RequestService } from "./services/request.service"; -import { DynamicResource, Resource, ResourceAction, ResourceLink, ResourceSocket } from "./models"; +import { Resource, ResourceAction, ResourceLink, ResourceSocket } from "./models"; export enum HateoasFeatureKind { AntiForgery, @@ -49,30 +49,52 @@ export interface MetadataProvider { socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined; } -function isResource(resource: unknown): resource is DynamicResource { +function isResource(resource: unknown): resource is Resource { return typeof resource === 'object' && resource !== null; } -function isResourceLinkCollection(resourceLinks: unknown): resourceLinks is Record { +function isResourceLinkRecord(resourceLinks: unknown): resourceLinks is Record { return typeof resourceLinks === 'object' && resourceLinks !== null; } +function isResourceActionRecord(resourceActions: unknown): resourceActions is Record { + return typeof resourceActions === 'object' && resourceActions !== null; +} + +function isResourceSocketRecord(resourceSockets: unknown): resourceSockets is Record { + return typeof resourceSockets === 'object' && resourceSockets !== null; +} + function isResourceLink(resourceLink: unknown): resourceLink is ResourceLink { - return typeof resourceLink === 'object' && resourceLink !== null && 'href' in resourceLink; + return typeof resourceLink === 'object' && resourceLink !== null && 'href' in resourceLink; +} + +function isResourceAction(resourceAction: unknown): resourceAction is ResourceAction { + return typeof resourceAction === 'object' && resourceAction !== null && 'href' in resourceAction && 'method' in resourceAction; +} + +function isResourceSocket(resourceSocket: unknown): resourceSocket is ResourceSocket { + return typeof resourceSocket === 'object' && resourceSocket !== null && 'href' in resourceSocket && 'method' in resourceSocket; } const defaultMetadataProvider: MetadataProvider = { linkLookup(resource: unknown, linkName: string): ResourceLink | undefined { - if(isResource(resource) && isResourceLinkCollection(resource['_links'] && isResourceLink(resource['_links'][linkName]))) + if(isResource(resource) && isResourceLinkRecord(resource['_links']) && isResourceLink(resource['_links'][linkName])) return resource['_links'][linkName]; else return undefined; }, actionLookup(resource: unknown, actionName: string): ResourceAction | undefined { - return (resource as any)?._actions?.[actionName]; + if(isResource(resource) && isResourceActionRecord(resource['_actions']) && isResourceAction(resource['_actions'][actionName])) + return resource['_actions'][actionName]; + else + return undefined; }, socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined { - return (resource as any)?._sockets?.[socketName]; + if(isResource(resource) && isResourceSocketRecord(resource['_sockets']) && isResourceSocket(resource['_sockets'][socketName])) + return resource['_sockets'][socketName]; + else + return undefined; } } diff --git a/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts b/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts index 317da5a..809ec5e 100644 --- a/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts +++ b/libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts @@ -5,13 +5,13 @@ import { HATEOAS_METADATA_PROVIDER, MetadataProvider } from '../provide'; const dummyHateoasMetadataProvider: MetadataProvider = { linkLookup(resource, linkName) { - return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; + return { href: (resource as Record>)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink; }, actionLookup(resource, actionName) { - return { href: (resource as any)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction; + return { href: (resource as Record>)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction; }, socketLookup(resource, socketName) { - return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; + return { href: (resource as Record>)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket; } } diff --git a/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts b/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts index 4d32cec..ab6deb8 100644 --- a/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts +++ b/libs/ngrx-hateoas/src/lib/services/request.service.spec.ts @@ -165,9 +165,13 @@ describe('RequestService', () => { const serverRequest = httpTestingController.expectOne('/api/test'); expect(serverRequest.request.method).toBe('GET'); serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' }); - const clientRequest = await clientRequestPromise; - expect(clientRequest).toBeUndefined(); - expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + try { + await clientRequestPromise; + } catch { + expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + return; + } + expect(false).toBeTrue(); }); it('makes PUT requests correctly', async () => { @@ -175,9 +179,13 @@ describe('RequestService', () => { const serverRequest = httpTestingController.expectOne('/api/test'); expect(serverRequest.request.method).toBe('PUT'); serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' }); - const clientRequest = await clientRequestPromise; - expect(clientRequest).toBeUndefined(); - expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + try { + await clientRequestPromise; + } catch { + expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + return; + } + expect(false).toBeTrue(); }); it('makes POST requests correctly', async () => { @@ -185,9 +193,13 @@ describe('RequestService', () => { const serverRequest = httpTestingController.expectOne('/api/test'); expect(serverRequest.request.method).toBe('POST'); serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' }); - const clientRequest = await clientRequestPromise; - expect(clientRequest).toBeUndefined(); - expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + try { + await clientRequestPromise; + } catch { + expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + return; + } + expect(false).toBeTrue(); }); it('makes DELETE requests correctly', async () => { @@ -195,9 +207,13 @@ describe('RequestService', () => { const serverRequest = httpTestingController.expectOne('/api/test'); expect(serverRequest.request.method).toBe('DELETE'); serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' }); - const clientRequest = await clientRequestPromise; - expect(clientRequest).toBeUndefined(); - expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + try { + await clientRequestPromise; + } catch { + expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute'); + return; + } + expect(true).toBe(false); }); }); diff --git a/libs/ngrx-hateoas/src/lib/services/request.service.ts b/libs/ngrx-hateoas/src/lib/services/request.service.ts index f6b1724..5782b01 100644 --- a/libs/ngrx-hateoas/src/lib/services/request.service.ts +++ b/libs/ngrx-hateoas/src/lib/services/request.service.ts @@ -14,7 +14,7 @@ export class RequestService { private httpClient = inject(HttpClient); - public async request(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: unknown): Promise { + public async request(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: unknown): Promise { let headers = new HttpHeaders().set('Content-Type', 'application/json'); if(this.customHeadersOptions) { @@ -38,10 +38,8 @@ export class RequestService { // Redirect to sign in const currentUrl = this.window.location.href; this.window.location.href = `${this.loginRedirectOptions.loginUrl}?${this.loginRedirectOptions.redirectUrlParamName}=` + encodeURIComponent(currentUrl); - return undefined; - } else { - throw errorResponse; } + throw errorResponse; } } diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts index 881886c..b62b8e4 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts @@ -7,15 +7,17 @@ import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchab import { RequestService } from "../services/request.service"; import { HateoasService } from "../services/hateoas.service"; +export type ResourceStateProps = { + url: string, + isLoading: boolean, + isAvailable: boolean, + initiallyLoaded: boolean, + resource: TResource +} + export type LinkedHypermediaResourceState = { - [K in ResourceName]: { - url: string, - isLoading: boolean, - isAvailable: boolean, - initiallyLoaded: boolean, - resource: TResource - } + [K in ResourceName]: ResourceStateProps }; export type ConnectLinkedHypermediaResourceMethod = { @@ -52,6 +54,10 @@ type linkedRxInput = { linkName: string } +function getState(store: unknown, stateKey: string): ResourceStateProps { + return (store as Record>>)[stateKey]() +} + export function withLinkedHypermediaResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< { state: object; computed: Record>; methods: Record }, @@ -70,7 +76,7 @@ export function withLinkedHypermediaResource hateoasService.getLink(input.resource, input.linkName)?.href), filter(href => isValidHref(href)), map(href => href!), - filter(href => store[stateKey].url() !== href), - tap(href => patchState(store, { [stateKey]: { ...store[stateKey](), url: href, isLoading: true, isAvailable: true } })), + filter(href => getState(store, stateKey).url !== href), + tap(href => patchState(store, { [stateKey]: { ...getState(store, stateKey), url: href, isLoading: true, isAvailable: true } })), switchMap(href => requestService.request('GET', href)), - tap(resource => patchState(store, { [stateKey]: { ...store[stateKey](), resource, isLoading: false, initiallyLoaded: true } })) + tap(resource => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource, isLoading: false, initiallyLoaded: true } })) ) ); - const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource); + const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource: newVal } }), (store as Record>>)[stateKey].resource); return { [connectMehtodName]: (linkRoot: Signal, linkName: string) => { @@ -102,15 +108,15 @@ export function withLinkedHypermediaResource => { - const currentUrl = store[stateKey].url(); + const currentUrl = getState(store, stateKey).url; if(currentUrl) { - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: true } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: true } }); try { - const resource = await requestService.request('GET', currentUrl); - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource } }); + const resource = await requestService.request('GET', currentUrl); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource } }); } catch(e) { - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource: initialValue } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource: initialValue } }); throw e; } } From ef7e513c70c45714ca190f5cdefda87360a3811a Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:15:10 +0200 Subject: [PATCH 5/8] Fixed last linting issues --- .../store-features/with-hypermedia-action.ts | 46 +++++++++++-------- .../with-hypermedia-resource.ts | 39 +++++++++------- .../with-initial-hypermedia-resource.ts | 5 +- .../with-linked-hypermedia-resource.ts | 10 ++-- .../store-features/with-patchable-resource.ts | 1 + .../src/lib/util/deep-patchable-signal.ts | 1 + 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts index 18bff31..dca3a4e 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts @@ -7,18 +7,20 @@ import { isValidHref } from "../util/is-valid-href"; import { RequestService } from "../services/request.service"; import { HateoasService } from "../services/hateoas.service"; +export type HypermediaActionProps = { + href: string + method: 'PUT' | 'POST' | 'DELETE', + isAvailable: boolean + isExecuting: boolean + hasExecutedSuccessfully: boolean + hasExecutedWithError: boolean + hasError: boolean + error: unknown +} + export type HypermediaActionState = { - [K in ActionName]: { - href: string - method: string - isAvailable: boolean - isExecuting: boolean - hasExecutedSuccessfully: boolean - hasExecutedWithError: boolean - hasError: boolean - error: any - } + [K in ActionName]: HypermediaActionProps }; export type ExecuteHypermediaActionMethod = { @@ -45,6 +47,10 @@ type actionRxInput = { action: string } +function getState(store: unknown, stateKey: string): HypermediaActionProps { + return (store as Record>)[stateKey]() +} + export function withHypermediaAction( actionName: ActionName): SignalStoreFeature< { state: object; computed: Record>; methods: Record }, @@ -70,32 +76,32 @@ export function withHypermediaAction(actionName: Acti hasExecutedSuccessfully: false, hasExecutedWithError: false, hasError: false, - error: null + error: null as unknown } }), - withMethods((store: any, requestService = inject(RequestService)) => { + withMethods((store, requestService = inject(RequestService)) => { const hateoasService = inject(HateoasService); let internalResourceLink: Signal | null = null; const rxConnectToResource = rxMethod( pipe( - tap(() => patchState(store, { [stateKey]: { ...store[stateKey](), href: '', method: '', isAvailable: false } })), + tap(() => patchState(store, { [stateKey]: { ...getState(store, stateKey), href: '', method: '', isAvailable: false } })), map(input => hateoasService.getAction(input.resource, input.action)), filter(action => isValidHref(action?.href) && isValidActionVerb(action?.method)), map(action => action!), - tap(action => patchState(store, { [stateKey]: { ...store[stateKey](), href: action.href, method: action.method, isAvailable: true } })) + tap(action => patchState(store, { [stateKey]: { ...getState(store, stateKey), href: action.href, method: action.method, isAvailable: true } })) ) ); return { [executeMethodName]: async (): Promise => { - if(store[stateKey].isAvailable() && internalResourceLink !== null) { - const method = store[stateKey].method(); - const href = store[stateKey].href(); + if(getState(store, stateKey).isAvailable && internalResourceLink !== null) { + const method = getState(store, stateKey).method; + const href = getState(store, stateKey).href; const body = method !== 'DELETE' ? internalResourceLink() : undefined - patchState(store, { [stateKey]: { ...store[stateKey](), + patchState(store, { [stateKey]: { ...getState(store, stateKey), isExecuting: true, hasExecutedSuccessfully: false, hasExecutedWithError: false, @@ -105,9 +111,9 @@ export function withHypermediaAction(actionName: Acti try { await requestService.request(method, href, body); - patchState(store, { [stateKey]: { ...store[stateKey](), isExecuting: false, hasExecutedSuccessfully: true } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isExecuting: false, hasExecutedSuccessfully: true } }); } catch(e) { - patchState(store, { [stateKey]: { ...store[stateKey](), isExecuting: false, hasExecutedWithError: true, hasError: true, error: e } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isExecuting: false, hasExecutedWithError: true, hasError: true, error: e } }); throw e; } } diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts index 28e8e67..9ded1f0 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts @@ -4,14 +4,15 @@ import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchab import { HateoasService } from "../services/hateoas.service"; import { RequestService } from "../services/request.service"; -export type HypermediaResourceState = -{ - [K in ResourceName]: { - url: string, +export type HypermediaResourceProps = { + url: string, isLoading: boolean, isLoaded: boolean, resource: TResource - } +} + +export type HypermediaResourceState = { + [K in ResourceName]: HypermediaResourceProps }; export type LoadHypermediaResourceFromUrlMethod = { @@ -52,6 +53,10 @@ export type HypermediaResourceMethods = & ReloadHypermediaResourceMethod & GetAsPatchableHypermediaResourceMethod; +function getState(store: unknown, stateKey: string): HypermediaResourceProps { + return (store as Record>>)[stateKey]() +} + export function withHypermediaResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< { state: object; computed: Record>; methods: Record }, @@ -78,26 +83,26 @@ export function withHypermediaResource(r resource: initialValue, } }), - withMethods((store: any) => { + withMethods((store) => { const requestService = inject(RequestService); const hateoasService = inject(HateoasService); - const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource); + const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource: newVal } }), (store as Record>>)[stateKey].resource); const loadFromUrlMethod = async (url: string | null, fromCache = false): Promise => { if(!url) { - patchState(store, { [stateKey]: { ...store[stateKey](), url: '', isLoading: false, isLoaded: false, resource: initialValue } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), url: '', isLoading: false, isLoaded: false, resource: initialValue } }); return Promise.resolve(); } else { - if(!fromCache || hateoasService.getLink(store[stateKey].resource(), 'self')?.href !== url) { - patchState(store, { [stateKey]: { ...store[stateKey](), url: '', isLoading: true } }); + if(!fromCache || hateoasService.getLink(getState(store, stateKey).resource, 'self')?.href !== url) { + patchState(store, { [stateKey]: { ...getState(store, stateKey), url: '', isLoading: true } }); try { const resource = await requestService.request('GET', url); - patchState(store, { [stateKey]: { ...store[stateKey](), url, isLoading: false, isLoaded: true, resource} }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), url, isLoading: false, isLoaded: true, resource} }); } catch(e) { - patchState(store, { [stateKey]: { ...store[stateKey](), url, isLoading: false, resource: initialValue} }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), url, isLoading: false, resource: initialValue} }); throw e; } } @@ -112,16 +117,16 @@ export function withHypermediaResource(r }; const reloadMethod = async (): Promise => { - const selfUrl = hateoasService.getLink(store[stateKey].resource(), 'self')?.href; - const url = selfUrl ?? store[stateKey].url(); + const selfUrl = hateoasService.getLink(getState(store, stateKey).resource, 'self')?.href; + const url = selfUrl ?? getState(store, stateKey).url; if(url) { - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: true, url } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: true, url } }); try { const resource = await requestService.request('GET', url); - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource } }); } catch(e) { - patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource: initialValue } }); + patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource: initialValue } }); throw e; } } diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts index 01d97d7..7008214 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts @@ -18,14 +18,15 @@ export function withInitialHypermediaResource(resourceName, initialValue), withHooks({ - onInit(store: any) { + onInit(store) { let initialUrl; if(typeof url === 'string') { initialUrl = url; } else { initialUrl = url(); } - store[loadFromUrlMethodName](initialUrl); + + (store[loadFromUrlMethodName] as (url: string) => void)(initialUrl); } }) ); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts index b62b8e4..f73388b 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.ts @@ -7,7 +7,7 @@ import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchab import { RequestService } from "../services/request.service"; import { HateoasService } from "../services/hateoas.service"; -export type ResourceStateProps = { +export type LinkedHypermediaResourceProps = { url: string, isLoading: boolean, isAvailable: boolean, @@ -17,7 +17,7 @@ export type ResourceStateProps = { export type LinkedHypermediaResourceState = { - [K in ResourceName]: ResourceStateProps + [K in ResourceName]: LinkedHypermediaResourceProps }; export type ConnectLinkedHypermediaResourceMethod = { @@ -54,8 +54,8 @@ type linkedRxInput = { linkName: string } -function getState(store: unknown, stateKey: string): ResourceStateProps { - return (store as Record>>)[stateKey]() +function getState(store: unknown, stateKey: string): LinkedHypermediaResourceProps { + return (store as Record>>)[stateKey]() } export function withLinkedHypermediaResource( @@ -100,7 +100,7 @@ export function withLinkedHypermediaResource(newVal => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource: newVal } }), (store as Record>>)[stateKey].resource); + const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource: newVal } }), (store as Record>>)[stateKey].resource); return { [connectMehtodName]: (linkRoot: Signal, linkName: string) => { diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts index 8960921..39e1b96 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts @@ -30,6 +30,7 @@ export function withPatchableResource(re const getAsPatchableMethodName = generateGetPatchableResourceMethodName(resourceName); return signalStoreFeature( + // eslint-disable-next-line @typescript-eslint/no-explicit-any withMethods((store: any) => { const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource); diff --git a/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.ts b/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.ts index 019fa93..ab22976 100644 --- a/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.ts +++ b/libs/ngrx-hateoas/src/lib/util/deep-patchable-signal.ts @@ -16,6 +16,7 @@ export type DeepPatchableSignal = Signal & Patchable & export function toDeepPatchableSignal(patchFunc: (newVal: T) => void, signal: Signal): DeepPatchableSignal { return new Proxy(signal, { + // eslint-disable-next-line @typescript-eslint/no-explicit-any get(target: any, prop) { if (prop === 'patch' || prop === 'set') { From f0c2bb1a9cf1c4babb5c727eef08406eb0ca25f8 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:34:54 +0200 Subject: [PATCH 6/8] Adjustments to pipes --- apps/playground/src/app/app.component.ts | 4 ---- .../ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts | 8 ++++---- libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.ts | 10 ++-------- libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts | 6 +++--- libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.ts | 10 ++-------- .../ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts | 8 ++++---- libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.ts | 10 ++-------- 7 files changed, 17 insertions(+), 39 deletions(-) diff --git a/apps/playground/src/app/app.component.ts b/apps/playground/src/app/app.component.ts index 818d645..fff247b 100644 --- a/apps/playground/src/app/app.component.ts +++ b/apps/playground/src/app/app.component.ts @@ -14,10 +14,6 @@ export class AppComponent { appState = inject(AppState); rootApiLoaded = this.appState.rootApi.isLoaded; - constructor() { - - } - logIn() { window.location.href = './login?redirectUri=' + encodeURIComponent(window.origin); } diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts index 6a2f738..b187116 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts @@ -19,12 +19,12 @@ describe('GetActionPipe', () => { it('gets an action from hypermedia json', () => { const transformResult = getActionPipe.transform(testModel, 'create'); - expect(transformResult.href).toBe('/api/test'); - expect(transformResult.method).toBe('POST'); + expect(transformResult?.href).toBe('/api/test'); + expect(transformResult?.method).toBe('POST'); }); - it('throws an exception for a non existing action', () => { - expect(() => getActionPipe.transform(testModel, 'create1')).toThrowError('The requested action does not exist on the specified resource. Use the "hasAction" pipe to check the existance of the link first'); + it('returns undefined for a non existing action', () => { + expect(() => getActionPipe.transform(testModel, 'create1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.ts index 391ff2b..2033c63 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.ts @@ -10,14 +10,8 @@ export class GetActionPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: unknown, actionName: string): ResourceAction { - const action = this.hateoasService.getAction(resource, actionName); - - if (action === undefined) { - throw new Error('The requested action does not exist on the specified resource. Use the "hasAction" pipe to check the existance of the link first'); - } - - return action; + transform(resource: unknown, actionName: string): ResourceAction | undefined { + return this.hateoasService.getAction(resource, actionName); } } diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts index b9128f9..cf870c3 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts @@ -19,11 +19,11 @@ describe('GetLinkPipe', () => { it('gets a link from hypermedia json', () => { const transformResult = getLinkPipe.transform(testModel, 'foo'); - expect(transformResult.href).toBe('/api/foo'); + expect(transformResult?.href).toBe('/api/foo'); }); - it('throws an exception for a non existing link', () => { - expect(() => getLinkPipe.transform(testModel, 'foo1')).toThrowError('The requested link does not exist on the specified resource. Use the "hasLink" pipe to check the existance of the link first'); + it('returns undefined for a non existing link', () => { + expect(() => getLinkPipe.transform(testModel, 'foo1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.ts index 6b13eb7..ca35bae 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.ts @@ -10,14 +10,8 @@ export class GetLinkPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: unknown, linkName: string): ResourceLink { - const link = this.hateoasService.getLink(resource, linkName); - - if (link === undefined) { - throw new Error('The requested link does not exist on the specified resource. Use the "hasLink" pipe to check the existance of the link first'); - } - - return link; + transform(resource: unknown, linkName: string): ResourceLink | undefined { + return this.hateoasService.getLink(resource, linkName); } } diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts index b7d132f..5cc0830 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts @@ -19,12 +19,12 @@ describe('GetSocketPipe', () => { it('gets a socket from hypermedia json', () => { const transformResult = getSocketPipe.transform(testModel, 'foo'); - expect(transformResult.href).toBe('/api/foo'); - expect(transformResult.method).toBe('newData'); + expect(transformResult?.href).toBe('/api/foo'); + expect(transformResult?.method).toBe('newData'); }); - it('throws an exception for a non existing socket', () => { - expect(() => getSocketPipe.transform(testModel, 'foo1')).toThrowError('The requested socket does not exist on the specified resource. Use the "hasSocket" pipe to check the existance of the link first'); + it('returns undefined for a non existing socket', () => { + expect(() => getSocketPipe.transform(testModel, 'foo1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.ts b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.ts index 1b214df..2a7ae24 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.ts @@ -10,14 +10,8 @@ export class GetSocketPipe implements PipeTransform { private hateoasService = inject(HateoasService); - transform(resource: unknown, socketName: string): ResourceSocket { - const socket = this.hateoasService.getSocket(resource, socketName); - - if (socket === undefined) { - throw new Error('The requested socket does not exist on the specified resource. Use the "hasSocket" pipe to check the existance of the link first'); - } - - return socket; + transform(resource: unknown, socketName: string): ResourceSocket | undefined { + return this.hateoasService.getSocket(resource, socketName); } } From 669d6de45908e2c7ea71636fb455e499420c8361 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:38:31 +0200 Subject: [PATCH 7/8] Refactored state naming of HypermediaAction and HypermediaResource --- apps/playground/src/app/app.component.ts | 2 +- apps/playground/src/app/app.state.ts | 2 +- apps/playground/src/app/core/core.state.ts | 2 +- .../flight-create/flight-create.component.ts | 4 +- .../flight-edit/flight-edit.component.html | 32 ++++---- .../flight-search.component.html | 2 +- .../flight-search/flight-search.component.ts | 2 +- .../src/app/flight/flight.routes.ts | 2 +- .../playground/src/app/flight/flight.state.ts | 10 +-- .../flight-summary-card.component.html | 2 +- .../src/lib/pipes/get-action.pipe.spec.ts | 2 +- .../src/lib/pipes/get-link.pipe.spec.ts | 2 +- .../src/lib/pipes/get-socket.pipe.spec.ts | 2 +- .../store-features/with-hypermedia-action.ts | 56 ++++++++----- .../with-hypermedia-resource.spec.ts | 58 ++++++------- .../with-hypermedia-resource.ts | 82 +++++++++++++------ .../with-initial-hypermedia-resource.spec.ts | 16 ++-- .../with-initial-hypermedia-resource.ts | 9 +- .../with-linked-hypermedia-resource.spec.ts | 2 +- .../with-patchable-resource.spec.ts | 14 ++-- .../store-features/with-patchable-resource.ts | 12 ++- 21 files changed, 181 insertions(+), 134 deletions(-) diff --git a/apps/playground/src/app/app.component.ts b/apps/playground/src/app/app.component.ts index fff247b..6f4b5ae 100644 --- a/apps/playground/src/app/app.component.ts +++ b/apps/playground/src/app/app.component.ts @@ -12,7 +12,7 @@ import { AppState } from './app.state'; export class AppComponent { appState = inject(AppState); - rootApiLoaded = this.appState.rootApi.isLoaded; + rootApiLoaded = this.appState.rootApiState.isLoaded; logIn() { window.location.href = './login?redirectUri=' + encodeURIComponent(window.origin); diff --git a/apps/playground/src/app/app.state.ts b/apps/playground/src/app/app.state.ts index 07299c7..6856e90 100644 --- a/apps/playground/src/app/app.state.ts +++ b/apps/playground/src/app/app.state.ts @@ -7,7 +7,7 @@ export const AppState = signalStore( withLinkedHypermediaResource('userInfo', { name: '', preferred_username: '' }), withHooks({ onInit(store) { - store.connectUserInfo(store.rootApi.resource, 'userinfo') + store.connectUserInfo(store.rootApi, 'userinfo') } }) ); diff --git a/apps/playground/src/app/core/core.state.ts b/apps/playground/src/app/core/core.state.ts index 34c723d..2ac4358 100644 --- a/apps/playground/src/app/core/core.state.ts +++ b/apps/playground/src/app/core/core.state.ts @@ -9,7 +9,7 @@ export const CoreState = signalStore( withLinkedHypermediaResource('homeVm', initialHomeVm), withHooks({ onInit(store) { - store.connectHomeVm(inject(AppState).rootApi.resource, 'homeVm') + store.connectHomeVm(inject(AppState).rootApi, 'homeVm') } }) ); diff --git a/apps/playground/src/app/flight/flight-create/flight-create.component.ts b/apps/playground/src/app/flight/flight-create/flight-create.component.ts index 0fdade6..ada73c1 100644 --- a/apps/playground/src/app/flight/flight-create/flight-create.component.ts +++ b/apps/playground/src/app/flight/flight-create/flight-create.component.ts @@ -18,7 +18,7 @@ export class FlightCreateComponent { viewModel = this.flightState.getFlightCreateVmAsPatchable(); - saveEnabled = this.flightState.createFlight.isAvailable; + saveEnabled = this.flightState.createFlightState.isAvailable; aircrafts = this.viewModel.aircrafts; @@ -27,7 +27,7 @@ export class FlightCreateComponent { flightOperator = this.viewModel.template.operator; async onSaveFlight() { - await this.flightState.executeCreateFlight(); + await this.flightState.createFlight(); this.location.back(); } } diff --git a/apps/playground/src/app/flight/flight-edit/flight-edit.component.html b/apps/playground/src/app/flight/flight-edit/flight-edit.component.html index d11e44b..5121291 100644 --- a/apps/playground/src/app/flight/flight-edit/flight-edit.component.html +++ b/apps/playground/src/app/flight/flight-edit/flight-edit.component.html @@ -6,29 +6,29 @@
- + - + - + @if(flightPrice(); as flightPriceValue) { - + } diff --git a/apps/playground/src/app/flight/flight-search/flight-search.component.html b/apps/playground/src/app/flight/flight-search/flight-search.component.html index 0eef8db..c4b789b 100644 --- a/apps/playground/src/app/flight/flight-search/flight-search.component.html +++ b/apps/playground/src/app/flight/flight-search/flight-search.component.html @@ -47,7 +47,7 @@
@if(flight | hasLink:'flightEditVm') { - Edit + Edit }
diff --git a/apps/playground/src/app/flight/flight-search/flight-search.component.ts b/apps/playground/src/app/flight/flight-search/flight-search.component.ts index 34b329c..eb3fbfb 100644 --- a/apps/playground/src/app/flight/flight-search/flight-search.component.ts +++ b/apps/playground/src/app/flight/flight-search/flight-search.component.ts @@ -15,7 +15,7 @@ export class FlightSearchComponent { hateoasService = inject(HateoasService); router = inject(Router); flightState = inject(FlightState); - showCreate = this.flightState.createFlight.isAvailable; + showCreate = this.flightState.createFlightState.isAvailable; viewModel = this.flightState.getFlightSearchVmAsPatchable(); from = this.viewModel['from']; to = this.viewModel['to']; diff --git a/apps/playground/src/app/flight/flight.routes.ts b/apps/playground/src/app/flight/flight.routes.ts index a43a928..3553534 100644 --- a/apps/playground/src/app/flight/flight.routes.ts +++ b/apps/playground/src/app/flight/flight.routes.ts @@ -14,7 +14,7 @@ export const FLIHGT_ROUTES: Routes = [{ }, { path: "search", component: FlightSearchComponent, - canActivate: [() => inject(FlightState).loadFlightSearchVmFromLink(inject(AppState).rootApi.resource(), 'flightSearchVm')] + canActivate: [() => inject(FlightState).loadFlightSearchVmFromLink(inject(AppState).rootApi(), 'flightSearchVm')] }, { path: "search/:url", component: FlightSearchComponent, diff --git a/apps/playground/src/app/flight/flight.state.ts b/apps/playground/src/app/flight/flight.state.ts index b619a35..0509b56 100644 --- a/apps/playground/src/app/flight/flight.state.ts +++ b/apps/playground/src/app/flight/flight.state.ts @@ -14,11 +14,11 @@ export const FlightState = signalStore( withHypermediaAction('createFlight'), withHooks({ onInit(store) { - store.connectUpdateFlightConnection(store.flightEditVm.resource.flight.connection, 'update'); - store.connectUpdateFlightTimes(store.flightEditVm.resource.flight.times, 'update'); - store.connectUpdateFlightOperator(store.flightEditVm.resource.flight.operator, 'update'); - store.connectUpdateFlightPrice(store.flightEditVm.resource.flight.price, 'update'); - store.connectFlightCreateVm(store.flightSearchVm.resource, 'flightCreateVm'); + store.connectUpdateFlightConnection(store.flightEditVm.flight.connection, 'update'); + store.connectUpdateFlightTimes(store.flightEditVm.flight.times, 'update'); + store.connectUpdateFlightOperator(store.flightEditVm.flight.operator, 'update'); + store.connectUpdateFlightPrice(store.flightEditVm.flight.price, 'update'); + store.connectFlightCreateVm(store.flightSearchVm, 'flightCreateVm'); store.connectCreateFlight(store.flightCreateVm.resource.template, 'create'); } }) diff --git a/apps/playground/src/app/flight/shared/flight-summary-card/flight-summary-card.component.html b/apps/playground/src/app/flight/shared/flight-summary-card/flight-summary-card.component.html index 5994965..25b2189 100644 --- a/apps/playground/src/app/flight/shared/flight-summary-card/flight-summary-card.component.html +++ b/apps/playground/src/app/flight/shared/flight-summary-card/flight-summary-card.component.html @@ -10,7 +10,7 @@
{{flight.connection.from}} - {{flight.connection.to}}
diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts index b187116..5987685 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-action.pipe.spec.ts @@ -24,7 +24,7 @@ describe('GetActionPipe', () => { }); it('returns undefined for a non existing action', () => { - expect(() => getActionPipe.transform(testModel, 'create1')).toBeUndefined(); + expect(getActionPipe.transform(testModel, 'create1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts index cf870c3..648f90a 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-link.pipe.spec.ts @@ -23,7 +23,7 @@ describe('GetLinkPipe', () => { }); it('returns undefined for a non existing link', () => { - expect(() => getLinkPipe.transform(testModel, 'foo1')).toBeUndefined(); + expect(getLinkPipe.transform(testModel, 'foo1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts index 5cc0830..2ce7ead 100644 --- a/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts +++ b/libs/ngrx-hateoas/src/lib/pipes/get-socket.pipe.spec.ts @@ -24,7 +24,7 @@ describe('GetSocketPipe', () => { }); it('returns undefined for a non existing socket', () => { - expect(() => getSocketPipe.transform(testModel, 'foo1')).toBeUndefined(); + expect(getSocketPipe.transform(testModel, 'foo1')).toBeUndefined(); }); }); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts index dca3a4e..c9037ee 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.ts @@ -7,9 +7,9 @@ import { isValidHref } from "../util/is-valid-href"; import { RequestService } from "../services/request.service"; import { HateoasService } from "../services/hateoas.service"; -export type HypermediaActionProps = { +export type HypermediaActionStateProps = { href: string - method: 'PUT' | 'POST' | 'DELETE', + method: '' | 'PUT' | 'POST' | 'DELETE', isAvailable: boolean isExecuting: boolean hasExecutedSuccessfully: boolean @@ -18,17 +18,17 @@ export type HypermediaActionProps = { error: unknown } -export type HypermediaActionState = +export type HypermediaActionStoreState = { - [K in ActionName]: HypermediaActionProps + [K in `${ActionName}State`]: HypermediaActionStateProps }; export type ExecuteHypermediaActionMethod = { - [K in ActionName as `execute${Capitalize}`]: () => Promise + [K in ActionName]: () => Promise }; export function generateExecuteHypermediaActionMethodName(actionName: string) { - return `execute${actionName.charAt(0).toUpperCase() + actionName.slice(1)}`; + return actionName; } export type ConnectHypermediaActionMethod = { @@ -47,22 +47,30 @@ type actionRxInput = { action: string } -function getState(store: unknown, stateKey: string): HypermediaActionProps { - return (store as Record>)[stateKey]() +function getState(store: unknown, stateKey: string): HypermediaActionStateProps { + return (store as Record>)[stateKey]() +} + +function updateState(stateKey: string, partialState: Partial) { + return (state: any) => ({ [stateKey]: { ...state[stateKey], ...partialState } }); } export function withHypermediaAction( actionName: ActionName): SignalStoreFeature< - { state: object; computed: Record>; methods: Record }, + { + state: object; + computed: Record>; + methods: Record + }, { - state: HypermediaActionState; + state: HypermediaActionStoreState; computed: Record>; methods: HypermediaActionMethods; } >; export function withHypermediaAction(actionName: ActionName) { - const stateKey = `${actionName}`; + const stateKey = `${actionName}State`; const executeMethodName = generateExecuteHypermediaActionMethodName(actionName); const connectMehtodName = generateConnectHypermediaActionMethodName(actionName); @@ -86,11 +94,11 @@ export function withHypermediaAction(actionName: Acti const rxConnectToResource = rxMethod( pipe( - tap(() => patchState(store, { [stateKey]: { ...getState(store, stateKey), href: '', method: '', isAvailable: false } })), + tap(() => patchState(store, updateState(stateKey, { href: '', method: '', isAvailable: false } ))), map(input => hateoasService.getAction(input.resource, input.action)), filter(action => isValidHref(action?.href) && isValidActionVerb(action?.method)), map(action => action!), - tap(action => patchState(store, { [stateKey]: { ...getState(store, stateKey), href: action.href, method: action.method, isAvailable: true } })) + tap(action => patchState(store, updateState(stateKey, { href: action.href, method: action.method, isAvailable: true } ))) ) ); @@ -99,21 +107,25 @@ export function withHypermediaAction(actionName: Acti if(getState(store, stateKey).isAvailable && internalResourceLink !== null) { const method = getState(store, stateKey).method; const href = getState(store, stateKey).href; + + if(!method || !href) throw new Error('Action is not available'); + const body = method !== 'DELETE' ? internalResourceLink() : undefined - patchState(store, { [stateKey]: { ...getState(store, stateKey), - isExecuting: true, - hasExecutedSuccessfully: false, - hasExecutedWithError: false, - hasError: false, - error: null - } }); + patchState(store, + updateState(stateKey, { + isExecuting: true, + hasExecutedSuccessfully: false, + hasExecutedWithError: false, + hasError: false, + error: null + })); try { await requestService.request(method, href, body); - patchState(store, { [stateKey]: { ...getState(store, stateKey), isExecuting: false, hasExecutedSuccessfully: true } }); + patchState(store, updateState(stateKey, { isExecuting: false, hasExecutedSuccessfully: true } )); } catch(e) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), isExecuting: false, hasExecutedWithError: true, hasError: true, error: e } }); + patchState(store, updateState(stateKey, { isExecuting: false, hasExecutedWithError: true, hasError: true, error: e } )); throw e; } } diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.spec.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.spec.ts index 3b5d94c..706024e 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.spec.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.spec.ts @@ -56,10 +56,10 @@ describe('withHypermediaResource', () => { }); it('sets correct initial resource state', () => { - expect(store.testModel.url()).toBe(''); - expect(store.testModel.isLoaded()).toBeFalse(); - expect(store.testModel.isLoading()).toBeFalse(); - expect(store.testModel.resource()).toBe(initialTestModel); + expect(store.testModelState.url()).toBe(''); + expect(store.testModelState.isLoaded()).toBeFalse(); + expect(store.testModelState.isLoading()).toBeFalse(); + expect(store.testModel()).toBe(initialTestModel); }); it('has correct resource methods', () => { @@ -79,18 +79,18 @@ describe('withHypermediaResource', () => { const loadPromise = store.loadTestModelFromUrl('api/test-model'); - expect(store.testModel.isLoading()).toBeTrue(); - expect(store.testModel.isLoaded()).toBeFalse(); + expect(store.testModelState.isLoading()).toBeTrue(); + expect(store.testModelState.isLoaded()).toBeFalse(); httpTestingController.expectOne('api/test-model').flush(resourceFromUrl); httpTestingController.verify(); await loadPromise; - expect(store.testModel.url()).toBe('api/test-model'); - expect(store.testModel.isLoading()).toBeFalse(); - expect(store.testModel.isLoaded()).toBeTrue(); - expect(store.testModel.resource.objProp.stringProp()).toBe('from Url'); + expect(store.testModelState.url()).toBe('api/test-model'); + expect(store.testModelState.isLoading()).toBeFalse(); + expect(store.testModelState.isLoaded()).toBeTrue(); + expect(store.testModel.objProp.stringProp()).toBe('from Url'); }); it('loads the resource from link and sets state correctly', async () => { @@ -101,20 +101,20 @@ describe('withHypermediaResource', () => { } }; - const loadPromise = store.loadTestModelFromLink(store.rootModel.resource(), 'testModel'); + const loadPromise = store.loadTestModelFromLink(store.rootModel(), 'testModel'); - expect(store.testModel.isLoading()).toBeTrue(); - expect(store.testModel.isLoaded()).toBeFalse(); + expect(store.testModelState.isLoading()).toBeTrue(); + expect(store.testModelState.isLoaded()).toBeFalse(); httpTestingController.expectOne('api/test-model?origin=fromLink').flush(resourceFromLink); httpTestingController.verify(); await loadPromise; - expect(store.testModel.url()).toBe('api/test-model?origin=fromLink'); - expect(store.testModel.isLoading()).toBeFalse(); - expect(store.testModel.isLoaded()).toBeTrue(); - expect(store.testModel.resource.objProp.stringProp()).toBe('from Link'); + expect(store.testModelState.url()).toBe('api/test-model?origin=fromLink'); + expect(store.testModelState.isLoading()).toBeFalse(); + expect(store.testModelState.isLoaded()).toBeTrue(); + expect(store.testModel.objProp.stringProp()).toBe('from Link'); }); it('reloads the resource and sets state correctly', async () => { @@ -138,18 +138,18 @@ describe('withHypermediaResource', () => { loadPromise = store.reloadTestModel(); - expect(store.testModel.isLoading()).toBeTrue(); - expect(store.testModel.isLoaded()).toBeTrue(); + expect(store.testModelState.isLoading()).toBeTrue(); + expect(store.testModelState.isLoaded()).toBeTrue(); httpTestingController.expectOne('api/test-model').flush(resourceFromUrlReloaded); httpTestingController.verify(); await loadPromise; - expect(store.testModel.url()).toBe('api/test-model'); - expect(store.testModel.isLoading()).toBeFalse(); - expect(store.testModel.isLoaded()).toBeTrue(); - expect(store.testModel.resource.objProp.stringProp()).toBe('from Url Reloaded'); + expect(store.testModelState.url()).toBe('api/test-model'); + expect(store.testModelState.isLoading()).toBeFalse(); + expect(store.testModelState.isLoaded()).toBeTrue(); + expect(store.testModel.objProp.stringProp()).toBe('from Url Reloaded'); }); it('gets the resource as patchable', () => { @@ -157,13 +157,13 @@ describe('withHypermediaResource', () => { resource.patch({ numProp: 5, objProp: { stringProp: 'patched1' } }); - expect(store.testModel.resource.numProp()).toBe(5); - expect(store.testModel.resource.objProp.stringProp()).toBe('patched1'); + expect(store.testModel.numProp()).toBe(5); + expect(store.testModel.objProp.stringProp()).toBe('patched1'); resource.objProp.stringProp.patch('patched2'); - expect(store.testModel.resource.numProp()).toBe(5); - expect(store.testModel.resource.objProp.stringProp()).toBe('patched2'); + expect(store.testModel.numProp()).toBe(5); + expect(store.testModel.objProp.stringProp()).toBe('patched2'); }); it('uses self url on reload', async () => { @@ -184,8 +184,8 @@ describe('withHypermediaResource', () => { loadPromise = store.reloadTestModel(); - expect(store.testModel.isLoading()).toBeTrue(); - expect(store.testModel.isLoaded()).toBeTrue(); + expect(store.testModelState.isLoading()).toBeTrue(); + expect(store.testModelState.isLoaded()).toBeTrue(); httpTestingController.expectOne('/self-url'); httpTestingController.verify(); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts index 9ded1f0..5fd05c8 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-resource.ts @@ -4,17 +4,24 @@ import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchab import { HateoasService } from "../services/hateoas.service"; import { RequestService } from "../services/request.service"; -export type HypermediaResourceProps = { +export type HypermediaResourceStateProps = { url: string, - isLoading: boolean, - isLoaded: boolean, - resource: TResource + isLoading: boolean, + isLoaded: boolean } -export type HypermediaResourceState = { - [K in ResourceName]: HypermediaResourceProps +export type HypermediaResourceData = { + [K in `${ResourceName}`]: TResource }; +export type HypermediaResourceState = { + [K in `${ResourceName}State`]: HypermediaResourceStateProps +}; + +export type HypermediaResourceStoreState = + HypermediaResourceData + & HypermediaResourceState; + export type LoadHypermediaResourceFromUrlMethod = { [K in ResourceName as `load${Capitalize}FromUrl`]: (url: string | null, fromCache?: boolean) => Promise }; @@ -47,28 +54,45 @@ export function generateGetAsPatchableHypermediaResourceMethodName(resourceName: return `get${resourceName.charAt(0).toUpperCase() + resourceName.slice(1)}AsPatchable`; } -export type HypermediaResourceMethods = +export type HypermediaResourceStoreMethods = LoadHypermediaResourceFromUrlMethod & LoadHypermediaResourceFromLinkMethod & ReloadHypermediaResourceMethod & GetAsPatchableHypermediaResourceMethod; -function getState(store: unknown, stateKey: string): HypermediaResourceProps { - return (store as Record>>)[stateKey]() +function getData(store: unknown, dataKey: string): TResource { + return (store as Record>)[dataKey]() +} + +function getState(store: unknown, stateKey: string): HypermediaResourceStateProps { + return (store as Record>)[stateKey]() +} + +function updateData(dataKey: string, data: TResource) { + return () => ({ [dataKey]: data}); +} + +function updateState(stateKey: string, partialState: Partial) { + return (state: any) => ({ [stateKey]: { ...state[stateKey], ...partialState } }); } export function withHypermediaResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< - { state: object; computed: Record>; methods: Record }, + { + state: object; + computed: Record>; + methods: Record + }, { - state: HypermediaResourceState; + state: HypermediaResourceStoreState; computed: Record>; - methods: HypermediaResourceMethods; + methods: HypermediaResourceStoreMethods; } >; export function withHypermediaResource(resourceName: ResourceName, initialValue: TResource) { - const stateKey = `${resourceName}`; + const dataKey = `${resourceName}`; + const stateKey = `${resourceName}State`; const loadFromUrlMethodName = generateLoadHypermediaResourceFromUrlMethodName(resourceName); const loadFromLinkMethodName = generateLoadHypermediaResourceFromLinkMethodName(resourceName); const reloadMethodName = generateReloadHypermediaResourceMethodName(resourceName); @@ -79,30 +103,36 @@ export function withHypermediaResource(r [stateKey]: { url: '', isLoading: false, - isLoaded: false, - resource: initialValue, - } + isLoaded: false + }, + [dataKey]: initialValue }), withMethods((store) => { const requestService = inject(RequestService); const hateoasService = inject(HateoasService); - const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...getState(store, stateKey), resource: newVal } }), (store as Record>>)[stateKey].resource); + const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [dataKey]: newVal }), (store as Record>)[dataKey]); const loadFromUrlMethod = async (url: string | null, fromCache = false): Promise => { if(!url) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), url: '', isLoading: false, isLoaded: false, resource: initialValue } }); + patchState(store, + updateData(dataKey, initialValue), + updateState(stateKey, { url: '', isLoading: false, isLoaded: false })); return Promise.resolve(); } else { - if(!fromCache || hateoasService.getLink(getState(store, stateKey).resource, 'self')?.href !== url) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), url: '', isLoading: true } }); + if(!fromCache || hateoasService.getLink(getData(store, dataKey), 'self')?.href !== url) { + patchState(store, updateState(stateKey, { url: '', isLoading: true })); try { const resource = await requestService.request('GET', url); - patchState(store, { [stateKey]: { ...getState(store, stateKey), url, isLoading: false, isLoaded: true, resource} }); + patchState(store, + updateData(dataKey, resource), + updateState(stateKey, { url, isLoading: false, isLoaded: true })); } catch(e) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), url, isLoading: false, resource: initialValue} }); + patchState(store, + updateData(dataKey, initialValue), + updateState(stateKey, { url, isLoading: false })); throw e; } } @@ -117,16 +147,16 @@ export function withHypermediaResource(r }; const reloadMethod = async (): Promise => { - const selfUrl = hateoasService.getLink(getState(store, stateKey).resource, 'self')?.href; + const selfUrl = hateoasService.getLink(getData(store, dataKey), 'self')?.href; const url = selfUrl ?? getState(store, stateKey).url; if(url) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: true, url } }); + patchState(store, updateState(stateKey, { url, isLoading: true })); try { const resource = await requestService.request('GET', url); - patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource } }); + patchState(store, updateData(dataKey, resource), updateState(stateKey, { isLoading: false })); } catch(e) { - patchState(store, { [stateKey]: { ...getState(store, stateKey), isLoading: false, resource: initialValue } }); + patchState(store, updateData(dataKey, initialValue), updateState(stateKey, { isLoading: false })); throw e; } } diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.spec.ts b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.spec.ts index 81bd4e3..ccedcd6 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.spec.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.spec.ts @@ -42,24 +42,24 @@ describe('withInitialHypermediaResource', () => { it('requrests model from fixed url automaticallay and provides correct states', fakeAsync(() => { const store = TestBed.inject(TestStoreWithFixedUrl); - expect(store.rootModel.isLoaded()).toBeFalse(); - expect(store.rootModel.isLoading()).toBeTrue(); + expect(store.rootModelState.isLoaded()).toBeFalse(); + expect(store.rootModelState.isLoading()).toBeTrue(); httpTestingController.expectOne('/api').flush(initialRootModel); httpTestingController.verify(); tick(); - expect(store.rootModel.isLoaded()).toBeTrue(); - expect(store.rootModel.isLoading()).toBeFalse(); + expect(store.rootModelState.isLoaded()).toBeTrue(); + expect(store.rootModelState.isLoading()).toBeFalse(); })); it('requests model from injected url automaticallay and provides correct states', fakeAsync(() => { const store = TestBed.inject(TestStoreWithInjectedUrl); - expect(store.rootModel.isLoaded()).toBeFalse(); - expect(store.rootModel.isLoading()).toBeTrue(); + expect(store.rootModelState.isLoaded()).toBeFalse(); + expect(store.rootModelState.isLoading()).toBeTrue(); httpTestingController.expectOne('/api/injected').flush(initialRootModel); httpTestingController.verify(); tick(); - expect(store.rootModel.isLoaded()).toBeTrue(); - expect(store.rootModel.isLoading()).toBeFalse(); + expect(store.rootModelState.isLoaded()).toBeTrue(); + expect(store.rootModelState.isLoading()).toBeFalse(); })); }); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts index 7008214..391fa5b 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-initial-hypermedia-resource.ts @@ -1,14 +1,15 @@ import { SignalStoreFeature, signalStoreFeature, withHooks } from "@ngrx/signals"; -import { withHypermediaResource, HypermediaResourceState, HypermediaResourceMethods, generateLoadHypermediaResourceFromUrlMethodName } from "./with-hypermedia-resource"; +import { withHypermediaResource, HypermediaResourceStoreState, HypermediaResourceStoreMethods, generateLoadHypermediaResourceFromUrlMethodName } from "./with-hypermedia-resource"; import { Signal } from "@angular/core"; export function withInitialHypermediaResource( resourceName: ResourceName, initialValue: TResource, url: string | (() => string)): SignalStoreFeature< - { state: object; computed: Record>; methods: Record }, + { + state: object; computed: Record>; methods: Record }, { - state: HypermediaResourceState; + state: HypermediaResourceStoreState; computed: Record>; - methods: HypermediaResourceMethods; + methods: HypermediaResourceStoreMethods; } >; export function withInitialHypermediaResource(resourceName: ResourceName, initialValue: TResource, url: string | (() => string)) { diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.spec.ts b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.spec.ts index f4799d5..b431f23 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.spec.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-linked-hypermedia-resource.spec.ts @@ -33,7 +33,7 @@ const TestStore = signalStore( withLinkedHypermediaResource('testModel', initialTestModel), withHooks({ onInit(store) { - store.connectTestModel(store.rootModel.resource, 'testModel'); + store.connectTestModel(store.rootModel, 'testModel'); }, }) ); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts index 7331904..19d86ef 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; import { signalStore, withState } from '@ngrx/signals'; -import { HypermediaResourceState } from './with-hypermedia-resource'; +import { HypermediaResourceData } from './with-hypermedia-resource'; import { withPatchableResource } from './with-patchable-resource'; @@ -23,7 +23,7 @@ const initialTestModel: TestModel = { const TestStore = signalStore( { providedIn: 'root' }, - withState>({ model: { url: '', isLoading: false, isLoaded: false, resource: initialTestModel }}), + withState>({ model: initialTestModel }), withPatchableResource('model', initialTestModel) ); @@ -37,19 +37,19 @@ describe('withPatchableResource', () => { }); it('sets correct initial resource state', () => { - expect(store.model.resource()).toBe(initialTestModel); + expect(store.model()).toBe(initialTestModel); }); it('patches a signal inside the signal store', () => { const patchableSignal = store.getModelAsPatchable(); - expect(store.model.resource.objProp.stringProp()).toBe('initial string'); - expect(store.model.resource()).toBe(initialTestModel); + expect(store.model.objProp.stringProp()).toBe('initial string'); + expect(store.model()).toBe(initialTestModel); patchableSignal.objProp.patch({ stringProp: 'patched' }); - expect(store.model.resource.objProp.stringProp()).toBe('patched'); - expect(store.model.resource()).not.toBe(initialTestModel); + expect(store.model.objProp.stringProp()).toBe('patched'); + expect(store.model()).not.toBe(initialTestModel); }); }); diff --git a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts index 39e1b96..e0bd103 100644 --- a/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts +++ b/libs/ngrx-hateoas/src/lib/store-features/with-patchable-resource.ts @@ -1,5 +1,5 @@ import { SignalStoreFeature, patchState, signalStoreFeature, withMethods } from "@ngrx/signals"; -import { HypermediaResourceState } from "./with-hypermedia-resource"; +import { HypermediaResourceData } from "./with-hypermedia-resource"; import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchable-signal"; import { Signal } from "@angular/core"; @@ -16,7 +16,11 @@ export type PatchableResourceMethods = export function withPatchableResource( resourceName: ResourceName, initialValue: TResource): SignalStoreFeature< - { state: HypermediaResourceState; computed: Record>; methods: Record }, + { + state: HypermediaResourceData; + computed: Record>; + methods: Record + }, { state: object, computed: Record>, @@ -26,14 +30,14 @@ export function withPatchableResource( // eslint-disable-next-line @typescript-eslint/no-unused-vars export function withPatchableResource(resourceName: ResourceName, initialValue: TResource) { - const stateKey = `${resourceName}`; + const dataKey = `${resourceName}`; const getAsPatchableMethodName = generateGetPatchableResourceMethodName(resourceName); return signalStoreFeature( // eslint-disable-next-line @typescript-eslint/no-explicit-any withMethods((store: any) => { - const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource); + const patchableSignal = toDeepPatchableSignal(newVal => patchState(store, { [dataKey]: newVal }), store[dataKey]); return { [getAsPatchableMethodName]: (): DeepPatchableSignal => { From f3e2d4879e506d531713aad28f097c99564c38c7 Mon Sep 17 00:00:00 2001 From: Daniel Murrmann <9040811+fancyDevelopment@users.noreply.github.com> Date: Sat, 28 Sep 2024 22:14:41 +0200 Subject: [PATCH 8/8] Final Refactoring of naming changes --- apps/playground/src/app/app.component.html | 4 +- apps/playground/src/app/core/core.routes.ts | 2 +- .../src/app/core/home/home.component.ts | 2 +- .../src/app/flight/flight.routes.ts | 2 +- .../playground/src/app/flight/flight.state.ts | 2 +- libs/ngrx-hateoas/package.json | 2 +- .../with-hypermedia-action.spec.ts | 119 ++++++++++++++++++ .../store-features/with-hypermedia-action.ts | 1 + .../with-hypermedia-resource.ts | 1 + .../with-initial-hypermedia-resource.ts | 5 +- .../with-linked-hypermedia-resource.spec.ts | 42 +++---- .../with-linked-hypermedia-resource.ts | 69 ++++++---- 12 files changed, 201 insertions(+), 50 deletions(-) create mode 100644 libs/ngrx-hateoas/src/lib/store-features/with-hypermedia-action.spec.ts diff --git a/apps/playground/src/app/app.component.html b/apps/playground/src/app/app.component.html index f0b2043..8c7780e 100644 --- a/apps/playground/src/app/app.component.html +++ b/apps/playground/src/app/app.component.html @@ -10,14 +10,14 @@