From 7af7794b5f76845142ba638e817e2c8a2169fa3f Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Wed, 8 Oct 2025 20:12:37 -0600 Subject: [PATCH 1/9] feat: first tests --- package-lock.json | 487 ++++++++++++++++++++++++- package.json | 5 +- src/offset.test.ts | 76 ++++ src/utils/createMeshFromObject.test.ts | 87 +++++ src/utils/hashTable.test.ts | 111 ++++++ src/utils/offsetObjectHash.test.ts | 131 +++++++ vite.config.js => vite.config.ts | 16 +- 7 files changed, 905 insertions(+), 8 deletions(-) create mode 100644 src/offset.test.ts create mode 100644 src/utils/createMeshFromObject.test.ts create mode 100644 src/utils/hashTable.test.ts create mode 100644 src/utils/offsetObjectHash.test.ts rename vite.config.js => vite.config.ts (83%) diff --git a/package-lock.json b/package-lock.json index 927d6d8..2da05b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "license": "MIT", "devDependencies": { "@biomejs/biome": "2.2.4", + "@types/node": "^24.6.1", "@types/three": "^0.180.0", "three": "^0.180.0", "typescript": "^5.9.2", "vite": "^5.0.0", - "vite-plugin-dts": "^4.5.4" + "vite-plugin-dts": "^4.5.4", + "vitest": "^3.2.4" }, "engines": { "node": ">=20" @@ -1086,12 +1088,39 @@ "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", "dev": true }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, + "node_modules/@types/node": { + "version": "24.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.1.tgz", + "integrity": "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.13.0" + } + }, "node_modules/@types/stats.js": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", @@ -1119,6 +1148,131 @@ "integrity": "sha512-GPe4AsfOSpqWd3xA/0gwoKod13ChcfV67trvxaW2krUbgb9gxQjnCx8zGshzMl8LSHZlNH5gQ8LNScsDuc7nGQ==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.23", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", @@ -1303,6 +1457,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1318,6 +1482,43 @@ "balanced-match": "^1.0.0" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/compare-versions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", @@ -1353,6 +1554,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1365,6 +1576,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1409,6 +1627,16 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exsolve": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", @@ -1421,6 +1649,24 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -1530,6 +1776,13 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1577,6 +1830,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1696,6 +1956,16 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1862,6 +2132,13 @@ "node": ">=10" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1886,6 +2163,20 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -1907,6 +2198,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -1940,6 +2244,67 @@ "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", "dev": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/typescript": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", @@ -1959,6 +2324,13 @@ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true }, + "node_modules/undici-types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", + "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -2036,6 +2408,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-dts": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", @@ -2062,12 +2457,102 @@ } } }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "dev": true }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 1ca4661..618349e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "scripts": { "check": "biome check --write ./src/**/*.ts ./demo/**", "check:ci": "biome check ./src/**/*.ts ./demo/**", + "test": "vitest", "dev": "vite", "build:demo": "vite build --mode demo", "build:lib": "vite build", @@ -46,11 +47,13 @@ }, "devDependencies": { "@biomejs/biome": "2.2.4", + "@types/node": "^24.6.1", "@types/three": "^0.180.0", "three": "^0.180.0", "typescript": "^5.9.2", "vite": "^5.0.0", - "vite-plugin-dts": "^4.5.4" + "vite-plugin-dts": "^4.5.4", + "vitest": "^3.2.4" }, "peerDependencies": { "three": "^0.143.0" diff --git a/src/offset.test.ts b/src/offset.test.ts new file mode 100644 index 0000000..828d6a0 --- /dev/null +++ b/src/offset.test.ts @@ -0,0 +1,76 @@ +import { Mesh, BufferGeometry, BufferAttribute, Points, PointsMaterial } from "three"; +import { STLExporter } from "three/examples/jsm/exporters/STLExporter.js"; +import { describe, it, expect, vi, beforeEach } from "vitest"; + +import { applyOffset, processGeometry } from "./offset"; +import { createMeshFromObject } from "./utils/createMeshFromObject"; +import { createOffsetMesh } from "./utils/offsetObjectHash"; + +vi.mock("three/examples/jsm/exporters/STLExporter.js"); +vi.mock("./utils/createMeshFromObject"); +vi.mock("./utils/offsetObjectHash"); + +describe("applyOffset", () => { + let mockMesh: Mesh; + let mockExporter: any; + let mockResult: string; + + beforeEach(() => { + mockMesh = new Mesh(); + mockResult = "mock stl data"; + mockExporter = { + parse: vi.fn().mockReturnValue(mockResult), + }; + (STLExporter as any).mockImplementation(() => mockExporter); + (createOffsetMesh as any).mockReturnValue("mock object"); + (createMeshFromObject as any).mockResolvedValue(new Mesh()); + }); + + it("should export mesh as STL and create offset mesh", async () => { + const offset = 2; + const result = await applyOffset(mockMesh, offset); + + expect(STLExporter).toHaveBeenCalled(); + expect(mockExporter.parse).toHaveBeenCalledWith(mockMesh, { binary: false }); + expect(createOffsetMesh).toHaveBeenCalledWith(mockResult, offset); + expect(createMeshFromObject).toHaveBeenCalledWith("mock object"); + expect(result.name).toBe("offset"); + }); +}); + +describe("processGeometry", () => { + let mockGeometry: BufferGeometry; + + beforeEach(() => { + mockGeometry = new BufferGeometry(); + const vertices = new Float32Array([0, 0, 0, 1, 1, 1, 2, 2, 2]); + const normals = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); + mockGeometry.setAttribute("position", new BufferAttribute(vertices, 3)); + mockGeometry.setAttribute("normal", new BufferAttribute(normals, 3)); + }); + + it("should create points with offset applied to vertices", () => { + const offset = 0.5; + const result = processGeometry(mockGeometry, offset); + + expect(result).toBeInstanceOf(Points); + expect(result.name).toBe("offset"); + expect(result.material).toBeInstanceOf(PointsMaterial); + expect((result.material as PointsMaterial).color.getHex()).toBe(0xff0000); + expect((result.material as PointsMaterial).size).toBe(0.7); + + const positions = result.geometry.attributes.position.array; + expect(positions[0]).toBe(0.5); // 0.5 * 1 + 0 + expect(positions[1]).toBe(0); // 0.5 * 0 + 0 + expect(positions[2]).toBe(0); // 0.5 * 0 + 0 + }); + + it("should handle negative offset", () => { + const offset = -1; + const result = processGeometry(mockGeometry, offset); + + const positions = result.geometry.attributes.position.array; + expect(positions[0]).toBe(-1); // -1 * 1 + 0 + expect(positions[4]).toBe(0); // -1 * 1 + 1 + }); +}); diff --git a/src/utils/createMeshFromObject.test.ts b/src/utils/createMeshFromObject.test.ts new file mode 100644 index 0000000..e6c4f93 --- /dev/null +++ b/src/utils/createMeshFromObject.test.ts @@ -0,0 +1,87 @@ +import * as THREE from "three"; +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; +import { createMeshFromObject } from "./createMeshFromObject"; +import type { InitialObject } from "@/types"; + +vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ + mergeVertices: vi.fn((geometry) => geometry), +})); + +describe("createMeshFromObject", () => { + let mockObject: InitialObject[]; + + beforeEach(() => { + mockObject = [ + { + face: 0, + normal: [0, 0, 1], + vertices: [ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + ], + }, + { + face: 1, + normal: [0, 1, 0], + vertices: [ + [0, 1, 0], + [1, 1, 0], + [0, 2, 0], + ], + }, + ]; + }); + + it("should create a mesh with the correct geometry and material", async () => { + const mesh = await createMeshFromObject(mockObject); + + expect(mesh).toBeInstanceOf(THREE.Mesh); + expect(mesh.material).toBeInstanceOf(THREE.MeshStandardMaterial); + + const material = mesh.material as THREE.MeshStandardMaterial; + expect(material.color.getHex()).toBe(0x0000ff); + expect(material.opacity).toBe(0.2); + expect(material.transparent).toBe(true); + expect(material.side).toBe(THREE.DoubleSide); + expect(material.vertexColors).toBe(true); + expect(material.wireframe).toBe(true); + + const geometry = mesh.geometry as THREE.BufferGeometry; + expect(geometry.attributes.position.count).toBe(6); // 2 triangles * 3 vertices + expect(geometry.attributes.normal.count).toBe(6); + expect(geometry.attributes.color.count).toBe(6); + expect(mergeVertices).toHaveBeenCalledWith(geometry); + }); + + it("should correctly set vertex positions and normals", async () => { + const mesh = await createMeshFromObject(mockObject); + const geometry = mesh.geometry as THREE.BufferGeometry; + + const positions = geometry.attributes.position.array; + const normals = geometry.attributes.normal.array; + + expect(positions).toEqual( + new Float32Array([ + 0, 0, 0, 1, 0, 0, 0, 1, 0, // First object + 0, 1, 0, 1, 1, 0, 0, 2, 0, // Second object + ]) + ); + + expect(normals).toEqual( + new Float32Array([ + 0, 0, 1, 0, 0, 1, 0, 0, 1, // First object + 0, 0, 1, 0, 0, 1, 0, 0, 1, // Second object + ]) + ); + }); + + it("should set vertex colors to white", async () => { + const mesh = await createMeshFromObject(mockObject); + const geometry = mesh.geometry as THREE.BufferGeometry; + + const colors = geometry.attributes.color.array; + expect(colors).toEqual(new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])); + }); +}); \ No newline at end of file diff --git a/src/utils/hashTable.test.ts b/src/utils/hashTable.test.ts new file mode 100644 index 0000000..c24cfcd --- /dev/null +++ b/src/utils/hashTable.test.ts @@ -0,0 +1,111 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { HashTable } from "./hashTable"; + +describe("HashTable", () => { + let table: HashTable; + + beforeEach(() => { + table = new HashTable(5); + }); + + it("initializes with correct length and size", () => { + expect(table.data.length).toBe(5); + expect(table.size).toBe(0); + expect(table.data.every(b => b === undefined)).toBe(true); + }); + + it("adds a new key and stores value array", () => { + table.set("foo", 1); + const bucketIndex = table.hash("foo"); + const bucket = table.data[bucketIndex]!; + expect(bucket.length).toBe(1); + expect(bucket[0][0]).toBe("foo"); + expect(bucket[0][1]).toEqual([1]); + expect(table.size).toBe(1); + }); + + it("appends value for existing primitive key without increasing size", () => { + table.set("foo", 1); + table.set("foo", 2); + const bucket = table.data[table.hash("foo")]!; + expect(bucket.length).toBe(1); + expect(bucket[0][1]).toEqual([1, 2]); + expect(table.size).toBe(1); + }); + + it("treats different object instances with same JSON as same key", () => { + const a1 = { x: 1 }; + const a2 = { x: 1 }; + table.set(a1, "v1"); + table.set(a2, "v2"); + const bucket = table.data[table.hash(a1)]!; + expect(bucket.length).toBe(1); + expect(bucket[0][1]).toEqual(["v1", "v2"]); + expect(table.size).toBe(1); + }); + + it("treats objects with different property order as different keys", () => { + const o1 = { a: 1, b: 2 }; + const o2 = { b: 2, a: 1 }; // JSON.stringify order differs + table.set(o1, "first"); + table.set(o2, "second"); + const bucket = table.data[table.hash(o1)]!; + // Could be same hash or different; count total size instead + expect(table.size).toBe(2); + const flatEntries = table.data.filter(Boolean).flat(); + const values = flatEntries.map(e => e[1]); + expect(values).toContainEqual(["first"]); + expect(values).toContainEqual(["second"]); + }); + + it("handles hash collisions by chaining", () => { + // 'ab' and 'ba' have same char code sum => same hash in length 5 + table.set("ab", 1); + table.set("ba", 2); + const idx = table.hash("ab"); + expect(idx).toBe(table.hash("ba")); + const bucket = table.data[idx]!; + expect(bucket.length).toBe(2); + const entryAb = bucket.find(e => e[0] === "ab")!; + const entryBa = bucket.find(e => e[0] === "ba")!; + expect(entryAb[1]).toEqual([1]); + expect(entryBa[1]).toEqual([2]); + expect(table.size).toBe(2); + }); + + it("adds multiple values for same object key reference", () => { + const key = { id: 7 }; + table.set(key, "a"); + table.set(key, "b"); + const bucket = table.data[table.hash(key)]!; + expect(bucket.length).toBe(1); + expect(bucket[0][1]).toEqual(["a", "b"]); + expect(table.size).toBe(1); + }); + + it("hash is deterministic for same key", () => { + const key = "stable"; + const h1 = table.hash(key); + const h2 = table.hash(key); + expect(h1).toBe(h2); + }); + + it("separates different primitive keys with different hash buckets when possible", () => { + table.set("a", 1); + table.set("b", 2); + const hA = table.hash("a"); + const hB = table.hash("b"); + const sameBucket = hA === hB; + const bucketA = table.data[hA]!; + if (sameBucket) { + expect(bucketA.length).toBeGreaterThanOrEqual(2); + } else { + const bucketB = table.data[hB]!; + expect(bucketA.length).toBe(1); + expect(bucketB.length).toBe(1); + } + expect(table.size).toBe( + sameBucket ? 2 : 2 + ); + }); +}); \ No newline at end of file diff --git a/src/utils/offsetObjectHash.test.ts b/src/utils/offsetObjectHash.test.ts new file mode 100644 index 0000000..2952cb5 --- /dev/null +++ b/src/utils/offsetObjectHash.test.ts @@ -0,0 +1,131 @@ +import { describe, it, expect } from "vitest"; + +// Helper for approximate comparison +const closeTo = (a: number, b: number, eps = 1e-6) => Math.abs(a - b) < eps; + +describe('createOffsetMesh basic behavior', () => { + it('returns unchanged vertices when offset is 0', async () => { + const { createOffsetMesh } = await import('./offsetObjectHash'); + + const stl = ` +solid test +facet normal 0 0 1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 1 0 + endloop +endfacet +endsolid test +`; + const res = createOffsetMesh(stl, 0); + expect(res).toHaveLength(1); + const face = res[0]; + expect(face.vertices).toEqual([ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + ]); + expect(face.normal).toHaveLength(3); + const len = Math.hypot(face.normal[0], face.normal[1], face.normal[2]); + expect(closeTo(len, 1)).toBe(true); + }); + + it('applies positive offset along face normal', async () => { + const { createOffsetMesh } = await import('./offsetObjectHash'); + const offset = 0.1; + const stl = ` +solid single +facet normal 0 0 1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 1 0 + endloop +endfacet +endsolid single +`; + const res = createOffsetMesh(stl, offset); + const verts = res[0].vertices; + verts.forEach(v => { + expect(closeTo(v[2], offset)).toBe(true); + }); + }); + + it('averages normals for shared vertex across three orthogonal faces', async () => { + const { createOffsetMesh } = await import('./offsetObjectHash'); + const offset = 2; + const stl = ` +solid corner +facet normal 1 0 0 + outer loop + vertex 0 0 0 + vertex 0 1 0 + vertex 0 0 1 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 0 1 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 1 0 + endloop +endfacet +endsolid corner +`; + const res = createOffsetMesh(stl, offset); + expect(res).toHaveLength(3); + + const sqrt3 = Math.sqrt(3); + const expCorner = offset / sqrt3; // component for (0,0,0) vertex + + // Collect all occurrences of the transformed original corner vertex + const cornerVertices = res.flatMap(f => f.vertices.filter(v => + closeTo(v[0], expCorner) && closeTo(v[1], expCorner) && closeTo(v[2], expCorner) + )); + // The shared vertex should appear once per face + expect(cornerVertices.length).toBe(3); + + // Check a vertex shared by two faces: (0,1,0) -> normals (1,0,0)+(0,0,1) + const sqrt2 = Math.sqrt(2); + const expTwo = offset / sqrt2; + const transformed_0_1_0 = res.flatMap(f => f.vertices).find(v => + closeTo(v[0], expTwo) && closeTo(v[1], 1) && closeTo(v[2], expTwo) + ); + expect(transformed_0_1_0).toBeTruthy(); + + // Validate normals are unit length + res.forEach(f => { + expect(f.normal.length).toBe(3); + const len = Math.hypot(f.normal[0], f.normal[1], f.normal[2]); + expect(closeTo(len, 1)).toBe(true); + }); + }); + + it('supports negative offset (moves opposite direction)', async () => { + const { createOffsetMesh } = await import('./offsetObjectHash'); + const stl = ` +solid neg +facet normal 0 0 1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 1 0 + endloop +endfacet +endsolid neg +`; + const offset = -0.25; + const res = createOffsetMesh(stl, offset); + res[0].vertices.forEach(v => { + expect(closeTo(v[2], offset)).toBe(true); + }); + }); +}); \ No newline at end of file diff --git a/vite.config.js b/vite.config.ts similarity index 83% rename from vite.config.js rename to vite.config.ts index 70b7195..3a26624 100644 --- a/vite.config.js +++ b/vite.config.ts @@ -1,10 +1,14 @@ -import { defineConfig } from "vite" -import path from "path" -import dts from "vite-plugin-dts" +import { defineConfig } from "vite"; +import path from "node:path"; +import dts from "vite-plugin-dts"; export default defineConfig(({ command, mode }) => { + console.log(`Command: ${command}, Mode: ${mode}`); if (mode === "demo" || command === "serve") { return { + test: { + root: "./src", + }, root: "demo", resolve: { alias: { @@ -18,7 +22,7 @@ export default defineConfig(({ command, mode }) => { server: { port: 9000, }, - } + }; } else { return { resolve: { @@ -50,6 +54,6 @@ export default defineConfig(({ command, mode }) => { outDir: path.resolve(__dirname, "dist/lib"), emptyOutDir: true, }, - } + }; } -}) +}); From 7983d771b52445d8e8f9e008565b3a4a4cb9cb63 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Wed, 15 Oct 2025 17:57:12 -0600 Subject: [PATCH 2/9] chore: upgrades --- package-lock.json | 459 ++++++++++++++++++++++++++++------------------ package.json | 8 +- 2 files changed, 281 insertions(+), 186 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2da05b8..86d9a52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "0.3.1", "license": "MIT", "devDependencies": { - "@biomejs/biome": "2.2.4", - "@types/node": "^24.6.1", + "@biomejs/biome": "2.2.6", + "@types/node": "^24.7.2", "@types/three": "^0.180.0", "three": "^0.180.0", - "typescript": "^5.9.2", - "vite": "^5.0.0", + "typescript": "^5.9.3", + "vite": "^7.1.10", "vite-plugin-dts": "^4.5.4", "vitest": "^3.2.4" }, @@ -72,9 +72,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.4.tgz", - "integrity": "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.6.tgz", + "integrity": "sha512-yKTCNGhek0rL5OEW1jbLeZX8LHaM8yk7+3JRGv08my+gkpmtb5dDE+54r2ZjZx0ediFEn1pYBOJSmOdDP9xtFw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -88,20 +88,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.2.4", - "@biomejs/cli-darwin-x64": "2.2.4", - "@biomejs/cli-linux-arm64": "2.2.4", - "@biomejs/cli-linux-arm64-musl": "2.2.4", - "@biomejs/cli-linux-x64": "2.2.4", - "@biomejs/cli-linux-x64-musl": "2.2.4", - "@biomejs/cli-win32-arm64": "2.2.4", - "@biomejs/cli-win32-x64": "2.2.4" + "@biomejs/cli-darwin-arm64": "2.2.6", + "@biomejs/cli-darwin-x64": "2.2.6", + "@biomejs/cli-linux-arm64": "2.2.6", + "@biomejs/cli-linux-arm64-musl": "2.2.6", + "@biomejs/cli-linux-x64": "2.2.6", + "@biomejs/cli-linux-x64-musl": "2.2.6", + "@biomejs/cli-win32-arm64": "2.2.6", + "@biomejs/cli-win32-x64": "2.2.6" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz", - "integrity": "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.6.tgz", + "integrity": "sha512-UZPmn3M45CjTYulgcrFJFZv7YmK3pTxTJDrFYlNElT2FNnkkX4fsxjExTSMeWKQYoZjvekpH5cvrYZZlWu3yfA==", "cpu": [ "arm64" ], @@ -116,9 +116,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz", - "integrity": "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.6.tgz", + "integrity": "sha512-HOUIquhHVgh/jvxyClpwlpl/oeMqntlteL89YqjuFDiZ091P0vhHccwz+8muu3nTyHWM5FQslt+4Jdcd67+xWQ==", "cpu": [ "x64" ], @@ -133,9 +133,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.4.tgz", - "integrity": "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.6.tgz", + "integrity": "sha512-BpGtuMJGN+o8pQjvYsUKZ+4JEErxdSmcRD/JG3mXoWc6zrcA7OkuyGFN1mDggO0Q1n7qXxo/PcupHk8gzijt5g==", "cpu": [ "arm64" ], @@ -150,9 +150,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz", - "integrity": "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.6.tgz", + "integrity": "sha512-TjCenQq3N6g1C+5UT3jE1bIiJb5MWQvulpUngTIpFsL4StVAUXucWD0SL9MCW89Tm6awWfeXBbZBAhJwjyFbRQ==", "cpu": [ "arm64" ], @@ -167,9 +167,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.4.tgz", - "integrity": "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.6.tgz", + "integrity": "sha512-1HaM/dpI/1Z68zp8ZdT6EiBq+/O/z97a2AiHMl+VAdv5/ELckFt9EvRb8hDHpk8hUMoz03gXkC7VPXOVtU7faA==", "cpu": [ "x64" ], @@ -184,9 +184,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz", - "integrity": "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.6.tgz", + "integrity": "sha512-1ZcBux8zVM3JhWN2ZCPaYf0+ogxXG316uaoXJdgoPZcdK/rmRcRY7PqHdAos2ExzvjIdvhQp72UcveI98hgOog==", "cpu": [ "x64" ], @@ -201,9 +201,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.4.tgz", - "integrity": "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.6.tgz", + "integrity": "sha512-h3A88G8PGM1ryTeZyLlSdfC/gz3e95EJw9BZmA6Po412DRqwqPBa2Y9U+4ZSGUAXCsnSQE00jLV8Pyrh0d+jQw==", "cpu": [ "arm64" ], @@ -218,9 +218,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.4.tgz", - "integrity": "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.6.tgz", + "integrity": "sha512-yx0CqeOhPjYQ5ZXgPfu8QYkgBhVJyvWe36as7jRuPrKPO5ylVDfwVtPQ+K/mooNTADW0IhxOZm3aPu16dP8yNQ==", "cpu": [ "x64" ], @@ -241,371 +241,445 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@isaacs/balanced-match": { @@ -1112,13 +1186,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "24.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.1.tgz", - "integrity": "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.13.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/stats.js": { @@ -1584,41 +1658,45 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/estree-walker": { @@ -2306,10 +2384,11 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2325,9 +2404,9 @@ "dev": true }, "node_modules/undici-types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", - "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true, "license": "MIT" }, @@ -2350,20 +2429,24 @@ } }, "node_modules/vite": { - "version": "5.4.20", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", - "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2372,19 +2455,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -2405,6 +2494,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, diff --git a/package.json b/package.json index 618349e..83631bf 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,12 @@ "node": ">=20" }, "devDependencies": { - "@biomejs/biome": "2.2.4", - "@types/node": "^24.6.1", + "@biomejs/biome": "2.2.6", + "@types/node": "^24.7.2", "@types/three": "^0.180.0", "three": "^0.180.0", - "typescript": "^5.9.2", - "vite": "^5.0.0", + "typescript": "^5.9.3", + "vite": "^7.1.10", "vite-plugin-dts": "^4.5.4", "vitest": "^3.2.4" }, From 96d83b7c4a4e2961d25d61851e3d03788117c861 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Wed, 15 Oct 2025 17:58:25 -0600 Subject: [PATCH 3/9] chore: formatting fixes --- src/utils/createMeshFromObject.test.ts | 54 +++++++++++++++++++++----- src/utils/hashTable.test.ts | 16 ++++---- src/utils/offsetObjectHash.test.ts | 40 +++++++++---------- 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/utils/createMeshFromObject.test.ts b/src/utils/createMeshFromObject.test.ts index e6c4f93..429168a 100644 --- a/src/utils/createMeshFromObject.test.ts +++ b/src/utils/createMeshFromObject.test.ts @@ -1,8 +1,8 @@ import * as THREE from "three"; -import { describe, it, expect, beforeEach, vi } from "vitest"; import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; -import { createMeshFromObject } from "./createMeshFromObject"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { InitialObject } from "@/types"; +import { createMeshFromObject } from "./createMeshFromObject"; vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ mergeVertices: vi.fn((geometry) => geometry), @@ -64,16 +64,48 @@ describe("createMeshFromObject", () => { expect(positions).toEqual( new Float32Array([ - 0, 0, 0, 1, 0, 0, 0, 1, 0, // First object - 0, 1, 0, 1, 1, 0, 0, 2, 0, // Second object - ]) + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, // First object + 0, + 1, + 0, + 1, + 1, + 0, + 0, + 2, + 0, // Second object + ]), ); expect(normals).toEqual( new Float32Array([ - 0, 0, 1, 0, 0, 1, 0, 0, 1, // First object - 0, 0, 1, 0, 0, 1, 0, 0, 1, // Second object - ]) + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, // First object + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, // Second object + ]), ); }); @@ -82,6 +114,8 @@ describe("createMeshFromObject", () => { const geometry = mesh.geometry as THREE.BufferGeometry; const colors = geometry.attributes.color.array; - expect(colors).toEqual(new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])); + expect(colors).toEqual( + new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]), + ); }); -}); \ No newline at end of file +}); diff --git a/src/utils/hashTable.test.ts b/src/utils/hashTable.test.ts index c24cfcd..4506246 100644 --- a/src/utils/hashTable.test.ts +++ b/src/utils/hashTable.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import { HashTable } from "./hashTable"; describe("HashTable", () => { @@ -11,7 +11,7 @@ describe("HashTable", () => { it("initializes with correct length and size", () => { expect(table.data.length).toBe(5); expect(table.size).toBe(0); - expect(table.data.every(b => b === undefined)).toBe(true); + expect(table.data.every((b) => b === undefined)).toBe(true); }); it("adds a new key and stores value array", () => { @@ -53,7 +53,7 @@ describe("HashTable", () => { // Could be same hash or different; count total size instead expect(table.size).toBe(2); const flatEntries = table.data.filter(Boolean).flat(); - const values = flatEntries.map(e => e[1]); + const values = flatEntries.map((e) => e[1]); expect(values).toContainEqual(["first"]); expect(values).toContainEqual(["second"]); }); @@ -66,8 +66,8 @@ describe("HashTable", () => { expect(idx).toBe(table.hash("ba")); const bucket = table.data[idx]!; expect(bucket.length).toBe(2); - const entryAb = bucket.find(e => e[0] === "ab")!; - const entryBa = bucket.find(e => e[0] === "ba")!; + const entryAb = bucket.find((e) => e[0] === "ab")!; + const entryBa = bucket.find((e) => e[0] === "ba")!; expect(entryAb[1]).toEqual([1]); expect(entryBa[1]).toEqual([2]); expect(table.size).toBe(2); @@ -104,8 +104,6 @@ describe("HashTable", () => { expect(bucketA.length).toBe(1); expect(bucketB.length).toBe(1); } - expect(table.size).toBe( - sameBucket ? 2 : 2 - ); + expect(table.size).toBe(sameBucket ? 2 : 2); }); -}); \ No newline at end of file +}); diff --git a/src/utils/offsetObjectHash.test.ts b/src/utils/offsetObjectHash.test.ts index 2952cb5..da6dfed 100644 --- a/src/utils/offsetObjectHash.test.ts +++ b/src/utils/offsetObjectHash.test.ts @@ -1,11 +1,11 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; // Helper for approximate comparison const closeTo = (a: number, b: number, eps = 1e-6) => Math.abs(a - b) < eps; -describe('createOffsetMesh basic behavior', () => { - it('returns unchanged vertices when offset is 0', async () => { - const { createOffsetMesh } = await import('./offsetObjectHash'); +describe("createOffsetMesh basic behavior", () => { + it("returns unchanged vertices when offset is 0", async () => { + const { createOffsetMesh } = await import("./offsetObjectHash"); const stl = ` solid test @@ -31,8 +31,8 @@ endsolid test expect(closeTo(len, 1)).toBe(true); }); - it('applies positive offset along face normal', async () => { - const { createOffsetMesh } = await import('./offsetObjectHash'); + it("applies positive offset along face normal", async () => { + const { createOffsetMesh } = await import("./offsetObjectHash"); const offset = 0.1; const stl = ` solid single @@ -47,13 +47,13 @@ endsolid single `; const res = createOffsetMesh(stl, offset); const verts = res[0].vertices; - verts.forEach(v => { + verts.forEach((v) => { expect(closeTo(v[2], offset)).toBe(true); }); }); - it('averages normals for shared vertex across three orthogonal faces', async () => { - const { createOffsetMesh } = await import('./offsetObjectHash'); + it("averages normals for shared vertex across three orthogonal faces", async () => { + const { createOffsetMesh } = await import("./offsetObjectHash"); const offset = 2; const stl = ` solid corner @@ -87,30 +87,30 @@ endsolid corner const expCorner = offset / sqrt3; // component for (0,0,0) vertex // Collect all occurrences of the transformed original corner vertex - const cornerVertices = res.flatMap(f => f.vertices.filter(v => - closeTo(v[0], expCorner) && closeTo(v[1], expCorner) && closeTo(v[2], expCorner) - )); + const cornerVertices = res.flatMap((f) => + f.vertices.filter((v) => closeTo(v[0], expCorner) && closeTo(v[1], expCorner) && closeTo(v[2], expCorner)), + ); // The shared vertex should appear once per face expect(cornerVertices.length).toBe(3); // Check a vertex shared by two faces: (0,1,0) -> normals (1,0,0)+(0,0,1) const sqrt2 = Math.sqrt(2); const expTwo = offset / sqrt2; - const transformed_0_1_0 = res.flatMap(f => f.vertices).find(v => - closeTo(v[0], expTwo) && closeTo(v[1], 1) && closeTo(v[2], expTwo) - ); + const transformed_0_1_0 = res + .flatMap((f) => f.vertices) + .find((v) => closeTo(v[0], expTwo) && closeTo(v[1], 1) && closeTo(v[2], expTwo)); expect(transformed_0_1_0).toBeTruthy(); // Validate normals are unit length - res.forEach(f => { + res.forEach((f) => { expect(f.normal.length).toBe(3); const len = Math.hypot(f.normal[0], f.normal[1], f.normal[2]); expect(closeTo(len, 1)).toBe(true); }); }); - it('supports negative offset (moves opposite direction)', async () => { - const { createOffsetMesh } = await import('./offsetObjectHash'); + it("supports negative offset (moves opposite direction)", async () => { + const { createOffsetMesh } = await import("./offsetObjectHash"); const stl = ` solid neg facet normal 0 0 1 @@ -124,8 +124,8 @@ endsolid neg `; const offset = -0.25; const res = createOffsetMesh(stl, offset); - res[0].vertices.forEach(v => { + res[0].vertices.forEach((v) => { expect(closeTo(v[2], offset)).toBe(true); }); }); -}); \ No newline at end of file +}); From 96a94dc745d1cb20320ad86625330c9489557043 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Wed, 15 Oct 2025 18:32:28 -0600 Subject: [PATCH 4/9] chore: type and formatting cleaning --- src/index.ts | 2 +- src/types.ts | 8 ++--- src/utils/createMeshFromObject.test.ts | 4 +-- src/utils/createMeshFromObject.ts | 6 ++-- src/utils/hashTable.test.ts | 27 +++++++++------- src/utils/offsetObjectHash.ts | 44 ++++++++++++++++++-------- 6 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8ec54d0..a3a3a72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { applyOffset, processGeometry } from "./offset" export { createMeshFromObject } from "./utils/createMeshFromObject" export { createOffsetMesh } from "./utils/offsetObjectHash" -export type { InitialObject } from "./types" +export type { VertexUsageInfo as InitialObject } from "./types" diff --git a/src/types.ts b/src/types.ts index 29b804d..1405f8c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,7 @@ -export interface InitialObject { - face: number; - normal: number[]; - vertices: number[][]; -} - export interface VertexUsageInfo { facetIndex: number; + face: number; normal: number[]; + vertices: number[][]; vertexPositionInTheObject: number; } diff --git a/src/utils/createMeshFromObject.test.ts b/src/utils/createMeshFromObject.test.ts index 429168a..3205c58 100644 --- a/src/utils/createMeshFromObject.test.ts +++ b/src/utils/createMeshFromObject.test.ts @@ -1,7 +1,7 @@ import * as THREE from "three"; import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { InitialObject } from "@/types"; +import type { VertexUsageInfo } from "@/types"; import { createMeshFromObject } from "./createMeshFromObject"; vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ @@ -9,7 +9,7 @@ vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ })); describe("createMeshFromObject", () => { - let mockObject: InitialObject[]; + let mockObject: Pick[]; beforeEach(() => { mockObject = [ diff --git a/src/utils/createMeshFromObject.ts b/src/utils/createMeshFromObject.ts index ba7906c..45384d6 100644 --- a/src/utils/createMeshFromObject.ts +++ b/src/utils/createMeshFromObject.ts @@ -1,13 +1,15 @@ import * as THREE from "three"; import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; -import type { InitialObject } from "@/types"; +import type { VertexUsageInfo } from "@/types"; /** * Creates a mesh from an objects with normals and vertices * @param {Object} object This is an object with normals and vertices * @returns THREE.Mesh */ -export const createMeshFromObject = (object: InitialObject[]): Promise => +export const createMeshFromObject = ( + object: Pick[], +): Promise => new Promise((resolve) => { const geometry = new THREE.BufferGeometry(); const material = new THREE.MeshStandardMaterial({ diff --git a/src/utils/hashTable.test.ts b/src/utils/hashTable.test.ts index 4506246..b00b1a2 100644 --- a/src/utils/hashTable.test.ts +++ b/src/utils/hashTable.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { HashTable } from "./hashTable"; describe("HashTable", () => { - let table: HashTable; + let table: HashTable; beforeEach(() => { table = new HashTable(5); @@ -17,7 +17,7 @@ describe("HashTable", () => { it("adds a new key and stores value array", () => { table.set("foo", 1); const bucketIndex = table.hash("foo"); - const bucket = table.data[bucketIndex]!; + const bucket = table.data[bucketIndex]; expect(bucket.length).toBe(1); expect(bucket[0][0]).toBe("foo"); expect(bucket[0][1]).toEqual([1]); @@ -27,7 +27,7 @@ describe("HashTable", () => { it("appends value for existing primitive key without increasing size", () => { table.set("foo", 1); table.set("foo", 2); - const bucket = table.data[table.hash("foo")]!; + const bucket = table.data[table.hash("foo")]; expect(bucket.length).toBe(1); expect(bucket[0][1]).toEqual([1, 2]); expect(table.size).toBe(1); @@ -38,7 +38,7 @@ describe("HashTable", () => { const a2 = { x: 1 }; table.set(a1, "v1"); table.set(a2, "v2"); - const bucket = table.data[table.hash(a1)]!; + const bucket = table.data[table.hash(a1)]; expect(bucket.length).toBe(1); expect(bucket[0][1]).toEqual(["v1", "v2"]); expect(table.size).toBe(1); @@ -49,7 +49,7 @@ describe("HashTable", () => { const o2 = { b: 2, a: 1 }; // JSON.stringify order differs table.set(o1, "first"); table.set(o2, "second"); - const bucket = table.data[table.hash(o1)]!; + // Could be same hash or different; count total size instead expect(table.size).toBe(2); const flatEntries = table.data.filter(Boolean).flat(); @@ -64,10 +64,15 @@ describe("HashTable", () => { table.set("ba", 2); const idx = table.hash("ab"); expect(idx).toBe(table.hash("ba")); - const bucket = table.data[idx]!; + const bucket = table.data[idx]; expect(bucket.length).toBe(2); - const entryAb = bucket.find((e) => e[0] === "ab")!; - const entryBa = bucket.find((e) => e[0] === "ba")!; + const entryAb = bucket.find((e) => e[0] === "ab"); + const entryBa = bucket.find((e) => e[0] === "ba"); + + if (!entryAb || !entryBa) { + throw new Error("Entries not found in collision bucket"); + } + expect(entryAb[1]).toEqual([1]); expect(entryBa[1]).toEqual([2]); expect(table.size).toBe(2); @@ -77,7 +82,7 @@ describe("HashTable", () => { const key = { id: 7 }; table.set(key, "a"); table.set(key, "b"); - const bucket = table.data[table.hash(key)]!; + const bucket = table.data[table.hash(key)]; expect(bucket.length).toBe(1); expect(bucket[0][1]).toEqual(["a", "b"]); expect(table.size).toBe(1); @@ -96,11 +101,11 @@ describe("HashTable", () => { const hA = table.hash("a"); const hB = table.hash("b"); const sameBucket = hA === hB; - const bucketA = table.data[hA]!; + const bucketA = table.data[hA]; if (sameBucket) { expect(bucketA.length).toBeGreaterThanOrEqual(2); } else { - const bucketB = table.data[hB]!; + const bucketB = table.data[hB]; expect(bucketA.length).toBe(1); expect(bucketB.length).toBe(1); } diff --git a/src/utils/offsetObjectHash.ts b/src/utils/offsetObjectHash.ts index f42c24f..fd90263 100644 --- a/src/utils/offsetObjectHash.ts +++ b/src/utils/offsetObjectHash.ts @@ -1,4 +1,4 @@ -import type { InitialObject, VertexUsageInfo } from "@/types"; +import type { VertexUsageInfo } from "@/types"; import { HashTable } from "./hashTable"; /** @@ -7,7 +7,10 @@ import { HashTable } from "./hashTable"; * @param {Number} offset The offset number to offset the mesh * @returns The new object with the offset */ -export const createOffsetMesh = (data: string, offset: number): InitialObject[] => { +export const createOffsetMesh = ( + data: string, + offset: number, +): Pick[] => { const initialObjects = parseASCII(data); const hashTable = createHashTableWithObject(initialObjects); @@ -30,37 +33,39 @@ export const createOffsetMesh = (data: string, offset: number): InitialObject[] * @param {String} data The content of the file * @returns The content of the file in an array of objects */ -const parseASCII = (data: string): InitialObject[] => { +const parseASCII = (data: string): Pick[] => { const patternSolid = /solid([\s\S]*?)endsolid/g; const patternFace = /facet([\s\S]*?)endfacet/g; const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; const patternVertex = new RegExp(`vertex${patternFloat}${patternFloat}${patternFloat}`, "g"); const patternNormal = new RegExp(`normal${patternFloat}${patternFloat}${patternFloat}`, "g"); - - let result: RegExpExecArray | null; const initialObjects = []; - result = patternSolid.exec(data); + let result: RegExpExecArray | null = patternSolid.exec(data); + while (result !== null) { const solid = result[0]; - let faceNumber = 0; + let faceNumber = 0; let faceResult = patternFace.exec(solid); + while (faceResult !== null) { const text = faceResult[0]; - - faceNumber++; const normalV: number[] = []; const verticesV: number[][] = []; let normalResult = patternNormal.exec(text); + + faceNumber++; + while (normalResult !== null) { normalV.push(parseFloat(normalResult[1]), parseFloat(normalResult[2]), parseFloat(normalResult[3])); normalResult = patternNormal.exec(text); } let vertexResult = patternVertex.exec(text); + while (vertexResult !== null) { const verticesXYZ: number[] = [ parseFloat(vertexResult[1]), @@ -78,11 +83,13 @@ const parseASCII = (data: string): InitialObject[] => { }; initialObjects.push(initialObject); + faceResult = patternFace.exec(solid); } result = patternSolid.exec(data); } + return initialObjects; }; @@ -91,8 +98,12 @@ const parseASCII = (data: string): InitialObject[] => { * @param {Array} initialObjects The data from the file * @returns HashTable of objects with the same vertex */ -const createHashTableWithObject = (initialObjects: InitialObject[]): HashTable => { - const hashTable = new HashTable(initialObjects.length); +const createHashTableWithObject = ( + initialObjects: Pick[], +): HashTable> => { + const hashTable = new HashTable>( + initialObjects.length, + ); for (let i = 0; i < initialObjects.length; i++) { for (let j = 0; j < initialObjects[i].vertices.length; j++) { @@ -116,7 +127,11 @@ const createHashTableWithObject = (initialObjects: InitialObject[]): HashTable, newObjects: InitialObject[], offset: number): InitialObject[] => { +const calcOffset = ( + hashTable: HashTable>, + newObjects: Pick[], + offset: number, +): Pick[] => { for (let i = 0; i < hashTable.data.length; i++) { if (hashTable.data[i]) { for (let j = 0; j < hashTable.data[i].length; j++) { @@ -142,6 +157,7 @@ const calcOffset = (hashTable: HashTable, newObjects: InitialObject[], newObjects.map((item) => { const newNormal = calculateNewNormal(item.vertices[0], item.vertices[1], item.vertices[2]); + return { ...item, normal: item.normal.push(newNormal[0], newNormal[1], newNormal[2]), @@ -156,7 +172,9 @@ const calcOffset = (hashTable: HashTable, newObjects: InitialObject[], * @param {Array} tempList Array of objects with the same vertex * @returns The sum of all the normals */ -const calcNormalsSum = (tempList: VertexUsageInfo[]): number[] => { +const calcNormalsSum = ( + tempList: Pick[], +): number[] => { // const result = tempList.reduce((prev, curr) => prev + curr.normal, 0); let sumX = 0; let sumY = 0; From 33e3cf2f3b1742f7f1d707621d470dc67892cd71 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Wed, 15 Oct 2025 18:34:20 -0600 Subject: [PATCH 5/9] chore: formatting for index.ts fix --- package.json | 2 +- src/index.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 83631bf..6bee4be 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ } }, "scripts": { - "check": "biome check --write ./src/**/*.ts ./demo/**", + "check": "biome check --write ./src/index.ts ./src/**/*.ts ./demo/**", "check:ci": "biome check ./src/**/*.ts ./demo/**", "test": "vitest", "dev": "vite", diff --git a/src/index.ts b/src/index.ts index a3a3a72..a764a69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { applyOffset, processGeometry } from "./offset" -export { createMeshFromObject } from "./utils/createMeshFromObject" -export { createOffsetMesh } from "./utils/offsetObjectHash" -export type { VertexUsageInfo as InitialObject } from "./types" +export { applyOffset, processGeometry } from "./offset"; +export type { VertexUsageInfo as InitialObject } from "./types"; +export { createMeshFromObject } from "./utils/createMeshFromObject"; +export { createOffsetMesh } from "./utils/offsetObjectHash"; From 53d35299a108230a982caf8b296afadf038c52c2 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Sun, 19 Oct 2025 18:27:11 -0600 Subject: [PATCH 6/9] chore: update node version and package.json exports order --- .nvmrc | 2 +- package-lock.json | 4 ++++ package.json | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.nvmrc b/.nvmrc index 2edeafb..cabf43b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 \ No newline at end of file +24 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 86d9a52..5dca3b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1191,6 +1191,7 @@ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -2160,6 +2161,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -2389,6 +2391,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2434,6 +2437,7 @@ "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/package.json b/package.json index 6bee4be..1bbe9d1 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ ], "exports": { ".": { - "import": "./lib/index.es.js", - "require": "./lib/index.umd.js", - "types": "./lib/index.d.ts" + "types": "./dist/lib/index.d.ts", + "import": "./dist/lib/index.es.js", + "require": "./dist/lib/index.umd.js" } }, "scripts": { From c06a198418daad5f6e34bc5e4577db98a008d5da Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Thu, 23 Oct 2025 15:58:48 -0600 Subject: [PATCH 7/9] chore: complete node v25 usage and test CI usage --- .github/workflows/pr-checks.yml | 12 +- .nvmrc | 2 +- demo/index.html | 2 +- demo/{index.js => index.ts} | 75 +++--- package-lock.json | 417 ++++++++++++++------------------ package.json | 13 +- src/offset.test.ts | 165 +++++++------ tsconfig.json | 4 +- vite.config.ts | 79 +++--- 9 files changed, 374 insertions(+), 395 deletions(-) rename demo/{index.js => index.ts} (62%) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 75b6813..45e0656 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 20 + node-version: 25 cache: npm - name: Install dependencies @@ -28,12 +28,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 20 + node-version: 25 cache: npm - name: Install dependencies diff --git a/.nvmrc b/.nvmrc index cabf43b..410b14d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24 \ No newline at end of file +25 \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index c1c92bd..782b5f5 100644 --- a/demo/index.html +++ b/demo/index.html @@ -35,6 +35,6 @@ - + diff --git a/demo/index.js b/demo/index.ts similarity index 62% rename from demo/index.js rename to demo/index.ts index b1a34c7..ad5c04b 100644 --- a/demo/index.js +++ b/demo/index.ts @@ -5,7 +5,8 @@ import * as THREE from "three"; import { TrackballControls } from "three/examples/jsm/controls/TrackballControls.js"; import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js"; -let renderer, scene; +let renderer: THREE.WebGLRenderer; +let scene: THREE.Scene; function init() { const sizes = { @@ -35,7 +36,7 @@ function init() { // the light follow the camera position controls.addEventListener("change", lightUpdate); - function lightUpdate() { + function lightUpdate(): void { directionalLight.position.copy(camera.position); } @@ -51,7 +52,7 @@ function init() { directionalLight.castShadow = true; scene.add(directionalLight); - function onWindowResize() { + function onWindowResize(): void { // Update sizes sizes.width = window.innerWidth; sizes.height = window.innerHeight - 100; @@ -65,7 +66,7 @@ function init() { renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); } - function render() { + function render(): void { requestAnimationFrame(render); controls.update(); renderer.render(scene, camera); @@ -76,16 +77,16 @@ function init() { init(); -let mesh; +let mesh: THREE.Mesh | null = null; -const offsetElement = document.getElementById("offset"); -const btnMeshOffset = document.getElementById("meshOffset"); -const btnPointOffset = document.getElementById("pointOffset"); +const offsetElement = document.getElementById("offset") as HTMLInputElement; +const btnMeshOffset = document.getElementById("meshOffset") as HTMLButtonElement; +const btnPointOffset = document.getElementById("pointOffset") as HTMLButtonElement; -btnMeshOffset.addEventListener("click", async (e) => { +btnMeshOffset?.addEventListener("click", async (e: Event) => { e.preventDefault(); - const offset = offsetElement.value; + const offset = Number(offsetElement?.value || 0); if (mesh) { const meshOffset = await applyOffset(mesh, offset); @@ -93,10 +94,10 @@ btnMeshOffset.addEventListener("click", async (e) => { } }); -btnPointOffset.addEventListener("click", (e) => { +btnPointOffset?.addEventListener("click", (e: Event) => { e.preventDefault(); - const offset = offsetElement.value; + const offset = Number(offsetElement?.value || 0); if (mesh) { const newMesh = processGeometry(mesh.geometry, offset); @@ -105,23 +106,29 @@ btnPointOffset.addEventListener("click", (e) => { }); // Load the file and get the geometry -document.getElementById("file").onchange = (e) => { +const fileInput = document.getElementById("file") as HTMLInputElement; +fileInput?.addEventListener("change", (e: Event) => { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + + if (!file) return; + const reader = new FileReader(); reader.onload = () => { - const geometry = new STLLoader().parse(reader.result); - - createMeshFromFile(geometry); + if (reader.result) { + const geometry = new STLLoader().parse(reader.result); + createMeshFromFile(geometry); + } }; - reader.readAsArrayBuffer(e.target.files[0]); -}; + reader.readAsArrayBuffer(file); +}); /** * Creates the mesh from the file's geometry - * @param {THREE.BufferGeometry} geometry */ -const createMeshFromFile = (geometry) => { +const createMeshFromFile = (geometry: THREE.BufferGeometry): void => { if (mesh) { scene.remove(mesh); } @@ -135,32 +142,36 @@ const createMeshFromFile = (geometry) => { scene.add(mesh); }; -const removeAddOffset = (mesh) => { - const offsetMesh = scene.children.filter((item) => item.name === mesh.name); +const removeAddOffset = (offsetMesh: THREE.Mesh | THREE.Points): void => { + const existingMesh = scene.children.filter((item) => item.name === offsetMesh.name); - if (offsetMesh.length === 0) { - scene.add(mesh); + if (existingMesh.length === 0) { + scene.add(offsetMesh); } else { - scene.remove(offsetMesh[0]); - scene.add(mesh); + scene.remove(existingMesh[0]); + scene.add(offsetMesh); } }; // Button to clear the scene -const btnClearScene = document.getElementById("clearScene"); +const btnClearScene = document.getElementById("clearScene") as HTMLButtonElement; -btnClearScene.addEventListener("click", () => { +btnClearScene?.addEventListener("click", () => { clearScene(); }); -const clearScene = () => { +const clearScene = (): void => { const meshes = scene.children.filter((item) => item.type === "Mesh" || item.type === "Points"); if (meshes.length > 0) { - for (const mesh of meshes) { - scene.remove(mesh); + for (const meshItem of meshes) { + scene.remove(meshItem); } - document.getElementById("file").value = ""; + const fileInput = document.getElementById("file") as HTMLInputElement; + if (fileInput) { + fileInput.value = ""; + } } + mesh = null; }; diff --git a/package-lock.json b/package-lock.json index 5dca3b9..37ac20b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,17 +9,18 @@ "version": "0.3.1", "license": "MIT", "devDependencies": { - "@biomejs/biome": "2.2.6", - "@types/node": "^24.7.2", + "@biomejs/biome": "2.2.7", + "@types/node": "^24.9.1", "@types/three": "^0.180.0", "three": "^0.180.0", "typescript": "^5.9.3", - "vite": "^7.1.10", + "vite": "^7.1.12", "vite-plugin-dts": "^4.5.4", - "vitest": "^3.2.4" + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.0.2" }, "engines": { - "node": ">=20" + "node": ">=25" }, "peerDependencies": { "three": "^0.143.0" @@ -72,9 +73,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.6.tgz", - "integrity": "sha512-yKTCNGhek0rL5OEW1jbLeZX8LHaM8yk7+3JRGv08my+gkpmtb5dDE+54r2ZjZx0ediFEn1pYBOJSmOdDP9xtFw==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.7.tgz", + "integrity": "sha512-1a8j0UP1vXVUf3UzMZEJ/zS2VgAG6wU6Cuh/I764sUGI+MCnJs/9WaojHYBDCxCMLTgU60/WqnYof85emXmSBA==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -88,20 +89,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.2.6", - "@biomejs/cli-darwin-x64": "2.2.6", - "@biomejs/cli-linux-arm64": "2.2.6", - "@biomejs/cli-linux-arm64-musl": "2.2.6", - "@biomejs/cli-linux-x64": "2.2.6", - "@biomejs/cli-linux-x64-musl": "2.2.6", - "@biomejs/cli-win32-arm64": "2.2.6", - "@biomejs/cli-win32-x64": "2.2.6" + "@biomejs/cli-darwin-arm64": "2.2.7", + "@biomejs/cli-darwin-x64": "2.2.7", + "@biomejs/cli-linux-arm64": "2.2.7", + "@biomejs/cli-linux-arm64-musl": "2.2.7", + "@biomejs/cli-linux-x64": "2.2.7", + "@biomejs/cli-linux-x64-musl": "2.2.7", + "@biomejs/cli-win32-arm64": "2.2.7", + "@biomejs/cli-win32-x64": "2.2.7" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.6.tgz", - "integrity": "sha512-UZPmn3M45CjTYulgcrFJFZv7YmK3pTxTJDrFYlNElT2FNnkkX4fsxjExTSMeWKQYoZjvekpH5cvrYZZlWu3yfA==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.7.tgz", + "integrity": "sha512-xBUUsebnO2/Qj1v7eZmKUy2ZcFkZ4/jLUkxN02Qup1RPoRaiW9AKXHrqS3L7iX6PzofHY2xuZ+Pb9kAcpoe0qA==", "cpu": [ "arm64" ], @@ -116,9 +117,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.6.tgz", - "integrity": "sha512-HOUIquhHVgh/jvxyClpwlpl/oeMqntlteL89YqjuFDiZ091P0vhHccwz+8muu3nTyHWM5FQslt+4Jdcd67+xWQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.7.tgz", + "integrity": "sha512-vsY4NhmxqgfLJufr9XUnC+yGUPJiXAc1mz6FcjaAmuIuLwfghN4uQO7hnW2AneGyoi2mNe9Jbvf6Qtq4AjzrFg==", "cpu": [ "x64" ], @@ -133,9 +134,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.6.tgz", - "integrity": "sha512-BpGtuMJGN+o8pQjvYsUKZ+4JEErxdSmcRD/JG3mXoWc6zrcA7OkuyGFN1mDggO0Q1n7qXxo/PcupHk8gzijt5g==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.7.tgz", + "integrity": "sha512-nUdco104rjV9dULi1VssQ5R/kX2jE/Z2sDjyqS+siV9sTQda0DwmEUixFNRCWvZJRRiZUWhgiDFJ4n7RowO8Mg==", "cpu": [ "arm64" ], @@ -150,9 +151,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.6.tgz", - "integrity": "sha512-TjCenQq3N6g1C+5UT3jE1bIiJb5MWQvulpUngTIpFsL4StVAUXucWD0SL9MCW89Tm6awWfeXBbZBAhJwjyFbRQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.7.tgz", + "integrity": "sha512-FrTwvKO/7t5HbVTvhlMOTOVQLAcR7r4O4iFQhEpZXUtBfosHqrX/JJlX7daPawoe14MDcCu9CDg0zLVpTuDvuQ==", "cpu": [ "arm64" ], @@ -167,9 +168,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.6.tgz", - "integrity": "sha512-1HaM/dpI/1Z68zp8ZdT6EiBq+/O/z97a2AiHMl+VAdv5/ELckFt9EvRb8hDHpk8hUMoz03gXkC7VPXOVtU7faA==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.7.tgz", + "integrity": "sha512-tPTcGAIEOOZrj2tQ7fdraWlaxNKApBw6l4In8wQQV1IyxnAexqi0hykHzKEX8hKKctf5gxGBfNCzyIvqpj4CFQ==", "cpu": [ "x64" ], @@ -184,9 +185,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.6.tgz", - "integrity": "sha512-1ZcBux8zVM3JhWN2ZCPaYf0+ogxXG316uaoXJdgoPZcdK/rmRcRY7PqHdAos2ExzvjIdvhQp72UcveI98hgOog==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.7.tgz", + "integrity": "sha512-MnsysF5s/iLC5wnYvuMseOy+m8Pd4bWG1uwlVyy2AUbfjAVUgtbYbboc5wMXljFrDY7e6rLjLTR4S2xqDpGlQg==", "cpu": [ "x64" ], @@ -201,9 +202,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.6.tgz", - "integrity": "sha512-h3A88G8PGM1ryTeZyLlSdfC/gz3e95EJw9BZmA6Po412DRqwqPBa2Y9U+4ZSGUAXCsnSQE00jLV8Pyrh0d+jQw==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.7.tgz", + "integrity": "sha512-h5D1jhwA2b7cFXerYiJfXHSzzAMFFoEDL5Mc2BgiaEw0iaSgSso/3Nc6FbOR55aTQISql+IpB4PS7JoV26Gdbw==", "cpu": [ "arm64" ], @@ -218,9 +219,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.6.tgz", - "integrity": "sha512-yx0CqeOhPjYQ5ZXgPfu8QYkgBhVJyvWe36as7jRuPrKPO5ylVDfwVtPQ+K/mooNTADW0IhxOZm3aPu16dP8yNQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.7.tgz", + "integrity": "sha512-URqAJi0kONyBKG4V9NVafHLDtm6IHmF4qPYi/b6x7MD6jxpWeJiTCO6R5+xDlWckX2T/OGv6Yq3nkz6s0M8Ykw==", "cpu": [ "x64" ], @@ -1150,6 +1151,13 @@ "string-argv": "~0.3.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", @@ -1163,13 +1171,14 @@ "dev": true }, "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, "node_modules/@types/deep-eql": { @@ -1186,14 +1195,14 @@ "dev": true }, "node_modules/@types/node": { - "version": "24.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", - "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.14.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/stats.js": { @@ -1224,39 +1233,40 @@ "dev": true }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.2.tgz", + "integrity": "sha512-izQY+ABWqL2Vyr5+LNo3m16nLLTAzLn8em6i5uxqsrWRhdgzdN5JIHrpFVGBAYRGDAbtwE+yD4Heu8gsBSWTVQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.0.2", + "@vitest/utils": "4.0.2", + "chai": "^6.0.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.2.tgz", + "integrity": "sha512-oiny+oBSGU9vHMA1DPdO+t1GVidCRuA4lKSG6rbo5SrCiTCGl7bTCyTaUkwxDpUkiSxEVneeXW4LJ4fg3H56dw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.0.2", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.19" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -1278,42 +1288,41 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.2.tgz", + "integrity": "sha512-PhrSiljryCz5nUDhHla5ihXYy2iRCBob+rNqlu34dA+KZIllVR39rUGny5R3kLgDgw3r8GW1ptOo64WbieMkeQ==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.2.tgz", + "integrity": "sha512-mPS5T/ZDuO6J5rsQiA76CFmlHtos7dnCvL14I1Oo8SbcjIhJd6kirFmekovfYLRygdF0gJe6SA5asCKIWKw1tw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.2", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.2.tgz", + "integrity": "sha512-NibujZAh+fTQlpGdP8J2pZcsPg7EPjiLUOUq9In++4p35vc9xIFMkXfQDbBSpijqZPe6i2hEKrUCbKu70/sPzw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.2", + "magic-string": "^0.30.19", "pathe": "^2.0.3" }, "funding": { @@ -1321,28 +1330,24 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.2.tgz", + "integrity": "sha512-KrTWRXFPYrbhD0iUXeoA8BMXl81nvemj5D8sc7NbTlRvCeUWo36JheOWtAUCafcNi0G72ycAdsvWQVSOxy/3TA==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.2.tgz", + "integrity": "sha512-H9jFzZb/5B5Qh7ajPUWMJ8UYGxQ4EQTaNLSm3icXs/oXkzQ1jqfcWDEJ4U3LkFPZOd6QW8M2MYjz32poW+KKqg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.2", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1557,43 +1562,16 @@ "balanced-match": "^1.0.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/compare-versions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", @@ -1629,16 +1607,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1789,6 +1757,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1855,13 +1830,6 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1909,13 +1877,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2035,16 +1996,6 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2278,19 +2229,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -2355,34 +2293,35 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", "dev": true, "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, "engines": { - "node": ">=14.0.0" + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/typescript": { @@ -2407,9 +2346,9 @@ "dev": true }, "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -2432,9 +2371,9 @@ } }, "node_modules/vite": { - "version": "7.1.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", - "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", "peer": true, @@ -2507,29 +2446,6 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vite-plugin-dts": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", @@ -2556,42 +2472,59 @@ } } }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.2.tgz", + "integrity": "sha512-SXrA2ZzOPulX479d8W13RqKSmvHb9Bfg71eW7Fbs6ZjUFcCCXyt/OzFCkNyiUE8mFlPHa4ZVUGw0ky+5ndKnrg==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.2", + "@vitest/mocker": "4.0.2", + "@vitest/pretty-format": "4.0.2", + "@vitest/runner": "4.0.2", + "@vitest/snapshot": "4.0.2", + "@vitest/spy": "4.0.2", + "@vitest/utils": "4.0.2", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.19", "pathe": "^2.0.3", - "picomatch": "^4.0.2", + "picomatch": "^4.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2599,9 +2532,11 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.2", + "@vitest/browser-preview": "4.0.2", + "@vitest/browser-webdriverio": "4.0.2", + "@vitest/ui": "4.0.2", "happy-dom": "*", "jsdom": "*" }, @@ -2615,7 +2550,13 @@ "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { diff --git a/package.json b/package.json index 1bbe9d1..761ea88 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "scripts": { "check": "biome check --write ./src/index.ts ./src/**/*.ts ./demo/**", "check:ci": "biome check ./src/**/*.ts ./demo/**", - "test": "vitest", + "test": "vitest --mode test --run", "dev": "vite", "build:demo": "vite build --mode demo", "build:lib": "vite build", @@ -43,17 +43,18 @@ }, "homepage": "https://github.com/AngyDev/threejs-offset#readme", "engines": { - "node": ">=20" + "node": ">=25" }, "devDependencies": { - "@biomejs/biome": "2.2.6", - "@types/node": "^24.7.2", + "@biomejs/biome": "2.2.7", + "@types/node": "^24.9.1", "@types/three": "^0.180.0", "three": "^0.180.0", "typescript": "^5.9.3", - "vite": "^7.1.10", + "vite": "^7.1.12", "vite-plugin-dts": "^4.5.4", - "vitest": "^3.2.4" + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.0.2" }, "peerDependencies": { "three": "^0.143.0" diff --git a/src/offset.test.ts b/src/offset.test.ts index 828d6a0..c48ac12 100644 --- a/src/offset.test.ts +++ b/src/offset.test.ts @@ -1,76 +1,101 @@ -import { Mesh, BufferGeometry, BufferAttribute, Points, PointsMaterial } from "three"; -import { STLExporter } from "three/examples/jsm/exporters/STLExporter.js"; -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, vi, beforeEach } from 'vitest' +import * as THREE from 'three' +import { applyOffset, processGeometry } from './offset' -import { applyOffset, processGeometry } from "./offset"; -import { createMeshFromObject } from "./utils/createMeshFromObject"; -import { createOffsetMesh } from "./utils/offsetObjectHash"; +// Mock dependencies +const mockParse = vi.fn().mockReturnValue('mocked stl data') -vi.mock("three/examples/jsm/exporters/STLExporter.js"); -vi.mock("./utils/createMeshFromObject"); -vi.mock("./utils/offsetObjectHash"); +vi.mock('three/examples/jsm/exporters/STLExporter.js', () => ({ + // biome-ignore lint/complexity/useArrowFunction: required for mocking classes + STLExporter: vi.fn().mockImplementation(function() { + return { + parse: mockParse + } + }) +})) -describe("applyOffset", () => { - let mockMesh: Mesh; - let mockExporter: any; - let mockResult: string; +vi.mock('./utils/createMeshFromObject', () => ({ + createMeshFromObject: vi.fn().mockResolvedValue(new THREE.Mesh()) +})) - beforeEach(() => { - mockMesh = new Mesh(); - mockResult = "mock stl data"; - mockExporter = { - parse: vi.fn().mockReturnValue(mockResult), - }; - (STLExporter as any).mockImplementation(() => mockExporter); - (createOffsetMesh as any).mockReturnValue("mock object"); - (createMeshFromObject as any).mockResolvedValue(new Mesh()); - }); - - it("should export mesh as STL and create offset mesh", async () => { - const offset = 2; - const result = await applyOffset(mockMesh, offset); - - expect(STLExporter).toHaveBeenCalled(); - expect(mockExporter.parse).toHaveBeenCalledWith(mockMesh, { binary: false }); - expect(createOffsetMesh).toHaveBeenCalledWith(mockResult, offset); - expect(createMeshFromObject).toHaveBeenCalledWith("mock object"); - expect(result.name).toBe("offset"); - }); -}); - -describe("processGeometry", () => { - let mockGeometry: BufferGeometry; +vi.mock('./utils/offsetObjectHash', () => ({ + createOffsetMesh: vi.fn().mockReturnValue({}) +})) +describe('offset', () => { beforeEach(() => { - mockGeometry = new BufferGeometry(); - const vertices = new Float32Array([0, 0, 0, 1, 1, 1, 2, 2, 2]); - const normals = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); - mockGeometry.setAttribute("position", new BufferAttribute(vertices, 3)); - mockGeometry.setAttribute("normal", new BufferAttribute(normals, 3)); - }); - - it("should create points with offset applied to vertices", () => { - const offset = 0.5; - const result = processGeometry(mockGeometry, offset); - - expect(result).toBeInstanceOf(Points); - expect(result.name).toBe("offset"); - expect(result.material).toBeInstanceOf(PointsMaterial); - expect((result.material as PointsMaterial).color.getHex()).toBe(0xff0000); - expect((result.material as PointsMaterial).size).toBe(0.7); - - const positions = result.geometry.attributes.position.array; - expect(positions[0]).toBe(0.5); // 0.5 * 1 + 0 - expect(positions[1]).toBe(0); // 0.5 * 0 + 0 - expect(positions[2]).toBe(0); // 0.5 * 0 + 0 - }); - - it("should handle negative offset", () => { - const offset = -1; - const result = processGeometry(mockGeometry, offset); - - const positions = result.geometry.attributes.position.array; - expect(positions[0]).toBe(-1); // -1 * 1 + 0 - expect(positions[4]).toBe(0); // -1 * 1 + 1 - }); -}); + vi.clearAllMocks() + }) + + describe('applyOffset', () => { + it('should create an offset mesh with correct name', async () => { + const mockMesh = new THREE.Mesh() + const offset = 2.5 + + const result = await applyOffset(mockMesh, offset) + + expect(result.name).toBe('offset') + }) + + it('should call STLExporter with correct parameters', async () => { + const mockMesh = new THREE.Mesh() + const offset = 1.0 + + await applyOffset(mockMesh, offset) + + const { STLExporter } = await import('three/examples/jsm/exporters/STLExporter.js') + expect(STLExporter).toHaveBeenCalled() + expect(mockParse).toHaveBeenCalledWith(mockMesh, { binary: false }) + }) + }) + + describe('processGeometry', () => { + it('should create points with offset applied to vertices', () => { + const geometry = new THREE.BufferGeometry() + const vertices = new Float32Array([1, 2, 3, 4, 5, 6]) + const normals = new Float32Array([0, 1, 0, 1, 0, 0]) + geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)) + geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3)) + + const offset = 2.0 + const result = processGeometry(geometry, offset) + + expect(result).toBeInstanceOf(THREE.Points) + expect(result.name).toBe('offset') + expect(result.material).toBeInstanceOf(THREE.PointsMaterial) + }) + + it('should apply offset correctly to vertex positions', () => { + const geometry = new THREE.BufferGeometry() + const vertices = new Float32Array([0, 0, 0, 3, 3, 3]) + const normals = new Float32Array([1, 0, 0, 0, 1, 0]) + geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)) + geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3)) + + const offset = 1.0 + const result = processGeometry(geometry, offset) + const resultPositions = result.geometry.attributes.position.array as Float32Array + + expect(resultPositions[0]).toBe(1) // 1 * 1 + 0 + expect(resultPositions[1]).toBe(0) // 0 * 1 + 0 + expect(resultPositions[2]).toBe(0) // 0 * 1 + 0 + expect(resultPositions[3]).toBe(3) // 0 * 1 + 3 + expect(resultPositions[4]).toBe(4) // 1 * 1 + 3 + expect(resultPositions[5]).toBe(3) // 0 * 1 + 3 + }) + + it('should create material with correct properties', () => { + const geometry = new THREE.BufferGeometry() + const vertices = new Float32Array([1, 2, 3]) + const normals = new Float32Array([0, 1, 0]) + geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)) + geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3)) + + const result = processGeometry(geometry, 1.0) + const material = result.material as THREE.PointsMaterial + + expect(material.color.getHex()).toBe(0xff0000) + expect(material.size).toBe(0.7) + }) + }) +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 25e00ce..ebf4b27 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ "@/*": ["src/*"] } }, - "include": ["src"], - "exclude": ["node_modules", "dist", "demo"] + "include": ["src", "demo"], + "exclude": ["node_modules", "dist"] } diff --git a/vite.config.ts b/vite.config.ts index 3a26624..42892e2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,20 +1,24 @@ -import { defineConfig } from "vite"; import path from "node:path"; +import { defineConfig } from "vite"; import dts from "vite-plugin-dts"; +import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig(({ command, mode }) => { - console.log(`Command: ${command}, Mode: ${mode}`); + const baseConfig = { + plugins: [tsconfigPaths()], + test: { + root: "./src", + }, + }; + + if (mode === "test") { + return baseConfig; + } + if (mode === "demo" || command === "serve") { return { - test: { - root: "./src", - }, + ...baseConfig, root: "demo", - resolve: { - alias: { - "@": path.resolve(__dirname, "src"), - }, - }, build: { outDir: path.resolve(__dirname, "dist/demo"), emptyOutDir: true, @@ -23,37 +27,34 @@ export default defineConfig(({ command, mode }) => { port: 9000, }, }; - } else { - return { - resolve: { - alias: { - "@": path.resolve(__dirname, "src"), - }, + } + + return { + ...baseConfig, + plugins: [ + ...baseConfig.plugins, + dts({ + insertTypesEntry: true, + rollupTypes: true, + }), + ], + build: { + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "threejsOffset", + fileName: (format) => `index.${format}.js`, + formats: ["es", "umd"], }, - plugins: [ - dts({ - insertTypesEntry: true, - rollupTypes: true, - }), - ], - build: { - lib: { - entry: path.resolve(__dirname, "src/index.ts"), - name: "threejsOffset", - fileName: (format) => `index.${format}.js`, - formats: ["es", "umd"], - }, - rollupOptions: { - external: ["three"], - output: { - globals: { - three: "THREE", - }, + rollupOptions: { + external: ["three"], + output: { + globals: { + three: "THREE", }, }, - outDir: path.resolve(__dirname, "dist/lib"), - emptyOutDir: true, }, - }; - } + outDir: path.resolve(__dirname, "dist/lib"), + emptyOutDir: true, + }, + }; }); From 42e962597c06f39e5c5e61d618d99c64af67d62f Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Thu, 23 Oct 2025 16:09:29 -0600 Subject: [PATCH 8/9] build trigger --- demo/index.ts | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/index.ts b/demo/index.ts index ad5c04b..e3f12e4 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -110,7 +110,7 @@ const fileInput = document.getElementById("file") as HTMLInputElement; fileInput?.addEventListener("change", (e: Event) => { const target = e.target as HTMLInputElement; const file = target.files?.[0]; - + if (!file) return; const reader = new FileReader(); diff --git a/package.json b/package.json index 761ea88..1366acb 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ } }, "scripts": { - "check": "biome check --write ./src/index.ts ./src/**/*.ts ./demo/**", - "check:ci": "biome check ./src/**/*.ts ./demo/**", + "check": "biome check --write ./src/index.ts ./src/**/*.ts ./demo/*.ts", + "check:ci": "biome check ./src/**/*.ts ./demo/*.ts", "test": "vitest --mode test --run", "dev": "vite", "build:demo": "vite build --mode demo", From c6ccf7c62b8851659107fb8900a7ad375cbfa504 Mon Sep 17 00:00:00 2001 From: andrewgremlich Date: Fri, 31 Oct 2025 18:54:09 -0600 Subject: [PATCH 9/9] reverting typing and making type compatible --- src/index.ts | 2 +- src/types.ts | 8 ++++-- src/utils/createMeshFromObject.test.ts | 4 +-- src/utils/createMeshFromObject.ts | 6 ++--- src/utils/offsetObjectHash.ts | 35 +++++++++++--------------- 5 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/index.ts b/src/index.ts index a764a69..6eb83b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { applyOffset, processGeometry } from "./offset"; -export type { VertexUsageInfo as InitialObject } from "./types"; +export type { InitialObject, VertexUsageInfo } from "./types"; export { createMeshFromObject } from "./utils/createMeshFromObject"; export { createOffsetMesh } from "./utils/offsetObjectHash"; diff --git a/src/types.ts b/src/types.ts index 1405f8c..29b804d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,11 @@ -export interface VertexUsageInfo { - facetIndex: number; +export interface InitialObject { face: number; normal: number[]; vertices: number[][]; +} + +export interface VertexUsageInfo { + facetIndex: number; + normal: number[]; vertexPositionInTheObject: number; } diff --git a/src/utils/createMeshFromObject.test.ts b/src/utils/createMeshFromObject.test.ts index 3205c58..429168a 100644 --- a/src/utils/createMeshFromObject.test.ts +++ b/src/utils/createMeshFromObject.test.ts @@ -1,7 +1,7 @@ import * as THREE from "three"; import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { VertexUsageInfo } from "@/types"; +import type { InitialObject } from "@/types"; import { createMeshFromObject } from "./createMeshFromObject"; vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ @@ -9,7 +9,7 @@ vi.mock("three/examples/jsm/utils/BufferGeometryUtils", () => ({ })); describe("createMeshFromObject", () => { - let mockObject: Pick[]; + let mockObject: InitialObject[]; beforeEach(() => { mockObject = [ diff --git a/src/utils/createMeshFromObject.ts b/src/utils/createMeshFromObject.ts index 45384d6..ba7906c 100644 --- a/src/utils/createMeshFromObject.ts +++ b/src/utils/createMeshFromObject.ts @@ -1,15 +1,13 @@ import * as THREE from "three"; import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils"; -import type { VertexUsageInfo } from "@/types"; +import type { InitialObject } from "@/types"; /** * Creates a mesh from an objects with normals and vertices * @param {Object} object This is an object with normals and vertices * @returns THREE.Mesh */ -export const createMeshFromObject = ( - object: Pick[], -): Promise => +export const createMeshFromObject = (object: InitialObject[]): Promise => new Promise((resolve) => { const geometry = new THREE.BufferGeometry(); const material = new THREE.MeshStandardMaterial({ diff --git a/src/utils/offsetObjectHash.ts b/src/utils/offsetObjectHash.ts index fd90263..5f4bdc5 100644 --- a/src/utils/offsetObjectHash.ts +++ b/src/utils/offsetObjectHash.ts @@ -1,4 +1,4 @@ -import type { VertexUsageInfo } from "@/types"; +import type { InitialObject, VertexUsageInfo } from "@/types"; import { HashTable } from "./hashTable"; /** @@ -7,10 +7,7 @@ import { HashTable } from "./hashTable"; * @param {Number} offset The offset number to offset the mesh * @returns The new object with the offset */ -export const createOffsetMesh = ( - data: string, - offset: number, -): Pick[] => { +export const createOffsetMesh = (data: string, offset: number): InitialObject[] => { const initialObjects = parseASCII(data); const hashTable = createHashTableWithObject(initialObjects); @@ -33,7 +30,7 @@ export const createOffsetMesh = ( * @param {String} data The content of the file * @returns The content of the file in an array of objects */ -const parseASCII = (data: string): Pick[] => { +const parseASCII = (data: string): InitialObject[] => { const patternSolid = /solid([\s\S]*?)endsolid/g; const patternFace = /facet([\s\S]*?)endfacet/g; @@ -99,11 +96,9 @@ const parseASCII = (data: string): Pick[], -): HashTable> => { - const hashTable = new HashTable>( - initialObjects.length, - ); + initialObjects: InitialObject[], +): HashTable> => { + const hashTable = new HashTable>(initialObjects.length); for (let i = 0; i < initialObjects.length; i++) { for (let j = 0; j < initialObjects[i].vertices.length; j++) { @@ -128,10 +123,10 @@ const createHashTableWithObject = ( * @returns The new objects with the offset */ const calcOffset = ( - hashTable: HashTable>, - newObjects: Pick[], + hashTable: HashTable>, + newObjects: InitialObject[], offset: number, -): Pick[] => { +): InitialObject[] => { for (let i = 0; i < hashTable.data.length; i++) { if (hashTable.data[i]) { for (let j = 0; j < hashTable.data[i].length; j++) { @@ -146,7 +141,7 @@ const calcOffset = ( hashTable.data[i][j][1].forEach((list) => { newObjects.forEach((newObject) => { - if (newObject.face === list.face) { + if (newObject.face === list.face && list.vertexPositionInTheObject !== undefined) { newObject.vertices[list.vertexPositionInTheObject] = newPosition; } }); @@ -172,18 +167,16 @@ const calcOffset = ( * @param {Array} tempList Array of objects with the same vertex * @returns The sum of all the normals */ -const calcNormalsSum = ( - tempList: Pick[], -): number[] => { +const calcNormalsSum = (tempList: Partial[]): [number, number, number] => { // const result = tempList.reduce((prev, curr) => prev + curr.normal, 0); let sumX = 0; let sumY = 0; let sumZ = 0; tempList.forEach((item) => { - sumX += item.normal[0]; - sumY += item.normal[1]; - sumZ += item.normal[2]; + sumX += item.normal ? item.normal[0] : 0; + sumY += item.normal ? item.normal[1] : 0; + sumZ += item.normal ? item.normal[2] : 0; }); return [sumX, sumY, sumZ];