diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 5391fa1..b8e5f3a 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -26,5 +26,11 @@ jobs: - name: Prettier Check run: pnpm run prettiercheck + - name: Circular Dependencies Check + run: pnpm run circularcheck + - name: Test run: pnpm run test + + - name: Build + run: pnpm run build diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 71d6499..f9b3a9a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -31,6 +31,9 @@ jobs: - name: Prettier Check run: pnpm run prettiercheck + - name: Circular Dependencies Check + run: pnpm run circularcheck + - name: Test run: pnpm run test diff --git a/package.json b/package.json index db2b3e7..72b215b 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,17 @@ "clean": "rimraf lib", "build": "pnpm clean && tsc -p tsconfig.build.json", "typecheck": "tsc", - "prettiercheck": "prettier -c ." + "prettiercheck": "prettier -c .", + "circularcheck": "madge --circular --extensions ts ./src" }, "devDependencies": { "@jest/globals": "^29.7.0", "jest": "^29.7.0", + "madge": "^8.0.0", "prettier": "^3.2.5", "rimraf": "^5.0.5", "ts-jest": "^29.1.2", - "typescript": "~5.3.3", - "zod": "^3.22.4" + "typescript": "~5.6.3", + "zod": "^3.23.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7be0be1..6b5f9ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ devDependencies: jest: specifier: ^29.7.0 version: 29.7.0 + madge: + specifier: ^8.0.0 + version: 8.0.0(typescript@5.6.3) prettier: specifier: ^3.2.5 version: 3.2.5 @@ -19,13 +22,13 @@ devDependencies: version: 5.0.5 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.6.3) typescript: - specifier: ~5.3.3 - version: 5.3.3 + specifier: ~5.6.3 + version: 5.6.3 zod: - specifier: ^3.22.4 - version: 3.22.4 + specifier: ^3.23.8 + version: 3.23.8 packages: @@ -159,11 +162,21 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-string-parser@7.25.7: + resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-validator-identifier@7.25.7: + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -197,6 +210,14 @@ packages: '@babel/types': 7.23.9 dev: true + /@babel/parser@7.25.8: + resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.25.8 + dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -362,10 +383,27 @@ packages: to-fast-properties: 2.0.0 dev: true + /@babel/types@7.25.8: + resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + to-fast-properties: 2.0.0 + dev: true + /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@dependents/detective-less@5.0.0: + resolution: {integrity: sha512-D/9dozteKcutI5OdxJd8rU+fL6XgaaRg60sPPJWkT33OCiRfkCu5wO5B/yXTaaL2e6EB0lcCBGe5E0XscZCvvQ==} + engines: {node: '>=18'} + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.0 + dev: true + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -631,6 +669,10 @@ packages: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + dev: true + /@jridgewell/trace-mapping@0.3.22: resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} dependencies: @@ -638,6 +680,27 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -661,6 +724,33 @@ packages: '@sinonjs/commons': 3.0.1 dev: true + /@ts-graphviz/adapter@2.0.5: + resolution: {integrity: sha512-K/xd2SJskbSLcUz9uYW9IDy26I3Oyutj/LREjJgcuLMxT3um4sZfy9LiUhGErHjxLRaNcaDVGSsmWeiNuhidXg==} + engines: {node: '>=18'} + dependencies: + '@ts-graphviz/common': 2.1.4 + dev: true + + /@ts-graphviz/ast@2.0.5: + resolution: {integrity: sha512-HVT+Bn/smDzmKNJFccwgrpJaEUMPzXQ8d84JcNugzTHNUVgxAIe2Vbf4ug351YJpowivQp6/N7XCluQMjtgi5w==} + engines: {node: '>=18'} + dependencies: + '@ts-graphviz/common': 2.1.4 + dev: true + + /@ts-graphviz/common@2.1.4: + resolution: {integrity: sha512-PNEzOgE4vgvorp/a4Ev26jVNtiX200yODoyPa8r6GfpPZbxWKW6bdXF6xWqzMkQoO1CnJOYJx2VANDbGqCqCCw==} + engines: {node: '>=18'} + dev: true + + /@ts-graphviz/core@2.0.5: + resolution: {integrity: sha512-YwaCGAG3Hs0nhxl+2lVuwuTTAK3GO2XHqOGvGIwXQB16nV858rrR5w2YmWCw9nhd11uLTStxLsCAhI9koWBqDA==} + engines: {node: '>=18'} + dependencies: + '@ts-graphviz/ast': 2.0.5 + '@ts-graphviz/common': 2.1.4 + dev: true + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -732,6 +822,83 @@ packages: '@types/yargs-parser': 21.0.3 dev: true + /@typescript-eslint/types@7.18.0: + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + + /@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3): + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/visitor-keys@7.18.0: + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@vue/compiler-core@3.5.12: + resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} + dependencies: + '@babel/parser': 7.25.8 + '@vue/shared': 3.5.12 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + dev: true + + /@vue/compiler-dom@3.5.12: + resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} + dependencies: + '@vue/compiler-core': 3.5.12 + '@vue/shared': 3.5.12 + dev: true + + /@vue/compiler-sfc@3.5.12: + resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} + dependencies: + '@babel/parser': 7.25.8 + '@vue/compiler-core': 3.5.12 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 + estree-walker: 2.0.2 + magic-string: 0.30.12 + postcss: 8.4.47 + source-map-js: 1.2.1 + dev: true + + /@vue/compiler-ssr@3.5.12: + resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} + dependencies: + '@vue/compiler-dom': 3.5.12 + '@vue/shared': 3.5.12 + dev: true + + /@vue/shared@3.5.12: + resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} + dev: true + /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -773,6 +940,10 @@ packages: engines: {node: '>=12'} dev: true + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -781,12 +952,26 @@ packages: picomatch: 2.3.1 dev: true + /app-module-path@2.2.0: + resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + dev: true + /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 dev: true + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /ast-module-types@6.0.0: + resolution: {integrity: sha512-LFRg7178Fw5R4FAEwZxVqiRI8IxSM+Ay2UBrHoCerXNme+kMMMfz7T3xDGV/c2fer87hcrtgJGsnSOfUrPK6ng==} + engines: {node: '>=18'} + dev: true + /babel-jest@29.7.0(@babel/core@7.23.9): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -863,6 +1048,18 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -911,6 +1108,13 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -961,6 +1165,18 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -970,6 +1186,11 @@ packages: wrap-ansi: 7.0.0 dev: true + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -1000,6 +1221,20 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + dev: true + + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: true + + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -1057,21 +1292,142 @@ packages: optional: true dev: true + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: true + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} dev: true + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + + /dependency-tree@11.0.1: + resolution: {integrity: sha512-eCt7HSKIC9NxgIykG2DRq3Aewn9UhVS14MB3rEn6l/AsEI1FBg6ZGSlCU0SZ6Tjm2kkhj6/8c2pViinuyKELhg==} + engines: {node: '>=18'} + hasBin: true + dependencies: + commander: 12.1.0 + filing-cabinet: 5.0.2 + precinct: 12.1.2 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} dev: true + /detective-amd@6.0.0: + resolution: {integrity: sha512-NTqfYfwNsW7AQltKSEaWR66hGkTeD52Kz3eRQ+nfkA9ZFZt3iifRCWh+yZ/m6t3H42JFwVFTrml/D64R2PAIOA==} + engines: {node: '>=18'} + hasBin: true + dependencies: + ast-module-types: 6.0.0 + escodegen: 2.1.0 + get-amd-module-type: 6.0.0 + node-source-walk: 7.0.0 + dev: true + + /detective-cjs@6.0.0: + resolution: {integrity: sha512-R55jTS6Kkmy6ukdrbzY4x+I7KkXiuDPpFzUViFV/tm2PBGtTCjkh9ZmTuJc1SaziMHJOe636dtiZLEuzBL9drg==} + engines: {node: '>=18'} + dependencies: + ast-module-types: 6.0.0 + node-source-walk: 7.0.0 + dev: true + + /detective-es6@5.0.0: + resolution: {integrity: sha512-NGTnzjvgeMW1khUSEXCzPDoraLenWbUjCFjwxReH+Ir+P6LGjYtaBbAvITWn2H0VSC+eM7/9LFOTAkrta6hNYg==} + engines: {node: '>=18'} + dependencies: + node-source-walk: 7.0.0 + dev: true + + /detective-postcss@7.0.0(postcss@8.4.47): + resolution: {integrity: sha512-pSXA6dyqmBPBuERpoOKKTUUjQCZwZPLRbd1VdsTbt6W+m/+6ROl4BbE87yQBUtLoK7yX8pvXHdKyM/xNIW9F7A==} + engines: {node: ^14.0.0 || >=16.0.0} + peerDependencies: + postcss: ^8.4.38 + dependencies: + is-url: 1.2.4 + postcss: 8.4.47 + postcss-values-parser: 6.0.2(postcss@8.4.47) + dev: true + + /detective-sass@6.0.0: + resolution: {integrity: sha512-h5GCfFMkPm4ZUUfGHVPKNHKT8jV7cSmgK+s4dgQH4/dIUNh9/huR1fjEQrblOQNDalSU7k7g+tiW9LJ+nVEUhg==} + engines: {node: '>=18'} + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.0 + dev: true + + /detective-scss@5.0.0: + resolution: {integrity: sha512-Y64HyMqntdsCh1qAH7ci95dk0nnpA29g319w/5d/oYcHolcGUVJbIhOirOFjfN1KnMAXAFm5FIkZ4l2EKFGgxg==} + engines: {node: '>=18'} + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.0 + dev: true + + /detective-stylus@5.0.0: + resolution: {integrity: sha512-KMHOsPY6aq3196WteVhkY5FF+6Nnc/r7q741E+Gq+Ax9mhE2iwj8Hlw8pl+749hPDRDBHZ2WlgOjP+twIG61vQ==} + engines: {node: '>=18'} + dev: true + + /detective-typescript@13.0.0(typescript@5.6.3): + resolution: {integrity: sha512-tcMYfiFWoUejSbvSblw90NDt76/4mNftYCX0SMnVRYzSXv8Fvo06hi4JOPdNvVNxRtCAKg3MJ3cBJh+ygEMH+A==} + engines: {node: ^14.14.0 || >=16.0.0} + peerDependencies: + typescript: ^5.4.4 + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) + ast-module-types: 6.0.0 + node-source-walk: 7.0.0 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true + + /detective-vue2@2.1.0(typescript@5.6.3): + resolution: {integrity: sha512-IHQVhwk7dKaJ+GHBsL27mS9NRO1/vLZJPSODqtJgKquij0/UL8NvrbXbADbYeTkwyh1ReW/v9u9IRyEO5dvGZg==} + engines: {node: '>=18'} + peerDependencies: + typescript: ^5.4.4 + dependencies: + '@dependents/detective-less': 5.0.0 + '@vue/compiler-sfc': 3.5.12 + detective-es6: 5.0.0 + detective-sass: 6.0.0 + detective-scss: 5.0.0 + detective-stylus: 5.0.0 + detective-typescript: 13.0.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -1093,6 +1449,19 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -1114,12 +1483,43 @@ packages: engines: {node: '>=8'} dev: true + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true dev: true + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1151,16 +1551,51 @@ packages: jest-util: 29.7.0 dev: true + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + /fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: bser: 2.1.1 dev: true + /filing-cabinet@5.0.2: + resolution: {integrity: sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==} + engines: {node: '>=18'} + hasBin: true + dependencies: + app-module-path: 2.2.0 + commander: 12.1.0 + enhanced-resolve: 5.17.1 + module-definition: 6.0.0 + module-lookup-amd: 9.0.2 + resolve: 1.22.8 + resolve-dependency-path: 4.0.0 + sass-lookup: 6.0.1 + stylus-lookup: 6.0.0 + tsconfig-paths: 4.2.0 + typescript: 5.6.3 + dev: true + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -1205,11 +1640,23 @@ packages: engines: {node: '>=6.9.0'} dev: true + /get-amd-module-type@6.0.0: + resolution: {integrity: sha512-hFM7oivtlgJ3d6XWD6G47l8Wyh/C6vFw5G24Kk1Tbq85yh5gcM8Fne5/lFhiuxB+RT6+SI7I1ThB9lG4FBh3jw==} + engines: {node: '>=18'} + dependencies: + ast-module-types: 6.0.0 + node-source-walk: 7.0.0 + dev: true + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true + /get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + dev: true + /get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -1220,6 +1667,13 @@ packages: engines: {node: '>=10'} dev: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} @@ -1248,6 +1702,26 @@ packages: engines: {node: '>=4'} dev: true + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /gonzales-pe@4.3.0: + resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} + engines: {node: '>=0.6.0'} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true @@ -1278,6 +1752,15 @@ packages: engines: {node: '>=10.17.0'} dev: true + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: true + /import-local@3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} engines: {node: '>=8'} @@ -1303,6 +1786,10 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true @@ -1313,6 +1800,11 @@ packages: hasown: 2.0.1 dev: true + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1323,16 +1815,52 @@ packages: engines: {node: '>=6'} dev: true + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: true + /is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + dev: true + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} dev: true + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-url-superb@4.0.0: + resolution: {integrity: sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==} + engines: {node: '>=10'} + dev: true + + /is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -1868,6 +2396,14 @@ packages: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: true + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} @@ -1886,6 +2422,39 @@ packages: yallist: 4.0.0 dev: true + /madge@8.0.0(typescript@5.6.3): + resolution: {integrity: sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: ^5.4.4 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + chalk: 4.1.2 + commander: 7.2.0 + commondir: 1.0.1 + debug: 4.3.4 + dependency-tree: 11.0.1 + ora: 5.4.1 + pluralize: 8.0.0 + pretty-ms: 7.0.1 + rc: 1.2.8 + stream-to-array: 2.3.0 + ts-graphviz: 2.1.4 + typescript: 5.6.3 + walkdir: 0.4.1 + transitivePeerDependencies: + - supports-color + dev: true + + /magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -1907,6 +2476,11 @@ packages: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -1933,15 +2507,52 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} dev: true + /module-definition@6.0.0: + resolution: {integrity: sha512-sEGP5nKEXU7fGSZUML/coJbrO+yQtxcppDAYWRE9ovWsTbFoUHB2qDUx564WUzDaBHXsD46JBbIK5WVTwCyu3w==} + engines: {node: '>=18'} + hasBin: true + dependencies: + ast-module-types: 6.0.0 + node-source-walk: 7.0.0 + dev: true + + /module-lookup-amd@9.0.2: + resolution: {integrity: sha512-p7PzSVEWiW9fHRX9oM+V4aV5B2nCVddVNv4DZ/JB6t9GsXY4E+ZVhPpnwUX7bbJyGeeVZqhS8q/JZ/H77IqPFA==} + engines: {node: '>=18'} + hasBin: true + dependencies: + commander: 12.1.0 + glob: 7.2.3 + requirejs: 2.3.7 + requirejs-config-file: 4.0.0 + dev: true + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -1954,6 +2565,13 @@ packages: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true + /node-source-walk@7.0.0: + resolution: {integrity: sha512-1uiY543L+N7Og4yswvlm5NCKgPKDEXd9AUR9Jh3gen6oOeBsesr6LqhXom1er3eRzSUcVRWXzhv8tSNrIfGHKw==} + engines: {node: '>=18'} + dependencies: + '@babel/parser': 7.25.8 + dev: true + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1979,6 +2597,21 @@ packages: mimic-fn: 2.1.0 dev: true + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2015,6 +2648,11 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2042,10 +2680,19 @@ packages: minipass: 7.0.4 dev: true + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2063,6 +2710,56 @@ packages: find-up: 4.1.0 dev: true + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true + + /postcss-values-parser@6.0.2(postcss@8.4.47): + resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.2.9 + dependencies: + color-name: 1.1.4 + is-url-superb: 4.0.0 + postcss: 8.4.47 + quote-unquote: 1.0.0 + dev: true + + /postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: true + + /precinct@12.1.2: + resolution: {integrity: sha512-x2qVN3oSOp3D05ihCd8XdkIPuEQsyte7PSxzLqiRgktu79S5Dr1I75/S+zAup8/0cwjoiJTQztE9h0/sWp9bJQ==} + engines: {node: '>=18'} + hasBin: true + dependencies: + '@dependents/detective-less': 5.0.0 + commander: 12.1.0 + detective-amd: 6.0.0 + detective-cjs: 6.0.0 + detective-es6: 5.0.0 + detective-postcss: 7.0.0(postcss@8.4.47) + detective-sass: 6.0.0 + detective-scss: 5.0.0 + detective-stylus: 5.0.0 + detective-typescript: 13.0.0(typescript@5.6.3) + detective-vue2: 2.1.0(typescript@5.6.3) + module-definition: 6.0.0 + node-source-walk: 7.0.0 + postcss: 8.4.47 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true + /prettier@3.2.5: resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} engines: {node: '>=14'} @@ -2078,6 +2775,13 @@ packages: react-is: 18.2.0 dev: true + /pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + dependencies: + parse-ms: 2.1.0 + dev: true + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -2090,15 +2794,56 @@ packages: resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} dev: true + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quote-unquote@1.0.0: + resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} + dev: true + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: true + /requirejs-config-file@4.0.0: + resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==} + engines: {node: '>=10.13.0'} + dependencies: + esprima: 4.0.1 + stringify-object: 3.3.0 + dev: true + + /requirejs@2.3.7: + resolution: {integrity: sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -2106,6 +2851,11 @@ packages: resolve-from: 5.0.0 dev: true + /resolve-dependency-path@4.0.0: + resolution: {integrity: sha512-hlY1SybBGm5aYN3PC4rp15MzsJLM1w+MEA/4KU3UBPfz4S0lL3FL6mgv7JgaA8a+ZTeEQAiF1a1BuN2nkqiIlg==} + engines: {node: '>=18'} + dev: true + /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -2125,6 +2875,19 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + /rimraf@5.0.5: resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} engines: {node: '>=14'} @@ -2133,6 +2896,24 @@ packages: glob: 10.3.10 dev: true + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /sass-lookup@6.0.1: + resolution: {integrity: sha512-nl9Wxbj9RjEJA5SSV0hSDoU2zYGtE+ANaDS4OFUR7nYrquvBFvPKZZtQHe3lvnxCcylEDV00KUijjdMTUElcVQ==} + engines: {node: '>=18'} + hasBin: true + dependencies: + commander: 12.1.0 + dev: true + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2176,6 +2957,11 @@ packages: engines: {node: '>=8'} dev: true + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -2199,6 +2985,12 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + dependencies: + any-promise: 1.3.0 + dev: true + /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -2225,6 +3017,21 @@ packages: strip-ansi: 7.1.0 dev: true + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2239,6 +3046,11 @@ packages: ansi-regex: 6.0.1 dev: true + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} @@ -2249,11 +3061,24 @@ packages: engines: {node: '>=6'} dev: true + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true + /stylus-lookup@6.0.0: + resolution: {integrity: sha512-RaWKxAvPnIXrdby+UWCr1WRfa+lrPMSJPySte4Q6a+rWyjeJyFOLJxr5GrAVfcMCsfVlCuzTAJ/ysYT8p8do7Q==} + engines: {node: '>=18'} + hasBin: true + dependencies: + commander: 12.1.0 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2280,6 +3105,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -2305,7 +3135,26 @@ packages: is-number: 7.0.0 dev: true - /ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3): + /ts-api-utils@1.3.0(typescript@5.6.3): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.6.3 + dev: true + + /ts-graphviz@2.1.4: + resolution: {integrity: sha512-0g465/ES70H0h5rcLUqaenKqNYekQaR9W0m0xUGy3FxueGujpGr+0GN2YWlgLIYSE2Xg0W7Uq1Qqnn7Cg+Af2w==} + engines: {node: '>=18'} + dependencies: + '@ts-graphviz/adapter': 2.0.5 + '@ts-graphviz/ast': 2.0.5 + '@ts-graphviz/common': 2.1.4 + '@ts-graphviz/core': 2.0.5 + dev: true + + /ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.6.3): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -2335,10 +3184,19 @@ packages: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.0 - typescript: 5.3.3 + typescript: 5.6.3 yargs-parser: 21.1.1 dev: true + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} @@ -2349,8 +3207,8 @@ packages: engines: {node: '>=10'} dev: true - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + /typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -2370,6 +3228,10 @@ packages: picocolors: 1.0.0 dev: true + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + /v8-to-istanbul@9.2.0: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} @@ -2379,12 +3241,23 @@ packages: convert-source-map: 2.0.0 dev: true + /walkdir@0.4.1: + resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} + engines: {node: '>=6.0.0'} + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 dev: true + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2459,6 +3332,6 @@ packages: engines: {node: '>=10'} dev: true - /zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: true diff --git a/src/base.ts b/src/base.ts index 2acb31d..e384521 100644 --- a/src/base.ts +++ b/src/base.ts @@ -1,6 +1,22 @@ import { z } from "zod"; -import { GeoJSONBBoxSchema } from "./bbox"; +import { GeoJSONBbox, GeoJSONBboxSchema, GeoJSONBboxSchemaType } from "./bbox"; -export const GeoJSONBaseSchema = z.object({ - bbox: GeoJSONBBoxSchema.optional(), +export type GeoJSONBase = { + bbox?: GeoJSONBbox; +}; + +export type GeoJSONBaseSchemaInnerType = { + bbox: z.ZodOptional; +}; + +export type GeoJSONBaseSchemaType = z.ZodObject< + GeoJSONBaseSchemaInnerType, + "strip", + z.ZodTypeAny, + GeoJSONBase, + GeoJSONBase +>; + +export const GeoJSONBaseSchema: GeoJSONBaseSchemaType = z.object({ + bbox: GeoJSONBboxSchema.optional(), }); diff --git a/src/bbox.ts b/src/bbox.ts index 6b765d4..15b27a0 100644 --- a/src/bbox.ts +++ b/src/bbox.ts @@ -1,7 +1,12 @@ import { z } from "zod"; -export const GeoJSONBBoxSchema = z +export type GeoJSONBbox = [number, number, number, number, ...number[]]; + +export type GeoJSONBboxSchemaInnerType = z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], z.ZodNumber>; + +export type GeoJSONBboxSchemaType = z.ZodEffects; + +export const GeoJSONBboxSchema: GeoJSONBboxSchemaType = z .tuple([z.number(), z.number(), z.number(), z.number()]) .rest(z.number()) .refine((bbox) => bbox.length % 2 === 0, "Bounding box must have an even number of elements"); -export type GeoJSONBbox = z.infer; diff --git a/src/feature.ts b/src/feature.ts index 7406e96..72740c6 100644 --- a/src/feature.ts +++ b/src/feature.ts @@ -1,43 +1,29 @@ import { z } from "zod"; import { GeoJSONBaseSchema } from "./base"; -import { GeoJSONGeometry, GeoJSONGeometryGenericSchema } from "./geometry"; -import { bboxEquals, getBboxForGeometry, INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; - -export type ValidatableGeoJSONFeature = { bbox?: number[]; geometry: GeoJSONGeometry | null }; - -const INVALID_FEATURE_KEYS_ISSUE = { - code: "custom" as const, - message: 'GeoJSON feature cannot have "coordinates", "features", or "geometries" keys', -}; - -function validFeatureKeys(feature: Record): boolean { - return !("coordinates" in feature) && !("features" in feature) && !("geometries" in feature); -} - -function validFeatureBbox({ bbox, geometry }: ValidatableGeoJSONFeature): boolean { - if (!bbox || !geometry) { - return true; - } - const expectedBbox = getBboxForGeometry(geometry); - return bboxEquals(expectedBbox, bbox); -} +import { GeoJSONGeometryGenericSchema } from "./geometry/geometry"; +import { INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; +import { + GeoJSON2DPositionSchema, + GeoJSON3DPositionSchema, + GeoJSONPosition, + GeoJSONPositionSchema, +} from "./geometry/position"; +import { GeoJSONTypeSchema } from "./type"; +import { validBboxForFeature } from "./validation/bbox"; export const GeoJSONFeatureGenericSchema =

(positionSchema: z.ZodSchema

) => GeoJSONBaseSchema.extend({ id: z.string().or(z.number()).optional(), - type: z.literal("Feature"), + type: z.literal(GeoJSONTypeSchema.enum.Feature), geometry: GeoJSONGeometryGenericSchema(positionSchema).nullable(), properties: z.object({}).passthrough().nullable(), + coordinates: z.never({ message: "GeoJSON Feature cannot have a 'coordinates' key" }).optional(), + features: z.never({ message: "GeoJSON Feature cannot have a 'features' key" }).optional(), + geometries: z.never({ message: "GeoJSON Feature cannot have a 'geometries' key" }).optional(), }) .passthrough() .superRefine((val, ctx) => { - if (!validFeatureKeys(val)) { - ctx.addIssue(INVALID_FEATURE_KEYS_ISSUE); - } - - // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics - if (!validFeatureBbox(val as ValidatableGeoJSONFeature)) { + if (!validBboxForFeature(val)) { ctx.addIssue(INVALID_BBOX_ISSUE); } }); diff --git a/src/feature_collection.ts b/src/feature_collection.ts index 9910040..51d121a 100644 --- a/src/feature_collection.ts +++ b/src/feature_collection.ts @@ -1,75 +1,39 @@ import { z } from "zod"; import { GeoJSONBaseSchema } from "./base"; -import { GeoJSONBbox } from "./bbox"; -import { GeoJSONFeature, GeoJSONFeatureGenericSchema } from "./feature"; -import { GeoJSONGeometry } from "./geometry"; -import { bboxEquals, getBboxForGeometries, INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; -import { getDimensionForGeometry } from "./geometry/validation/dimension"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; - -type ValidatableGeoJSONFeatureCollection = { features: GeoJSONFeature[]; bbox?: GeoJSONBbox }; - -const INVALID_FEATURE_COLLECTION_KEYS_ISSUE = { - code: "custom" as const, - message: 'GeoJSON feature cannot have "coordinates", "geometry", "properties", or "geometries" keys', -}; - -const INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE = { - code: "custom" as const, - message: "Invalid dimensions. All features in feature collection must have the same dimension.", -}; - -function getGeometries({ features }: ValidatableGeoJSONFeatureCollection): GeoJSONGeometry[] { - return features.map((feature) => feature.geometry).filter((x): x is GeoJSONGeometry => x != null); -} - -function validFeatureCollection(collection: Record): boolean { - return ( - !("coordinates" in collection) && - !("geometry" in collection) && - !("properties" in collection) && - !("geometries" in collection) - ); -} - -function validFeatureCollectionDimensions(collection: ValidatableGeoJSONFeatureCollection): boolean { - const geometries = getGeometries(collection); - const dimension = getDimensionForGeometry(geometries[0]); - return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); -} - -function validFeatureCollectionBbox({ features, bbox }: ValidatableGeoJSONFeatureCollection) { - if (!bbox) { - return true; - } - const expectedBbox = getBboxForGeometries(getGeometries({ features })); - return bboxEquals(expectedBbox, bbox); -} +import { GeoJSONFeatureGenericSchema } from "./feature"; +import { INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; +import { + GeoJSON2DPositionSchema, + GeoJSON3DPositionSchema, + GeoJSONPosition, + GeoJSONPositionSchema, +} from "./geometry/position"; +import { GeoJSONTypeSchema } from "./type"; +import { validBboxForFeatureCollection } from "./validation/bbox"; +import { + INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE, + validDimensionsForFeatureCollection, +} from "./validation/dimension"; export const GeoJSONFeatureCollectionGenericSchema =

(positionSchema: z.ZodSchema

) => GeoJSONBaseSchema.extend({ - type: z.literal("FeatureCollection"), + type: z.literal(GeoJSONTypeSchema.enum.FeatureCollection), features: z.array(GeoJSONFeatureGenericSchema(positionSchema)), + coordinates: z.never({ message: "GeoJSON feature collection cannot have a 'coordinates' key" }).optional(), + geometry: z.never({ message: "GeoJSON feature collection cannot have a 'geometry' key" }).optional(), + properties: z.never({ message: "GeoJSON feature collection cannot have a 'properties' key" }).optional(), + geometries: z.never({ message: "GeoJSON feature collection cannot have a 'geometries' key" }).optional(), }) .passthrough() .superRefine((val, ctx) => { - if (!validFeatureCollection(val)) { - ctx.addIssue(INVALID_FEATURE_COLLECTION_KEYS_ISSUE); - return; - } - if (!val.features.length) { return; } - - // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics - if (!validFeatureCollectionDimensions(val as ValidatableGeoJSONFeatureCollection)) { + if (!validDimensionsForFeatureCollection(val)) { ctx.addIssue(INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE); return; } - - // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics - if (!validFeatureCollectionBbox(val as ValidatableGeoJSONFeatureCollection)) { + if (!validBboxForFeatureCollection(val)) { ctx.addIssue(INVALID_BBOX_ISSUE); return; } diff --git a/src/geojson.ts b/src/geojson.ts new file mode 100644 index 0000000..3715526 --- /dev/null +++ b/src/geojson.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import { GeoJSONFeatureGenericSchema } from "./feature"; +import { GeoJSONFeatureCollectionGenericSchema } from "./feature_collection"; +import { GeoJSONGeometryGenericSchema } from "./geometry/geometry"; +import { + GeoJSON2DPositionSchema, + GeoJSON3DPositionSchema, + GeoJSONPosition, + GeoJSONPositionSchema, +} from "./geometry/position"; + +export const GeoJSONGenericSchema =

(positionSchema: z.ZodSchema

) => + z.union([ + GeoJSONGeometryGenericSchema(positionSchema), + GeoJSONFeatureGenericSchema(positionSchema), + GeoJSONFeatureCollectionGenericSchema(positionSchema), + ]); + +export const GeoJSONSchema = GeoJSONGenericSchema(GeoJSONPositionSchema); +export type GeoJSON = z.infer; + +export const GeoJSON2DSchema = GeoJSONGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2D = z.infer; + +export const GeoJSON3DSchema = GeoJSONGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3D = z.infer; diff --git a/src/geometry/_simple.ts b/src/geometry/_simple.ts deleted file mode 100644 index 88a12db..0000000 --- a/src/geometry/_simple.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { z } from "zod"; -import { GeoJSONPosition } from "../position"; -import { GeoJSONLineStringGenericSchema } from "./line_string"; -import { GeoJSONMultiLineStringGenericSchema } from "./multi_line_string"; -import { GeoJSONMultiPointGenericSchema } from "./multi_point"; -import { GeoJSONMultiPolygonGenericSchema } from "./multi_polygon"; -import { GeoJSONPointGenericSchema } from "./point"; -import { GeoJSONPolygonGenericSchema } from "./polygon"; - -export const _GeoJSONSimpleGeometryGenericSchema =

(positionSchema: z.ZodSchema

) => - z.union([ - GeoJSONPointGenericSchema(positionSchema), - GeoJSONLineStringGenericSchema(positionSchema), - GeoJSONMultiPointGenericSchema(positionSchema), - GeoJSONPolygonGenericSchema(positionSchema), - GeoJSONMultiLineStringGenericSchema(positionSchema), - GeoJSONMultiPolygonGenericSchema(positionSchema), - ]); diff --git a/src/geometry/index.ts b/src/geometry/geometry.ts similarity index 52% rename from src/geometry/index.ts rename to src/geometry/geometry.ts index a47a10b..2adf16f 100644 --- a/src/geometry/index.ts +++ b/src/geometry/geometry.ts @@ -1,18 +1,22 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; -import { _GeoJSONSimpleGeometryGenericSchema } from "./_simple"; -import { GeoJSONGeometryCollectionSchema } from "./geometry_collection"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONSimpleGeometryGenericSchema, GeoJSONSimpleGeometryGenericSchemaType } from "./helper/simple"; +import { + GeoJSONGeometryCollectionGenericSchema, + GeoJSONGeometryCollectionGenericSchemaType, +} from "./geometry_collection"; -export * from "./geometry_collection"; -export * from "./line_string"; -export * from "./multi_line_string"; -export * from "./multi_point"; -export * from "./multi_polygon"; -export * from "./point"; -export * from "./polygon"; +export type GeoJSONGeometryGenericSchemaType

= z.ZodUnion< + [GeoJSONSimpleGeometryGenericSchemaType

, GeoJSONGeometryCollectionGenericSchemaType

] +>; -export const GeoJSONGeometryGenericSchema =

(positionSchema: z.ZodSchema

) => - _GeoJSONSimpleGeometryGenericSchema(positionSchema).or(GeoJSONGeometryCollectionSchema); +export const GeoJSONGeometryGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONGeometryGenericSchemaType

=> + z.union([ + GeoJSONSimpleGeometryGenericSchema(positionSchema), + GeoJSONGeometryCollectionGenericSchema(positionSchema), + ]); export type GeoJSONGenericGeometry

= z.infer< ReturnType> >; diff --git a/src/geometry/geometry_collection.ts b/src/geometry/geometry_collection.ts index 7954075..cb118ad 100644 --- a/src/geometry/geometry_collection.ts +++ b/src/geometry/geometry_collection.ts @@ -1,74 +1,49 @@ import { z } from "zod"; -import { _GeoJSONSimpleGeometryGenericSchema } from "./_simple"; -import { bboxEquals, getBboxForGeometries, INVALID_BBOX_ISSUE } from "./validation/bbox"; -import { getDimensionForGeometry } from "./validation/dimension"; -import { GeoJSONBaseSchema } from "../base"; -import { GeoJSONGeometry } from "./index"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; - -type ValidatableGeometryCollection = { geometries: GeoJSONGeometry[]; bbox?: number[] }; - -const INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE = { - code: "custom" as const, - message: - 'GeoJSON geometry collection object cannot have "geometry", "coordinates", "properties", or "features" keys', -}; - -const INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE = { - code: "custom" as const, - message: "Invalid geometry collection dimensions. All geometries must have the same dimension.", +import { GeoJSONGeometryBaseGenericSchemaType } from "./helper/base"; +import { GeoJSONSimpleGeometryGenericSchema, GeoJSONSimpleGeometryGenericSchemaType } from "./helper/simple"; +import { GeoJSONGeometryTypeSchema } from "./type"; +import { INVALID_BBOX_ISSUE, validBboxForCollection } from "./validation/bbox"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONBaseSchema, GeoJSONBaseSchemaInnerType } from "../base"; +import { INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE, validDimensionsForCollection } from "./validation/dimension"; + +type GeoJSONGeometryCollectionGenericSchemaInnerType

= GeoJSONBaseSchemaInnerType & { + type: z.ZodLiteral; + coordinates: z.ZodOptional; + geometry: z.ZodOptional; + properties: z.ZodOptional; + features: z.ZodOptional; + geometries: z.ZodArray>; }; -function validGeometryCollectionKeys(collection: Record): boolean { - return ( - !("coordinates" in collection) && - !("geometry" in collection) && - !("properties" in collection) && - !("features" in collection) - ); -} - -function validGeometryCollectionDimension({ geometries }: ValidatableGeometryCollection): boolean { - if (geometries == null) return false; - let dimension = getDimensionForGeometry(geometries[0]); - return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); -} - -function validGeometryCollectionBbox({ bbox, geometries }: ValidatableGeometryCollection): boolean { - if (!bbox) { - return true; - } - const expectedBbox = getBboxForGeometries(geometries); - return bboxEquals(bbox, expectedBbox); -} - -const _GeoJSONGeometryCollectionBaseSchema = GeoJSONBaseSchema.extend({ - type: z.literal("GeometryCollection"), -}); - -export const GeoJSONGeometryCollectionGenericSchema =

(positionSchema: z.ZodSchema

) => - _GeoJSONGeometryCollectionBaseSchema - .extend({ - geometries: _GeoJSONSimpleGeometryGenericSchema(positionSchema).array(), - }) +export type GeoJSONGeometryCollectionGenericSchemaType

= + GeoJSONGeometryBaseGenericSchemaType>; + +export const GeoJSONGeometryCollectionGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONGeometryCollectionGenericSchemaType

=> + GeoJSONBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.GeometryCollection), + coordinates: z.never({ message: "GeoJSON geometry collection cannot have a 'coordinates' key" }).optional(), + geometry: z.never({ message: "GeoJSON geometry collection cannot have a 'geometry' key" }).optional(), + properties: z.never({ message: "GeoJSON geometry collection cannot have a 'properties' key" }).optional(), + features: z.never({ message: "GeoJSON geometry collection cannot have a 'features' key" }).optional(), + // > The value of "geometries" is an array. Each element of this array is a + // GeoJSON Geometry object. It is possible for this array to be empty. (RFC 7946, section 3.1.8) + // > To maximize interoperability, implementations SHOULD avoid nested + // GeometryCollections. (RFC 7946, section 3.1.8) + geometries: GeoJSONSimpleGeometryGenericSchema(positionSchema).array(), + }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryCollectionKeys(val)) { - ctx.addIssue(INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE); - return; - } if (!val.geometries.length) { return; } - - // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics - if (!validGeometryCollectionDimension(val as ValidatableGeometryCollection)) { + if (!validDimensionsForCollection(val)) { ctx.addIssue(INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE); return; } - - // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics - if (!validGeometryCollectionBbox(val as ValidatableGeometryCollection)) { + if (!validBboxForCollection(val)) { ctx.addIssue(INVALID_BBOX_ISSUE); return; } diff --git a/src/geometry/helper/base.ts b/src/geometry/helper/base.ts new file mode 100644 index 0000000..12b3db1 --- /dev/null +++ b/src/geometry/helper/base.ts @@ -0,0 +1,41 @@ +import { objectInputType, objectOutputType, objectUtil, z } from "zod"; +import { GeoJSONBase, GeoJSONBaseSchema, GeoJSONBaseSchemaInnerType } from "../../base"; + +export type GeoJSONGeometryBase = GeoJSONBase & { + geometry?: never; + properties?: never; + features?: never; + geometries?: never; +}; + +export type GeoJSONGeometryBaseSchemaInnerType = GeoJSONBaseSchemaInnerType & { + geometry: z.ZodOptional; + properties: z.ZodOptional; + features: z.ZodOptional; + geometries: z.ZodOptional; +}; + +export type GeoJSONGeometryBaseSchemaType = z.ZodObject< + GeoJSONGeometryBaseSchemaInnerType, + "strip", + z.ZodTypeAny, + GeoJSONGeometryBase, + GeoJSONGeometryBase +>; + +export const GeoJSONGeometryBaseSchema: GeoJSONGeometryBaseSchemaType = GeoJSONBaseSchema.extend({ + geometry: z.never({ message: "GeoJSON geometry cannot have a 'geometry' key" }).optional(), + properties: z.never({ message: "GeoJSON geometry cannot have a 'properties' key" }).optional(), + features: z.never({ message: "GeoJSON geometry cannot have a 'features' key" }).optional(), + geometries: z.never({ message: "GeoJSON geometry cannot have a 'geometries' key" }).optional(), +}); + +type ExtendGeoJSONGeometryBaseSchemaInnerType = objectUtil.extendShape< + GeoJSONGeometryBaseSchemaInnerType, + InnerType +>; +export type GeoJSONGeometryBaseGenericSchemaType = z.ZodEffects< + z.ZodObject, "passthrough", z.ZodTypeAny>, + objectOutputType, z.ZodTypeAny, "passthrough">, + objectInputType, z.ZodTypeAny, "passthrough"> +>; diff --git a/src/geometry/helper/simple.ts b/src/geometry/helper/simple.ts new file mode 100644 index 0000000..b090f33 --- /dev/null +++ b/src/geometry/helper/simple.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { GeoJSONPosition } from "../position"; +import { GeoJSONLineStringGenericSchema, GeoJSONLineStringGenericSchemaType } from "../line_string"; +import { GeoJSONMultiLineStringGenericSchema, GeoJSONMultiLineStringGenericSchemaType } from "../multi_line_string"; +import { GeoJSONMultiPointGenericSchema, GeoJSONMultiPointGenericSchemaType } from "../multi_point"; +import { GeoJSONMultiPolygonGenericSchema, GeoJSONMultiPolygonGenericSchemaType } from "../multi_polygon"; +import { GeoJSONPointGenericSchema, GeoJSONPointGenericSchemaType } from "../point"; +import { GeoJSONPolygonGenericSchema, GeoJSONPolygonGenericSchemaType } from "../polygon"; + +export type GeoJSONSimpleGeometryGenericSchemaType

= z.ZodUnion< + [ + GeoJSONPointGenericSchemaType

, + GeoJSONLineStringGenericSchemaType

, + GeoJSONMultiPointGenericSchemaType

, + GeoJSONPolygonGenericSchemaType

, + GeoJSONMultiLineStringGenericSchemaType

, + GeoJSONMultiPolygonGenericSchemaType

, + ] +>; + +export const GeoJSONSimpleGeometryGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONSimpleGeometryGenericSchemaType

=> + z.union([ + GeoJSONPointGenericSchema(positionSchema), + GeoJSONLineStringGenericSchema(positionSchema), + GeoJSONMultiPointGenericSchema(positionSchema), + GeoJSONPolygonGenericSchema(positionSchema), + GeoJSONMultiLineStringGenericSchema(positionSchema), + GeoJSONMultiPolygonGenericSchema(positionSchema), + ]); diff --git a/src/geometry/line_string.ts b/src/geometry/line_string.ts index 04b6f90..560b9e9 100644 --- a/src/geometry/line_string.ts +++ b/src/geometry/line_string.ts @@ -1,22 +1,30 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; -import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionList } from "./validation/dimension"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; import { INVALID_BBOX_ISSUE, validBboxForPositionList } from "./validation/bbox"; -import { GeoJSONBaseSchema } from "../base"; +import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionList } from "./validation/dimension"; + +export type GeoJSONLineStringGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodTuple<[z.ZodSchema

, z.ZodSchema

], z.ZodSchema

