diff --git a/.eslintrc.json b/.eslintrc.json index e7b69112..11926505 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,6 +11,7 @@ }, "plugins": ["tsdoc"], "rules": { + "@typescript-eslint/no-restricted-types": "off", "@typescript-eslint/no-invalid-void-type": "off", "@typescript-eslint/no-useless-template-literals": "off", "import/no-unassigned-import": "off", diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..7548481c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,42 @@ +name: Publish Docs + +on: + push: + branches: ['main'] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Use NodeJS LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install + run: npm ci + - name: Build Docs + run: npm run docs + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './.docs' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 91a919b2..351e8d02 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage .idea .vscode +.docs diff --git a/package-lock.json b/package-lock.json index 79d98d9c..ee90111b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@esm-bundle/chai": "^4.3.4-fix.0", + "@mxssfd/typedoc-theme": "^1.1.7", "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.5.2", "@types/chai-as-promised": "^8.0.1", @@ -56,6 +57,8 @@ "sinon-chai": "^4.0.0", "stylelint": "^16.9.0", "tsx": "^4.19.1", + "typedoc": "^0.26.8", + "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.6.2", "typescript-eslint": "^8.8.1", "vite": "5.4.8", @@ -1478,6 +1481,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@mxssfd/typedoc-theme": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mxssfd/typedoc-theme/-/typedoc-theme-1.1.7.tgz", + "integrity": "sha512-dj4p0TjIoudD8j1u+Kf+KzcEc+je3IB/B1oohWcVJmMRfbw5uZYX6qOGHzfUzAPiU1pYe2u5Vo1IPqgLo58taA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "typedoc": "^0.26.7" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -1927,6 +1943,62 @@ "dev": true, "license": "MIT" }, + "node_modules/@shikijs/core": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.0.tgz", + "integrity": "sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz", + "integrity": "sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz", + "integrity": "sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.0.tgz", + "integrity": "sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -2086,6 +2158,16 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2124,6 +2206,16 @@ "@types/karma": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -2176,6 +2268,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", @@ -3028,6 +3127,17 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", @@ -3089,6 +3199,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -3216,6 +3348,17 @@ "node": ">=0.1.90" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3528,6 +3671,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -5340,6 +5497,44 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5377,6 +5572,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/htmlparser2": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", @@ -6673,6 +6879,16 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6823,6 +7039,13 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -6872,6 +7095,24 @@ "node": ">=10" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -6883,6 +7124,28 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -6890,6 +7153,13 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -6932,6 +7202,100 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7685,6 +8049,19 @@ "wrappy": "1" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8128,6 +8505,17 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -8135,6 +8523,16 @@ "dev": true, "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -8273,6 +8671,13 @@ "dev": true, "license": "MIT" }, + "node_modules/regex": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", + "integrity": "sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==", + "dev": true, + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -8643,6 +9048,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.0.tgz", + "integrity": "sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.22.0", + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -8882,6 +9302,17 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -9141,6 +9572,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -9665,6 +10111,17 @@ "node": ">=0.6" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -10305,6 +10762,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedoc": { + "version": "0.26.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.8.tgz", + "integrity": "sha512-QBF0BMbnNeUc6U7pRHY7Jb8pjhmiNWZNQT8LU6uk9qP9t3goP9bJptdlNqMC0wBB2w9sQrxjZt835bpRSSq1LA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.16.2", + "yaml": "^2.5.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-3.0.0.tgz", + "integrity": "sha512-R7D8fYrK34mBFZSlF1EqJxfqiUSlQSmyrCiQgTQD52nNm6+kUtqwiaqaNkuJ2rA2wBgWFecUA8JzHT7x2r7ePg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.26.x" + } + }, + "node_modules/typedoc/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", @@ -10343,6 +10859,13 @@ } } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -10380,6 +10903,79 @@ "dev": true, "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -10489,6 +11085,36 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", @@ -11240,6 +11866,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11342,6 +11981,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index c3c1c7f8..429aec39 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "build": "npm-run-all --parallel build:*", "build:esbuild": "tsx scripts/build.ts", "build:copy-dts": "tsx scripts/copy-dts.ts", - "build:tsc": "tsc --emitDeclarationOnly --isolatedModules -p tsconfig.build.json", + "build:tsc": "tsc --emitDeclarationOnly -p tsconfig.build.json", "start": "vite", "test": "karma start karma.config.cjs", "test:watch": "npm run test -- --watch", "test:coverage": "npm run test -- --coverage", - "docs": "npm run build && echo \"not implemented\" >&2 && exit 1", + "docs": "typedoc", "browserslist": "browserslist && browserslist --coverage", "prepack": "npm run clean:build", "prepare": "npm run build", @@ -58,6 +58,7 @@ }, "devDependencies": { "@esm-bundle/chai": "^4.3.4-fix.0", + "@mxssfd/typedoc-theme": "^1.1.7", "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.5.2", "@types/chai-as-promised": "^8.0.1", @@ -99,6 +100,8 @@ "sinon-chai": "^4.0.0", "stylelint": "^16.9.0", "tsx": "^4.19.1", + "typedoc": "^0.26.8", + "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.6.2", "typescript-eslint": "^8.8.1", "vite": "5.4.8", diff --git a/src/resolver/generateUrls.ts b/src/resolver/generateUrls.ts index 93efbc5b..31fe7578 100644 --- a/src/resolver/generateUrls.ts +++ b/src/resolver/generateUrls.ts @@ -10,12 +10,12 @@ import { parse, type ParseOptions, type Token, tokensToFunction, type TokensToFunctionOptions } from 'path-to-regexp'; import type { EmptyObject, Writable } from 'type-fest'; import Resolver from './resolver.js'; -import type { AnyObject, ChildrenCallback, IndexedParams, Params, Route } from './types.js'; +import type { ChildrenCallback, IndexedParams, Params, Route } from './types.js'; import { getRoutePath, isString } from './utils.js'; export type UrlParams = Readonly | number | string>>; -function cacheRoutes( +function cacheRoutes( routesByName: Map>>, route: Route, routes?: ReadonlyArray> | ChildrenCallback, @@ -38,7 +38,7 @@ function cacheRoutes( } } -function getRouteByName( +function getRouteByName( routesByName: Map>>, routeName: string, ): Route | undefined { @@ -57,7 +57,7 @@ function getRouteByName( export type StringifyQueryParams = (params: UrlParams) => string; -export type GenerateUrlOptions = ParseOptions & +export type GenerateUrlOptions = ParseOptions & Readonly<{ /** * Add a query string to generated url based on unknown route params. @@ -78,7 +78,7 @@ type RouteCacheRecord = Readonly<{ export type UrlGenerator = (routeName: string, params?: Params) => string; -function generateUrls( +function generateUrls( resolver: Resolver, options: GenerateUrlOptions = {}, ): UrlGenerator { diff --git a/src/resolver/matchRoute.ts b/src/resolver/matchRoute.ts index b1b5d386..5c0092d8 100644 --- a/src/resolver/matchRoute.ts +++ b/src/resolver/matchRoute.ts @@ -9,15 +9,15 @@ import type { Key } from 'path-to-regexp'; import matchPath, { type Match } from './matchPath.js'; -import type { AnyObject, IndexedParams, Route } from './types.js'; +import type { IndexedParams, Route } from './types.js'; import { getRoutePath, unwrapChildren } from './utils.js'; -export type MatchWithRoute = Match & +export type MatchWithRoute = Match & Readonly<{ route: Route; }>; -type RouteMatchIterator = Iterator< +type RouteMatchIterator = Iterator< MatchWithRoute, undefined, Route | undefined @@ -69,7 +69,7 @@ type RouteMatchIterator = Iterator< * Prefix matching can be enabled also by `children: true`. */ // eslint-disable-next-line @typescript-eslint/max-params -function matchRoute( +function matchRoute( route: Route, pathname: string, ignoreLeadingSlash?: boolean, diff --git a/src/resolver/resolveRoute.ts b/src/resolver/resolveRoute.ts index 72101aa9..9e8b43a2 100644 --- a/src/resolver/resolveRoute.ts +++ b/src/resolver/resolveRoute.ts @@ -6,11 +6,12 @@ * This source code is licensed under the MIT license found in the * LICENSE.txt file in the root directory of this source tree. */ -import type { ActionResult, AnyObject, MaybePromise, RouteContext } from './types.js'; +import type { ActionResult, MaybePromise, RouteContext } from './types.js'; import { isFunction } from './utils.js'; +/** @internal */ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -export default function resolveRoute( +export default function resolveRoute( context: RouteContext, ): MaybePromise>> { if (isFunction(context.route.action)) { diff --git a/src/resolver/resolver.ts b/src/resolver/resolver.ts index 10495b94..a9b73a15 100644 --- a/src/resolver/resolver.ts +++ b/src/resolver/resolver.ts @@ -9,19 +9,10 @@ import type { EmptyObject } from 'type-fest'; import matchRoute, { type MatchWithRoute } from './matchRoute.js'; import defaultResolveRoute from './resolveRoute.js'; -import type { - ActionResult, - AnyObject, - BasicRoutePart, - Match, - MaybePromise, - ResolveContext, - Route, - RouteContext, -} from './types.js'; +import type { ActionResult, Route, Match, MaybePromise, ResolveContext, RouteContext } from './types.js'; import { getNotFoundError, getRoutePath, isString, NotFoundError, notFoundResult, toArray } from './utils.js'; -function isDescendantRoute( +function isDescendantRoute( route?: Route, maybeParent?: Route, ) { @@ -35,7 +26,7 @@ function isDescendantRoute( return false; } -function isRouteContext(value: unknown): value is RouteContext { +function isRouteContext(value: unknown): value is RouteContext { return ( !!value && typeof value === 'object' && @@ -50,8 +41,18 @@ export interface ResolutionErrorOptions extends ErrorOptions { code?: number; } -export class ResolutionError extends Error { +/** + * An error that is thrown when a route resolution fails. + */ +export class ResolutionError extends Error { + /** + * A HTTP status code associated with the error. + */ readonly code?: number; + + /** + * The context object associated with the route that was not found. + */ readonly context: RouteContext; constructor(context: RouteContext, options?: ResolutionErrorOptions) { @@ -65,12 +66,15 @@ export class ResolutionError( +function updateChainForRoute( context: RouteContext, match: Match, ) { @@ -91,20 +95,32 @@ function updateChainForRoute( } } +/** + * A callback function that handles errors during route resolution. + */ export type ErrorHandlerCallback = (error: unknown) => T; -export type ResolveRouteCallback = ( +/** + * A callback function that resolves a route. It is used as a fallback in case + * the route is not correctly resolved. + */ +export type ResolveRouteCallback = ( context: RouteContext, ) => MaybePromise>>; -export type ResolverOptions = Readonly<{ +/** + * Options for the constructor of the `Resolver` class. + * + * @interface + */ +export type ResolverOptions = Readonly<{ baseUrl?: string; context?: RouteContext; errorHandler?: ErrorHandlerCallback; resolveRoute?: ResolveRouteCallback; }>; -export default class Resolver { +class Resolver { /** * The base URL for all routes in the router instance. By default, * if the base element exists in the ``, vaadin-router @@ -115,7 +131,7 @@ export default class Resolver; readonly errorHandler?: ErrorHandlerCallback; readonly resolveRoute: ResolveRouteCallback; - readonly #root: BasicRoutePart; + readonly #root: Route; constructor(routes: ReadonlyArray> | Route, options?: ResolverOptions); constructor( @@ -139,7 +155,7 @@ export default class Resolver undefined, path: '', - }; + } as Route; } else { this.#root = { ...routes, parent: undefined }; } @@ -154,16 +170,22 @@ export default class Resolver, + route: this.#root, search: '', chain: [], }; } + /** + * The root route. + */ get root(): Route { - return this.#root as Route; + return this.#root; } + /** + * The current route context. + */ get context(): RouteContext { return this.#context; } @@ -224,7 +246,7 @@ export default class Resolver, + this.#root, this.__normalizePathname(context.pathname) ?? context.pathname, !!this.baseUrl, ); @@ -272,7 +294,7 @@ export default class Resolver); + return await next(true, this.#root); } catch (error: unknown) { const _error = error instanceof NotFoundError @@ -293,8 +315,9 @@ export default class Resolver> | Route): void { + setRoutes(routes: ReadonlyArray> | Route): object { this.#root.__children = [...toArray(routes)]; + return {}; } /** @@ -334,3 +357,5 @@ export default class Resolver>; +/** + * Represents either a value or a promise of a value. + * + * @typeParam T - The type of the value. + */ export type MaybePromise = Promise | T; -// eslint-disable-next-line @typescript-eslint/no-invalid-void-type +/** + * A result of a {@link Route.action}. + * + * @typeParam T - The type of the result. + */ export type ActionResult = T | NotFoundResult | null | undefined | void; /* ======================== * Resolver-Specific Types * ======================== */ -export type ChildrenCallback = ( + +/** + * A function that dynamically creates children of a route. + * + * @typeParam T - The type of the result produced by the route. + * @typeParam R - The type of additional route-specific data. Defaults to an + * empty object. + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + * + * @param context - The context of the current route. + * + * @deprecated The route children callback is deprecated and will be removed in + * the next major version. + * + * @interface + */ +export type ChildrenCallback = ( context: RouteChildrenContext, ) => MaybePromise | ReadonlyArray> | void>; -export type BasicRoutePart = Readonly<{ +/** + * Defines a single route. + * + * A route represents a single or multiple sections in the URL. It defines the + * behavior of a page in response to URL updates. A route can act as a content + * producer or as middleware for child routes. + * + * @typeParam T - The type of the result produced by the route. + * @typeParam R - The type of additional route-specific data. Defaults to an + * empty object. + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + * + * @interface + */ +export type Route = Readonly<{ + /** + * The name of the route. + */ name?: string; + /** + * The path pattern that the route matches. + */ path: string; + /** + * An action that is executed when the route is resolved. + * + * Actions are executed recursively from the root route to the child route and + * can either produce content or perform actions before or after the child's + * action. + * + * @param context - The context of the current route. + * + * @returns The result of the route resolution. It could be either a value + * produced by the action or a new context to continue the resolution process. + * + * @internal + */ action?( this: Route, context: RouteContext, commands: never, ): MaybePromise>>; }> & { + /** @internal */ __children?: ReadonlyArray>; + /** @internal */ __synthetic?: true; children?: ReadonlyArray> | ChildrenCallback; parent?: Route; fullPath?: string; -}; - -export type Route = BasicRoutePart< - T, - R, - C -> & - R; +} & R; -export type Match = Readonly<{ +/** + * A matched route with its associated path. + * + * @typeParam T - The type of the result produced by the route. + * @typeParam R - The type of additional route-specific data. Defaults to an + * empty object. + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + * + * @internal + */ +export type Match = Readonly<{ + /** The path of the matched route. */ path: string; + /** The route object associated with the matched path. */ route?: Route; }>; -export type ChainItem = { +/** + * An item of the resolved route sequence. + * + * @typeParam T - The type of the result produced by the route. + * @typeParam R - The type of additional route-specific data. Defaults to an + * empty object. + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + * + * @interface + */ +export type ChainItem = { + /** A DOM element associated with the route. */ element?: Element; + /** The path of the route. */ path: string; + /** The route object containing route-specific information. */ route: Route; }; -export type ResolveContext = Readonly<{ +/** + * The context for a `resolve` operation that can be extended with + * the user-defined properties. + * + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + * + * @interface + */ +export type ResolveContext = Readonly<{ + /** The current location. */ pathname: string; }> & C; -export type RouteContext = Readonly<{ +/** + * The context for a {@link Route.action} that could be used to access the + * route-specific data during the resolution process. + * + * @typeParam T - The type of the result produced by the route. + * @typeParam R - The type of additional route-specific data. Defaults to + * `EmptyObject`. + * @typeParam C - The type of additional context-specific data. Defaults to + * `EmptyObject`. + * + * @interface + */ +export type RouteContext = Readonly<{ + /** + * The {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/hash | hash} + * fragment of the URL. + */ hash?: string; + /** + * The {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/search | search} + * query string of the URL. + */ search?: string; + /** + * The sequence of the resolved route items, so said the path from the root + * route to the current route. + */ chain?: Array>; + /** + * The parameters resolved from the current URL. + */ params: IndexedParams; + /** + * The resolver instance. + */ resolver?: Resolver; + /** + * The URL from which a redirect occurred. + */ redirectFrom?: string; + /** + * The current route. + */ route: Route; + /** + * Proceed to the next route in the chain, down the route tree. + */ next( resume?: boolean, parent?: Route, prevResult?: ActionResult>, ): Promise>>; }> & { + /** @internal */ __divergedChainIndex?: number; + /** @internal */ __redirectCount?: number; + /** @internal */ __renderId: number; + /** @internal */ __skipAttach?: boolean; + /** + * The result of the route resolution. It could be either a value produced by + * the {@link Route.action} or a new context to continue the resolution + * process. + */ result?: T | RouteContext; } & ResolveContext; -export type RouteChildrenContext = Omit< +/** + * Represents the context that is accessible from the route children callback. + * It is the a {@link RouteContext} without the 'next' property. + * + * @typeParam T - The type of the route parameters. + * @typeParam R - The type of the route's resolved data. Defaults to `EmptyObject`. + * @typeParam C - The type of the route's context. Defaults to `EmptyObject`. + * + * @deprecated The route children callback is deprecated and will be removed in + * the next major version. + * + * @interface + */ +export type RouteChildrenContext = Omit< RouteContext, 'next' >; export type PrimitiveParamValue = string | number | null; +/** + * The value of a parameter resolved from the URL. + */ export type ParamValue = PrimitiveParamValue | readonly PrimitiveParamValue[]; +/** + * The collection of parameters resolved from the URL. + * + * @remarks + * Parameters are the parts of the URL represented by the placeholders in the + * route path. Placeholders are often named, and these names become the keys of + * the `IndexedParams` object. + * + * If a placeholder is unnamed, its index in the path becomes the key. + */ export type IndexedParams = Readonly>; export type Params = IndexedParams | ParamValue[]; diff --git a/src/resolver/utils.ts b/src/resolver/utils.ts index 5542ac33..d8ba33aa 100644 --- a/src/resolver/utils.ts +++ b/src/resolver/utils.ts @@ -1,56 +1,83 @@ -import type { AnyObject, ChildrenCallback, Route, RouteContext } from './types.js'; +import type { ChildrenCallback, Route, RouteContext } from './types.js'; +/** + * {@inheritDoc "".NotFoundError} + */ +export const notFoundResult = Symbol('NotFoundResult'); + +/** + * A special result to be returned from a route action to indicate that the + * route was not found. + */ +export type NotFoundResult = typeof notFoundResult; + +/** + * An error to be thrown when a route is not found. + */ +export class NotFoundError extends Error { + /** + * The HTTP status code to be used when the route is not found. + */ + readonly code: number; + + /** + * The context object associated with the route that was not found. + */ + readonly context: RouteContext; + + constructor(context: RouteContext) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + super(log(`Page not found (${context.pathname})`)); + this.context = context; + this.code = 404; + } +} + +/** @internal */ export function isObject(o: unknown): o is object { // guard against null passing the typeof check return typeof o === 'object' && !!o; } -// eslint-disable-next-line @typescript-eslint/ban-types -export function isFunction(f: unknown): f is Function { +/** @internal */ +export function isFunction unknown>(f: unknown): f is F { return typeof f === 'function'; } +/** @internal */ export function isString(s: unknown): s is string { return typeof s === 'string'; } +/** @internal */ export function toArray(value: T | readonly T[] = []): readonly T[] { return Array.isArray(value) ? value : [value]; } +/** @internal */ export function log(msg: string): string { return `[Vaadin.Router] ${msg}`; } -export class NotFoundError extends Error { - readonly code: number; - readonly context: RouteContext; - - constructor(context: RouteContext) { - super(log(`Page not found (${context.pathname})`)); - this.context = context; - this.code = 404; - } -} - -export const notFoundResult = Symbol('NotFoundResult'); -export type NotFoundResult = typeof notFoundResult; - -export function getNotFoundError( +/** @internal */ +export function getNotFoundError( context: RouteContext, ): NotFoundError { return new NotFoundError(context); } +/** @internal */ export function resolvePath(path?: string | readonly string[]): string { return (Array.isArray(path) ? path[0] : path) ?? ''; } -export function getRoutePath(route: Route | undefined): string { +/** @internal */ +export function getRoutePath(route: Route | undefined): string { return resolvePath(route?.path); } -export function unwrapChildren( +/** @internal */ +export function unwrapChildren( children: ChildrenCallback | ReadonlyArray> | undefined, ): ReadonlyArray> | undefined { return Array.isArray>>(children) && children.length > 0 ? children : undefined; diff --git a/src/router.ts b/src/router.ts index 36d90a6b..600f24bb 100644 --- a/src/router.ts +++ b/src/router.ts @@ -9,7 +9,6 @@ import animate from './transitions/animate.js'; import { DEFAULT_TRIGGERS, setNavigationTriggers } from './triggers/navigation.js'; import type { ActionResult, - AnyObject, Commands, ChainItem, ContextExtension, @@ -99,7 +98,7 @@ const rootContext: RouteContext = { * * Only `setRoutes` has to be called manually, others are automatically invoked when creating a new instance. */ -export class Router extends Resolver< +export class Router extends Resolver< ActionValue, RouteExtension, ContextExtension @@ -125,6 +124,8 @@ export class Router; #urlForName?: ReturnType; @@ -325,7 +326,6 @@ export class Router | ReadonlyArray>, skipRender = false, diff --git a/src/types.d.ts b/src/types.d.ts index 717bf2e4..2ce30a08 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -17,21 +17,39 @@ import type { Router } from './router.js'; export type { ResolutionError, IndexedParams, Params, ParamValue, PrimitiveParamValue }; +/** + * A custom event that is triggered when the location changes. + */ export type VaadinRouterLocationChangedEvent = CustomEvent< Readonly<{ + /** The new location after the change */ location: RouterLocation; + /** The router instance that triggered the event */ router: Router; }> >; -export type VaadinRouterErrorEvent = CustomEvent< +/** + * A custom event triggered by an error occurred during route resolution. + * + * @typeParam R - The type of additional route-specific data. Defaults to an + * empty object. + * @typeParam C - The type of user-defined context-specific data. Defaults to an + * empty object. + */ +export type VaadinRouterErrorEvent = CustomEvent< Readonly<{ + /** The error object. */ error: ResolutionError; + /** The router instance that triggered the error event. */ router: Router; }> & RouteContext >; +/** + * A custom event triggered when the user navigates to a new location. + */ export type VaadinRouterGoEvent = CustomEvent; declare global { @@ -46,34 +64,64 @@ declare global { } } -export type AnyObject = Record; - +/** + * A context information for a redirect operation. + */ export type RedirectContextInfo = Readonly<{ + /** The original path from which the redirect is happening. */ from: string; + /** An object containing URL parameters related to the redirect. */ params: IndexedParams; + /** The pathname of the new URL to which the redirect is directed. */ pathname: string; }>; +/** + * A result that can be returned from a route action to request a redirect to + * a different location. + */ export interface RedirectResult { + /** The path info to redirect to. */ readonly redirect: RedirectContextInfo; } +/** + * A result that can be returned from a route action to prevent the navigation. + */ export interface PreventResult { + /** A flag indicating that the navigation should be prevented. */ readonly cancel: true; } +/** + * A controller to set up and tear down navigation event listeners. + */ export interface NavigationTrigger { + /** Sets up navigation listeners. */ activate(): void; + /** Tears down navigation listeners. */ inactivate(): void; } +/** + * A value of a result that can be returned from the router action. + */ export type ActionValue = HTMLElement | PreventResult | RedirectResult; -export type NextResult = _ActionResult>; +/** + * A result of the {@link RouteContext.next} function. + */ +export type NextResult = _ActionResult>; +/** + * A result of the {@link RouteExtension.action | Route.action}. + */ export type ActionResult = _ActionResult; -export type ChainItem = _ChainItem< +/** + * {@inheritDoc "".ChainItem} + */ +export type ChainItem = _ChainItem< ActionValue, RouteExtension, ContextExtension @@ -82,25 +130,51 @@ export type ChainItem = _ChainItem< element?: WebComponentInterface; }>; -export type ContextExtension = Readonly<{ +/** + * A specialized extension for the internal Resolver's + * {@link "".Context | Context} object that redefines some types to + * make it compatible with the {@link Router}. + * + * @internal + */ +export type ContextExtension = Readonly<{ resolver?: Router; chain?: Array>; }> & C; -// Readonly<{ -// next(resume?: boolean): Promise; -// }>; -export type ChildrenCallback = _ChildrenCallback< +/** + * {@inheritDoc "".ChildrenCallback} + */ +export type ChildrenCallback = _ChildrenCallback< ActionValue, RouteExtension, ContextExtension >; -export type RouteExtension = RequireAtLeastOne<{ +/** + * An specialized extension for the internal Resolver's {@link "".Route | Route} + * object that redefines some types to make it compatible with the + * {@link Router}. + * + * @internal + */ +export type RouteExtension = RequireAtLeastOne<{ children?: ChildrenCallback | ReadonlyArray>; component?: string; redirect?: string; + /** + * An action that is executed when the route is resolved. + * + * Actions are executed recursively from the root route to the child route and + * can either produce content or perform actions before or after the child's + * action. + * + * @param context - The context of the current route. + * + * @returns The result of the route resolution. It could be either a value + * produced by the action or a new context to continue the resolution process. + */ action?( this: Route, context: RouteContext, @@ -110,24 +184,40 @@ export type RouteExtension = RequireAt animate?: AnimateCustomClasses | boolean; } & R; -export type RouteContext = _RouteContext< +/** + * {@inheritDoc "".RouteContext} + * @interface + */ +export type RouteContext = _RouteContext< ActionValue, RouteExtension, ContextExtension >; +/** + * {@inheritDoc "".RouteChildrenContext} + * @interface + */ export type RouteChildrenContext< - R extends AnyObject = EmptyObject, - C extends AnyObject = EmptyObject, + R extends object = EmptyObject, + C extends object = EmptyObject, > = _RouteChildrenContext, ContextExtension>; -export type Route = _Route< +/** + * {@inheritDoc "".Route} + * @interface + */ +export type Route = _Route< ActionValue, RouteExtension, ContextExtension >; -export type RouterOptions = ResolverOptions< +/** + * {@inheritDoc "".ResolverOptions} + * @interface + */ +export type RouterOptions = ResolverOptions< ActionValue, RouteExtension, ContextExtension @@ -143,7 +233,7 @@ export type RouterOptions { +export interface RouterLocation { /** * The base URL used in the router. See [the `baseUrl` property * ](#/classes/Router#property-baseUrl) in the Router. @@ -318,7 +408,7 @@ export interface RouterLocation +export interface WebComponentInterface extends HTMLElement { location?: RouterLocation; name?: string; diff --git a/src/utils.ts b/src/utils.ts index e7fa86fe..cf52ca27 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,6 @@ import { isFunction, isObject, isString, log, toArray } from './resolver/utils.j import type { Router } from './router.js'; import type { ActionResult, - AnyObject, ChainItem, IndexedParams, RedirectResult, @@ -14,7 +13,8 @@ import type { WebComponentInterface, } from './types.js'; -export function ensureRoute(route?: Route): void { +/** @internal */ +export function ensureRoute(route?: Route): void { if (!route || !isString(route.path)) { throw new Error( log(`Expected route config to be an object with a "path" string property, or an array of such objects`), @@ -50,20 +50,23 @@ export function ensureRoute(route?: Ro } } -export function ensureRoutes( +/** @internal */ +export function ensureRoutes( routes: Route | ReadonlyArray>, ): void { toArray(routes).forEach((route) => ensureRoute(route)); } -export function copyContextWithoutNext({ +/** @internal */ +export function copyContextWithoutNext({ next: _, ...context }: RouteContext): Omit, 'next'> { return context; } -export function getPathnameForRouter( +/** @internal */ +export function getPathnameForRouter( pathname: string, router: Resolver, ): string { @@ -72,6 +75,7 @@ export function getPathnameForRouter>): string { return pathItems .map((pathItem) => pathItem.path) @@ -83,13 +87,16 @@ export function getMatchedPath(pathItems: ReadonlyArray(chain: ReadonlyArray>): string { +/** @internal */ +export function getRoutePath(chain: ReadonlyArray>): string { return getMatchedPath(chain.map((chainItem) => chainItem.route)); } -export type ResolverOnlyContext = Readonly<{ resolver: Router }>; +/** @internal */ +export type ResolverOnlyContext = Readonly<{ resolver: Router }>; -type PartialRouteContext = Readonly<{ +/** @internal */ +type PartialRouteContext = Readonly<{ chain?: ReadonlyArray>; hash?: string; params?: IndexedParams; @@ -99,14 +106,15 @@ type PartialRouteContext = Readonly<{ search?: string; }>; -export function createLocation({ +/** @internal */ +export function createLocation({ resolver, }: ResolverOnlyContext): RouterLocation; -export function createLocation( +export function createLocation( context: RouteContext, route?: Route, ): RouterLocation; -export function createLocation( +export function createLocation( { chain = [], hash = '', params = {}, pathname = '', redirectFrom, resolver, search = '' }: PartialRouteContext, route?: Route, ): RouterLocation { @@ -114,7 +122,12 @@ export function createLocation( return { baseUrl: resolver?.baseUrl ?? '', getUrl: (userParams = {}) => - resolver ? getPathnameForRouter(compile(getRoutePath(chain))({ ...params, ...userParams } as Partial>), resolver) : '', + resolver + ? getPathnameForRouter( + compile(getRoutePath(chain))({ ...params, ...userParams } as Partial>), + resolver, + ) + : '', hash, params, pathname, @@ -126,7 +139,8 @@ export function createLocation( }; } -export function createRedirect( +/** @internal */ +export function createRedirect( context: RouteContext, pathname: string, ): RedirectResult { @@ -140,7 +154,8 @@ export function createRedirect( }; } -export function renderElement>( +/** @internal */ +export function renderElement>( context: RouteContext, element: E, ): E { @@ -154,6 +169,7 @@ export function renderElement( callback: ((this: O, ...args: A) => R) | undefined, thisArg: O, @@ -166,10 +182,11 @@ export function maybeCall( return undefined; } +/** @internal */ export function amend< A extends readonly unknown[], N extends keyof O, - O extends AnyObject & { [key in N]: (this: O, ...args: A) => ActionResult | undefined }, + O extends object & { [key in N]: (this: O, ...args: A) => ActionResult | undefined }, >(fn: keyof O, obj: O | undefined, ...args: A): (result: ActionResult) => ActionResult | undefined { return (result: ActionResult) => { if (result && isObject(result) && ('cancel' in result || 'redirect' in result)) { @@ -180,7 +197,8 @@ export function amend< }; } -export function processNewChildren( +/** @internal */ +export function processNewChildren( newChildren: Route | ReadonlyArray> | undefined | void, route: Route, ): void { @@ -199,10 +217,12 @@ export function processNewChildren( route.__children = children; } +/** @internal */ export function fireRouterEvent(type: string, detail: unknown): boolean { return !window.dispatchEvent(new CustomEvent(`vaadin-router-${type}`, { cancelable: type === 'go', detail })); } +/** @internal */ export function logValue(value: unknown): string { if (typeof value !== 'object') { return String(value); diff --git a/test/router/dynamic-redirect.spec.ts b/test/router/dynamic-redirect.spec.ts index fe96a00a..f2e27a71 100644 --- a/test/router/dynamic-redirect.spec.ts +++ b/test/router/dynamic-redirect.spec.ts @@ -15,7 +15,6 @@ use(chaiAsPromised); describe('Vaadin.Router', () => { let outlet: HTMLElement; let router: Router; - let history: sinon.SinonStubbedInstance; before(() => { outlet = document.createElement('div'); @@ -27,8 +26,6 @@ describe('Vaadin.Router', () => { }); beforeEach(() => { - history = sinon.createStubInstance(History); - // create a new router instance router = new Router(outlet); }); diff --git a/test/router/test-utils.ts b/test/router/test-utils.ts index 40bba62b..b2905e01 100644 --- a/test/router/test-utils.ts +++ b/test/router/test-utils.ts @@ -3,7 +3,6 @@ import { expect } from '@esm-bundle/chai'; import type { Commands, RouteContext, Router, WebComponentInterface, Route } from '../../src/index.js'; -import type { AnyObject } from '../../src/resolver/types.js'; export async function waitForNavigation(): Promise { return await new Promise((resolve) => { @@ -21,7 +20,7 @@ export function verifyActiveRoutes(router: Router, expectedSegments: string[]): } function createWebComponentAction(method: T) { - return ( + return ( componentName: string, callback: WebComponentInterface[T], name: string = 'unknown', diff --git a/tsconfig.json b/tsconfig.json index 70b31b6a..7a89a309 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "useDefineForClassFields": true, "useUnknownInCatchVariables": true }, - "include": ["scripts", "src/**/*", "test/**/*", "./vite.config.ts", "./*.cjs"], - "exclude": ["scripts/**/*.js"] + "include": ["scripts/**/*", "src/**/*", "test/**/*", "/vite.config.ts", "/*.cjs"], + "exclude": ["node_modules", "dist/**/*", "scripts/**/*.js"] } diff --git a/tsdoc.json b/tsdoc.json new file mode 100644 index 00000000..8d551f9c --- /dev/null +++ b/tsdoc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "tagDefinitions": [ + { + "tagName": "@interface", + "syntaxKind": "modifier" + }, + { + "tagName": "@event", + "syntaxKind": "block" + } + ] +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 00000000..bbf3fa53 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["src/index.ts"], + "excludePrivate": true, + "out": ".docs", + "sort": ["alphabetical"], + "plugin": ["typedoc-plugin-missing-exports"], + "tsconfig": "tsconfig.build.json", + "useTsLinkResolution": false, + "excludeInternal": true, + "excludeExternals": true, + "validation": { + "notExported": true, + "invalidLink": true, + "notDocumented": true + } +}