diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adbae5c..c8f3ca1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,4 +32,6 @@ jobs: - run: pnpm typecheck + - run: pnpm test + - run: pnpm build diff --git a/README.md b/README.md index a1b963a..98def34 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ pnpm dev | `pnpm build` | 正式環境建置 | | `pnpm lint` | 執行 ESLint 檢查 | | `pnpm typecheck` | TypeScript 型別檢查 | +| `pnpm test` | 執行 `@zipkit/core` 單元測試 (Vitest) | | `pnpm format` | 格式化程式碼 (Prettier) | | `pnpm check` | 一次執行 typecheck + lint + format check | | `pnpm etl` | 從原始資料重新產生靜態 JSON | diff --git a/package.json b/package.json index 0dbdee6..b7553c7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint": "pnpm --filter web lint", "typecheck": "pnpm --recursive typecheck", "etl": "pnpm --filter web etl", + "test": "pnpm --recursive run test", "format": "prettier --write .", "format:check": "prettier --check .", "check": "pnpm --recursive typecheck && pnpm --filter web lint && prettier --check ." diff --git a/packages/core/package.json b/packages/core/package.json index 8c2c089..2526422 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,9 +12,12 @@ "main": "./src/index.ts", "types": "./src/index.ts", "scripts": { - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" }, "devDependencies": { - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^4.1.9" } } diff --git a/packages/core/src/format-english-address.test.ts b/packages/core/src/format-english-address.test.ts new file mode 100644 index 0000000..b157cf8 --- /dev/null +++ b/packages/core/src/format-english-address.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from "vitest"; +import { parseNumber, formatEnglishAddress } from "./format-english-address"; +import type { City, District, Road, AddressDetail } from "./types"; + +describe("parseNumber", () => { + it("parses a plain number", () => { + expect(parseNumber("33")).toEqual({ number: 33, sub: 0 }); + }); + + it("parses the 之 (sub-number) notation", () => { + expect(parseNumber("5之1")).toEqual({ number: 5, sub: 1 }); + expect(parseNumber("12之34")).toEqual({ number: 12, sub: 34 }); + }); + + it("trims surrounding whitespace", () => { + expect(parseNumber(" 7 ")).toEqual({ number: 7, sub: 0 }); + }); + + it("returns zeros for non-numeric or malformed input", () => { + expect(parseNumber("")).toEqual({ number: 0, sub: 0 }); + expect(parseNumber("abc")).toEqual({ number: 0, sub: 0 }); + expect(parseNumber("5之")).toEqual({ number: 0, sub: 0 }); // dangling 之 + expect(parseNumber("之5")).toEqual({ number: 0, sub: 0 }); // missing base + }); +}); + +describe("formatEnglishAddress", () => { + const city: City = { name: "臺北市", en: "Taipei City", districts: [] }; + const district: District = { + name: "大安區", + en: "Da'an Dist.", + zip3: "106", + }; + const road: Road = { name: "仁愛路四段", en: "Sec. 4, Ren'ai Rd." }; + + /** Build an AddressDetail with empty defaults. */ + function detail(overrides: Partial = {}): AddressDetail { + return { + lane: "", + alley: "", + number: "", + floor: "", + room: "", + ...overrides, + }; + } + + it("formats a full address in UPU order", () => { + const result = formatEnglishAddress( + city, + district, + road, + detail({ + room: "5", + floor: "4", + number: "12之3", + alley: "5", + lane: "100", + }), + ); + expect(result).toBe( + "Rm. 5, 4F., No. 12-3, Aly. 5, Ln. 100, Sec. 4, Ren'ai Rd., Da'an Dist., Taipei City 106, Taiwan (R.O.C.)", + ); + }); + + it("omits empty optional parts", () => { + const result = formatEnglishAddress( + city, + district, + road, + detail({ number: "33" }), + ); + expect(result).toBe( + "No. 33, Sec. 4, Ren'ai Rd., Da'an Dist., Taipei City 106, Taiwan (R.O.C.)", + ); + }); + + it("renders road / district / city / country even with no detail", () => { + const result = formatEnglishAddress(city, district, road, detail()); + expect(result).toBe( + "Sec. 4, Ren'ai Rd., Da'an Dist., Taipei City 106, Taiwan (R.O.C.)", + ); + }); + + it("converts 之 in the number to a hyphen", () => { + const result = formatEnglishAddress( + city, + district, + road, + detail({ number: "5之1" }), + ); + expect(result).toContain("No. 5-1"); + }); + + it("trims whitespace around detail fields", () => { + const result = formatEnglishAddress( + city, + district, + road, + detail({ number: " 12 ", room: " A " }), + ); + expect(result).toContain("Rm. A"); + expect(result).toContain("No. 12"); + }); +}); diff --git a/packages/core/src/lookup-zipcode.test.ts b/packages/core/src/lookup-zipcode.test.ts new file mode 100644 index 0000000..f2240dd --- /dev/null +++ b/packages/core/src/lookup-zipcode.test.ts @@ -0,0 +1,201 @@ +import { describe, it, expect } from "vitest"; +import { lookupZip6 } from "./lookup-zipcode"; +import type { ZipRange } from "./types"; + +/** + * Build a ZipRange with every field defaulting to 0 (= "no constraint"), + * overriding only the fields a test cares about. + */ +function range(overrides: Partial = {}): ZipRange { + return { + road: "Test Rd.", + zip6: "100000", + even: 0, + lane: 0, + lane1: 0, + alley: 0, + alley1: 0, + numStart: 0, + numStart1: 0, + numEnd: 0, + numEnd1: 0, + floorStart: 0, + floorEnd: 0, + ...overrides, + }; +} + +/** Readable wrapper around lookupZip6's positional signature. */ +function lookup( + ranges: ZipRange[], + road: string, + opts: { + lane?: number; + alley?: number; + number?: number; + sub?: number; + floor?: number; + } = {}, +): string | null { + const { lane = 0, alley = 0, number = 0, sub = 0, floor = 0 } = opts; + return lookupZip6(ranges, road, lane, alley, number, sub, floor); +} + +describe("lookupZip6", () => { + describe("road matching", () => { + it("returns null for an empty range list", () => { + expect(lookup([], "Test Rd.")).toBeNull(); + }); + + it("returns null when no range matches the road name", () => { + const ranges = [range({ road: "Other Rd.", zip6: "999999" })]; + expect(lookup(ranges, "Test Rd.")).toBeNull(); + }); + + it("matches a catch-all range (all constraints 0) for any input", () => { + const ranges = [range({ zip6: "111000" })]; + expect(lookup(ranges, "Test Rd.")).toBe("111000"); + expect( + lookup(ranges, "Test Rd.", { lane: 9, alley: 9, number: 99, floor: 9 }), + ).toBe("111000"); + }); + }); + + describe("lane constraint", () => { + it("matches only the exact lane when lane1 is unset", () => { + const ranges = [range({ lane: 5, zip6: "L5" })]; + expect(lookup(ranges, "Test Rd.", { lane: 5 })).toBe("L5"); + expect(lookup(ranges, "Test Rd.", { lane: 4 })).toBeNull(); + }); + + it("matches an inclusive lane range when lane1 is set", () => { + const ranges = [range({ lane: 5, lane1: 10, zip6: "L5_10" })]; + expect(lookup(ranges, "Test Rd.", { lane: 5 })).toBe("L5_10"); // lower bound + expect(lookup(ranges, "Test Rd.", { lane: 7 })).toBe("L5_10"); + expect(lookup(ranges, "Test Rd.", { lane: 10 })).toBe("L5_10"); // upper bound + expect(lookup(ranges, "Test Rd.", { lane: 4 })).toBeNull(); + expect(lookup(ranges, "Test Rd.", { lane: 11 })).toBeNull(); + }); + }); + + describe("alley constraint", () => { + it("matches only the exact alley when alley1 is unset", () => { + const ranges = [range({ alley: 3, zip6: "A3" })]; + expect(lookup(ranges, "Test Rd.", { alley: 3 })).toBe("A3"); + expect(lookup(ranges, "Test Rd.", { alley: 2 })).toBeNull(); + }); + + it("matches an inclusive alley range when alley1 is set", () => { + const ranges = [range({ alley: 2, alley1: 6, zip6: "A2_6" })]; + expect(lookup(ranges, "Test Rd.", { alley: 2 })).toBe("A2_6"); + expect(lookup(ranges, "Test Rd.", { alley: 6 })).toBe("A2_6"); + expect(lookup(ranges, "Test Rd.", { alley: 7 })).toBeNull(); + }); + }); + + describe("even/odd parity", () => { + it("even=1 matches odd numbers only", () => { + const ranges = [range({ even: 1, zip6: "ODD" })]; + expect(lookup(ranges, "Test Rd.", { number: 3 })).toBe("ODD"); + expect(lookup(ranges, "Test Rd.", { number: 4 })).toBeNull(); + }); + + it("even=2 matches even numbers only", () => { + const ranges = [range({ even: 2, zip6: "EVEN" })]; + expect(lookup(ranges, "Test Rd.", { number: 4 })).toBe("EVEN"); + expect(lookup(ranges, "Test Rd.", { number: 3 })).toBeNull(); + }); + + it("ignores the parity constraint when no number is given", () => { + const ranges = [range({ even: 1, zip6: "ODD" })]; + expect(lookup(ranges, "Test Rd.", { number: 0 })).toBe("ODD"); + }); + }); + + describe("number range", () => { + it("matches inside an inclusive [start, end] range", () => { + const ranges = [range({ numStart: 1, numEnd: 99, zip6: "N1_99" })]; + expect(lookup(ranges, "Test Rd.", { number: 1 })).toBe("N1_99"); // lower bound + expect(lookup(ranges, "Test Rd.", { number: 50 })).toBe("N1_99"); + expect(lookup(ranges, "Test Rd.", { number: 99 })).toBe("N1_99"); // upper bound + expect(lookup(ranges, "Test Rd.", { number: 100 })).toBeNull(); + }); + + it("rejects a number-constrained range when no number is given", () => { + const ranges = [range({ numStart: 1, numEnd: 99, zip6: "N1_99" })]; + expect(lookup(ranges, "Test Rd.", { number: 0 })).toBeNull(); + }); + + it("treats numEnd=9999 as an open upper bound", () => { + const ranges = [range({ numStart: 100, numEnd: 9999, zip6: "N100_UP" })]; + expect(lookup(ranges, "Test Rd.", { number: 5000 })).toBe("N100_UP"); + expect(lookup(ranges, "Test Rd.", { number: 50 })).toBeNull(); // below start + }); + + it("excludes a sub-number (之) that exceeds an exact number range", () => { + // Range covers exactly No. 5 (numStart=numEnd=5, no sub span). + const ranges = [range({ numStart: 5, numEnd: 5, zip6: "N5" })]; + expect(lookup(ranges, "Test Rd.", { number: 5, sub: 0 })).toBe("N5"); + expect(lookup(ranges, "Test Rd.", { number: 5, sub: 1 })).toBeNull(); + }); + + it("includes a sub-number (之) that falls within the range's sub span", () => { + // Range covers No. 5 through No. 5之5 (numEnd1=5). + const ranges = [ + range({ numStart: 5, numEnd: 5, numEnd1: 5, zip6: "N5_sub5" }), + ]; + expect(lookup(ranges, "Test Rd.", { number: 5, sub: 1 })).toBe("N5_sub5"); + expect(lookup(ranges, "Test Rd.", { number: 5, sub: 6 })).toBeNull(); + }); + }); + + describe("floor constraint", () => { + it("matches inside an inclusive floor range", () => { + const ranges = [range({ floorStart: 2, floorEnd: 5, zip6: "F2_5" })]; + expect(lookup(ranges, "Test Rd.", { floor: 2 })).toBe("F2_5"); // lower bound + expect(lookup(ranges, "Test Rd.", { floor: 3 })).toBe("F2_5"); + expect(lookup(ranges, "Test Rd.", { floor: 5 })).toBe("F2_5"); // upper bound + expect(lookup(ranges, "Test Rd.", { floor: 1 })).toBeNull(); + expect(lookup(ranges, "Test Rd.", { floor: 6 })).toBeNull(); + }); + + it("ignores the floor constraint when no floor is given", () => { + const ranges = [range({ floorStart: 2, floorEnd: 5, zip6: "F2_5" })]; + expect(lookup(ranges, "Test Rd.", { floor: 0 })).toBe("F2_5"); + }); + }); + + describe("specificity ordering", () => { + it("prefers the more specific range when several match", () => { + const ranges = [ + range({ zip6: "GENERAL" }), // score 0 + range({ lane: 5, zip6: "LANE5" }), // score 100 + ]; + // Both match lane 5 -> the lane-specific range wins. + expect(lookup(ranges, "Test Rd.", { lane: 5 })).toBe("LANE5"); + }); + + it("falls back to the general range when the specific one misses", () => { + const ranges = [ + range({ zip6: "GENERAL" }), + range({ lane: 5, zip6: "LANE5" }), + ]; + // Lane 7 only satisfies the catch-all range. + expect(lookup(ranges, "Test Rd.", { lane: 7 })).toBe("GENERAL"); + }); + + it("resolves a layered general / range / parity stack by score", () => { + const ranges = [ + range({ zip6: "GENERAL" }), // score 0 + range({ numStart: 1, numEnd: 50, zip6: "LOW" }), // score 20 + range({ numStart: 1, numEnd: 50, even: 1, zip6: "LOW_ODD" }), // score 30 + ]; + // 11 is odd and in [1,50] -> highest-scoring odd range wins. + expect(lookup(ranges, "Test Rd.", { number: 11 })).toBe("LOW_ODD"); + // 12 is even -> the odd range is rejected, the plain range wins. + expect(lookup(ranges, "Test Rd.", { number: 12 })).toBe("LOW"); + // 80 is outside [1,50] -> only the catch-all remains. + expect(lookup(ranges, "Test Rd.", { number: 80 })).toBe("GENERAL"); + }); + }); +}); diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..c1433e6 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eafff0..f1facf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: typescript: specifier: ^6.0.3 version: 6.0.3 + vitest: + specifier: ^4.1.9 + version: 4.1.9(@types/node@25.9.3)(vite@8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)) packages: @@ -306,6 +309,9 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} @@ -315,6 +321,9 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -853,6 +862,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -1046,6 +1058,104 @@ packages: '@types/react': optional: true + '@rolldown/binding-android-arm64@1.1.2': + resolution: {integrity: sha512-2cZ+7xRS+DBcuJBJKnfzsbleumJhBqSlJVpuzHC0nTqfd3QQ7Vx2/x5YR/D7cBamKSeWplwo82Fn9lqYUDEMfA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.2': + resolution: {integrity: sha512-RkPMJnygxsgOYdkfqgpwY0/Fzm8d0VQe6HGU2/B00Xa9eqdLbrII+DOKAodbJAn3ZL1AJxGHkZRPYazgGY6Ljw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.2': + resolution: {integrity: sha512-Uiczh6vFhwyfd7WNe7Q7mCA4KxAiLdz7jPE/WGizfRpIieoyFuNVMmM8HqZ9HwudTkY6/AeMQwlNJ9NJijguWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.2': + resolution: {integrity: sha512-+TpdtTRgHiJFjCVFbw311SuLk3KfytPOQQn+VlAEv+gBxYPtL7E6JS9e/tk+8CwxhIZvemJKo4rTKgfWNsKkkA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.2': + resolution: {integrity: sha512-4lv1/tkmi7ueIVHnyreaOeUpiZP26BH9rRy6hoYfR9310A2B9nUEVRDvBx69vx64Nr3eTPPRkyciqJJs+j9Jmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.2': + resolution: {integrity: sha512-gBSUVO0eaWgw1JMjK3gB8BMlX2Mk148s2lTiVT3e9vjVxbl7UDfMWWY8CfIaaqiXuM9fVTMxIpUz6CAo/B6Vlw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.2': + resolution: {integrity: sha512-LjQP/iZLBu8o8PjIfk4x3At0/mT6h282pvz8Z5LAyhGbu/kDezyO7ea62rF5uoqmgnIYqbN/MqJ3Si3Aymi7xQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.2': + resolution: {integrity: sha512-X/7bVLWelEsbyWDUSXt7zVsTniLLPIY2n1rH58qr78l9i7MNbbxBWD8gI2vRfBWf4NUXJCUuQnfZDsp32LqsfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.2': + resolution: {integrity: sha512-gb6dYKW/1KDorGXyy48glEBJs/sxVSC5pcVrox/pFGV4mvwSFeg2sK5L2tRkVsVlh7kueqOgg4GEcuipJcGuKg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.2': + resolution: {integrity: sha512-JY4w85pU3iAiJVMh5nuk4/Mh9GjMsupe8MrIN53rwxAZW64GKrWeJBuN6SxQg9QTU5uB1cxyhDzW8jqRn1EABw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.2': + resolution: {integrity: sha512-xvpA7o5KCYLB0Rwscmuylb1/zHHSUx4g4xilm4prC5jP76pEUlzBmMbgpbh7bVDbId4NcfT96gN5i6mE6UDaiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.2': + resolution: {integrity: sha512-p/ts6KBLjuk49Bp21XH77poQGt02iNz7ChgHep7tudPOaLinR/De/RHdxF8w8Yj4r/bF/bqXwH6PZrB2sA+Nvw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.2': + resolution: {integrity: sha512-VMu/wmrZ9hJzYlRhbw7jK5PODlugyKZ5mOdX78+lS8OvuFkWNQdz1pFLrI2p3P0pjXOmUZ7B48o5VnMH9QOGtg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.2': + resolution: {integrity: sha512-xtUJqs8qEkuSviS0n1tsohaPuz3a1SPhZywOji4Oo+sgrJs8daEDMZ0QtqL0OS7dx8PoVpg2J/ZZycPY5I2+Zg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.2': + resolution: {integrity: sha512-85YiLQqjUKgSO/Zjnf9e0XIn5Ymrh1fLDWBeAkZqpuBR/3R8TpfoHXuyblqyQrftSSgWO9qpcHN8mkyKsLraoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1056,6 +1166,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1157,6 +1270,12 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1426,6 +1545,35 @@ packages: vue-router: optional: true + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1517,6 +1665,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1599,6 +1751,10 @@ packages: caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1871,6 +2027,9 @@ packages: resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==} engines: {node: '>= 0.4'} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -2030,6 +2189,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2054,6 +2216,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express-rate-limit@8.5.2: resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} @@ -2854,6 +3020,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2930,6 +3100,9 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pbf@4.0.1: resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} hasBin: true @@ -3160,6 +3333,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown@1.1.2: + resolution: {integrity: sha512-x0CrQQqCXWGeI8dTvFfN/Dnv3yMKT9hv5jFjlOreKAx9wqLq9wz7VvLLHyaAXC90/CpggTu9SisSbsJJTPSjNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3256,6 +3434,9 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3283,10 +3464,16 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -3390,6 +3577,13 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -3397,6 +3591,10 @@ packages: tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3539,6 +3737,90 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@8.1.0: + resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.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 + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -3569,6 +3851,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3856,6 +4143,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 @@ -3871,6 +4164,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true @@ -4218,6 +4516,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.2 + optional: true + '@next/env@16.2.9': {} '@next/eslint-plugin-next@16.2.9': @@ -4270,6 +4575,8 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@oxc-project/types@0.137.0': {} + '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.17)(react@19.2.7)': @@ -4429,12 +4736,65 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + '@rolldown/binding-android-arm64@1.1.2': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.2': + optional: true + + '@rolldown/binding-darwin-x64@1.1.2': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.2': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.2': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.2': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.2': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.2': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.2': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.2': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.2': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.2': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.2': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.2': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.2': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rtsao/scc@1.1.0': {} '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4519,6 +4879,13 @@ snapshots: tslib: 2.8.1 optional: true + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/geojson@7946.0.16': {} @@ -4725,6 +5092,47 @@ snapshots: next: 16.2.9(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 + '@vitest/expect@4.1.9': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.9(vite@8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4))': + dependencies: + '@vitest/spy': 4.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4) + + '@vitest/pretty-format@4.1.9': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.9': + dependencies: + '@vitest/utils': 4.1.9 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.9': {} + + '@vitest/utils@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -4841,6 +5249,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} ast-types@0.16.1: @@ -4925,6 +5335,8 @@ snapshots: caniuse-lite@1.0.30001799: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -5222,6 +5634,8 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 + es-module-lexer@2.1.0: {} + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -5489,6 +5903,10 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} @@ -5526,6 +5944,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + expect-type@1.3.0: {} + express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 @@ -6296,6 +6716,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 + obug@2.1.3: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -6383,6 +6805,8 @@ snapshots: path-to-regexp@8.4.2: {} + pathe@2.0.3: {} + pbf@4.0.1: dependencies: resolve-protobuf-schema: 2.1.0 @@ -6559,6 +6983,27 @@ snapshots: reusify@1.1.0: {} + rolldown@1.1.2: + dependencies: + '@oxc-project/types': 0.137.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.2 + '@rolldown/binding-darwin-arm64': 1.1.2 + '@rolldown/binding-darwin-x64': 1.1.2 + '@rolldown/binding-freebsd-x64': 1.1.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.2 + '@rolldown/binding-linux-arm64-gnu': 1.1.2 + '@rolldown/binding-linux-arm64-musl': 1.1.2 + '@rolldown/binding-linux-ppc64-gnu': 1.1.2 + '@rolldown/binding-linux-s390x-gnu': 1.1.2 + '@rolldown/binding-linux-x64-gnu': 1.1.2 + '@rolldown/binding-linux-x64-musl': 1.1.2 + '@rolldown/binding-openharmony-arm64': 1.1.2 + '@rolldown/binding-wasm32-wasi': 1.1.2 + '@rolldown/binding-win32-arm64-msvc': 1.1.2 + '@rolldown/binding-win32-x64-msvc': 1.1.2 + router@2.2.0: dependencies: debug: 4.4.3 @@ -6760,6 +7205,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -6777,8 +7224,12 @@ snapshots: stable-hash@0.0.5: {} + stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.1.0: {} + stdin-discarder@0.2.2: {} stop-iteration-iterator@1.1.0: @@ -6895,6 +7346,10 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -6902,6 +7357,8 @@ snapshots: tinyqueue@3.0.0: {} + tinyrainbow@3.1.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7077,6 +7534,47 @@ snapshots: vary@1.1.2: {} + vite@8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.1.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.3 + esbuild: 0.28.0 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.22.4 + + vitest@4.1.9(@types/node@25.9.3)(vite@8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)): + dependencies: + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.0(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.3 + transitivePeerDependencies: + - msw + web-streams-polyfill@3.3.3: {} which-boxed-primitive@1.1.1: @@ -7128,6 +7626,11 @@ snapshots: dependencies: isexe: 3.1.5 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrappy@1.0.2: {}