>; +}; + +export type GeoJSONLineStringGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONLineStringGenericSchemaInnerType

+>; -export const GeoJSONLineStringGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("LineString"), - coordinates: z.array(positionSchema).min(2), +export const GeoJSONLineStringGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONLineStringGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.LineString), + // > For type "LineString", the "coordinates" member is an array of two or + // more positions. (RFC 7946, section 3.1.4) + coordinates: z.tuple([positionSchema, positionSchema]).rest(positionSchema), }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } - // Skip remaining checks if coordinates empty if (!val.coordinates.length) { return; diff --git a/src/geometry/multi_line_string.ts b/src/geometry/multi_line_string.ts index e7048cc..82f3a40 100644 --- a/src/geometry/multi_line_string.ts +++ b/src/geometry/multi_line_string.ts @@ -1,22 +1,32 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; import { INVALID_BBOX_ISSUE, validBboxForPositionGrid } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGrid } from "./validation/dimension"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONLineStringGenericSchema } from "./line_string"; -import { GeoJSONBaseSchema } from "../base"; +import { GeoJSONLineStringGenericSchema, GeoJSONLineStringGenericSchemaInnerType } from "./line_string"; -export const GeoJSONMultiLineStringGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("MultiLineString"), - coordinates: z.array(GeoJSONLineStringGenericSchema(positionSchema).innerType().shape.coordinates).min(1), +type GeoJSONMultiLineStringGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodArray["coordinates"]>; +}; + +export type GeoJSONMultiLineStringGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONMultiLineStringGenericSchemaInnerType

+>; + +export const GeoJSONMultiLineStringGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONMultiLineStringGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.MultiLineString), + // We allow an empty coordinates array + // > GeoJSON processors MAY interpret Geometry objects with empty "coordinates" + // arrays as null objects. (RFC 7946, section 3.1) + coordinates: z.array(GeoJSONLineStringGenericSchema(positionSchema).innerType().shape.coordinates), }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } // Skip remaining checks if coordinates array is empty if (!val.coordinates.length) { return; diff --git a/src/geometry/multi_point.ts b/src/geometry/multi_point.ts index 38ea2db..155b23a 100644 --- a/src/geometry/multi_point.ts +++ b/src/geometry/multi_point.ts @@ -1,21 +1,32 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; +import { GeoJSONPointGenericSchemaInnerType } from "./point"; import { INVALID_BBOX_ISSUE, validBboxForPositionList } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionList } from "./validation/dimension"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONMultiPointGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("MultiPoint"), - coordinates: z.array(positionSchema).min(1), +type GeoJSONMultiPointGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodArray["coordinates"]>; +}; + +export type GeoJSONMultiPointGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONMultiPointGenericSchemaInnerType

+>; + +export const GeoJSONMultiPointGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONMultiPointGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.MultiPoint), + // We allow an empty coordinates array + // > GeoJSON processors MAY interpret Geometry objects with empty "coordinates" + // arrays as null objects. (RFC 7946, section 3.1) + coordinates: z.array(positionSchema), }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } // Skip remaining checks if coordinates empty if (!val.coordinates.length) { return; diff --git a/src/geometry/multi_polygon.ts b/src/geometry/multi_polygon.ts index 19edab3..d15430b 100644 --- a/src/geometry/multi_polygon.ts +++ b/src/geometry/multi_polygon.ts @@ -1,31 +1,33 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; +import { GeoJSONPolygonGenericSchema, GeoJSONPolygonGenericSchemaInnerType } from "./polygon"; import { INVALID_BBOX_ISSUE, validBboxForPositionGridList } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGridList } from "./validation/dimension"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONPolygonGenericSchema, validPolygonRings } from "./polygon"; -import { GeoJSONBaseSchema } from "../base"; +import { INVALID_MULTI_POLYGON_LINEAR_RING_MESSAGE, validMultiPolygonLinearRings } from "./validation/linear_ring"; -const INVALID_LINEAR_RING_MESSAGE = { - code: "custom" as const, - message: "Invalid multi polygon. Each polygon inside the multi polygon must be made out of linear rings.", +type GeoJSONMultiPolygonGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodArray["coordinates"]>; }; -function validMultiPolygonLinearRings({ coordinates }: { coordinates: number[][][][] }) { - return coordinates.every((polygon) => validPolygonRings({ coordinates: polygon })); -} - -export const GeoJSONMultiPolygonGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("MultiPolygon"), - coordinates: z.array(GeoJSONPolygonGenericSchema(positionSchema).innerType().shape.coordinates).min(1), +export type GeoJSONMultiPolygonGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONMultiPolygonGenericSchemaInnerType

+>; + +export const GeoJSONMultiPolygonGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONMultiPolygonGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.MultiPolygon), + // We allow an empty coordinates array: + // > GeoJSON processors MAY interpret Geometry objects with empty "coordinates" + // arrays as null objects. (RFC 7946, section 3.1) + coordinates: z.array(GeoJSONPolygonGenericSchema(positionSchema).innerType().shape.coordinates), }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } // Skip remaining checks if coordinates array is empty if (!val.coordinates.length) { return; @@ -37,7 +39,7 @@ export const GeoJSONMultiPolygonGenericSchema =

(posi } if (!validMultiPolygonLinearRings(val)) { - ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); + ctx.addIssue(INVALID_MULTI_POLYGON_LINEAR_RING_MESSAGE); return; } diff --git a/src/geometry/point.ts b/src/geometry/point.ts index 72ed8ca..880063d 100644 --- a/src/geometry/point.ts +++ b/src/geometry/point.ts @@ -1,19 +1,27 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; import { INVALID_BBOX_ISSUE, validBboxForPosition } from "./validation/bbox"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONPointGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("Point"), +export type GeoJSONPointGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodSchema

; +}; + +export type GeoJSONPointGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONPointGenericSchemaInnerType

+>; + +export const GeoJSONPointGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONPointGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.Point), coordinates: positionSchema, }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - } if (!validBboxForPosition(val)) { ctx.addIssue(INVALID_BBOX_ISSUE); } diff --git a/src/geometry/polygon.ts b/src/geometry/polygon.ts index d6a6738..b1d8740 100644 --- a/src/geometry/polygon.ts +++ b/src/geometry/polygon.ts @@ -1,36 +1,37 @@ import { z } from "zod"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; +import { GeoJSONGeometryBaseGenericSchemaType, GeoJSONGeometryBaseSchema } from "./helper/base"; +import { GeoJSONGeometryTypeSchema } from "./type"; import { INVALID_BBOX_ISSUE, validBboxForPositionGrid } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGrid } from "./validation/dimension"; -import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONBaseSchema } from "../base"; +import { INVALID_POLYGON_LINEAR_RING_MESSAGE, validPolygonRings } from "./validation/linear_ring"; -const INVALID_LINEAR_RING_MESSAGE = { - code: "custom" as const, - message: "Invalid polygon. Each ring inside a polygon must form a linear ring.", +export type GeoJSONPolygonGenericSchemaInnerType

= { + type: z.ZodLiteral; + coordinates: z.ZodArray< + z.ZodTuple<[z.ZodSchema

, z.ZodSchema

, z.ZodSchema

, z.ZodSchema

], z.ZodSchema

> + >; }; -function validLinearRing(linearRing: number[][]): boolean { - const firstPosition = linearRing[0]; - const lastPosition = linearRing[linearRing.length - 1]; - return firstPosition.every((value, index) => value === lastPosition[index]); -} - -export function validPolygonRings({ coordinates: rings }: { coordinates: number[][][] }): boolean { - return rings.every(validLinearRing); -} - -export const GeoJSONPolygonGenericSchema =

(positionSchema: z.ZodSchema

) => - GeoJSONBaseSchema.extend({ - type: z.literal("Polygon"), - coordinates: z.array(z.array(positionSchema).min(4)), +export type GeoJSONPolygonGenericSchemaType

= GeoJSONGeometryBaseGenericSchemaType< + GeoJSONPolygonGenericSchemaInnerType

+>; + +export const GeoJSONPolygonGenericSchema =

( + positionSchema: z.ZodSchema

, +): GeoJSONPolygonGenericSchemaType

=> + GeoJSONGeometryBaseSchema.extend({ + type: z.literal(GeoJSONGeometryTypeSchema.enum.Polygon), + // We allow an empty coordinates array + // > GeoJSON processors MAY interpret Geometry objects with empty "coordinates" + // arrays as null objects. (RFC 7946, section 3.1) + coordinates: z.array( + // > A linear ring is a closed LineString with four or more positions (RFC 7946, section 3.1.6) + z.tuple([positionSchema, positionSchema, positionSchema, positionSchema]).rest(positionSchema), + ), }) .passthrough() .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } // Skip remaining checks if coordinates array is empty if (!val.coordinates.length) { return; @@ -41,7 +42,7 @@ export const GeoJSONPolygonGenericSchema =

(positionS return; } if (!validPolygonRings(val)) { - ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); + ctx.addIssue(INVALID_POLYGON_LINEAR_RING_MESSAGE); return; } if (!validBboxForPositionGrid(val)) { diff --git a/src/position.ts b/src/geometry/position.ts similarity index 54% rename from src/position.ts rename to src/geometry/position.ts index b1afa8f..ac1db15 100644 --- a/src/position.ts +++ b/src/geometry/position.ts @@ -1,16 +1,18 @@ import { z } from "zod"; -// GeoJSON positions and coordinates (see 3.1.1) -// Array: [longitude/easting, latitude/northing, altitude (optional), ...extra elements are unspecified and ambiguous] +// General GeoJSON position +// > A position is an array of numbers. There MUST be two or more +// elements. The first two elements are longitude and latitude, or +// easting and northing, precisely in that order and using decimal +// numbers. Altitude or elevation MAY be included as an optional third +// element (RFC 7946, Section 3.1.1) export const GeoJSONPositionSchema = z.tuple([z.number(), z.number()]).rest(z.number()); export type GeoJSONPosition = z.infer; -// Specific GeoJSON positions for 2 dimensions -// These are used to define the 2D geometries, features, and collections +// Specific 2D GeoJSON position export const GeoJSON2DPositionSchema = z.tuple([z.number(), z.number()]); export type GeoJSON2DPosition = z.infer; -// Specific GeoJSON positions for 3 dimensions -// These are used to define the 3D geometries, features, and collections +// Specific 3D GeoJSON position export const GeoJSON3DPositionSchema = z.tuple([z.number(), z.number(), z.number()]); export type GeoJSON3DPosition = z.infer; diff --git a/src/geometry/type.ts b/src/geometry/type.ts new file mode 100644 index 0000000..911e6f8 --- /dev/null +++ b/src/geometry/type.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; + +export const GeoJSONGeometryTypeSchema = z.enum([ + "Point", + "MultiPoint", + "LineString", + "MultiLineString", + "Polygon", + "MultiPolygon", + "GeometryCollection", +]); + +// The type of the enum object +export type GeoJSONGeometryEnumType = typeof GeoJSONGeometryTypeSchema.enum; +// The string literal type of the values in the enum +export type GeoJSONGeometryType = z.infer; diff --git a/src/geometry/validation/bbox.ts b/src/geometry/validation/bbox.ts index 496e5c1..05d4e1a 100644 --- a/src/geometry/validation/bbox.ts +++ b/src/geometry/validation/bbox.ts @@ -1,16 +1,28 @@ -import { GeoJSONGeometry } from "../index"; +import { GeoJSONPosition } from "../position"; +import { + ValidatableCollection, + ValidatableCoordinate, + ValidatableGeometry, + ValidatableGrid, + ValidatableGridList, + ValidatableList, +} from "./types"; -type BboxPositionOptions = { bbox?: number[]; coordinates?: number[] }; -type BboxPositionListOptions = { bbox?: number[]; coordinates?: number[][] }; -type BboxPositionGridOptions = { bbox?: number[]; coordinates?: number[][][] }; -type BboxPositionGridListOptions = { bbox?: number[]; coordinates?: number[][][][] }; +export const INVALID_BBOX_ISSUE = { + code: "custom" as const, + message: + "Invalid bbox. Bbox length must be 2 * n, where n is the dimension of the geometry. Bbox must be a valid extent for the geometry.", +}; + +// Easier to work with a simple array than a GeoJSONBbox object +type ValidatableBbox = number[]; /** * Checks if given bbox is valid for the given position. * @param bbox The bbox to validate * @param coordinates Contains the position */ -export function validBboxForPosition({ bbox, coordinates }: BboxPositionOptions): boolean { +export function validBboxForPosition({ bbox, coordinates }: ValidatableCoordinate): boolean { if (bbox == null) return true; if (coordinates == null) return false; @@ -26,7 +38,7 @@ export function validBboxForPosition({ bbox, coordinates }: BboxPositionOptions) * @param bbox The bbox to validate * @param coordinates Contains the list of positions */ -export function validBboxForPositionList({ bbox, coordinates }: BboxPositionListOptions): boolean { +export function validBboxForPositionList({ bbox, coordinates }: ValidatableList): boolean { if (bbox == null) return true; if (coordinates == null) return false; @@ -35,9 +47,7 @@ export function validBboxForPositionList({ bbox, coordinates }: BboxPositionList return false; } - const expectedBbox: number[] = []; - updateBboxForPositionList(expectedBbox, coordinates); - return bboxEquals(bbox, expectedBbox); + return bboxEquals(bbox, updateBboxForPositionList([], coordinates)); } /** @@ -45,7 +55,7 @@ export function validBboxForPositionList({ bbox, coordinates }: BboxPositionList * @param bbox The bbox to validate * @param coordinates Contains the grid of positions */ -export function validBboxForPositionGrid({ bbox, coordinates }: BboxPositionGridOptions): boolean { +export function validBboxForPositionGrid({ bbox, coordinates }: ValidatableGrid): boolean { if (bbox == null) return true; if (coordinates == null) return false; @@ -53,10 +63,7 @@ export function validBboxForPositionGrid({ bbox, coordinates }: BboxPositionGrid if (bbox.length !== 2 * dimension) { return false; } - - const expectedBbox: number[] = []; - updateBboxForPositionGrid(expectedBbox, coordinates); - return bboxEquals(bbox, expectedBbox); + return bboxEquals(bbox, updateBboxForPositionGrid([], coordinates)); } /** @@ -64,7 +71,7 @@ export function validBboxForPositionGrid({ bbox, coordinates }: BboxPositionGrid * @param bbox The bbox to validate * @param coordinates Contains the grid of positions */ -export function validBboxForPositionGridList({ bbox, coordinates }: BboxPositionGridListOptions): boolean { +export function validBboxForPositionGridList({ bbox, coordinates }: ValidatableGridList): boolean { if (bbox == null) return true; if (coordinates == null) return false; @@ -73,12 +80,18 @@ export function validBboxForPositionGridList({ bbox, coordinates }: BboxPosition return false; } - const expectedBbox: number[] = []; - updateBboxForPositionGridList(expectedBbox, coordinates); + return bboxEquals(bbox, updateBboxForPositionGridList([], coordinates)); +} + +export function validBboxForCollection({ bbox, geometries }: ValidatableCollection): boolean { + if (!bbox) { + return true; + } + const expectedBbox = getBboxForGeometries(geometries); return bboxEquals(bbox, expectedBbox); } -export function getBboxForGeometry(geometry: GeoJSONGeometry): number[] { +export function getBboxForGeometry(geometry: ValidatableGeometry): ValidatableBbox { switch (geometry.type) { case "Point": return updateBboxForPosition([], geometry.coordinates); @@ -95,11 +108,11 @@ export function getBboxForGeometry(geometry: GeoJSONGeometry): number[] { } } -export function getBboxForGeometries(geometries: GeoJSONGeometry[]): number[] { +export function getBboxForGeometries(geometries: ValidatableGeometry[]): ValidatableBbox { return mergeBboxs(geometries.map(getBboxForGeometry)); } -export function bboxEquals(bbox1: number[], bbox2: number[]): boolean { +export function bboxEquals(bbox1: ValidatableBbox, bbox2: ValidatableBbox): boolean { if (bbox1.length !== bbox2.length) { return false; } @@ -109,33 +122,42 @@ export function bboxEquals(bbox1: number[], bbox2: number[]): boolean { /** * NOTE: Mutates the given bbox. Performance optimisation to avoid unnecessary copies. */ -function updateBboxForPositionGridList(currentBbox: number[], positions: number[][][][]): number[] { - positions.forEach((positionGrid) => updateBboxForPositionGrid(currentBbox, positionGrid)); +function updateBboxForPositionGridList( + currentBbox: ValidatableBbox, + positions?: GeoJSONPosition[][][] | null, +): ValidatableBbox { + positions?.forEach((positionGrid) => updateBboxForPositionGrid(currentBbox, positionGrid)); return currentBbox; } /** * NOTE: Mutates the given bbox. Performance optimisation to avoid unnecessary copies. */ -function updateBboxForPositionGrid(currentBbox: number[], positions: number[][][]): number[] { - positions.forEach((positionList) => updateBboxForPositionList(currentBbox, positionList)); +function updateBboxForPositionGrid( + currentBbox: ValidatableBbox, + positions?: GeoJSONPosition[][] | null, +): ValidatableBbox { + positions?.forEach((positionList) => updateBboxForPositionList(currentBbox, positionList)); return currentBbox; } /** * NOTE: Mutates the given bbox. Performance optimisation to avoid unnecessary copies. */ -function updateBboxForPositionList(currentBbox: number[], positions: number[][]): number[] { - positions.forEach((position) => updateBboxForPosition(currentBbox, position)); +function updateBboxForPositionList( + currentBbox: ValidatableBbox, + positions?: GeoJSONPosition[] | null, +): ValidatableBbox { + positions?.forEach((position) => updateBboxForPosition(currentBbox, position)); return currentBbox; } /** * NOTE: Mutates the given bbox. Performance optimisation to avoid unnecessary copies. */ -function updateBboxForPosition(currentBbox: number[], position: number[]): number[] { - const dimension = position.length; - position.forEach((value, index) => { +function updateBboxForPosition(currentBbox: ValidatableBbox, position?: GeoJSONPosition | null): ValidatableBbox { + const dimension = position?.length ?? 0; + position?.forEach((value, index) => { const iMin = currentBbox[index]; const iMax = currentBbox[index + dimension]; if (iMin === undefined || value < iMin) { @@ -148,18 +170,12 @@ function updateBboxForPosition(currentBbox: number[], position: number[]): numbe return currentBbox; } -function mergeBboxs(bboxs: number[][]): number[] { +function mergeBboxs(bboxs: ValidatableBbox[]): ValidatableBbox { const dimension = bboxs[0].length / 2; - const mergedBbox: number[] = []; + const mergedBbox = []; for (let i = 0; i < dimension; i++) { mergedBbox[i] = Math.min(...bboxs.map((bbox) => bbox[i])); mergedBbox[i + dimension] = Math.max(...bboxs.map((bbox) => bbox[i + dimension])); } return mergedBbox; } - -export const INVALID_BBOX_ISSUE = { - code: "custom" as const, - message: - "Invalid bbox. Bbox length must be 2 * n, where n is the dimension of the geometry. Bbox must be a valid extent for the geometry.", -}; diff --git a/src/geometry/validation/dimension.ts b/src/geometry/validation/dimension.ts index 1918d53..a6b9252 100644 --- a/src/geometry/validation/dimension.ts +++ b/src/geometry/validation/dimension.ts @@ -1,54 +1,66 @@ -import { GeoJSONGeometry } from "../index"; +import { GeoJSONPosition } from "../position"; +import { ValidatableCollection, ValidatableGeometry } from "./types"; -export function validDimensionsForPositionList({ coordinates }: { coordinates: number[][] }): boolean { +export const INVALID_DIMENSIONS_ISSUE = { + code: "custom" as const, + message: "Invalid dimensions. All positions in the geometry must have the same dimension.", +}; + +export const INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE = { + code: "custom" as const, + message: "Invalid geometry collection dimensions. All geometries must have the same dimension.", +}; + +export function validDimensionsForPositionList({ coordinates }: { coordinates: GeoJSONPosition[] }): boolean { const dimension = coordinates[0].length; return sameDimensionsForPositions(dimension)(coordinates); } -export function validDimensionsForPositionGrid({ coordinates }: { coordinates: number[][][] }): boolean { +export function validDimensionsForPositionGrid({ coordinates }: { coordinates: GeoJSONPosition[][] }): boolean { let dimension = coordinates[0][0].length; return sameDimensionsForPositionGrid(dimension)(coordinates); } -export function validDimensionsForPositionGridList({ coordinates }: { coordinates: number[][][][] }): boolean { +export function validDimensionsForPositionGridList({ coordinates }: { coordinates: GeoJSONPosition[][][] }): boolean { let dimension = coordinates[0][0][0].length; return sameDimensionsForPositionGrids(dimension)(coordinates); } -export function getDimensionForGeometry(geometry: GeoJSONGeometry): number { +export function validDimensionsForCollection({ geometries }: ValidatableCollection): boolean { + if (geometries == null) return false; + let dimension = getDimensionForGeometry(geometries[0]); + return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); +} + +export function getDimensionForGeometry(geometry: ValidatableGeometry): number { switch (geometry.type) { case "Point": - return geometry.coordinates.length; + return geometry.coordinates?.length ?? 0; case "MultiPoint": case "LineString": - return geometry.coordinates[0].length; + return geometry.coordinates?.[0].length ?? 0; case "MultiLineString": case "Polygon": - return geometry.coordinates[0][0].length; + return geometry.coordinates?.[0][0].length ?? 0; case "MultiPolygon": - return geometry.coordinates[0][0][0].length; + return geometry.coordinates?.[0][0][0].length ?? 0; case "GeometryCollection": return getDimensionForGeometry(geometry.geometries[0]); } } -function sameDimensionsForPosition(dimension: number): (position: number[]) => boolean { +function sameDimensionsForPosition(dimension: number): (position: GeoJSONPosition) => boolean { return (position) => position.length === dimension; } -function sameDimensionsForPositions(dimension: number): (positions: number[][]) => boolean { +function sameDimensionsForPositions(dimension: number): (positions: GeoJSONPosition[]) => boolean { return (positions) => positions.every(sameDimensionsForPosition(dimension)); } -function sameDimensionsForPositionGrid(dimension: number): (positionGrid: number[][][]) => boolean { +function sameDimensionsForPositionGrid(dimension: number): (positionGrid: GeoJSONPosition[][]) => boolean { return (positionGrid) => positionGrid.every(sameDimensionsForPositions(dimension)); } -function sameDimensionsForPositionGrids(dimension: number): (positionGrids: number[][][][]) => boolean { +function sameDimensionsForPositionGrids(dimension: number): (positionGrids: GeoJSONPosition[][][]) => boolean { return (positionGrids) => positionGrids.every(sameDimensionsForPositionGrid(dimension)); } - -export const INVALID_DIMENSIONS_ISSUE = { - code: "custom" as const, - message: "Invalid dimensions. All positions in the geometry must have the same dimension.", -}; diff --git a/src/geometry/validation/keys.ts b/src/geometry/validation/keys.ts deleted file mode 100644 index 60e8958..0000000 --- a/src/geometry/validation/keys.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function validGeometryKeys(geometry: Record): boolean { - return ( - !("geometry" in geometry) && - !("properties" in geometry) && - !("features" in geometry) && - !("geometries" in geometry) - ); -} - -export const INVALID_KEYS_ISSUE = { - code: "custom" as const, - message: 'GeoJSON geometry object cannot have "geometry", "properties", "features", or "geometries" keys', -}; diff --git a/src/geometry/validation/linear_ring.ts b/src/geometry/validation/linear_ring.ts new file mode 100644 index 0000000..8ab24ac --- /dev/null +++ b/src/geometry/validation/linear_ring.ts @@ -0,0 +1,25 @@ +import { GeoJSONPosition } from "../position"; + +export const INVALID_POLYGON_LINEAR_RING_MESSAGE = { + code: "custom" as const, + message: "Invalid polygon. Each ring inside a polygon must form a linear ring.", +}; + +export const INVALID_MULTI_POLYGON_LINEAR_RING_MESSAGE = { + code: "custom" as const, + message: "Invalid multi polygon. Each polygon inside the multi polygon must be made out of linear rings.", +}; + +function validLinearRing(linearRing: GeoJSONPosition[]): boolean { + const firstPosition = linearRing[0]; + const lastPosition = linearRing[linearRing.length - 1]; + return firstPosition.every((value, index) => value === lastPosition[index]); +} + +export function validPolygonRings({ coordinates: rings }: { coordinates: GeoJSONPosition[][] }): boolean { + return rings.every(validLinearRing); +} + +export function validMultiPolygonLinearRings({ coordinates }: { coordinates: GeoJSONPosition[][][] }) { + return coordinates.every((polygon) => validPolygonRings({ coordinates: polygon })); +} diff --git a/src/geometry/validation/types.ts b/src/geometry/validation/types.ts new file mode 100644 index 0000000..751fd4a --- /dev/null +++ b/src/geometry/validation/types.ts @@ -0,0 +1,39 @@ +import { GeoJSONBbox } from "../../bbox"; +import { GeoJSONPosition } from "../position"; +import { GeoJSONGeometryEnumType } from "../type"; + +/** + * The bare essential types required to validate geometries + * Mainly used to prevent circular dependencies between geometry type definitions and validation functions + */ + +export type ValidatableCoordinate = { + type: GeoJSONGeometryEnumType["Point"]; + bbox?: GeoJSONBbox | null; + coordinates?: GeoJSONPosition | null; +}; +export type ValidatableList = { + type: GeoJSONGeometryEnumType["MultiPoint"] | GeoJSONGeometryEnumType["LineString"]; + bbox?: GeoJSONBbox | null; + coordinates?: GeoJSONPosition[] | null; +}; +export type ValidatableGrid = { + type: GeoJSONGeometryEnumType["MultiLineString"] | GeoJSONGeometryEnumType["Polygon"]; + bbox?: GeoJSONBbox | null; + coordinates?: GeoJSONPosition[][] | null; +}; +export type ValidatableGridList = { + type: GeoJSONGeometryEnumType["MultiPolygon"]; + bbox?: GeoJSONBbox | null; + coordinates?: GeoJSONPosition[][][] | null; +}; + +export type ValidatableSimpleGeometry = ValidatableCoordinate | ValidatableList | ValidatableGrid | ValidatableGridList; + +export type ValidatableCollection = { + type: GeoJSONGeometryEnumType["GeometryCollection"]; + geometries: ValidatableSimpleGeometry[]; + bbox?: GeoJSONBbox | null; +}; + +export type ValidatableGeometry = ValidatableSimpleGeometry | ValidatableCollection; diff --git a/src/index.ts b/src/index.ts index 938b8ad..4ac2445 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,31 +1,128 @@ // Derived from the GeoJSON spec: https://datatracker.ietf.org/doc/html/rfc7946 -import { z } from "zod"; -import { GeoJSONFeatureGenericSchema } from "./feature"; -import { GeoJSONFeatureCollectionGenericSchema } from "./feature_collection"; -import { GeoJSONGeometryGenericSchema } from "./geometry"; -import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; - -export * from "./feature"; -export * from "./feature_collection"; -export * from "./geometry"; -export * from "./bbox"; -export * from "./position"; -export * from "./type"; - -export const GeoJSONGenericSchema =

(positionSchema: z.ZodSchema

) => - z.union([ - GeoJSONGeometryGenericSchema(positionSchema), - GeoJSONFeatureGenericSchema(positionSchema), - GeoJSONFeatureCollectionGenericSchema(positionSchema), - ]); - -export const GeoJSONSchema = GeoJSONGenericSchema(GeoJSONPositionSchema); -export type GeoJSON = z.infer; - -export const GeoJSON2DSchema = GeoJSONGenericSchema(GeoJSON2DPositionSchema); -export type GeoJSON2D = z.infer; - -export const GeoJSON3DSchema = GeoJSONGenericSchema(GeoJSON3DPositionSchema); -export type GeoJSON3D = z.infer; - -// TODO: Improve error messages +// TODO: Add negative typing examples for all types (like in point tests) +// TODO: Make sure each exposed type & schema is tested + +export { + GeoJSONGeometryGenericSchema, + GeoJSONGeometrySchema, + type GeoJSONGeometry, + GeoJSON2DGeometrySchema, + type GeoJSON2DGeometry, + GeoJSON3DGeometrySchema, + type GeoJSON3DGeometry, +} from "./geometry/geometry"; + +export { + GeoJSONGeometryCollectionGenericSchema, + GeoJSONGeometryCollectionSchema, + type GeoJSONGeometryCollection, + GeoJSON2DGeometryCollectionSchema, + type GeoJSON2DGeometryCollection, + GeoJSON3DGeometryCollectionSchema, + type GeoJSON3DGeometryCollection, +} from "./geometry/geometry_collection"; + +export { + GeoJSONLineStringGenericSchema, + GeoJSONLineStringSchema, + type GeoJSONLineString, + GeoJSON2DLineStringSchema, + type GeoJSON2DLineString, + GeoJSON3DLineStringSchema, + type GeoJSON3DLineString, +} from "./geometry/line_string"; + +export { + GeoJSONMultiLineStringGenericSchema, + GeoJSONMultiLineStringSchema, + type GeoJSONMultiLineString, + GeoJSON2DMultiLineStringSchema, + type GeoJSON2DMultiLineString, + GeoJSON3DMultiLineStringSchema, + type GeoJSON3DMultiLineString, +} from "./geometry/multi_line_string"; + +export { + GeoJSONMultiPointGenericSchema, + GeoJSONMultiPointSchema, + type GeoJSONMultiPoint, + GeoJSON2DMultiPointSchema, + type GeoJSON2DMultiPoint, + GeoJSON3DMultiPointSchema, + type GeoJSON3DMultiPoint, +} from "./geometry/multi_point"; + +export { + GeoJSONMultiPolygonGenericSchema, + GeoJSONMultiPolygonSchema, + type GeoJSONMultiPolygon, + GeoJSON2DMultiPolygonSchema, + type GeoJSON2DMultiPolygon, + GeoJSON3DMultiPolygonSchema, + type GeoJSON3DMultiPolygon, +} from "./geometry/multi_polygon"; + +export { + GeoJSONPointGenericSchema, + GeoJSONPointSchema, + type GeoJSONPoint, + GeoJSON2DPointSchema, + type GeoJSON2DPoint, + GeoJSON3DPointSchema, + type GeoJSON3DPoint, +} from "./geometry/point"; + +export { + GeoJSONPolygonGenericSchema, + GeoJSONPolygonSchema, + type GeoJSONPolygon, + GeoJSON2DPolygonSchema, + type GeoJSON2DPolygon, + GeoJSON3DPolygonSchema, + type GeoJSON3DPolygon, +} from "./geometry/polygon"; + +export { + GeoJSONPositionSchema, + type GeoJSONPosition, + GeoJSON2DPositionSchema, + type GeoJSON2DPosition, + GeoJSON3DPositionSchema, + type GeoJSON3DPosition, +} from "./geometry/position"; + +export { GeoJSONGeometryTypeSchema, type GeoJSONGeometryEnumType, type GeoJSONGeometryType } from "./geometry/type"; + +export { GeoJSONBboxSchema, type GeoJSONBbox } from "./bbox"; + +export { + GeoJSONFeatureGenericSchema, + GeoJSONFeatureSchema, + type GeoJSONFeature, + GeoJSON2DFeatureSchema, + type GeoJSON2DFeature, + GeoJSON3DFeatureSchema, + type GeoJSON3DFeature, +} from "./feature"; + +export { + GeoJSONFeatureCollectionGenericSchema, + GeoJSONFeatureCollectionSchema, + type GeoJSONFeatureCollection, + GeoJSON2DFeatureCollectionSchema, + type GeoJSON2DFeatureCollection, + GeoJSON3DFeatureCollectionSchema, + type GeoJSON3DFeatureCollection, +} from "./feature_collection"; + +export { + GeoJSONGenericSchema, + GeoJSONSchema, + type GeoJSON, + GeoJSON2DSchema, + type GeoJSON2D, + GeoJSON3DSchema, + type GeoJSON3D, +} from "./geojson"; + +export { GeoJSONTypeSchema, type GeoJSONType } from "./type"; diff --git a/src/type.ts b/src/type.ts index 9bc91bd..cfecf91 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,18 +1,7 @@ // GeoJSON types and Geometry type (see 1.4) import { z } from "zod"; +import { GeoJSONGeometryTypeSchema } from "./geometry/type"; -export const GeoJSONGeometryTypeSchema = z.enum([ - "Point", - "MultiPoint", - "LineString", - "MultiLineString", - "Polygon", - "MultiPolygon", - "GeometryCollection", -]); - -export type GeoJSONGeometryType = z.infer; - -export const GeoJSONTypeSchema = z.enum(["Feature", "FeatureCollection"]).or(GeoJSONGeometryTypeSchema); +export const GeoJSONTypeSchema = z.enum(["Feature", "FeatureCollection", ...GeoJSONGeometryTypeSchema.options]); export type GeoJSONType = z.infer; diff --git a/src/validation/bbox.ts b/src/validation/bbox.ts new file mode 100644 index 0000000..23019b4 --- /dev/null +++ b/src/validation/bbox.ts @@ -0,0 +1,18 @@ +import { bboxEquals, getBboxForGeometries, getBboxForGeometry } from "../geometry/validation/bbox"; +import { getGeometries, ValidatableFeature, ValidatableFeatureCollection } from "./types"; + +export function validBboxForFeature({ bbox, geometry }: ValidatableFeature): boolean { + if (!bbox || !geometry) { + return true; + } + const expectedBbox = getBboxForGeometry(geometry); + return bboxEquals(expectedBbox, bbox); +} + +export function validBboxForFeatureCollection({ features, bbox }: ValidatableFeatureCollection) { + if (!bbox) { + return true; + } + const expectedBbox = getBboxForGeometries(getGeometries({ features })); + return bboxEquals(expectedBbox, bbox); +} diff --git a/src/validation/dimension.ts b/src/validation/dimension.ts new file mode 100644 index 0000000..5ba6051 --- /dev/null +++ b/src/validation/dimension.ts @@ -0,0 +1,13 @@ +import { getDimensionForGeometry } from "../geometry/validation/dimension"; +import { getGeometries, ValidatableFeatureCollection } from "./types"; + +export const INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE = { + code: "custom" as const, + message: "Invalid dimensions. All features in feature collection must have the same dimension.", +}; + +export function validDimensionsForFeatureCollection(collection: ValidatableFeatureCollection): boolean { + const geometries = getGeometries(collection); + const dimension = getDimensionForGeometry(geometries[0]); + return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); +} diff --git a/src/validation/types.ts b/src/validation/types.ts new file mode 100644 index 0000000..9723f64 --- /dev/null +++ b/src/validation/types.ts @@ -0,0 +1,21 @@ +/** + * The bare essential types required to validate features & feature collections + * Mainly used to prevent circular dependencies between feature type definitions and validation functions + */ + +import { GeoJSONBbox } from "../bbox"; +import { ValidatableCollection, ValidatableGeometry } from "../geometry/validation/types"; + +export type ValidatableFeature = { + bbox?: GeoJSONBbox | null; + geometry?: ValidatableGeometry | ValidatableCollection | null; +}; + +export type ValidatableFeatureCollection = { + bbox?: GeoJSONBbox | null; + features?: ValidatableFeature[] | null; +}; + +export function getGeometries({ features }: Pick): ValidatableGeometry[] { + return features?.map((feature) => feature.geometry).filter((x): x is ValidatableGeometry => x != null) ?? []; +} diff --git a/tests/bbox.test.ts b/tests/bbox.test.ts index 2b37c98..d630f62 100644 --- a/tests/bbox.test.ts +++ b/tests/bbox.test.ts @@ -1,43 +1,43 @@ import { describe, expect, it } from "@jest/globals"; import { ZodError } from "zod"; import { bbox2D, bbox3D, bbox4D } from "../examples/bbox"; -import { GeoJSONBBoxSchema, GeoJSONBbox } from "../src"; +import { GeoJSONBboxSchema, GeoJSONBbox } from "../src"; describe("GeoJSONBBox", () => { it("allows 2D bbox", () => { - expect(GeoJSONBBoxSchema.parse(bbox2D)).toEqual(bbox2D); + expect(GeoJSONBboxSchema.parse(bbox2D)).toEqual(bbox2D); }); it("allows 3D bbox", () => { - expect(GeoJSONBBoxSchema.parse(bbox3D)).toEqual(bbox3D); + expect(GeoJSONBboxSchema.parse(bbox3D)).toEqual(bbox3D); }); it("allows 4D bbox", () => { - expect(GeoJSONBBoxSchema.parse(bbox4D)).toEqual(bbox4D); + expect(GeoJSONBboxSchema.parse(bbox4D)).toEqual(bbox4D); }); it("does not allow an empty bbox", () => { - expect(() => GeoJSONBBoxSchema.parse([])).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse([])).toThrow(ZodError); }); it("does not allow a bbox with 1 position", () => { - expect(() => GeoJSONBBoxSchema.parse([0])).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse([0])).toThrow(ZodError); }); it("does not allow a bbox with 2 positions", () => { - expect(() => GeoJSONBBoxSchema.parse([0, 0])).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse([0, 0])).toThrow(ZodError); }); it("does not allow a bbox with 3 positions", () => { - expect(() => GeoJSONBBoxSchema.parse([0, 0, 0])).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse([0, 0, 0])).toThrow(ZodError); }); it("does not allow an uneven bbox", () => { - expect(() => GeoJSONBBoxSchema.parse([0.0, 3.0, -1.0, 2.0, 5.0])).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse([0.0, 3.0, -1.0, 2.0, 5.0])).toThrow(ZodError); }); it("does not allow a badly formatted bbox", () => { - expect(() => GeoJSONBBoxSchema.parse("bbox cannot be a string")).toThrow(ZodError); + expect(() => GeoJSONBboxSchema.parse("bbox cannot be a string")).toThrow(ZodError); }); describe("inference", () => { diff --git a/tests/geometry/multi_line_string.test.ts b/tests/geometry/multi_line_string.test.ts index fc3d837..499b1a2 100644 --- a/tests/geometry/multi_line_string.test.ts +++ b/tests/geometry/multi_line_string.test.ts @@ -49,13 +49,13 @@ describe("GeoJSONMultiLineString", () => { extraKey: "extra", }); }); + it("allows a multi-line string with empty coordinates", () => { + passGeoJSONMultiLineStringTest({ type: "MultiLineString", coordinates: [] }); + }); it("does not allow a 1D multi-line string", () => { failGeoJSONMultiLineStringTest({ type: "MultiLineString", coordinates: [[[0.0], [1.0]]] }); }); - it("does not allow a multi-line string with empty coordinates", () => { - failGeoJSONMultiLineStringTest({ type: "MultiLineString", coordinates: [] }); - }); it("does not allow a multi-line string without coordinates key", () => { failGeoJSONMultiLineStringTest({ type: "MultiLineString" }); }); diff --git a/tests/geometry/multi_point.test.ts b/tests/geometry/multi_point.test.ts index 3a771ae..aec29e3 100644 --- a/tests/geometry/multi_point.test.ts +++ b/tests/geometry/multi_point.test.ts @@ -37,13 +37,13 @@ describe("GeoJSONMultiPoint", () => { extraKey: "extra", }); }); + it("allows a multi-point with empty coordinates", () => { + passGeoJSONMultiPointTest({ type: "MultiPoint", coordinates: [] }); + }); it("does not allow a 1D multi-point", () => { failGeoJSONMultiPointTest({ type: "MultiPoint", coordinates: [[0.0], [1.0]] }); }); - it("does not allow a multi-point with empty coordinates", () => { - failGeoJSONMultiPointTest({ type: "MultiPoint", coordinates: [] }); - }); it("does not allow a multi-point without coordinates key", () => { failGeoJSONMultiPointTest({ type: "MultiPoint" }); }); diff --git a/tests/geometry/multi_polygon.test.ts b/tests/geometry/multi_polygon.test.ts index 146ce55..5ed3705 100644 --- a/tests/geometry/multi_polygon.test.ts +++ b/tests/geometry/multi_polygon.test.ts @@ -45,13 +45,13 @@ describe("GeoJSONMultiPolygon", () => { extraKey: "extra", }); }); + it("allows a multi-polygon with empty coordinates", () => { + passGeoJSONMultiPolygonTest({ type: "MultiPolygon", coordinates: [] }); + }); it("does not allow a 1D multi-polygon", () => { failGeoJSONMultiPolygonTest({ type: "MultiPolygon", coordinates: [[[[0.0], [1.0], [0.0], [0.0]]]] }); }); - it("does not allow a multi-polygon with empty coordinates", () => { - failGeoJSONMultiPolygonTest({ type: "MultiPolygon", coordinates: [] }); - }); it("does not allow a multi-polygon without coordinates key", () => { failGeoJSONMultiPolygonTest({ type: "MultiPolygon" }); }); diff --git a/tests/geometry/point.test.ts b/tests/geometry/point.test.ts index d0a7a9e..6d7e0d3 100644 --- a/tests/geometry/point.test.ts +++ b/tests/geometry/point.test.ts @@ -6,7 +6,14 @@ import { geoJsonPoint3D, geoJsonPoint3DWithBbox, } from "../../examples/geometry/point"; -import { GeoJSON2DPointSchema, GeoJSON3DPointSchema, GeoJSONPointSchema } from "../../src"; +import { + GeoJSON2DPoint, + GeoJSON2DPointSchema, + GeoJSON3DPoint, + GeoJSON3DPointSchema, + GeoJSONPoint, + GeoJSONPointSchema, +} from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONPointTest(value: unknown): void { @@ -129,3 +136,39 @@ describe("GeoJSONPoint", () => { }); }); }); + +/** + * Invalid GeoJSON Point to test types + */ +export const invalidGeoJsonPoint: GeoJSONPoint = { + // @ts-expect-error -- THIS SHOULD FAIL + type: "Hello", + // @ts-expect-error -- THIS SHOULD FAIL + coordinates: [1.0], + // @ts-expect-error -- THIS SHOULD FAIL + bbox: [1.0, 2.0], +}; + +/** + * Invalid 2D GeoJSON Point to test types + */ +export const invalidGeoJsonPoint2D: GeoJSON2DPoint = { + // @ts-expect-error -- THIS SHOULD FAIL + type: "Hello", + // @ts-expect-error -- THIS SHOULD FAIL + coordinates: [1.0, 2.0, 3.0], + // @ts-expect-error -- THIS SHOULD FAIL + bbox: [1.0], +}; + +/** + * Invalid 3D GeoJSON Point to test types + */ +export const invalidGeoJsonPoint3D: GeoJSON3DPoint = { + // @ts-expect-error -- THIS SHOULD FAIL + type: "Hello", + // @ts-expect-error -- THIS SHOULD FAIL + coordinates: [1.0, 2.0], + // @ts-expect-error -- THIS SHOULD FAIL + bbox: [1.0, 2.0, 3.0], +}; diff --git a/tsconfig.json b/tsconfig.json index ade21e9..175bd61 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "noUnusedParameters": true, "noImplicitReturns": true, "downlevelIteration": true, - "isolatedModules": true + "isolatedModules": true, + "noErrorTruncation": true }, "include": ["src/**/*", "tests/**/*"], "exclude": ["node_modules", "lib"]