From 994617beb884070e62e0a98e0b67fa1b6e0f67fe Mon Sep 17 00:00:00 2001 From: codeBelt Date: Tue, 20 Oct 2026 15:22:02 -0500 Subject: [PATCH 1/9] feat(react): make subscriptions synchronous and harden integrations Remove React timing options while preserving batched core subscriptions. Fix selector and auto-tracking lifecycles, mutable history restoration, and persistence hydration. Update library dependencies, migration docs, examples, and CI; add browser, SSR, and React 18/19 compatibility regressions. --- .changeset/fix-history-and-react-selectors.md | 5 + .changeset/synchronous-react-subscriptions.md | 9 + .github/workflows/ci.yml | 8 + .github/workflows/docs.yml | 5 + .github/workflows/react-compatibility.yml | 24 + .github/workflows/release.yml | 8 +- .gitignore | 4 + README.md | 6 +- bun.lock | 381 +++++--- .../reactivity/ReactiveFundamentalsDemo.tsx | 8 +- examples/react/src/pages/ReactivityPage.tsx | 2 +- package.json | 2 + packages/classy-store/package.json | 28 +- .../frameworks/react/react.behavior.test.tsx | 123 ++- .../frameworks/react/react.lifecycle.test.tsx | 426 ++++++++ .../src/frameworks/react/react.qa.test.tsx | 213 ++++ .../src/frameworks/react/react.ssr.test.tsx | 189 ++++ .../src/frameworks/react/react.test.tsx | 139 ++- .../src/frameworks/react/react.ts | 236 ++--- .../src/snapshot/snapshot.test.ts | 78 ++ .../classy-store/src/snapshot/snapshot.ts | 37 +- .../utils/cloneSnapshot/cloneSnapshot.test.ts | 140 +++ .../src/utils/cloneSnapshot/cloneSnapshot.ts | 44 + .../src/utils/history/history.test.ts | 99 ++ .../classy-store/src/utils/history/history.ts | 7 +- .../src/utils/persist/persist.test.ts | 164 +++- .../classy-store/src/utils/persist/persist.ts | 9 +- packages/classy-store/tests/browser/README.md | 20 + .../tests/browser/browser.store.ts | 42 + .../tests/browser/browser.test.ts | 16 + .../boundSelectorForm/BoundSelectorForm.tsx | 7 + .../boundTrackedForm/BoundTrackedForm.tsx | 7 + .../components/derivedForm/DerivedForm.tsx | 74 ++ .../components/inputForm/InputForm.tsx | 67 ++ .../components/selectorForm/SelectorForm.tsx | 8 + .../components/trackedForm/TrackedForm.tsx | 8 + .../classy-store/tests/browser/frontend.tsx | 19 + .../classy-store/tests/browser/index.html | 8 + .../classy-store/tests/browser/inputs.spec.ts | 125 +++ .../tests/browser/playwright.config.ts | 24 + .../classy-store/tests/browser/qa.spec.ts | 53 + packages/classy-store/tests/browser/server.ts | 8 + .../classy-store/tests/browser/tsconfig.json | 7 + .../tests/compatibility/README.md | 25 + .../tests/compatibility/consumer.fixture.tsx | 151 +++ .../compatibility/consumer.types.fixture.ts | 99 ++ .../compatibility/entrypoints.fixture.mjs | 50 + .../tests/compatibility/runCompatibility.mjs | 177 ++++ packages/classy-store/tsconfig.json | 1 + packages/classy-store/tsdown.config.ts | 18 +- website/docs/ARCHITECTURE.md | 53 +- website/docs/REACT_SSR.md | 89 ++ website/docs/TUTORIAL.md | 85 +- website/docs/index.md | 26 +- website/sidebars.ts | 1 + website/static/llms-full.txt | 908 ++++++++++++++++-- website/static/llms.txt | 6 +- 57 files changed, 3989 insertions(+), 587 deletions(-) create mode 100644 .changeset/fix-history-and-react-selectors.md create mode 100644 .changeset/synchronous-react-subscriptions.md create mode 100644 .github/workflows/react-compatibility.yml create mode 100644 packages/classy-store/src/frameworks/react/react.lifecycle.test.tsx create mode 100644 packages/classy-store/src/frameworks/react/react.qa.test.tsx create mode 100644 packages/classy-store/src/frameworks/react/react.ssr.test.tsx create mode 100644 packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.test.ts create mode 100644 packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.ts create mode 100644 packages/classy-store/tests/browser/README.md create mode 100644 packages/classy-store/tests/browser/browser.store.ts create mode 100644 packages/classy-store/tests/browser/browser.test.ts create mode 100644 packages/classy-store/tests/browser/components/boundSelectorForm/BoundSelectorForm.tsx create mode 100644 packages/classy-store/tests/browser/components/boundTrackedForm/BoundTrackedForm.tsx create mode 100644 packages/classy-store/tests/browser/components/derivedForm/DerivedForm.tsx create mode 100644 packages/classy-store/tests/browser/components/inputForm/InputForm.tsx create mode 100644 packages/classy-store/tests/browser/components/selectorForm/SelectorForm.tsx create mode 100644 packages/classy-store/tests/browser/components/trackedForm/TrackedForm.tsx create mode 100644 packages/classy-store/tests/browser/frontend.tsx create mode 100644 packages/classy-store/tests/browser/index.html create mode 100644 packages/classy-store/tests/browser/inputs.spec.ts create mode 100644 packages/classy-store/tests/browser/playwright.config.ts create mode 100644 packages/classy-store/tests/browser/qa.spec.ts create mode 100644 packages/classy-store/tests/browser/server.ts create mode 100644 packages/classy-store/tests/browser/tsconfig.json create mode 100644 packages/classy-store/tests/compatibility/README.md create mode 100644 packages/classy-store/tests/compatibility/consumer.fixture.tsx create mode 100644 packages/classy-store/tests/compatibility/consumer.types.fixture.ts create mode 100644 packages/classy-store/tests/compatibility/entrypoints.fixture.mjs create mode 100644 packages/classy-store/tests/compatibility/runCompatibility.mjs create mode 100644 website/docs/REACT_SSR.md diff --git a/.changeset/fix-history-and-react-selectors.md b/.changeset/fix-history-and-react-selectors.md new file mode 100644 index 0000000..93726ef --- /dev/null +++ b/.changeset/fix-history-and-react-selectors.md @@ -0,0 +1,5 @@ +--- +'@codebelt/classy-store': patch +--- + +Restore mutable nested state after undo/redo while preserving reactive collection methods and keeping recorded snapshots unchanged. Refresh React selections when selectors or equality functions change, even when the store has not mutated. Update the library's development dependencies to their latest stable versions and add regression coverage for both fixes. diff --git a/.changeset/synchronous-react-subscriptions.md b/.changeset/synchronous-react-subscriptions.md new file mode 100644 index 0000000..55e4c86 --- /dev/null +++ b/.changeset/synchronous-react-subscriptions.md @@ -0,0 +1,9 @@ +--- +"@codebelt/classy-store": minor +--- + +React subscriptions now always notify synchronously, fixing controlled-input caret jumps without configuration. Remove the `sync` option from `useClassyStore` and hooks returned by `createStoreHook`, and remove imports of `UseClassyStoreOptions`. Selector options still accept `isEqual`. Ordinary `subscribe()` callbacks remain microtask-batched and retain their existing `sync` option. + +React selectors may run after each mutation; several writes are not an atomic transaction. React can batch the resulting renders within an event handler. Auto-tracked reads are isolated per render so conditional and abandoned renders do not retain stale dependencies, including for class and null-prototype snapshots. Unchanged tracked references remain stable across parent renders. + +Persistence hydration restores mutable nested defaults and custom merged values while preserving prototypes and user accessors. Add real-browser input regressions, lifecycle and SSR coverage, and packed-package checks for React 18 and 19. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 934a767..6587cd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,9 +8,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '24' - uses: oven-sh/setup-bun@v2 + with: + bun-version: '1.4.3' - run: bun install --frozen-lockfile - run: bun run lint - run: bun run test - run: bun run typecheck - run: bun run build + - run: bunx playwright install --with-deps chromium firefox webkit + working-directory: packages/classy-store + - run: bun run test:browser diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f795302..eb70626 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,7 +18,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '24' - uses: oven-sh/setup-bun@v2 + with: + bun-version: '1.4.3' - run: bun install --frozen-lockfile - run: bun run build - run: bun run demos:build diff --git a/.github/workflows/react-compatibility.yml b/.github/workflows/react-compatibility.yml new file mode 100644 index 0000000..1d22961 --- /dev/null +++ b/.github/workflows/react-compatibility.yml @@ -0,0 +1,24 @@ +name: React compatibility + +on: + pull_request: + branches: [main] + +jobs: + react-compatibility: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + react: ['18.0.0', '18.3.1', '19.3.1'] + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '24' + - uses: oven-sh/setup-bun@v2 + with: + bun-version: '1.4.3' + - run: bun install --frozen-lockfile + - run: bun run build + - run: node packages/classy-store/tests/compatibility/runCompatibility.mjs "${{ matrix.react }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fb1787..2175774 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,12 +16,14 @@ jobs: steps: - uses: actions/checkout@v6 with: { fetch-depth: 0 } - - uses: oven-sh/setup-bun@v2 - - run: bun install --frozen-lockfile - - run: bun run build - uses: actions/setup-node@v6 with: node-version: '24' + - uses: oven-sh/setup-bun@v2 + with: + bun-version: '1.4.3' + - run: bun install --frozen-lockfile + - run: bun run build - name: Create Release PR or Detect Publishable Versions id: changesets uses: changesets/action@v1 diff --git a/.gitignore b/.gitignore index 453e907..c7ff628 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ website/build website/.docusaurus website/.cache-loader website/static/demos + +# Browser test artifacts +playwright-report +test-results diff --git a/README.md b/README.md index dcfe064..ff0a114 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,10 @@ function AddButton() { } ``` -For React controlled inputs, opt the field subscription into synchronous timing so React can preserve caret position and IME composition: +React subscriptions notify synchronously, so controlled inputs preserve caret position without extra options: ```tsx -const name = useClassyStore(formStore, (state) => state.name, {sync: true}); +const name = useClassyStore(formStore, (state) => state.name); return ( =17.0.0", @@ -178,7 +179,7 @@ "@angular/compiler-cli": ["@angular/compiler-cli@19.2.21", "", { "dependencies": { "@babel/core": "7.26.9", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", "reflect-metadata": "^0.2.0", "semver": "^7.0.0", "tslib": "^2.3.0", "yargs": "^17.2.1" }, "peerDependencies": { "@angular/compiler": "19.2.21", "typescript": ">=5.5 <5.9" }, "bin": { "ngc": "bundles/src/bin/ngc.js", "ngcc": "bundles/ngcc/index.js", "ng-xi18n": "bundles/src/bin/ng_xi18n.js" } }, "sha512-eAs+krq6McyuoySyC0tbYuY+rtovdtmyNUAgxGANPYJMdkTYHcHdF0HVbbaxUpFDvRC6rV9NDpZUazgykLZoYA=="], - "@angular/core": ["@angular/core@21.1.4", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/compiler": "21.1.4", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, "optionalPeers": ["@angular/compiler", "zone.js"] }, "sha512-QBDO5SaVYTVQ0fIN9Qd7US9cUCgs2vM9x6K18PTUKmygIkHVHTFdzwm4MO5gpCOFzJseGbS+dNzqn+v0PaKf9g=="], + "@angular/core": ["@angular/core@22.2.3", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/compiler": "22.2.3", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, "optionalPeers": ["@angular/compiler", "zone.js"] }, "sha512-ponarFrAZsPutT+htRt3PEmkt2/IB7DyrBy2f48sxsIx3ZA6xw09i2HOEBo+VBphlN6B/ynK9q+o04HPhdJ+dQ=="], "@angular/forms": ["@angular/forms@19.2.21", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "19.2.21", "@angular/core": "19.2.21", "@angular/platform-browser": "19.2.21", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-2r5V8JyInrmpLhBDKCmF2ELWUXxEVxyIm5wDqD7RxWnuGtEr08QlJmiuyqM58xWFmcaSx7s2c62OdCqAAUSQGg=="], @@ -228,7 +229,7 @@ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.2", "", {}, "sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], @@ -236,7 +237,7 @@ "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - "@babel/parser": ["@babel/parser@8.0.0-rc.2", "", { "dependencies": { "@babel/types": "^8.0.0-rc.2" }, "bin": "./bin/babel-parser.js" }, "sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ=="], + "@babel/parser": ["@babel/parser@7.29.9", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], @@ -436,12 +437,8 @@ "@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.6.3", "", {}, "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], @@ -494,7 +491,7 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], - "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.6.1", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.6.1" } }, "sha512-4Aji+soqukwUxq2DgHmkjxdGnG7hEiJuprqDlW4Wu6AQ0t8U9ItlICcM5to89pulIsEGrF1CkCoNrufQTcqb8A=="], + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^21.0.4" } }, "sha512-Bbr0hW9hhDhBf3Jp0EyDlkCbFgbr0z2jd3BMKYOYW+fOkPM4Nx195GMXyCtRCGEgoNn6i09FabXrSkNFm2ONzg=="], "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], @@ -692,8 +689,6 @@ "@napi-rs/nice-win32-x64-msvc": ["@napi-rs/nice-win32-x64-msvc@1.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], - "@next/env": ["@next/env@16.2.9", "", {}, "sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw=="], @@ -762,7 +757,7 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.13", "", { "os": "win32", "cpu": "x64" }, "sha512-6gy4hhQSjq/T/S9hC9m3NxY0RY+9Ww+XNlB+8koIMTsMSYEjk7Ho+hFHQz1Bn4W61Ub7Vykufg+jgDgPfa2GFA=="], - "@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], + "@oxc-project/types": ["@oxc-project/types@0.154.0", "", {}, "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA=="], "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], @@ -796,43 +791,45 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@playwright/test": ["@playwright/test@1.64.1", "", { "dependencies": { "playwright": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg=="], + "@preact/signals": ["@preact/signals@1.3.4", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g=="], "@preact/signals-core": ["@preact/signals-core@1.14.1", "", {}, "sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng=="], "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.3", "", { "os": "android", "cpu": "arm64" }, "sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.14", "", { "os": "android", "cpu": "arm" }, "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.14", "", { "os": "android", "cpu": "arm64" }, "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm" }, "sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.14", "", { "os": "linux", "cpu": "arm" }, "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.14", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.14", "", { "os": "linux", "cpu": "s390x" }, "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.3", "", { "os": "none", "cpu": "arm64" }, "sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.14", "", { "os": "none", "cpu": "arm64" }, "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "x64" }, "sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.14", "", { "os": "win32", "cpu": "x64" }, "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], @@ -944,8 +941,6 @@ "@tufjs/models": ["@tufjs/models@3.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.5" } }, "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -958,7 +953,7 @@ "@types/bonjour": ["@types/bonjour@3.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ=="], - "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/bun": ["@types/bun@1.4.3", "", { "dependencies": { "bun-types": "1.4.3" } }, "sha512-N67Pqy8JXbAY/j0tNraS3V5mubGvc6rrrNDmDCtZLlFzccbUJPDb0tvgUBC5q4m8ybkP93Jcn1QB+RYQ6PvBhw=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], @@ -978,8 +973,6 @@ "@types/http-proxy": ["@types/http-proxy@1.17.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw=="], - "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], @@ -994,9 +987,9 @@ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="], - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="], "@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], @@ -1016,6 +1009,46 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@1.2.0", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="], @@ -1026,27 +1059,27 @@ "@volar/typescript": ["@volar/typescript@2.4.15", "", { "dependencies": { "@volar/language-core": "2.4.15", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + "@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="], + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/compiler-core": "3.5.44", "@vue/compiler-dom": "3.5.44", "@vue/compiler-ssr": "3.5.44", "@vue/shared": "3.5.44", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.29", "source-map-js": "^1.2.2" } }, "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw=="], - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="], + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], "@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="], "@vue/language-core": ["@vue/language-core@2.2.12", "", { "dependencies": { "@volar/language-core": "2.4.15", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA=="], - "@vue/reactivity": ["@vue/reactivity@3.5.28", "", { "dependencies": { "@vue/shared": "3.5.28" } }, "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw=="], + "@vue/reactivity": ["@vue/reactivity@3.5.44", "", { "dependencies": { "@vue/shared": "3.5.44" } }, "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw=="], - "@vue/runtime-core": ["@vue/runtime-core@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ=="], + "@vue/runtime-core": ["@vue/runtime-core@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ=="], - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/runtime-core": "3.5.28", "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA=="], + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/runtime-core": "3.5.44", "@vue/shared": "3.5.44", "csstype": "^3.2.3" } }, "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg=="], - "@vue/server-renderer": ["@vue/server-renderer@3.5.28", "", { "dependencies": { "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "vue": "3.5.28" } }, "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg=="], + "@vue/server-renderer": ["@vue/server-renderer@3.5.44", "", { "dependencies": { "@vue/compiler-ssr": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ=="], - "@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], + "@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], @@ -1084,6 +1117,32 @@ "@yarnpkg/lockfile": ["@yarnpkg/lockfile@1.1.0", "", {}, "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="], + "@yuku-core/binding-android-arm64": ["@yuku-core/binding-android-arm64@0.19.0", "", { "os": "android", "cpu": "arm64" }, "sha512-wdL2XmTiZixAOaHk9Z0JJZ8u9Qm8d333Zjpc6gaxXFX+UR5BlsfxZ7a2+2Rm71pamppsPYGUGFq2oVLbh6DISw=="], + + "@yuku-core/binding-darwin-arm64": ["@yuku-core/binding-darwin-arm64@0.19.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7sNJa6XJhyetnTy64jw3NEi4YgUcezHk1G5GH9FSvXAuC8wIlbaOwT9lgq5qo5sspjrZJQlBR1TWflP49uRpMw=="], + + "@yuku-core/binding-darwin-x64": ["@yuku-core/binding-darwin-x64@0.19.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pTC7dYEc59isdyizc3nsuyFNBu2LU5wMYZ1be6UMr59PY/AJNtO1aBLWV0MgL/z7C1nv/2sX4TNjGgKLoeevJw=="], + + "@yuku-core/binding-freebsd-x64": ["@yuku-core/binding-freebsd-x64@0.19.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xrZjF6XOEwyLOiDIfrwDdRCS6ghxcJeirNC1KivQhVcUwlRonaU79S3/Uiyx3lBylPnT8qKJ3/aMT+H52nGTJQ=="], + + "@yuku-core/binding-linux-arm-gnu": ["@yuku-core/binding-linux-arm-gnu@0.19.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0OyIH53C0I1CbADHn9FSdCfX5qbpeLwJhmOVhonFJNYheybOnkSJ50qGMfTJIb8y5zrwqvLs7FVBvz6Fk8maZg=="], + + "@yuku-core/binding-linux-arm-musl": ["@yuku-core/binding-linux-arm-musl@0.19.0", "", { "os": "linux", "cpu": "arm" }, "sha512-z/iPfkCOCSXE34VXNt0uP1ORSW+4mEa9b5CiJcWZeqZH08g49bFU/jRM2T04f+Wpw5mJ6HIzNBiojxeAbrHjeg=="], + + "@yuku-core/binding-linux-arm64-gnu": ["@yuku-core/binding-linux-arm64-gnu@0.19.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jLNTi+gzaRwBJ+gwhCJ+rDb5dmx/8hEBx93UWd7hjMSqPTL1D1wvVSvXVAldV5kALvju7b0/m51EHEhs4+I1fA=="], + + "@yuku-core/binding-linux-arm64-musl": ["@yuku-core/binding-linux-arm64-musl@0.19.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YwyPIdW/rN2Fmv0MTFWi47FaB3F9g+2b+sJISyAw83Yd60RWH4T3Nx6XkAHHVN+uKJNBAd6So7/BVihSiFYvuQ=="], + + "@yuku-core/binding-linux-x64-gnu": ["@yuku-core/binding-linux-x64-gnu@0.19.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/vskaAXVX3ESR43FiTdfiYS7z1b9uoQJwVQ/0RgUHgN2sWoAyCZs+aX4CgU7eKGIHTVfp9VFwv+c+2XchYiEtQ=="], + + "@yuku-core/binding-linux-x64-musl": ["@yuku-core/binding-linux-x64-musl@0.19.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FuXL5igeSKib9Rye02TdH+D6SQezTyGxIATMu1Qsn/QywrzwpGl35OLpS2C1x5vpGLeVE2XgiLd5mVPFHmIglQ=="], + + "@yuku-core/binding-win32-arm64": ["@yuku-core/binding-win32-arm64@0.19.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-YuJAe7zEKT1Xa7LuHlhGJpPnYQeVtvo0rZvNjJRqkH0hQGOw4C8QeP30Tg5VKGzZfo2x8CjhjEhKt4TmLwfmdg=="], + + "@yuku-core/binding-win32-x64": ["@yuku-core/binding-win32-x64@0.19.0", "", { "os": "win32", "cpu": "x64" }, "sha512-o8iY/mXwlRkedp0LNTr4+TEfXTPQwoNZmv6k+1WIAd1ZXEn8xJJTBCtB0osaVvdZo/x/cNmV16UrWrrGpM5Mvw=="], + + "@yuku-toolchain/types": ["@yuku-toolchain/types@0.19.0", "", {}, "sha512-tFkNbsP4WXX9UMUC5syJGdK9dPMLK0PPaIuXCcCYH75sZ8MrebfBZ2XvCILRTw8AgLivSSCfDa4R67iYBDa4Kg=="], + "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], @@ -1114,8 +1173,6 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -1126,8 +1183,6 @@ "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], - "ast-kit": ["ast-kit@3.0.0-beta.1", "", { "dependencies": { "@babel/parser": "^8.0.0-beta.4", "estree-walker": "^3.0.3", "pathe": "^2.0.3" } }, "sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw=="], - "autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -1164,8 +1219,6 @@ "bippy": ["bippy@0.3.34", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": ">=17.0.1" } }, "sha512-vmptmU/20UdIWHHhq7qCSHhHzK7Ro3YJ1utU0fBG7ujUc58LEfTtilKxcF0IOgSjT5XLcm7CBzDjbv4lcKApGQ=="], - "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="], @@ -1184,17 +1237,19 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], + "bun": ["bun@1.3.13", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.13", "@oven/bun-darwin-x64": "1.3.13", "@oven/bun-darwin-x64-baseline": "1.3.13", "@oven/bun-linux-aarch64": "1.3.13", "@oven/bun-linux-aarch64-musl": "1.3.13", "@oven/bun-linux-x64": "1.3.13", "@oven/bun-linux-x64-baseline": "1.3.13", "@oven/bun-linux-x64-musl": "1.3.13", "@oven/bun-linux-x64-musl-baseline": "1.3.13", "@oven/bun-windows-aarch64": "1.3.13", "@oven/bun-windows-x64": "1.3.13", "@oven/bun-windows-x64-baseline": "1.3.13" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-b9T4xZ8KqCHs4+TkHJv540LG1B8OD7noKu0Qaizusx3jFtMDHY6osNqgbaOlwW2B8RB2AKzz+sjzlGKIGxIjZw=="], "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], - "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + "bun-types": ["bun-types@1.4.3", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-tinRJNGOmUN+tEAsn2y2e942n9JHRHR42iUYzDH75NmhJqVBokGSQkkfSFdNWwsANIdrK6ACH9t01M452YyTwg=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cac": ["cac@7.1.0", "", {}, "sha512-LMDU/U2yA4IPIY8KT0NsNPWEYZvAMkbgyspxJ6btJaYV+WMDOHAZCmB0lsTObiHaRS1V/ztKAl7+TSkTqXyeoA=="], "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], @@ -1340,7 +1395,7 @@ "dotenv": ["dotenv@8.6.0", "", {}, "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="], - "dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="], + "dts-resolver": ["dts-resolver@3.0.0", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -1354,7 +1409,7 @@ "emojis-list": ["emojis-list@3.0.0", "", {}, "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="], - "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "empathic": ["empathic@2.1.0", "", {}, "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -1470,7 +1525,7 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "get-tsconfig": ["get-tsconfig@5.0.0-beta.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw=="], "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -1488,7 +1543,7 @@ "handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="], - "happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="], + "happy-dom": ["happy-dom@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -1498,7 +1553,7 @@ "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], - "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], + "hookable": ["hookable@6.1.2", "", {}, "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg=="], "hosted-git-info": ["hosted-git-info@8.1.0", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw=="], @@ -1544,7 +1599,7 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - "import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], + "import-without-cache": ["import-without-cache@0.4.1", "", {}, "sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], @@ -1810,7 +1865,7 @@ "obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="], - "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "obug": ["obug@3.0.0", "", {}, "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -1872,11 +1927,9 @@ "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], @@ -1884,9 +1937,9 @@ "pkg-dir": ["pkg-dir@7.0.0", "", { "dependencies": { "find-up": "^6.3.0" } }, "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA=="], - "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], + "playwright": ["playwright@1.64.1", "", { "dependencies": { "playwright-core": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA=="], - "playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], + "playwright-core": ["playwright-core@1.64.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ=="], "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], @@ -1936,9 +1989,9 @@ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + "react": ["react@19.3.1", "", {}, "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw=="], - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + "react-dom": ["react-dom@19.3.1", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.1" } }, "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ=="], "react-scan": ["react-scan@0.4.3", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/generator": "^7.26.2", "@babel/types": "^7.26.0", "@clack/core": "^0.3.5", "@clack/prompts": "^0.8.2", "@pivanov/utils": "0.0.2", "@preact/signals": "^1.3.1", "@rollup/pluginutils": "^5.1.3", "@types/node": "^20.17.9", "bippy": "^0.3.8", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "kleur": "^4.1.5", "mri": "^1.2.0", "playwright": "^1.49.0", "preact": "^10.25.1", "tsx": "^4.19.3" }, "optionalDependencies": { "unplugin": "2.1.0" }, "peerDependencies": { "@remix-run/react": ">=1.0.0", "next": ">=13.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-router": "^5.0.0 || ^6.0.0 || ^7.0.0", "react-router-dom": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@remix-run/react", "next", "react-router", "react-router-dom"], "bin": { "react-scan": "bin/cli.js" } }, "sha512-jhAQuQ1nja6HUYrSpbmNFHqZPsRCXk8Yqu0lHoRIw9eb8N96uTfXCpVyQhTTnJ/nWqnwuvxbpKVG/oWZT8+iTQ=="], @@ -1986,9 +2039,9 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + "rolldown": ["rolldown@1.2.14", "", { "dependencies": { "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.14", "@rolldown/binding-android-arm64": "1.2.14", "@rolldown/binding-darwin-arm64": "1.2.14", "@rolldown/binding-darwin-x64": "1.2.14", "@rolldown/binding-freebsd-x64": "1.2.14", "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", "@rolldown/binding-linux-arm64-gnu": "1.2.14", "@rolldown/binding-linux-arm64-musl": "1.2.14", "@rolldown/binding-linux-ppc64-gnu": "1.2.14", "@rolldown/binding-linux-s390x-gnu": "1.2.14", "@rolldown/binding-linux-x64-gnu": "1.2.14", "@rolldown/binding-linux-x64-musl": "1.2.14", "@rolldown/binding-openharmony-arm64": "1.2.14", "@rolldown/binding-win32-arm64-msvc": "1.2.14", "@rolldown/binding-win32-x64-msvc": "1.2.14" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw=="], - "rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.5", "", { "dependencies": { "@babel/generator": "8.0.0-rc.2", "@babel/helper-validator-identifier": "8.0.0-rc.2", "@babel/parser": "8.0.0-rc.2", "@babel/types": "8.0.0-rc.2", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.6", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0 || ^6.0.0-beta", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-M/HXfM4cboo+jONx9Z0X+CUf3B5tCi7ni+kR5fUW50Fp9AlZk0oVLesibGWgCXDKFp5lpgQ9yhKoImUFjl3VZw=="], + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.28.8", "", { "dependencies": { "dts-resolver": "^3.0.0", "get-tsconfig": "5.0.0-beta.6", "obug": "^3.0.0", "yuku-ast": "^0.19.0", "yuku-codegen": "^0.19.0", "yuku-parser": "^0.19.0" }, "peerDependencies": { "@typescript/native-preview": "*", "@volar/typescript": "~2.4.0", "@vue/language-core": "~3.2.0 || ~3.3.0", "rolldown": "^1.2.0", "typescript": "^5.0.0 || ^6.0.0 || ~7.0.0", "vue-tsc": "~3.2.0 || ~3.3.0" }, "optionalPeers": ["@typescript/native-preview", "@volar/typescript", "@vue/language-core", "typescript", "vue-tsc"] }, "sha512-FVhfqxorCWcc20BVT5eVRfRjzLLl6mgzjcMR4gSBf0K4oQMXT0qoHhhzfXy0L7y83fQy7Ug5ZDNk90xM3/LiCQ=="], "rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="], @@ -2010,7 +2063,7 @@ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="], "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], @@ -2024,9 +2077,9 @@ "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], - "seroval": ["seroval@1.5.2", "", {}, "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q=="], + "seroval": ["seroval@1.6.11", "", {}, "sha512-hpb6XnMCGN/A+79Uf8V7/iTGkyyyazNn5X0CoCAp7Uy9vT3RT8jZ9GLcnxGoVjg3P8AArbe4ZVHq6C8oT2a0kA=="], - "seroval-plugins": ["seroval-plugins@1.5.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg=="], + "seroval-plugins": ["seroval-plugins@1.6.11", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-LmB3nd8pL39jKHKvnpTUjcjBtNwR8MLty/ydAR8qbfXt5j6/6KqR1atuw8cVmD2/lDxAkvAwq5tI/HsisD9Fqg=="], "serve-index": ["serve-index@1.9.2", "", { "dependencies": { "accepts": "~1.3.8", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", "http-errors": "~1.8.0", "mime-types": "~2.1.35", "parseurl": "~1.3.3" } }, "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ=="], @@ -2070,13 +2123,13 @@ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="], + "solid-js": ["solid-js@1.9.17", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.6.8", "seroval-plugins": "~1.6.8" } }, "sha512-4GAmLOWPfNV8H9Jfn2LSFTpNEG6jiOKPiwd5lnLGQtehHaX6l3TOLzJqQ7mqwB/3k1Yy3zyumFVopmc/tm34ow=="], "solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="], "source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], "source-map-loader": ["source-map-loader@5.0.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" }, "peerDependencies": { "webpack": "^5.72.1" } }, "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA=="], @@ -2142,9 +2195,9 @@ "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], - "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -2156,7 +2209,7 @@ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - "tsdown": ["tsdown@0.20.3", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-rc.3", "rolldown-plugin-dts": "^0.22.1", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.27" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-qWOUXSbe4jN8JZEgrkc/uhJpC8VN2QpNu3eZkBWwNuTEjc/Ik1kcc54ycfcQ5QPRHeu9OQXaLfCI3o7pEJgB2w=="], + "tsdown": ["tsdown@0.23.1", "", { "dependencies": { "cac": "^7.0.1", "defu": "^6.1.7", "empathic": "^2.1.0", "hookable": "^6.1.2", "import-without-cache": "^0.4.1", "obug": "^3.0.0", "picomatch": "^4.0.7", "rolldown": "~1.2.13", "rolldown-plugin-dts": "^0.28.7", "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0", "verkit": "^0.5.0" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.23.1", "@tsdown/exe": "0.23.1", "@vitejs/devtools": "*", "publint": "^0.3.8", "tsx": "*", "typescript": "^5.0.0 || ^6.0.0 || ^7.0.0", "unplugin-unused": ">=0.5.0", "unrun": "*" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "tsx", "typescript", "unplugin-unused", "unrun"], "bin": { "tsdown": "./dist/run.mjs" } }, "sha512-MSC2WSOts3in95J334/VIS7KPfor3eCSvndJ6KdoVj9Aak9Kh1aNfz9ws5owhdukuTNSXRdfkPEzA/ihpDD6SA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -2168,7 +2221,7 @@ "typed-assert": ["typed-assert@1.0.9", "", {}, "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], @@ -2194,8 +2247,6 @@ "unplugin": ["unplugin@2.1.0", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ=="], - "unrun": ["unrun@0.2.37", "", { "dependencies": { "rolldown": "1.0.0-rc.17" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-AA7vDuYsgeSYVzJMm16UKA+aXFKhy7nFqW9z5l7q44K4ppFWZAMqYS58ePRZbugMLPH0fwwMzD5A8nP0avxwZQ=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -2210,6 +2261,8 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "verkit": ["verkit@0.5.0", "", {}, "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g=="], + "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], "vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="], @@ -2218,7 +2271,7 @@ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], - "vue": ["vue@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", "@vue/runtime-dom": "3.5.28", "@vue/server-renderer": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg=="], + "vue": ["vue@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/compiler-sfc": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/server-renderer": "3.5.44", "@vue/shared": "3.5.44" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ=="], "vue-tsc": ["vue-tsc@2.2.12", "", { "dependencies": { "@volar/typescript": "2.4.15", "@vue/language-core": "2.2.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw=="], @@ -2262,7 +2315,7 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -2276,12 +2329,22 @@ "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], + "yuku-ast": ["yuku-ast@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0" } }, "sha512-PyCsiScaelhxBGzL8dyeJkLqvRZycoh78R31Vrt/WqYrm0hMFoKwIw/X7Dx5mBKLWAMlV9I9HEyBOHnj2gZVyw=="], + + "yuku-codegen": ["yuku-codegen@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0" } }, "sha512-uymAKHjDuaP4BEpkXC/BSLz4DUEF5oArZ5AuD3888nb2miChjWSgpPdojEJxm3pjpXkgUaCRJQyNAbnMN33wHg=="], + + "yuku-core": ["yuku-core@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0" }, "optionalDependencies": { "@yuku-core/binding-android-arm64": "0.19.0", "@yuku-core/binding-darwin-arm64": "0.19.0", "@yuku-core/binding-darwin-x64": "0.19.0", "@yuku-core/binding-freebsd-x64": "0.19.0", "@yuku-core/binding-linux-arm-gnu": "0.19.0", "@yuku-core/binding-linux-arm-musl": "0.19.0", "@yuku-core/binding-linux-arm64-gnu": "0.19.0", "@yuku-core/binding-linux-arm64-musl": "0.19.0", "@yuku-core/binding-linux-x64-gnu": "0.19.0", "@yuku-core/binding-linux-x64-musl": "0.19.0", "@yuku-core/binding-win32-arm64": "0.19.0", "@yuku-core/binding-win32-x64": "0.19.0" } }, "sha512-kYK9e1XdfirNhkipzW0wmMcSWcVF/CWnuugodfVYRCUwvoBefweL9HzB96OiKEhsx78V5WbUxbJqlizMX869Qg=="], + + "yuku-parser": ["yuku-parser@0.19.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.19.0", "yuku-core": "0.19.0" } }, "sha512-XrBh9ZiQK5mJVRxZBAvD6LwRyAYrK3MFtH3+onyuxFL5hFD44EC3QOmIC1FjTj6H2MMwq46/NAI+NZxLZPZgxg=="], + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], "zone.js": ["zone.js@0.15.1", "", {}, "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w=="], "@angular-devkit/architect/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + "@angular-devkit/build-angular/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@angular-devkit/build-angular/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], "@angular-devkit/build-angular/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], @@ -2290,6 +2353,8 @@ "@angular-devkit/build-webpack/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + "@angular-devkit/core/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@angular-devkit/core/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], "@angular-devkit/schematics/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], @@ -2300,6 +2365,8 @@ "@angular/build/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + "@angular/build/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@angular/build/rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], "@angular/build/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], @@ -2322,8 +2389,6 @@ "@angular/router/@angular/core": ["@angular/core@19.2.21", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" } }, "sha512-jdOEwkvUyF7VdJMURXkSB1yQy595wt1lSfSgUux9tZftA7UwamCsMdidUsKqRbdaI9M3r+RdIM7qrDAWwqzz+A=="], - "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], "@babel/core/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], @@ -2346,16 +2411,12 @@ "@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], - "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.2", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.2", "@babel/helper-validator-identifier": "^8.0.0-rc.2" } }, "sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw=="], + "@babel/parser/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], "@babel/plugin-transform-classes/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@babel/plugin-transform-modules-systemjs/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/plugin-transform-private-property-in-object/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], "@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2370,8 +2431,6 @@ "@babel/traverse/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@inquirer/checkbox/@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], "@inquirer/confirm/@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], @@ -2438,12 +2497,18 @@ "@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "@parcel/watcher/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@rollup/pluginutils/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "@tailwindcss/node/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -2462,15 +2527,15 @@ "@types/babel__template/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - "@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@vue/compiler-sfc/postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="], + "@vue/compiler-sfc/postcss": ["postcss@8.5.31", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg=="], + + "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + + "@vue/language-core/@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -2504,10 +2569,24 @@ "classy-store-nextjs-example/react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - "classy-store-react-example/@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "classy-store-nextjs-example/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "classy-store-react-example/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "classy-store-react-example/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "classy-store-react-example/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "classy-store-react-example/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "classy-store-react-example/tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "classy-store-solid-example/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "classy-store-svelte-example/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "classy-store-vue-example/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -2578,6 +2657,8 @@ "node-gyp/tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + "node-gyp/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -2588,7 +2669,7 @@ "pkg-dir/find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], - "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -2602,6 +2683,8 @@ "react-scan/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "react-scan/playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], + "read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], "resolve-url-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], @@ -2612,9 +2695,7 @@ "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "rolldown-plugin-dts/@babel/generator": ["@babel/generator@8.0.0-rc.2", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.2", "@babel/types": "^8.0.0-rc.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ=="], - - "rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.2", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.2", "@babel/helper-validator-identifier": "^8.0.0-rc.2" } }, "sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw=="], + "sass/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], @@ -2632,6 +2713,8 @@ "source-map-loader/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "source-map-loader/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "tar/fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], @@ -2642,14 +2725,18 @@ "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - "unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + "tsx/get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], - "unrun/rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], + "unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "vite/postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="], + "vite/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "vite-plugin-solid/@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], "webpack/watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], @@ -2658,6 +2745,8 @@ "webpack-dev-server/http-proxy-middleware": ["http-proxy-middleware@2.0.9", "", { "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", "micromatch": "^4.0.2" }, "peerDependencies": { "@types/express": "^4.17.13" }, "optionalPeers": ["@types/express"] }, "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q=="], + "webpack-dev-server/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -2722,7 +2811,9 @@ "@angular/compiler-cli/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.4", "", {}, "sha512-dluR3v287dp6YPF57kyKKrHPKffUeuxH1zQcF1WD30TeFzWXhDiVi1U6PkqaDB0++H1PeCwRhmYl4DvoerlPIw=="], + "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], "@inquirer/prompts/@inquirer/confirm/@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], @@ -2746,13 +2837,23 @@ "@npmcli/run-script/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@tailwindcss/postcss/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + + "beasties/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "cacache/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "cacache/tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "classy-store-react-example/@types/bun/bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "classy-store-nextjs-example/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "classy-store-react-example/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "cli-truncate/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -2764,6 +2865,8 @@ "copy-webpack-plugin/globby/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + "css-loader/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -2786,10 +2889,14 @@ "log-update/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "next/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "node-gyp/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "node-gyp/tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "node-gyp/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "pkg-dir/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], @@ -2856,9 +2963,13 @@ "react-scan/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "react-scan/playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + + "react-scan/playwright/playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], + "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.4", "", {}, "sha512-dluR3v287dp6YPF57kyKKrHPKffUeuxH1zQcF1WD30TeFzWXhDiVi1U6PkqaDB0++H1PeCwRhmYl4DvoerlPIw=="], + "resolve-url-loader/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -2926,36 +3037,6 @@ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], - "unrun/rolldown/@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], - - "unrun/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], - - "unrun/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="], - - "unrun/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="], - - "unrun/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="], - - "unrun/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="], - - "unrun/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="], - - "unrun/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="], - - "unrun/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="], - - "unrun/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="], - - "unrun/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="], - - "unrun/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="], - - "unrun/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="], - - "unrun/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="], - - "unrun/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], - "vite-plugin-solid/@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], "vite-plugin-solid/@babel/core/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], @@ -3016,6 +3097,8 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "vite/postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "webpack-dev-server/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "webpack-dev-server/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -3024,6 +3107,12 @@ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], diff --git a/examples/react/src/demos/reactivity/ReactiveFundamentalsDemo.tsx b/examples/react/src/demos/reactivity/ReactiveFundamentalsDemo.tsx index a755b8d..518b15c 100644 --- a/examples/react/src/demos/reactivity/ReactiveFundamentalsDemo.tsx +++ b/examples/react/src/demos/reactivity/ReactiveFundamentalsDemo.tsx @@ -55,13 +55,13 @@ function NameCard() { } function BatchCard() { - // 1000 mutations β†’ 1 render (microtask batching) + // 1000 mutations β†’ 1 render in this event (React batching) const count = useClassyStore(batchStore, (state) => state.count); return
{count}
; } function NameInput() { - const name = useClassyStore(formStore, (state) => state.name, { sync: true }); + const name = useClassyStore(formStore, (state) => state.name); return ( state.name, {sync: true}); + const name = useClassyStore(formStore, (state) => state.name); const renders = useRenderCount(); return ( @@ -152,7 +152,7 @@ export function ReactiveFundamentalsDemo() { return (

Reactivity

- Selectors, microtask batching, and async data fetching β€” watch the + Selectors, React render batching, and async data fetching β€” watch the render badges to see exactly which components re-render.

diff --git a/package.json b/package.json index 08bb5ef..4e3f7b8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "build": "bun run --filter @codebelt/classy-store build", "---------- Test --------------------------------------------------------": "", "test": "bun run --filter @codebelt/classy-store test", + "test:browser": "bun run --filter @codebelt/classy-store test:browser", + "test:react:compatibility": "bun run build && node packages/classy-store/tests/compatibility/runCompatibility.mjs", "---------- Lint --------------------------------------------------------": "", "lint": "biome check", "lint:fix": "biome check --write", diff --git a/packages/classy-store/package.json b/packages/classy-store/package.json index 5fe8f28..972d0b8 100644 --- a/packages/classy-store/package.json +++ b/packages/classy-store/package.json @@ -107,9 +107,10 @@ "---------- Build ------------------------------------------------------": "", "build": "tsdown", "---------- Test --------------------------------------------------------": "", - "test": "bun test", + "test": "bun test src", + "test:browser": "playwright test --config tests/browser/playwright.config.ts", "---------- Lint --------------------------------------------------------": "", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc --project tests/browser/tsconfig.json --noEmit", "prepublishOnly": "cp ../../README.md .", "-----------------------------------------------------------------------": "" }, @@ -141,17 +142,18 @@ } }, "devDependencies": { - "@angular/core": "21.1.4", - "@happy-dom/global-registrator": "20.6.1", - "@types/bun": "1.3.9", - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", - "react": "19.2.4", - "react-dom": "19.2.4", - "solid-js": "1.9.11", - "tsdown": "0.20.3", - "typescript": "5.9.3", - "vue": "3.5.28" + "@angular/core": "22.2.3", + "@happy-dom/global-registrator": "21.0.4", + "@playwright/test": "1.64.1", + "@types/bun": "1.4.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "react": "19.3.1", + "react-dom": "19.3.1", + "solid-js": "1.9.17", + "tsdown": "0.23.1", + "typescript": "7.0.2", + "vue": "3.5.44" }, "keywords": [ "classy-store", diff --git a/packages/classy-store/src/frameworks/react/react.behavior.test.tsx b/packages/classy-store/src/frameworks/react/react.behavior.test.tsx index 4bc5698..dd063de 100644 --- a/packages/classy-store/src/frameworks/react/react.behavior.test.tsx +++ b/packages/classy-store/src/frameworks/react/react.behavior.test.tsx @@ -1,5 +1,5 @@ import {afterEach, describe, expect, it, mock} from 'bun:test'; -import {act, type ReactNode} from 'react'; +import {act, type ReactNode, useLayoutEffect} from 'react'; import {createRoot, type Root} from 'react-dom/client'; import {createClassyStore} from '../../core/core'; import {shallowEqual} from '../../utils/equality/equality'; @@ -10,7 +10,16 @@ import {useClassyStore} from './react'; /** Flush queueMicrotask batching. */ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); +function createGate() { + let resolve: () => void = () => {}; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return {promise, resolve}; +} + let container: HTMLDivElement; +const roots = new Set(); function setup(): void { container = document.createElement('div'); @@ -18,12 +27,17 @@ function setup(): void { } function teardown(): void { - document.body.removeChild(container); + act(() => { + for (const root of roots) root.unmount(); + }); + roots.clear(); + container.remove(); } /** Render a component and return the Root (for unmount tests). */ function render(element: ReactNode): Root { const root = createRoot(container); + roots.add(root); act(() => { root.render(element); }); @@ -49,17 +63,17 @@ class TestStore { this.name = name; } - async asyncIncrement() { + async asyncIncrement(pause: Promise) { this.count++; - await Promise.resolve(); + await pause; this.count++; } - async asyncMultiAwait() { + async asyncMultiAwait(firstPause: Promise, secondPause: Promise) { this.count++; - await Promise.resolve(); + await firstPause; this.count++; - await Promise.resolve(); + await secondPause; this.count++; } @@ -236,18 +250,22 @@ describe('Batching β€” rapid mutations in a loop', () => { }); }); -// ── Block 3: Set-then-revert β€” no re-render ───────────────────────────────── +// ── Block 3: Set-then-revert β€” committed output ───────────────────────────────── -describe('Set-then-revert β€” no re-render when value returns to original', () => { +describe('Set-then-revert β€” commits the restored value', () => { afterEach(teardown); it('primitive revert (selector mode)', async () => { const s = createClassyStore(new TestStore()); const renderCount = mock(() => {}); + const committedValues: number[] = []; function Display() { const count = useClassyStore(s, (state) => state.count); renderCount(); + useLayoutEffect(() => { + committedValues.push(count); + }); return
{count}
; } @@ -262,19 +280,21 @@ describe('Set-then-revert β€” no re-render when value returns to original', () = }); expect(container.textContent).toBe('0'); - expect(renderCount).toHaveBeenCalledTimes(1); + expect(committedValues.length).toBeGreaterThan(0); + expect(committedValues.every((value) => value === 0)).toBe(true); }); - it('primitive revert (auto-tracked mode) β€” re-renders because snapshot ref changes', async () => { - // Unlike selector mode (which compares the extracted value), auto-tracked mode - // detects that the snapshot *object* is a new reference (version was bumped), - // so `getAutoTrackSnapshot` builds a new tracking proxy β†’ re-render. + it('primitive revert (auto-tracked mode)', async () => { const s = createClassyStore(new TestStore()); const renderCount = mock(() => {}); + const committedValues: number[] = []; function Display() { const snap = useClassyStore(s); renderCount(); + useLayoutEffect(() => { + committedValues.push(snap.count); + }); return
{snap.count}
; } @@ -289,16 +309,21 @@ describe('Set-then-revert β€” no re-render when value returns to original', () = }); expect(container.textContent).toBe('0'); - expect(renderCount).toHaveBeenCalledTimes(2); + expect(committedValues.length).toBeGreaterThan(0); + expect(committedValues.every((value) => value === 0)).toBe(true); }); it('nested object prop revert (selector on user.name)', async () => { const s = createClassyStore(new TestStore()); const renderCount = mock(() => {}); + const committedValues: string[] = []; function Display() { const name = useClassyStore(s, (state) => state.user.name); renderCount(); + useLayoutEffect(() => { + committedValues.push(name); + }); return
{name}
; } @@ -314,7 +339,8 @@ describe('Set-then-revert β€” no re-render when value returns to original', () = }); expect(container.textContent).toBe('Alice'); - expect(renderCount).toHaveBeenCalledTimes(1); + expect(committedValues.length).toBeGreaterThan(0); + expect(committedValues.every((value) => value === 'Alice')).toBe(true); }); it('unrelated prop revert β€” structural sharing preserves reference', async () => { @@ -349,8 +375,6 @@ describe('Async methods β€” re-renders split across await boundaries', () => { afterEach(teardown); it('1 await, mutations before + after (selector)', async () => { - // React batches both microtask notifications within act() into a single - // commit, so we see 1 re-render (not 2) for mutations on both sides of an await. const s = createClassyStore(new TestStore()); const renderCount = mock(() => {}); @@ -364,18 +388,21 @@ describe('Async methods β€” re-renders split across await boundaries', () => { render(); expect(renderCount).toHaveBeenCalledTimes(1); - await act(async () => { - await s.asyncIncrement(); - await flush(); + const gate = createGate(); + let completion: Promise | undefined; + act(() => { + completion = s.asyncIncrement(gate.promise); }); + expect(container.textContent).toBe('1'); + await act(async () => { + gate.resolve(); + await completion; + }); expect(container.textContent).toBe('2'); - expect(renderCount).toHaveBeenCalledTimes(2); // 1 initial + 1 re-render (batched) }); it('2 awaits, mutations between each (selector)', async () => { - // 3 mutations across 3 turns β€” React batches some of the microtask - // notifications, resulting in 2 re-renders rather than 3. const s = createClassyStore(new TestStore()); const renderCount = mock(() => {}); @@ -389,13 +416,22 @@ describe('Async methods β€” re-renders split across await boundaries', () => { render(); expect(renderCount).toHaveBeenCalledTimes(1); - await act(async () => { - await s.asyncMultiAwait(); - await flush(); + const firstGate = createGate(); + const secondGate = createGate(); + let completion: Promise | undefined; + act(() => { + completion = s.asyncMultiAwait(firstGate.promise, secondGate.promise); }); + expect(container.textContent).toBe('1'); + + await act(async () => firstGate.resolve()); + expect(container.textContent).toBe('2'); + await act(async () => { + secondGate.resolve(); + await completion; + }); expect(container.textContent).toBe('3'); - expect(renderCount).toHaveBeenCalledTimes(3); // 1 initial + 2 re-renders (partially batched) }); it('mutations only before await (selector)', async () => { @@ -458,13 +494,18 @@ describe('Async methods β€” re-renders split across await boundaries', () => { render(); expect(renderCount).toHaveBeenCalledTimes(1); - await act(async () => { - await s.asyncIncrement(); - await flush(); + const gate = createGate(); + let completion: Promise | undefined; + act(() => { + completion = s.asyncIncrement(gate.promise); }); + expect(container.textContent).toBe('1'); + await act(async () => { + gate.resolve(); + await completion; + }); expect(container.textContent).toBe('2'); - expect(renderCount).toHaveBeenCalledTimes(2); // 1 initial + 1 re-render (batched) }); }); @@ -545,10 +586,7 @@ describe('Multiple components sharing the same store', () => { expect(renderCountB).toHaveBeenCalledTimes(2); }); - it('auto-tracked, both re-render when store notifies', async () => { - // In auto-tracked mode, when the store notifies all subscribers, each - // component's getSnapshot produces a new snapshot ref β€” both components - // re-render, unlike selector mode which compares extracted values. + it('auto-tracked components update only for properties they read', async () => { const s = createClassyStore(new TestStore()); const renderCountA = mock(() => {}); const renderCountB = mock(() => {}); @@ -581,7 +619,7 @@ describe('Multiple components sharing the same store', () => { }); expect(renderCountA).toHaveBeenCalledTimes(2); - expect(renderCountB).toHaveBeenCalledTimes(2); + expect(renderCountB).toHaveBeenCalledTimes(1); }); it('batched multi-prop, both affected', async () => { @@ -639,13 +677,13 @@ describe('Unmount safety', () => { expect(container.textContent).toBe('0'); await act(async () => { - s.increment(); // schedules microtask notification - root.unmount(); // unsubscribes synchronously - await flush(); // microtask fires with no listeners + s.increment(); + root.unmount(); + roots.delete(root); + await flush(); }); - // If we reach here without throwing, the test passes. - expect(true).toBe(true); + expect(container.textContent).toBe(''); }); it('unmount and remount with same store shows latest state', async () => { @@ -663,6 +701,7 @@ describe('Unmount safety', () => { // Unmount act(() => { root.unmount(); + roots.delete(root); }); // Mutate while no component is mounted diff --git a/packages/classy-store/src/frameworks/react/react.lifecycle.test.tsx b/packages/classy-store/src/frameworks/react/react.lifecycle.test.tsx new file mode 100644 index 0000000..2ee3a99 --- /dev/null +++ b/packages/classy-store/src/frameworks/react/react.lifecycle.test.tsx @@ -0,0 +1,426 @@ +import {afterEach, describe, expect, mock, test} from 'bun:test'; +import { + act, + type ReactNode, + StrictMode, + Suspense, + startTransition, + useEffect, +} from 'react'; +import {createRoot, type Root} from 'react-dom/client'; +import {createClassyStore, getInternal} from '../../core/core'; +import {PROXYABLE} from '../../utils/internal/internal'; +import {useClassyStore, useLocalStore} from './react'; + +class LifecycleStore { + first = 'First'; + second = 'Second'; +} + +type Field = 'first' | 'second'; + +function useSelectedField(store: LifecycleStore, field: Field) { + return useClassyStore(store, (state) => state[field]); +} + +function useTrackedField(store: LifecycleStore, field: Field) { + return useClassyStore(store)[field]; +} + +const modes = [ + {description: 'selector', useField: useSelectedField}, + {description: 'auto-tracked', useField: useTrackedField}, +]; + +class NestedLifecycleStore extends LifecycleStore { + static [PROXYABLE] = true; +} + +const trackingShapes = [ + { + description: 'plain object', + createValue: () => ({first: 'First', second: 'Second'}), + }, + { + description: 'null-prototype object', + createValue: () => + Object.assign(Object.create(null), { + first: 'First', + second: 'Second', + }) as LifecycleStore, + }, + { + description: 'nested opted-in class', + createValue: () => new NestedLifecycleStore(), + }, +]; + +const mountedRoots = new Map(); + +function mount(element: ReactNode) { + const container = document.createElement('div'); + document.body.appendChild(container); + const root = createRoot(container); + mountedRoots.set(root, container); + act(() => root.render(element)); + return {root, container}; +} + +function unmount(root: Root) { + const container = mountedRoots.get(root); + act(() => root.unmount()); + container?.remove(); + mountedRoots.delete(root); +} + +function subscriberCount(store: LifecycleStore) { + const internal = getInternal(store); + return internal.batchedListeners.size + internal.syncListeners.size; +} + +function createSuspension() { + let ready = false; + let resolve: () => void = () => {}; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return { + read() { + if (!ready) throw promise; + }, + resolve() { + ready = true; + resolve(); + }, + }; +} + +afterEach(() => { + for (const root of mountedRoots.keys()) unmount(root); +}); + +describe('useClassyStore lifecycle', () => { + test.each( + trackingShapes, + )('tracks nested conditional reads on a $description', async ({ + createValue, + }) => { + const store = createClassyStore({node: createValue()}); + const renderCount = mock(() => {}); + + function Display({field}: {field: Field}) { + const value = useClassyStore(store).node[field]; + renderCount(); + return {value}; + } + + const {root, container} = mount(); + renderCount.mockClear(); + await act(async () => { + store.node.second = 'Hidden update'; + }); + expect(renderCount).not.toHaveBeenCalled(); + act(() => root.render()); + expect(container.textContent).toBe('Hidden update'); + renderCount.mockClear(); + await act(async () => { + store.node.first = 'Old selection'; + }); + expect(renderCount).not.toHaveBeenCalled(); + await act(async () => { + store.node.second = 'Visible update'; + }); + expect(container.textContent).toBe('Visible update'); + }); + + test.each( + modes, + )('cleans up StrictMode subscriptions and supports remounting ($description)', async ({ + useField, + }) => { + const store = createClassyStore(new LifecycleStore()); + + function Display() { + const value = useField(store, 'first'); + return {value}; + } + + const {root, container} = mount( + + + , + ); + expect(subscriberCount(store)).toBe(1); + await act(async () => { + store.first = 'Updated'; + }); + expect(container.textContent).toBe('Updated'); + + unmount(root); + expect(subscriberCount(store)).toBe(0); + store.first = 'While unmounted'; + + const remount = mount( + + + , + ); + expect(remount.container.textContent).toBe('While unmounted'); + expect(subscriberCount(store)).toBe(1); + unmount(remount.root); + expect(subscriberCount(store)).toBe(0); + }); + + test.each( + modes, + )('switches store instances even when selected values are equal ($description)', async ({ + useField, + }) => { + const firstStore = createClassyStore(new LifecycleStore()); + const secondStore = createClassyStore(new LifecycleStore()); + const renderCount = mock(() => {}); + + function Display({store}: {store: LifecycleStore}) { + const value = useField(store, 'first'); + renderCount(); + return {value}; + } + + const {root, container} = mount(); + act(() => root.render()); + expect(container.textContent).toBe('First'); + expect(subscriberCount(firstStore)).toBe(0); + expect(subscriberCount(secondStore)).toBe(1); + renderCount.mockClear(); + + await act(async () => { + firstStore.first = 'Old store'; + }); + expect(renderCount).not.toHaveBeenCalled(); + expect(container.textContent).toBe('First'); + + await act(async () => { + secondStore.first = 'New store'; + }); + expect(container.textContent).toBe('New store'); + }); + + test.each( + modes, + )('tracks the currently rendered conditional property ($description)', async ({ + useField, + }) => { + const store = createClassyStore(new LifecycleStore()); + const renderCount = mock(() => {}); + + function Display({field}: {field: Field}) { + const value = useField(store, field); + renderCount(); + return {value}; + } + + const {root, container} = mount(); + renderCount.mockClear(); + await act(async () => { + store.second = 'Changed while hidden'; + }); + expect(renderCount).not.toHaveBeenCalled(); + + act(() => root.render()); + expect(container.textContent).toBe('Changed while hidden'); + renderCount.mockClear(); + await act(async () => { + store.first = 'No longer read'; + }); + expect(renderCount).not.toHaveBeenCalled(); + + await act(async () => { + store.second = 'Current property'; + }); + expect(container.textContent).toBe('Current property'); + }); + + test.each( + modes, + )('keeps the committed selection live while a transition suspends ($description)', async ({ + useField, + }) => { + const store = createClassyStore(new LifecycleStore()); + const suspension = createSuspension(); + const attemptedFields = mock((_field: Field) => {}); + + function Display({field}: {field: Field}) { + const value = useField(store, field); + attemptedFields(field); + if (field === 'second') suspension.read(); + return {value}; + } + + const {root, container} = mount( + + + , + ); + await act(async () => { + startTransition(() => { + root.render( + + + , + ); + }); + }); + expect(attemptedFields).toHaveBeenCalledWith('second'); + expect(container.textContent).toBe('First'); + + await act(async () => { + store.first = 'Committed update'; + }); + expect(container.textContent).toBe('Committed update'); + await act(async () => { + store.second = 'Pending update'; + }); + expect(container.textContent).toBe('Committed update'); + + await act(async () => suspension.resolve()); + expect(container.textContent).toBe('Pending update'); + expect(subscriberCount(store)).toBe(1); + }); + + test.each( + modes, + )('discards reads from an abandoned suspended render ($description)', async ({ + useField, + }) => { + const store = createClassyStore(new LifecycleStore()); + const suspension = createSuspension(); + const renderCount = mock(() => {}); + + function Display({field}: {field: Field}) { + const value = useField(store, field); + renderCount(); + if (field === 'second') suspension.read(); + return {value}; + } + + const {root, container} = mount( + + + , + ); + await act(async () => { + startTransition(() => { + root.render( + + + , + ); + }); + }); + act(() => { + root.render( + + + , + ); + }); + renderCount.mockClear(); + await act(async () => { + store.second = 'Abandoned branch'; + }); + expect(renderCount).not.toHaveBeenCalled(); + expect(container.textContent).toBe('First'); + + await act(async () => suspension.resolve()); + await act(async () => { + store.first = 'Still subscribed'; + }); + expect(container.textContent).toBe('Still subscribed'); + }); + + test.each( + modes, + )('reads the latest state when an initial suspended mount resumes ($description)', async ({ + useField, + }) => { + const store = createClassyStore(new LifecycleStore()); + const suspension = createSuspension(); + + function Display() { + const value = useField(store, 'first'); + suspension.read(); + return {value}; + } + + const {container} = mount( + + + , + ); + expect(container.textContent).toBe('Loading'); + expect(subscriberCount(store)).toBe(0); + store.first = 'Before commit'; + await act(async () => suspension.resolve()); + expect(container.textContent).toBe('Before commit'); + expect(subscriberCount(store)).toBe(1); + }); +}); + +describe('useLocalStore lifecycle', () => { + test.each( + modes, + )('isolates mounted instances and replaces state on a keyed remount ($description)', async ({ + useField, + }) => { + const stores = new Map(); + + function Display({id, label}: {id: string; label: string}) { + const store = useLocalStore(() => new LifecycleStore()); + const value = useField(store, 'first'); + useEffect(() => { + stores.set(id, store); + return () => { + stores.delete(id); + }; + }, [id, store]); + return ( + + {label}:{value}| + + ); + } + + const {root, container} = mount( + + + + , + ); + const firstStore = stores.get('first'); + const secondStore = stores.get('second'); + expect(firstStore).toBeDefined(); + expect(secondStore).toBeDefined(); + expect(firstStore).not.toBe(secondStore); + if (!firstStore || !secondStore) throw new Error('Stores did not mount'); + + await act(async () => { + firstStore.first = 'Local update'; + }); + expect(container.textContent).toBe('A:Local update|B:First|'); + act(() => { + root.render( + + + + , + ); + }); + expect(stores.get('first')).toBe(firstStore); + expect(stores.get('second')).not.toBe(secondStore); + expect(container.textContent).toBe('Updated A:Local update|New B:First|'); + expect(subscriberCount(secondStore)).toBe(0); + unmount(root); + expect(stores.size).toBe(0); + expect(subscriberCount(firstStore)).toBe(0); + }); +}); diff --git a/packages/classy-store/src/frameworks/react/react.qa.test.tsx b/packages/classy-store/src/frameworks/react/react.qa.test.tsx new file mode 100644 index 0000000..fefc029 --- /dev/null +++ b/packages/classy-store/src/frameworks/react/react.qa.test.tsx @@ -0,0 +1,213 @@ +import {afterEach, describe, expect, mock, test} from 'bun:test'; +import { + act, + memo, + type ReactNode, + Suspense, + startTransition, + useEffect, + useRef, + useState, +} from 'react'; +import {createRoot, type Root} from 'react-dom/client'; +import {createClassyStore} from '../../core/core'; +import {useClassyStore} from './react'; + +const mountedRoots = new Map(); + +function mount(element: ReactNode) { + const container = document.createElement('div'); + document.body.appendChild(container); + const root = createRoot(container); + mountedRoots.set(root, container); + act(() => root.render(element)); + return {root, container}; +} + +afterEach(() => { + for (const [root, container] of mountedRoots) { + act(() => root.unmount()); + container.remove(); + } + mountedRoots.clear(); +}); + +describe('useClassyStore independent QA', () => { + test('preserves nested snapshot identity across local state renders', () => { + const store = createClassyStore({items: ['First']}); + const effect = mock(() => {}); + + function Display() { + const state = useClassyStore(store); + const items = state.items; + const updates = useRef(0); + const [processed, setProcessed] = useState([]); + useEffect(() => { + effect(); + // Bound the probe so an unstable dependency cannot hang the suite. + if (updates.current < 3) { + updates.current++; + setProcessed([...items]); + } + }, [items]); + return ( + + {processed[0]}:{updates.current} + + ); + } + + const {container} = mount(); + expect(container.textContent).toBe('First:1'); + expect(effect).toHaveBeenCalledTimes(1); + }); + + test('recovers from a selector that throws between related property writes', async () => { + const store = createClassyStore({ + selected: 'first', + records: {first: {name: 'First'}} as Record, + }); + const invalidSelections = mock(() => {}); + const selectName = (state: typeof store) => { + const record = state.records[state.selected]; + if (!record) invalidSelections(); + return record.name; + }; + + function Display() { + const value = useClassyStore(store, selectName); + return {value}; + } + + const {container} = mount(); + await act(async () => { + store.selected = 'second'; + store.records = {second: {name: 'Second'}}; + }); + expect(invalidSelections).toHaveBeenCalled(); + expect(container.textContent).toBe('Second'); + await act(async () => { + store.records.second.name = 'Updated'; + }); + expect(container.textContent).toBe('Updated'); + }); + + test('recovers while array deletion produces transient empty slots', async () => { + const store = createClassyStore({ + items: [{name: 'First'}, {name: 'Second'}], + }); + + function Display() { + const names = useClassyStore(store, (state) => + state.items.map((item) => item.name).join(','), + ); + return {names}; + } + + const {container} = mount(); + await act(async () => { + store.items.splice(0, 1); + }); + expect(container.textContent).toBe('Second'); + await act(async () => { + store.items[0].name = 'Updated'; + }); + expect(container.textContent).toBe('Updated'); + }); + + test('keeps committed nested reads live through an abandoned transition', async () => { + const store = createClassyStore({node: {first: 'First', second: 'Second'}}); + const pending = new Promise(() => {}); + const attempts = mock((_field: 'first' | 'second') => {}); + + function Display({field}: {field: 'first' | 'second'}) { + const value = useClassyStore(store).node[field]; + attempts(field); + if (field === 'second') throw pending; + return {value}; + } + + const {root, container} = mount( + + + , + ); + await act(async () => { + startTransition(() => + root.render( + + + , + ), + ); + }); + expect(attempts).toHaveBeenCalledWith('second'); + await act(async () => { + store.node.first = 'Committed update'; + }); + expect(container.textContent).toBe('Committed update'); + act(() => + root.render( + + + , + ), + ); + attempts.mockClear(); + await act(async () => { + store.node.second = 'Discarded branch'; + }); + expect(attempts).not.toHaveBeenCalled(); + await act(async () => { + store.node.first = 'Still current'; + }); + expect(container.textContent).toBe('Still current'); + }); + + test('keeps a memoized child live after its parent renders with unchanged data', async () => { + const store = createClassyStore({node: {name: 'First', unrelated: 0}}); + const childRenders = mock(() => {}); + const Child = memo(function Child({node}: {node: {readonly name: string}}) { + childRenders(); + return {node.name}; + }); + + function Display({label}: {label: string}) { + const state = useClassyStore(store); + return ( +
+ {label}: +
+ ); + } + + const {root, container} = mount(); + act(() => root.render()); + expect(childRenders).toHaveBeenCalledTimes(1); + await act(async () => { + store.node.name = 'Store update'; + }); + expect(container.textContent).toBe('Parent update:Store update'); + expect(childRenders).toHaveBeenCalledTimes(2); + }); + + test('tracks nested object values held by class symbol fields', () => { + const key = Symbol('state'); + class SymbolStore { + [key] = {name: 'First'}; + } + const store = createClassyStore(new SymbolStore()); + + function Display() { + const state = useClassyStore(store); + return {state[key].name}; + } + + const {container} = mount(); + expect(container.textContent).toBe('First'); + act(() => { + store[key].name = 'Updated'; + }); + expect(container.textContent).toBe('Updated'); + }); +}); diff --git a/packages/classy-store/src/frameworks/react/react.ssr.test.tsx b/packages/classy-store/src/frameworks/react/react.ssr.test.tsx new file mode 100644 index 0000000..b592469 --- /dev/null +++ b/packages/classy-store/src/frameworks/react/react.ssr.test.tsx @@ -0,0 +1,189 @@ +import {afterEach, describe, expect, mock, test} from 'bun:test'; +import {act, useEffect} from 'react'; +import {hydrateRoot, type Root} from 'react-dom/client'; +import {renderToString} from 'react-dom/server'; +import {createClassyStore} from '../../core/core'; +import {snapshot} from '../../snapshot/snapshot'; +import {persist} from '../../utils/persist/persist'; +import {createStoreHook, useClassyStore} from './react'; + +class RequestStore { + constructor( + public user: {name: string}, + public count = 0, + ) {} + + increment() { + this.count++; + } +} + +type HydrationViewProps = { + store: RequestStore; + useBoundStore: ReturnType>; + onMount?: () => undefined | (() => void); +}; + +function HydrationView({store, useBoundStore, onMount}: HydrationViewProps) { + const tracked = useClassyStore(store); + const selected = useClassyStore( + store, + (state) => `${state.user.name}:${state.count}`, + ); + const bound = useBoundStore((state) => state.count); + useEffect(() => onMount?.(), [onMount]); + + return ( +
+ {`${tracked.user.name}:${tracked.count}`} + {selected} + {bound} +
+ ); +} + +const mounted: {root: Root; container: HTMLDivElement}[] = []; + +afterEach(async () => { + for (const {root, container} of mounted.splice(0)) { + await act(async () => root.unmount()); + container.remove(); + } +}); + +function renderRequest(store: RequestStore): string { + return renderToString( + , + ); +} + +describe('React server rendering and hydration', () => { + test('keeps server requests isolated', () => { + const firstRequest = createClassyStore( + new RequestStore({name: 'Alice'}, 2), + ); + const secondRequest = createClassyStore(new RequestStore({name: 'Bob'}, 7)); + + const firstHtml = renderRequest(firstRequest); + const secondHtml = renderRequest(secondRequest); + firstRequest.user.name = 'Updated Alice'; + firstRequest.increment(); + + expect(firstHtml).toContain('Alice:2'); + expect(secondHtml).toContain('Bob:7'); + expect(renderRequest(secondRequest)).toBe(secondHtml); + expect(secondRequest.count).toBe(7); + expect(secondRequest.user.name).toBe('Bob'); + }); + + test('hydrates serialized initial data without replacing server DOM', async () => { + const serverStore = createClassyStore(new RequestStore({name: 'Alice'}, 2)); + const initialData = JSON.stringify(snapshot(serverStore)); + const container = document.createElement('div'); + container.innerHTML = renderRequest(serverStore); + document.body.appendChild(container); + const serverElement = container.firstElementChild; + + const parsed: {user: {name: string}; count: number} = + JSON.parse(initialData); + const clientStore = createClassyStore( + new RequestStore(parsed.user, parsed.count), + ); + const onRecoverableError = mock(() => {}); + + await act(async () => { + const root = hydrateRoot( + container, + , + {onRecoverableError}, + ); + mounted.push({root, container}); + }); + + expect(onRecoverableError).not.toHaveBeenCalled(); + expect(container.firstElementChild).toBe(serverElement); + expect(container.textContent).toBe('Alice:2Alice:22'); + + act(() => { + clientStore.user.name = 'Alicia'; + clientStore.increment(); + }); + + expect(container.textContent).toBe('Alicia:3Alicia:33'); + expect(serverStore.user.name).toBe('Alice'); + expect(serverStore.count).toBe(2); + }); + + test('defers persisted data until after hydration and cleans up on unmount', async () => { + const serverStore = createClassyStore( + new RequestStore({name: 'Server'}, 1), + ); + const clientStore = createClassyStore( + new RequestStore({name: 'Server'}, 1), + ); + const container = document.createElement('div'); + container.innerHTML = renderRequest(serverStore); + document.body.appendChild(container); + const serverElement = container.firstElementChild; + const onRecoverableError = mock(() => {}); + + let resolveSavedState: (value: string) => void = () => {}; + const savedState = new Promise((resolve) => { + resolveSavedState = resolve; + }); + const storage = { + getItem: mock(() => savedState), + setItem: mock(() => {}), + removeItem: mock(() => {}), + }; + let rehydration: Promise | undefined; + const onMount = () => { + const persistence = persist(clientStore, { + name: 'request-store', + storage, + skipHydration: true, + }); + rehydration = persistence.rehydrate(); + return () => persistence.unsubscribe(); + }; + + expect(storage.getItem).not.toHaveBeenCalled(); + await act(async () => { + const root = hydrateRoot( + container, + , + {onRecoverableError}, + ); + mounted.push({root, container}); + }); + + expect(container.firstElementChild).toBe(serverElement); + expect(container.textContent).toBe('Server:1Server:11'); + expect(storage.getItem).toHaveBeenCalledTimes(1); + + await act(async () => { + resolveSavedState( + JSON.stringify({version: 0, state: {user: {name: 'Saved'}, count: 8}}), + ); + await rehydration; + }); + + expect(onRecoverableError).not.toHaveBeenCalled(); + expect(container.textContent).toBe('Saved:8Saved:88'); + expect(storage.setItem).not.toHaveBeenCalled(); + + await act(async () => mounted[0].root.unmount()); + mounted.splice(0); + container.remove(); + clientStore.increment(); + await Promise.resolve(); + expect(storage.setItem).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/classy-store/src/frameworks/react/react.test.tsx b/packages/classy-store/src/frameworks/react/react.test.tsx index 68d7bc0..e7954a4 100644 --- a/packages/classy-store/src/frameworks/react/react.test.tsx +++ b/packages/classy-store/src/frameworks/react/react.test.tsx @@ -1,6 +1,6 @@ import {afterEach, describe, expect, it, mock} from 'bun:test'; import {act, type ReactNode} from 'react'; -import {createRoot} from 'react-dom/client'; +import {createRoot, type Root} from 'react-dom/client'; import {createClassyStore} from '../../core/core'; import {createStoreHook, useClassyStore, useLocalStore} from './react'; @@ -10,6 +10,7 @@ import {createStoreHook, useClassyStore, useLocalStore} from './react'; const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); let container: HTMLDivElement; +const mountedRoots = new Set(); function setup(): void { container = document.createElement('div'); @@ -17,12 +18,15 @@ function setup(): void { } function teardown(): void { - document.body.removeChild(container); + for (const root of mountedRoots) act(() => root.unmount()); + mountedRoots.clear(); + container.remove(); } /** Render a component and return a helper to update. */ function render(element: ReactNode): void { const root = createRoot(container); + mountedRoots.add(root); act(() => { root.render(element); }); @@ -79,7 +83,7 @@ describe('useClassyStore β€” selector mode', () => { expect(renderCount).toHaveBeenCalledTimes(2); }); - it('supports sync selector subscriptions', () => { + it('uses synchronous selector subscriptions by default', () => { class Counter { count = 0; increment() { @@ -90,9 +94,7 @@ describe('useClassyStore β€” selector mode', () => { const renderCount = mock(() => {}); function Display() { - const count = useClassyStore(store, (state) => state.count, { - sync: true, - }); + const count = useClassyStore(store, (state) => state.count); renderCount(); return
{count}
; } @@ -291,12 +293,12 @@ describe('useClassyStore β€” auto-tracked mode', () => { expect(renderCount).toHaveBeenCalledTimes(2); }); - it('supports sync auto-tracked subscriptions', () => { + it('uses synchronous auto-tracked subscriptions by default', () => { const store = createClassyStore({count: 0, name: 'hello'}); const renderCount = mock(() => {}); function Display() { - const snap = useClassyStore(store, {sync: true}); + const snap = useClassyStore(store); renderCount(); return
{snap.count}
; } @@ -565,15 +567,13 @@ describe('useLocalStore', () => { expect(container.textContent).toBe('world'); expect(renderCount).toHaveBeenCalledTimes(2); - // Change count β€” NOT accessed by component, but auto-tracked mode - // re-renders because the snapshot reference changes on any mutation. - // (Documented behavior β€” see "Set-then-revert" in TUTORIAL.md.) + // Change count β€” not read during render, so no update is needed. await act(async () => { storeRef.count = 99; await flush(); }); - expect(renderCount).toHaveBeenCalledTimes(3); + expect(renderCount).toHaveBeenCalledTimes(2); }); }); @@ -665,6 +665,111 @@ describe('useClassyStore β€” multiple stores', () => { describe('useClassyStore β€” selector edge cases', () => { afterEach(teardown); + it.each([ + {description: 'direct hook', bound: false}, + {description: 'bound hook', bound: true}, + ])('updates prop-dependent selectors without a store mutation ($description)', async ({ + bound, + }) => { + const store = createClassyStore({names: ['Alice', 'Bob']}); + const useStore = createStoreHook(store); + + function DirectDisplay({index}: {index: number}) { + const name = useClassyStore(store, (state) => state.names[index]); + return
{name}
; + } + + function BoundDisplay({index}: {index: number}) { + const name = useStore((state) => state.names[index]); + return
{name}
; + } + + const Display = bound ? BoundDisplay : DirectDisplay; + setup(); + const root = createRoot(container); + + try { + act(() => root.render()); + expect(container.textContent).toBe('Alice'); + + act(() => root.render()); + expect(container.textContent).toBe('Bob'); + + await act(async () => { + store.names[1] = 'Bobby'; + await flush(); + }); + expect(container.textContent).toBe('Bobby'); + + act(() => root.render()); + expect(container.textContent).toBe('Alice'); + } finally { + act(() => root.unmount()); + } + }); + + it('reevaluates the selection when the equality function changes', async () => { + const store = createClassyStore({count: 0}); + const selector = (state: {readonly count: number}) => state.count; + const ignoreChanges = () => true; + + function Display({ignore}: {ignore: boolean}) { + const count = useClassyStore(store, selector, { + isEqual: ignore ? ignoreChanges : Object.is, + }); + return
{count}
; + } + + setup(); + const root = createRoot(container); + + try { + act(() => root.render()); + await act(async () => { + store.count = 1; + await flush(); + }); + expect(container.textContent).toBe('0'); + + act(() => root.render()); + expect(container.textContent).toBe('1'); + } finally { + act(() => root.unmount()); + } + }); + + it('preserves equal selection references when the selector changes', () => { + const store = createClassyStore({names: ['Alice', 'Alice', 'Bob']}); + const selections: {name: string}[] = []; + + function Display({index}: {index: number}) { + const selected = useClassyStore( + store, + (state) => ({name: state.names[index]}), + {isEqual: (previous, next) => previous.name === next.name}, + ); + selections.push(selected); + return
{selected.name}
; + } + + setup(); + const root = createRoot(container); + + try { + act(() => root.render()); + const initialSelection = selections.at(-1); + + act(() => root.render()); + expect(selections.at(-1)).toBe(initialSelection); + + act(() => root.render()); + expect(container.textContent).toBe('Bob'); + expect(selections.at(-1)).not.toBe(initialSelection); + } finally { + act(() => root.unmount()); + } + }); + it('handles selector returning undefined', () => { const s = createClassyStore({data: null as string | null}); @@ -741,7 +846,7 @@ describe('useClassyStore β€” selector edge cases', () => { it('selector returning undefined twice in a row does not over-invoke selector', async () => { // Pre-fix, the fast path used `resultRef.current !== undefined` which // skipped memoization whenever the selector legitimately returned - // `undefined`. With `hasResultRef` the fast path engages. + // `undefined`. A cached selection must distinguish it from a cache miss. const s = createClassyStore({maybe: undefined as string | undefined}); const renderCount = mock(() => {}); const selector = mock((state: {maybe: string | undefined}) => state.maybe); @@ -849,7 +954,7 @@ describe('createStoreHook', () => { expect(container.textContent).toBe('1'); }); - it('supports sync selector options', () => { + it('uses synchronous selector subscriptions by default', () => { class Counter { count = 0; increment() { @@ -860,7 +965,7 @@ describe('createStoreHook', () => { const useCounter = createStoreHook(store); function Display() { - const count = useCounter((state) => state.count, {sync: true}); + const count = useCounter((state) => state.count); return
{count}
; } @@ -900,12 +1005,12 @@ describe('createStoreHook', () => { expect(renderCount).toHaveBeenCalledTimes(2); }); - it('supports sync auto-tracked options', () => { + it('uses synchronous auto-tracked subscriptions by default', () => { const store = createClassyStore({count: 0, name: 'hello'}); const useStore = createStoreHook(store); function Display() { - const snap = useStore({sync: true}); + const snap = useStore(); return
{snap.count}
; } diff --git a/packages/classy-store/src/frameworks/react/react.ts b/packages/classy-store/src/frameworks/react/react.ts index cf8d46e..99cb0da 100644 --- a/packages/classy-store/src/frameworks/react/react.ts +++ b/packages/classy-store/src/frameworks/react/react.ts @@ -1,5 +1,12 @@ import {createProxy, isChanged} from 'proxy-compare'; -import {useCallback, useRef, useState, useSyncExternalStore} from 'react'; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + useSyncExternalStore, +} from 'react'; import { subscribe as coreSubscribe, createClassyStore, @@ -10,11 +17,7 @@ import type {Snapshot} from '../../types'; type EqualityFn = (a: T, b: T) => boolean; -export type UseClassyStoreOptions = { - sync?: boolean; -}; - -export type UseClassyStoreSelectorOptions = UseClassyStoreOptions & { +export type UseClassyStoreSelectorOptions = { isEqual?: EqualityFn; }; @@ -28,7 +31,7 @@ export type UseClassyStoreSelectorOptions = UseClassyStoreOptions & { * * @param proxyStore - A reactive proxy created by `createClassyStore()`. * @param selector - Picks data from the immutable snapshot. - * @param options - Controls equality and subscriber notification timing. + * @param options - Controls selected-value equality. */ export function useClassyStore( proxyStore: T, @@ -43,166 +46,116 @@ export function useClassyStore( * The component only re-renders when a property it actually read changes. * * @param proxyStore - A reactive proxy created by `createClassyStore()`. - * @param options - Controls subscriber notification timing. */ -export function useClassyStore( - proxyStore: T, - options?: UseClassyStoreOptions, -): Snapshot; +export function useClassyStore(proxyStore: T): Snapshot; // ── Implementation ──────────────────────────────────────────────────────────── export function useClassyStore( proxyStore: T, - selectorOrOptions?: ((snap: Snapshot) => S) | UseClassyStoreOptions, + selector?: (snap: Snapshot) => S, selectorOptions?: UseClassyStoreSelectorOptions, ): Snapshot | S { // Validate that the argument is actually a store proxy (throws if not). getInternal(proxyStore); - const selector = - typeof selectorOrOptions === 'function' ? selectorOrOptions : undefined; - const options: - | UseClassyStoreOptions - | UseClassyStoreSelectorOptions - | undefined = - typeof selectorOrOptions === 'function' - ? selectorOptions - : selectorOrOptions; - const sync = options?.sync === true; const isEqual = selectorOptions?.isEqual; - // Stable subscribe function (internal identity never changes for a given store). + // React must observe mutations during the event that changes a controlled + // input. Ordinary core subscriptions retain their default microtask batching. const subscribe = useCallback( (onStoreChange: () => void) => - coreSubscribe(proxyStore, onStoreChange, sync ? {sync: true} : undefined), - [proxyStore, sync], + coreSubscribe(proxyStore, onStoreChange, {sync: true}), + [proxyStore], ); // ── Refs used by both modes (always allocated to satisfy Rules of Hooks) ── - // Selector mode refs - const snapRef = useRef | undefined>(undefined); - const resultRef = useRef(undefined); - // Tracks whether `resultRef` holds a real prior result (so a selector that - // legitimately returns `undefined` still benefits from the fast path). - const hasResultRef = useRef(false); + // Keep only committed selections shared between selector instances. + // Each selector gets its own snapshot cache so concurrent renders do not + // overwrite one another's cached snapshot or selected result. + const committedSelectionRef = useRef<{value: S} | undefined>(undefined); + const getSelectedSnapshot = useMemo( + () => + selector + ? createSelectorSnapshotGetter( + proxyStore, + selector, + committedSelectionRef, + isEqual, + ) + : undefined, + [proxyStore, selector, isEqual], + ); - // Auto-track mode refs - const affected = useRef(new WeakMap()).current; + // Each render owns its read set. A suspended or abandoned render cannot + // overwrite the committed getter's tracked properties. Proxy identity is + // cached across renders so unchanged values remain safe effect dependencies. + const affected = new WeakMap(); const proxyCache = useRef(new WeakMap()).current; - const prevSnapRef = useRef | undefined>(undefined); - const wrappedRef = useRef | undefined>(undefined); - - // ── Single getSnapshot for useSyncExternalStore ─────────────────────────── - - const getSnapshot = (): Snapshot | S => - selector - ? getSelectorSnapshot( - proxyStore, - snapRef, - resultRef, - hasResultRef, - selector, - isEqual, - ) - : getAutoTrackSnapshot( - proxyStore, - affected, - proxyCache, - prevSnapRef, - wrappedRef, - ); - - return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); + let previousAutoSnapshot: Snapshot | undefined; + const getSnapshot = (): Snapshot | S => { + if (getSelectedSnapshot) return getSelectedSnapshot(); + const nextSnapshot = snapshot(proxyStore); + if ( + previousAutoSnapshot && + !isChanged(previousAutoSnapshot, nextSnapshot, affected) + ) { + return previousAutoSnapshot; + } + previousAutoSnapshot = nextSnapshot; + return nextSnapshot; + }; + + const result = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); + + useEffect(() => { + committedSelectionRef.current = selector ? {value: result as S} : undefined; + }, [result, selector]); + + return selector + ? result + : (createProxy(result as Snapshot, affected, proxyCache) as Snapshot); } // ── Selector mode logic (pure function, no hooks) ───────────────────────────── /** - * `getSnapshot` implementation for selector mode. - * - * Fast-paths when the snapshot reference hasn't changed (O(1)). Otherwise - * runs the selector against the new snapshot and compares the result to the - * previous one via `Object.is` (or `options.isEqual`). Returns the previous - * result reference when equal, preventing unnecessary React re-renders. + * Create a cached `getSnapshot` for one store, selector, and equality function. * - * Pure function -- no hooks, safe to call from `useSyncExternalStore`. + * Re-evaluates when any of those inputs change, even if the store did not. + * Repeated reads of the same snapshot return the cached result, including + * `undefined`. Equal selections retain their reference across selector changes. */ -function getSelectorSnapshot( +function createSelectorSnapshotGetter( proxyStore: T, - snapRef: React.RefObject | undefined>, - resultRef: React.RefObject, - hasResultRef: React.RefObject, selector: (snap: Snapshot) => S, + committedSelectionRef: React.RefObject<{value: S} | undefined>, isEqual?: EqualityFn, -): S { - const nextSnap = snapshot(proxyStore); - - // Fast path: same snapshot reference β†’ same result. - if (snapRef.current === nextSnap && hasResultRef.current) { - return resultRef.current as S; - } - - const nextResult = selector(nextSnap); - snapRef.current = nextSnap; - - // Check equality with previous result. - if ( - hasResultRef.current && - (isEqual - ? isEqual(resultRef.current as S, nextResult) - : Object.is(resultRef.current as S, nextResult)) - ) { - return resultRef.current as S; - } - - resultRef.current = nextResult; - hasResultRef.current = true; - return nextResult; -} - -// ── Auto-tracked (selectorless) mode logic (pure function, no hooks) ────────── - -/** - * `getSnapshot` implementation for auto-tracked (selectorless) mode. - * - * Uses `proxy-compare` to diff only the properties the component actually read. - * If the snapshot reference is the same, returns the cached tracking proxy. - * If the snapshot changed but no tracked property differs (`isChanged` returns - * false), also returns the cached proxy -- avoiding re-render. Only when a - * relevant property changed does it create a new `createProxy` wrapper. - * - * Pure function -- no hooks, safe to call from `useSyncExternalStore`. - */ -function getAutoTrackSnapshot( - proxyStore: T, - affected: WeakMap, - proxyCache: WeakMap, - prevSnapRef: React.RefObject | undefined>, - wrappedRef: React.RefObject | undefined>, -): Snapshot { - const nextSnap = snapshot(proxyStore); - - // If the raw snapshot is the same reference, nothing changed. - if (prevSnapRef.current === nextSnap) { - return wrappedRef.current as Snapshot; - } - - // Check if any property the component actually read has changed. - if ( - prevSnapRef.current !== undefined && - !isChanged(prevSnapRef.current, nextSnap, affected) - ) { - // No property the component cares about changed β†’ return same wrapped proxy. - return wrappedRef.current as Snapshot; - } - - // Something relevant changed β€” create a new tracking proxy. - prevSnapRef.current = nextSnap; - const wrapped = createProxy(nextSnap, affected, proxyCache) as Snapshot; - wrappedRef.current = wrapped; - return wrapped; +): () => S { + let previousSnapshot: Snapshot | undefined; + let selection: {value: S} | undefined; + + return () => { + const nextSnapshot = snapshot(proxyStore); + if (previousSnapshot === nextSnapshot && selection) { + return selection.value; + } + + const nextResult = selector(nextSnapshot); + const previousSelection = selection ?? committedSelectionRef.current; + + selection = + previousSelection && + (isEqual + ? isEqual(previousSelection.value, nextResult) + : Object.is(previousSelection.value, nextResult)) + ? previousSelection + : {value: nextResult}; + previousSnapshot = nextSnapshot; + + return selection.value; + }; } // ── Bound store hook factory ───────────────────────────────────────────────── @@ -234,18 +187,18 @@ export function createStoreHook(proxyStore: T) { // Fail fast at creation time rather than on first render. getInternal(proxyStore); - function useStore(options?: UseClassyStoreOptions): Snapshot; + function useStore(): Snapshot; function useStore( selector: (snap: Snapshot) => S, options?: UseClassyStoreSelectorOptions, ): S; function useStore( - selectorOrOptions?: ((snap: Snapshot) => S) | UseClassyStoreOptions, + selector?: (snap: Snapshot) => S, options?: UseClassyStoreSelectorOptions, ): Snapshot | S { return useClassyStore( proxyStore, - selectorOrOptions as (snap: Snapshot) => S, + selector as (snap: Snapshot) => S, options, ); } @@ -259,14 +212,15 @@ export function createStoreHook(proxyStore: T) { * component. When the component unmounts, the store becomes unreferenced and is * garbage collected (all internal bookkeeping uses `WeakMap`). * - * The factory function runs **once** per mount (via `useState` initializer). + * The factory is a `useState` initializer and must be pure. React StrictMode + * may invoke it more than once in development. * Each component instance gets its own isolated store. * * Use the returned proxy with `useClassyStore()` to read state in the same component * or pass it down via props/context to share within a subtree. * * @param factory - A function that returns a class instance (or plain object). - * Called once per component mount. + * Used as the component state initializer. * @returns A reactive store proxy scoped to the component's lifetime. * * @example diff --git a/packages/classy-store/src/snapshot/snapshot.test.ts b/packages/classy-store/src/snapshot/snapshot.test.ts index ca7ec3e..44472c6 100644 --- a/packages/classy-store/src/snapshot/snapshot.test.ts +++ b/packages/classy-store/src/snapshot/snapshot.test.ts @@ -1,11 +1,89 @@ import {describe, expect, it} from 'bun:test'; +import {createProxy, isChanged} from 'proxy-compare'; import {createClassyStore} from '../core/core'; +import {PROXYABLE} from '../utils/internal/internal'; import {snapshot} from './snapshot'; /** Helper: flush the queueMicrotask-based batching. */ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); describe('snapshot()', () => { + describe('property read tracking', () => { + it('tracks class-root properties without subscribing to unrelated fields', () => { + class Store { + count = 0; + other = 0; + } + const store = createClassyStore(new Store()); + const previous = snapshot(store); + const affected = new WeakMap(); + const tracked = createProxy(previous, affected); + expect(tracked.count).toBe(0); + + store.other++; + expect(isChanged(previous, snapshot(store), affected)).toBe(false); + store.count++; + expect(isChanged(previous, snapshot(store), affected)).toBe(true); + }); + + it.each([ + {description: 'previously accessed', accessBeforeSnapshot: true}, + {description: 'previously unaccessed', accessBeforeSnapshot: false}, + ])('tracks $description nested opted-in classes', ({ + accessBeforeSnapshot, + }) => { + class Child { + static [PROXYABLE] = true; + count = 0; + other = 0; + } + const store = createClassyStore({child: new Child()}); + if (accessBeforeSnapshot) expect(store.child.count).toBe(0); + const previous = snapshot(store); + const affected = new WeakMap(); + const tracked = createProxy(previous, affected); + expect(tracked.child.count).toBe(0); + + store.child.other++; + expect(isChanged(previous, snapshot(store), affected)).toBe(false); + store.child.count++; + expect(isChanged(previous, snapshot(store), affected)).toBe(true); + }); + + it('tracks null-prototype store properties', () => { + const store = createClassyStore( + Object.assign(Object.create(null), {count: 0, other: 0}) as { + count: number; + other: number; + }, + ); + const previous = snapshot(store); + const affected = new WeakMap(); + expect(createProxy(previous, affected).count).toBe(0); + store.other++; + expect(isChanged(previous, snapshot(store), affected)).toBe(false); + store.count++; + expect(isChanged(previous, snapshot(store), affected)).toBe(true); + }); + + it('leaves frozen opaque values unwrapped and usable', () => { + class OpaqueValue { + value = 3; + } + const date = Object.freeze(new Date('2026-01-01')); + const map = Object.freeze(new Map([['count', 2]])); + const opaque = Object.freeze(new OpaqueValue()); + const store = createClassyStore({date, map, opaque}); + const tracked = createProxy(snapshot(store), new WeakMap()); + + expect(tracked.date).toBe(date); + expect(tracked.date.getTime()).toBe(date.getTime()); + expect(tracked.map).toBe(map); + expect(tracked.map.get('count')).toBe(2); + expect(tracked.opaque).toBe(opaque); + }); + }); + // ── Freezing ────────────────────────────────────────────────────────────── describe('freezing', () => { diff --git a/packages/classy-store/src/snapshot/snapshot.ts b/packages/classy-store/src/snapshot/snapshot.ts index 80d1e65..3d870ca 100644 --- a/packages/classy-store/src/snapshot/snapshot.ts +++ b/packages/classy-store/src/snapshot/snapshot.ts @@ -1,3 +1,4 @@ +import {markToTrack} from 'proxy-compare'; import {getInternal} from '../core/core'; import type {Snapshot, StoreInternal} from '../types'; import {canProxy, findGetterDescriptor} from '../utils/internal/internal'; @@ -26,6 +27,14 @@ const snapshotGetterCache = new WeakMap< Map >(); +// Restoration must distinguish generated memoization getters from user accessors. +const memoizedSnapshotGetters = new WeakSet<() => unknown>(); + +/** Internal helper for restoring live prototype getters from a snapshot. */ +export function isMemoizedSnapshotGetter(getter: (() => unknown) | undefined) { + return getter !== undefined && memoizedSnapshotGetters.has(getter); +} + // ── Cross-snapshot getter memoization ───────────────────────────────────────── /** @@ -157,6 +166,22 @@ function evaluateSnapshotGetter( // ── Internal helpers ────────────────────────────────────────────────────────── +function markSnapshotForTracking(value: object): void { + // proxy-compare 3 copies frozen string descriptors, but leaves symbol + // descriptors frozen. Wrapping an object-valued symbol then violates Proxy + // invariants. Compare those nodes as a whole until that dependency is fixed. + const hasObjectSymbol = Object.getOwnPropertySymbols(value).some((key) => { + const descriptor = Object.getOwnPropertyDescriptor(value, key); + return ( + descriptor && + 'value' in descriptor && + typeof descriptor.value === 'object' && + descriptor.value !== null + ); + }); + markToTrack(value, !hasObjectSymbol); +} + /** * Resolve a single property value into its snapshot equivalent. * @@ -220,6 +245,9 @@ function deepFreezeClone(value: object, inProgress: WeakSet): object { } } + // Class and null-prototype snapshots need an explicit opt-in for read tracking. + // Only cloned state is marked; opaque leaves retain their existing behavior. + markSnapshotForTracking(clone); Object.freeze(clone); untrackedCache.set(value, clone); inProgress.delete(value); @@ -271,10 +299,12 @@ function installMemoizedGetters( ): void { const getters = collectGetters(target); for (const [key, getterFn] of getters) { + function memoizedGetter(this: object) { + return evaluateSnapshotGetter(this, target, key, getterFn); + } + memoizedSnapshotGetters.add(memoizedGetter); Object.defineProperty(snap, key, { - get() { - return evaluateSnapshotGetter(this as object, target, key, getterFn); - }, + get: memoizedGetter, enumerable: true, configurable: true, // required so Object.freeze can make it non-configurable }); @@ -345,6 +375,7 @@ function createSnapshotRecursive( installMemoizedGetters(snap as Record, target); } + markSnapshotForTracking(snap); Object.freeze(snap); // Cache AFTER populating + freezing. The reference is stable. snapshotCache.set(target, [internal.version, snap]); diff --git a/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.test.ts b/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.test.ts new file mode 100644 index 0000000..d07f6a3 --- /dev/null +++ b/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.test.ts @@ -0,0 +1,140 @@ +import {describe, expect, it} from 'bun:test'; +import {createClassyStore} from '../../core/core'; +import {snapshot} from '../../snapshot/snapshot'; +import {PROXYABLE} from '../internal/internal'; +import {cloneSnapshotValue} from './cloneSnapshot'; + +describe('cloneSnapshotValue', () => { + it('copies frozen graphs without losing aliases, symbols, or opaque leaves', () => { + const symbol = Symbol('metadata'); + const date = new Date('2026-01-01'); + const map = new Map([['key', 'value']]); + const method = () => 'value'; + const shared = Object.freeze({count: 1}); + const source = Object.create(null); + Object.defineProperties(source, { + first: {value: shared, enumerable: true}, + second: {value: shared, enumerable: false}, + date: {value: date, enumerable: true}, + map: {value: map, enumerable: true}, + method: {value: method, enumerable: true}, + [symbol]: {value: shared, enumerable: true}, + }); + Object.freeze(source); + + const clone = cloneSnapshotValue(source, new WeakMap()) as typeof source; + + expect(Object.getPrototypeOf(clone)).toBeNull(); + expect(clone.first).not.toBe(shared); + expect(clone.first).toBe(clone.second); + expect(clone[symbol]).toBe(clone.first); + expect(clone.date).toBe(date); + expect(clone.map).toBe(map); + expect(clone.method).toBe(method); + expect(Object.getOwnPropertyDescriptor(clone, 'second')).toMatchObject({ + enumerable: false, + configurable: true, + writable: true, + }); + clone.first.count = 2; + expect(shared.count).toBe(1); + }); + + it('retains cycles without linking the copy back to the source', () => { + const source: {count: number; self?: unknown} = {count: 1}; + source.self = source; + Object.freeze(source); + + const clone = cloneSnapshotValue(source, new WeakMap()) as typeof source; + + expect(clone).not.toBe(source); + expect(clone.self).toBe(clone); + clone.count = 2; + expect(source.count).toBe(1); + }); + + it('preserves sparse array holes, trailing length, and custom properties', () => { + const symbol = Symbol('metadata'); + const source = new Array<{count: number}>(5); + source[2] = Object.freeze({count: 1}); + Object.defineProperty(source, symbol, {value: source[2]}); + Object.freeze(source); + + const clone = cloneSnapshotValue(source, new WeakMap()) as typeof source; + + expect(clone).toHaveLength(5); + expect(0 in clone).toBe(false); + expect(1 in clone).toBe(false); + expect(2 in clone).toBe(true); + expect(4 in clone).toBe(false); + expect(Reflect.get(clone, symbol)).toBe(clone[2]); + clone[2].count = 2; + clone.push({count: 3}); + expect(source[2].count).toBe(1); + expect(source).toHaveLength(5); + }); + + it('restores live prototype getters from memoized snapshots', () => { + class Profile { + static [PROXYABLE] = true; + name = 'Alice'; + + get greeting() { + return `Hello, ${this.name}`; + } + } + const store = createClassyStore(new Profile()); + const previous = snapshot(store); + expect(previous.greeting).toBe('Hello, Alice'); + + const clone = cloneSnapshotValue(previous, new WeakMap()) as Profile; + clone.name = 'Alicia'; + + expect(clone).toBeInstanceOf(Profile); + expect(clone.greeting).toBe('Hello, Alicia'); + expect(previous.greeting).toBe('Hello, Alice'); + }); + + it('preserves frozen custom accessors that override prototype getters', () => { + class Profile { + static [PROXYABLE] = true; + name = 'Alice'; + + get greeting() { + return `Hello, ${this.name}`; + } + } + const source = new Profile(); + Object.defineProperty(source, 'greeting', { + get(this: Profile) { + return `Welcome, ${this.name}`; + }, + enumerable: true, + configurable: true, + }); + Object.freeze(source); + + const clone = cloneSnapshotValue(source, new WeakMap()) as Profile; + clone.name = 'Alicia'; + + expect(clone.greeting).toBe('Welcome, Alicia'); + expect(source.greeting).toBe('Welcome, Alice'); + }); + + it('preserves the prototype of custom array values', () => { + class Names extends Array { + get firstName() { + return this[0]; + } + } + const source = new Names('Alice'); + Object.freeze(source); + + const clone = cloneSnapshotValue(source, new WeakMap()) as Names; + + expect(clone).toBeInstanceOf(Names); + clone[0] = 'Alicia'; + expect(clone.firstName).toBe('Alicia'); + expect(source.firstName).toBe('Alice'); + }); +}); diff --git a/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.ts b/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.ts new file mode 100644 index 0000000..dc268d4 --- /dev/null +++ b/packages/classy-store/src/utils/cloneSnapshot/cloneSnapshot.ts @@ -0,0 +1,44 @@ +import {isMemoizedSnapshotGetter} from '../../snapshot/snapshot'; +import {canProxy} from '../internal/internal'; + +/** Restore proxyable snapshot data as mutable objects, preserving prototypes. */ +export function cloneSnapshotValue( + value: unknown, + clones: WeakMap, +): unknown { + // Match snapshot leaf semantics: opaque values and functions pass through. + if (!canProxy(value)) return value; + const cached = clones.get(value); + if (cached) return cached; + + const clone: object = Array.isArray(value) + ? Object.setPrototypeOf( + new Array(value.length), + Object.getPrototypeOf(value), + ) + : Object.create(Object.getPrototypeOf(value)); + clones.set(value, clone); + + for (const key of Reflect.ownKeys(value)) { + if (Array.isArray(value) && key === 'length') continue; + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (!descriptor) continue; + if (!('value' in descriptor)) { + // Snapshot getters cache frozen values; use the original prototype getters. + if (isMemoizedSnapshotGetter(descriptor.get)) { + continue; + } + // Custom persistence merges and transforms can supply their own accessors. + Object.defineProperty(clone, key, {...descriptor, configurable: true}); + continue; + } + Object.defineProperty(clone, key, { + value: cloneSnapshotValue(descriptor.value, clones), + enumerable: descriptor.enumerable, + configurable: true, + writable: true, + }); + } + + return clone; +} diff --git a/packages/classy-store/src/utils/history/history.test.ts b/packages/classy-store/src/utils/history/history.test.ts index c47aee6..2931f07 100644 --- a/packages/classy-store/src/utils/history/history.test.ts +++ b/packages/classy-store/src/utils/history/history.test.ts @@ -1,5 +1,7 @@ import {describe, expect, it} from 'bun:test'; +import {reactiveMap, reactiveSet} from '../../collections/collections'; import {createClassyStore} from '../../core/core'; +import {snapshot} from '../../snapshot/snapshot'; import {withHistory} from './history'; /** Flush the queueMicrotask-based batching. */ @@ -8,6 +10,103 @@ const tick = () => new Promise((resolve) => setTimeout(resolve, 0)); // ── Tests ──────────────────────────────────────────────────────────────────── describe('withHistory', () => { + it.each([ + {description: 'undo', operation: 'undo' as const}, + {description: 'redo', operation: 'redo' as const}, + ])('keeps nested state editable after $description without changing history', async ({ + operation, + }) => { + const store = createClassyStore({ + items: [{name: 'initial'}], + details: {nested: {count: 0}}, + }); + const history = withHistory(store); + + try { + store.items[0].name = 'recorded'; + store.details.nested.count = 1; + await tick(); + + history.undo(); + await tick(); + if (operation === 'redo') { + history.redo(); + await tick(); + } + const restored = snapshot(store); + const restoredName = operation === 'undo' ? 'initial' : 'recorded'; + const restoredCount = operation === 'undo' ? 0 : 1; + + store.items.push({name: 'added'}); + store.items[0].name = 'edited'; + store.details.nested.count = 2; + await tick(); + + expect(snapshot(store).items).toEqual([ + {name: 'edited'}, + {name: 'added'}, + ]); + expect(store.details.nested.count).toBe(2); + expect(restored.items).toEqual([{name: restoredName}]); + expect(restored.details.nested.count).toBe(restoredCount); + expect(history.canRedo).toBe(false); + + history.undo(); + await tick(); + expect(snapshot(store).items).toEqual([{name: restoredName}]); + expect(store.details.nested.count).toBe(restoredCount); + + history.redo(); + await tick(); + expect(snapshot(store).items).toEqual([ + {name: 'edited'}, + {name: 'added'}, + ]); + expect(store.details.nested.count).toBe(2); + } finally { + history.dispose(); + } + }); + + it('preserves reactive collection methods and getters after restoration', async () => { + const date = new Date('2026-01-01'); + const store = createClassyStore({ + users: reactiveMap([['alice', {name: 'Alice', date}]]), + tags: reactiveSet(['initial']), + }); + const history = withHistory(store); + + try { + store.users.set('bob', {name: 'Bob', date}); + store.tags.add('recorded'); + await tick(); + + history.undo(); + await tick(); + + store.users.set('carol', {name: 'Carol', date}); + store.tags.add('edited'); + const alice = store.users.get('alice'); + if (!alice) throw new Error('Expected the restored user'); + alice.name = 'Alicia'; + await tick(); + + expect(store.users.size).toBe(2); + expect(store.tags.size).toBe(2); + expect(store.users.get('alice')?.name).toBe('Alicia'); + expect(store.users.get('alice')?.date).toBe(date); + expect(Array.from(store.tags)).toEqual(['initial', 'edited']); + + history.undo(); + await tick(); + expect(store.users.size).toBe(1); + expect(store.users.get('alice')?.name).toBe('Alice'); + expect(Array.from(store.tags)).toEqual(['initial']); + } finally { + history.dispose(); + } + }); + it('captures initial state as history[0]', () => { class Store { count = 0; diff --git a/packages/classy-store/src/utils/history/history.ts b/packages/classy-store/src/utils/history/history.ts index ba998e6..478058b 100644 --- a/packages/classy-store/src/utils/history/history.ts +++ b/packages/classy-store/src/utils/history/history.ts @@ -1,6 +1,7 @@ import {subscribe} from '../../core/core'; import {snapshot} from '../../snapshot/snapshot'; import type {Snapshot} from '../../types'; +import {cloneSnapshotValue} from '../cloneSnapshot/cloneSnapshot'; import {findGetterDescriptor} from '../internal/internal'; // ── Types ──────────────────────────────────────────────────────────────────── @@ -57,6 +58,7 @@ export function withHistory( */ function applySnapshot(snap: Snapshot): void { const snapRecord = snap as Record; + const clones = new WeakMap(); for (const key of Object.keys(snapRecord)) { // Skip getters @@ -65,7 +67,10 @@ export function withHistory( if (typeof (proxyStore as Record)[key] === 'function') { continue; } - (proxyStore as Record)[key] = snapRecord[key]; + (proxyStore as Record)[key] = cloneSnapshotValue( + snapRecord[key], + clones, + ); } } diff --git a/packages/classy-store/src/utils/persist/persist.test.ts b/packages/classy-store/src/utils/persist/persist.test.ts index b26a461..868a6d8 100644 --- a/packages/classy-store/src/utils/persist/persist.test.ts +++ b/packages/classy-store/src/utils/persist/persist.test.ts @@ -1,7 +1,9 @@ import {describe, expect, it, mock} from 'bun:test'; import type {ReactiveMap} from '../../collections/collections'; -import {reactiveMap} from '../../collections/collections'; +import {reactiveMap, reactiveSet} from '../../collections/collections'; import {createClassyStore} from '../../core/core'; +import {snapshot} from '../../snapshot/snapshot'; +import {PROXYABLE} from '../internal/internal'; import type {StorageAdapter} from './persist'; import {persist} from './persist'; @@ -413,6 +415,166 @@ describe('persist()', () => { // ── Merge strategies ──────────────────────────────────────────────────── describe('merge strategies', () => { + it.each([ + {description: 'shallow initial hydration', manual: false, custom: false}, + {description: 'shallow rehydration', manual: true, custom: false}, + {description: 'custom initial hydration', manual: false, custom: true}, + {description: 'custom rehydration', manual: true, custom: true}, + ])('keeps omitted nested defaults editable after $description', async ({ + manual, + custom, + }) => { + const storage = createMockStorage(); + storage.data.set( + 'test', + JSON.stringify({version: 0, state: {theme: 'dark'}}), + ); + const setItem = mock(storage.setItem.bind(storage)); + storage.setItem = setItem; + const store = createClassyStore({ + theme: 'light', + items: [{name: 'initial'}], + preferences: {display: {compact: false}}, + }); + const beforeHydration = snapshot(store); + const handle = persist(store, { + name: 'test', + storage, + skipHydration: manual, + merge: custom + ? (persisted, current) => ({ + theme: persisted.theme, + items: [...(current.items as unknown[])], + preferences: {...(current.preferences as object)}, + }) + : 'shallow', + }); + + try { + if (manual) await handle.rehydrate(); + await handle.hydrated; + const restored = snapshot(store); + + store.items.push({name: 'added'}); + store.items[0].name = 'edited'; + store.preferences.display.compact = true; + + expect(setItem).not.toHaveBeenCalled(); + await tick(); + expect(setItem).toHaveBeenCalledTimes(1); + expect(parseStored(storage, 'test')?.state).toEqual({ + theme: 'dark', + items: [{name: 'edited'}, {name: 'added'}], + preferences: {display: {compact: true}}, + }); + expect(beforeHydration.theme).toBe('light'); + expect(restored.theme).toBe('dark'); + for (const previous of [beforeHydration, restored]) { + expect(previous.items).toEqual([{name: 'initial'}]); + expect(previous.preferences.display.compact).toBe(false); + } + } finally { + handle.unsubscribe(); + } + }); + + it('preserves reactive defaults and opaque leaves during partial hydration', async () => { + class Profile { + static [PROXYABLE] = true; + name = 'Alice'; + + get greeting() { + return `Hello, ${this.name}`; + } + + rename(name: string) { + this.name = name; + } + } + + const storage = createMockStorage(); + storage.data.set( + 'test', + JSON.stringify({version: 0, state: {theme: 'dark'}}), + ); + const date = new Date('2026-01-01'); + const opaque = new Map([['original', 1]]); + const store = createClassyStore({ + theme: 'light', + profile: new Profile(), + users: reactiveMap([['alice', {name: 'Alice', date}]]), + tags: reactiveSet(['initial']), + opaque, + }); + const beforeHydration = snapshot(store); + const handle = persist(store, {name: 'test', storage}); + + try { + await handle.hydrated; + expect(store.profile).toBeInstanceOf(Profile); + expect(store.profile.greeting).toBe('Hello, Alice'); + expect(store.users.size).toBe(1); + expect(store.tags.size).toBe(1); + + store.profile.rename('Alicia'); + store.users.set('bob', {name: 'Bob', date}); + store.tags.add('added'); + const alice = store.users.get('alice'); + if (!alice) throw new Error('Expected the restored user'); + alice.name = 'Alicia'; + + expect(store.profile.greeting).toBe('Hello, Alicia'); + expect(store.users.size).toBe(2); + expect(store.tags.size).toBe(2); + expect(alice.date).toBe(date); + expect(store.opaque).toBe(opaque); + expect(beforeHydration.profile.greeting).toBe('Hello, Alice'); + expect(beforeHydration.users._entries).toEqual([ + ['alice', {name: 'Alice', date}], + ]); + expect(beforeHydration.users.size).toBe(1); + expect(beforeHydration.tags.size).toBe(1); + } finally { + handle.unsubscribe(); + } + }); + + it('preserves own accessors returned by a custom merge', async () => { + function createProfile(name: string) { + return { + name, + get greeting() { + return `Hello, ${this.name}`; + }, + }; + } + + const storage = createMockStorage(); + storage.data.set( + 'test', + JSON.stringify({version: 0, state: {profile: {name: 'Alice'}}}), + ); + const store = createClassyStore({ + profile: createProfile('default'), + }); + const handle = persist(store, { + name: 'test', + storage, + merge: (persisted) => ({ + profile: createProfile((persisted.profile as {name: string}).name), + }), + }); + + try { + await handle.hydrated; + expect(store.profile.greeting).toBe('Hello, Alice'); + store.profile.name = 'Alicia'; + expect(store.profile.greeting).toBe('Hello, Alicia'); + } finally { + handle.unsubscribe(); + } + }); + it('shallow merge (default): preserves new properties not in storage', async () => { const storage = createMockStorage(); storage.data.set( diff --git a/packages/classy-store/src/utils/persist/persist.ts b/packages/classy-store/src/utils/persist/persist.ts index 8e12c2d..d68667b 100644 --- a/packages/classy-store/src/utils/persist/persist.ts +++ b/packages/classy-store/src/utils/persist/persist.ts @@ -1,5 +1,6 @@ import {subscribe} from '../../core/core'; import {snapshot} from '../../snapshot/snapshot'; +import {cloneSnapshotValue} from '../cloneSnapshot/cloneSnapshot'; import {findGetterDescriptor} from '../internal/internal'; // ── Types ──────────────────────────────────────────────────────────────────── @@ -421,10 +422,16 @@ export function persist( merged = {...currentState, ...state}; } + // Merged defaults may still reference frozen snapshot branches. Restore + // mutable copies without changing snapshots retained by callers or merges. + const clones = new WeakMap(); // Apply to store proxy (goes through SET traps β†’ reactivity). for (const key of propKeys) { if (key in merged) { - (proxyStore as Record)[key] = merged[key]; + (proxyStore as Record)[key] = cloneSnapshotValue( + merged[key], + clones, + ); } } } diff --git a/packages/classy-store/tests/browser/README.md b/packages/classy-store/tests/browser/README.md new file mode 100644 index 0000000..9f56393 --- /dev/null +++ b/packages/classy-store/tests/browser/README.md @@ -0,0 +1,20 @@ +# React browser regressions + +From the repository root: + +```sh +bun install --frozen-lockfile +cd packages/classy-store +bunx playwright install chromium firefox webkit +bun run test:browser +``` + +CI also installs the Linux system dependencies with `playwright install --with-deps`. The suite starts an isolated local Bun server and tests the source React binding in Chromium, Firefox, and WebKit. Package exports and React version compatibility are tested separately in `tests/compatibility`. + +Every test fails on browser console errors or uncaught page errors. Native clipboard replacement and subsequent caret behavior are tested in Chromium; Firefox and WebKit explicitly skip those cases because they do not expose the same clipboard permissions API. + +The four hook forms (selector, auto-tracked, bound selector, and bound auto-tracked) cover middle insertion, selection replacement, deletion, multiline and Unicode input, checkbox changes, composition event sequences, and multi-property/array updates. The latter asserts one React commit in the event handler and one ordinary subscription callback after the microtask flush. + +Composition tests combine synthetic boundaries with browser text insertion. They do not automate a native operating-system IME candidate picker; that still needs a manual smoke test. Firefox emits additional composition boundaries during text insertion, so the suite checks balanced boundaries and the final value/caret rather than a browser-specific event count. + +The caret tests failed under the previous default batched React subscriptions: inserting `XY` in `abcdef` at position 2 produced `abXcdefY`. No `sync` option is passed by any fixture. diff --git a/packages/classy-store/tests/browser/browser.store.ts b/packages/classy-store/tests/browser/browser.store.ts new file mode 100644 index 0000000..b97fdcb --- /dev/null +++ b/packages/classy-store/tests/browser/browser.store.ts @@ -0,0 +1,42 @@ +import {createClassyStore, subscribe} from '../../src/core/core'; +import {createStoreHook} from '../../src/frameworks/react/react'; + +class InputStore { + text = 'abcdef'; + notes = 'first line'; + checked = false; + edits = 0; + first = 'First'; + last = 'Last'; + items = ['a', 'b', 'c']; + + updateTogether() { + this.first = 'Ada'; + this.last = 'Lovelace'; + this.items.splice(0, 2, 'x'); + this.items.push('z'); + this.items.reverse(); + } +} + +export const inputStore = createClassyStore(new InputStore()); +export const useInputStore = createStoreHook(inputStore); +export const browserProbe = { + notifications: 0, + notificationsDuringEvent: -1, + commits: [] as string[], + eventValues: [] as string[], + compositions: [] as string[], +}; + +subscribe(inputStore, () => { + browserProbe.notifications++; +}); + +declare global { + interface Window { + browserProbe: typeof browserProbe; + } +} + +window.browserProbe = browserProbe; diff --git a/packages/classy-store/tests/browser/browser.test.ts b/packages/classy-store/tests/browser/browser.test.ts new file mode 100644 index 0000000..d3fec40 --- /dev/null +++ b/packages/classy-store/tests/browser/browser.test.ts @@ -0,0 +1,16 @@ +import {test as base, expect} from '@playwright/test'; + +export const test = base.extend<{browserErrors: undefined}>({ + browserErrors: [ + async ({page}, use) => { + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()); + }); + await use(undefined); + expect(errors, 'Browser exceptions and console errors').toEqual([]); + }, + {auto: true}, + ], +}); diff --git a/packages/classy-store/tests/browser/components/boundSelectorForm/BoundSelectorForm.tsx b/packages/classy-store/tests/browser/components/boundSelectorForm/BoundSelectorForm.tsx new file mode 100644 index 0000000..5a26eb7 --- /dev/null +++ b/packages/classy-store/tests/browser/components/boundSelectorForm/BoundSelectorForm.tsx @@ -0,0 +1,7 @@ +import {useInputStore} from '../../browser.store'; +import {InputForm} from '../inputForm/InputForm'; + +export function BoundSelectorForm() { + const state = useInputStore((snapshot) => snapshot); + return ; +} diff --git a/packages/classy-store/tests/browser/components/boundTrackedForm/BoundTrackedForm.tsx b/packages/classy-store/tests/browser/components/boundTrackedForm/BoundTrackedForm.tsx new file mode 100644 index 0000000..a23e827 --- /dev/null +++ b/packages/classy-store/tests/browser/components/boundTrackedForm/BoundTrackedForm.tsx @@ -0,0 +1,7 @@ +import {useInputStore} from '../../browser.store'; +import {InputForm} from '../inputForm/InputForm'; + +export function BoundTrackedForm() { + const state = useInputStore(); + return ; +} diff --git a/packages/classy-store/tests/browser/components/derivedForm/DerivedForm.tsx b/packages/classy-store/tests/browser/components/derivedForm/DerivedForm.tsx new file mode 100644 index 0000000..03b9d71 --- /dev/null +++ b/packages/classy-store/tests/browser/components/derivedForm/DerivedForm.tsx @@ -0,0 +1,74 @@ +import {useLayoutEffect} from 'react'; +import {createClassyStore, subscribe} from '../../../../src/core/core'; +import { + createStoreHook, + useClassyStore, +} from '../../../../src/frameworks/react/react'; + +class DerivedStore { + selectedId = 1; + items = [ + {id: 1, name: 'Ada'}, + {id: 2, name: 'Grace'}, + {id: 3, name: 'Linus'}, + ]; + + get selectedName() { + // Valid before and after replaceSelection, but not between its writes. + const selected = this.items.find((item) => item.id === this.selectedId); + if (!selected) throw new Error('Selected item is temporarily absent'); + return selected.name; + } + + get names() { + return this.items.map((item) => item.name).join(','); + } + + replaceSelection() { + this.items.splice(0, 2, {id: 4, name: 'Hedy'}); + this.selectedId = 4; + this.items.push({id: 5, name: 'Katherine'}); + this.items.reverse(); + } +} + +const store = createClassyStore(new DerivedStore()); +const useStore = createStoreHook(store); +let ordinaryNotifications = 0; +subscribe(store, () => ordinaryNotifications++); + +export function DerivedForm() { + const selected = useClassyStore(store, (state) => state.selectedName); + const tracked = useClassyStore(store); + const boundSelected = useStore((state) => state.names); + const boundTracked = useStore(); + const values = [ + selected, + tracked.selectedName, + boundSelected, + boundTracked.names, + ].join('|'); + + useLayoutEffect(() => { + const output = document.getElementById('derived-commits'); + if (output) output.textContent += `${values}\n`; + }); + + return ( +
+ {values} +
+      
+      
+    
+ ); +} diff --git a/packages/classy-store/tests/browser/components/inputForm/InputForm.tsx b/packages/classy-store/tests/browser/components/inputForm/InputForm.tsx new file mode 100644 index 0000000..ae3c3ac --- /dev/null +++ b/packages/classy-store/tests/browser/components/inputForm/InputForm.tsx @@ -0,0 +1,67 @@ +import {useLayoutEffect} from 'react'; +import type {Snapshot} from '../../../../src/types'; +import {browserProbe, inputStore} from '../../browser.store'; + +type Props = {state: Snapshot}; + +export function InputForm({state}: Props) { + const fullName = `${state.first} ${state.last}`; + const items = state.items.join(','); + useLayoutEffect(() => { + browserProbe.commits.push(`${fullName}:${items}`); + }); + + return ( +
+ +