├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── assets
└── logo.png
├── benchs
├── complex.mjs
├── memoryUsage.mjs
└── propagate.mjs
├── build.js
├── package.json
├── pnpm-lock.yaml
├── src
├── index.ts
└── system.ts
├── tests
├── build.spec.ts
├── computed.spec.ts
├── effect.spec.ts
├── effectScope.spec.ts
├── issue_48.spec.ts
├── topology.spec.ts
└── untrack.spec.ts
├── tsconfig.json
└── tsslint.config.ts
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: testing
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | node-version: [22]
12 | os: [macos-latest]
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - uses: pnpm/action-setup@v4
18 |
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
24 | # install pnpm
25 | - run: pnpm install --frozen-lockfile
26 | - run: pnpm run build
27 | - run: pnpm run check
28 | - run: pnpm run test
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | cjs
3 | esm
4 | types
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.format.semicolons": "insert",
3 | "editor.insertSpaces": false,
4 | "editor.detectIndentation": false,
5 | "json.format.keepLines": true,
6 | "typescript.tsdk": "node_modules/typescript/lib",
7 | "[typescript]": {
8 | "editor.defaultFormatter": "vscode.typescript-language-features"
9 | },
10 | "[javascript]": {
11 | "editor.defaultFormatter": "vscode.typescript-language-features"
12 | },
13 | "[json]": {
14 | "editor.defaultFormatter": "vscode.json-language-features"
15 | },
16 | "[jsonc]": {
17 | "editor.defaultFormatter": "vscode.json-language-features"
18 | }
19 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-present Johnson Chu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | # alien-signals
11 |
12 | This project explores a push-pull based signal algorithm. Its current implementation is similar to or related to certain other frontend projects:
13 |
14 | - Propagation algorithm of Vue 3
15 | - Preact’s double-linked-list approach (https://preactjs.com/blog/signal-boosting/)
16 | - Inner effects scheduling of Svelte
17 | - Graph-coloring approach of Reactively (https://milomg.dev/2022-12-01/reactivity)
18 |
19 | We impose some constraints (such as not using Array/Set/Map and disallowing function recursion in [the algorithmic core](https://github.com/stackblitz/alien-signals/blob/master/src/system.ts)) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.
20 |
21 | Even though Vue 3.4 is already optimized, alien-signals is still noticeably faster. (I wrote code for both, and since they share similar algorithms, they’re quite comparable.)
22 |
23 |
24 |
25 | > Benchmark repo: https://github.com/transitive-bullshit/js-reactivity-benchmark
26 |
27 | ## Background
28 |
29 | I spent considerable time [optimizing Vue 3.4’s reactivity system](https://github.com/vuejs/core/pull/5912), gaining experience along the way. Since Vue 3.5 [switched to a pull-based algorithm similar to Preact](https://github.com/vuejs/core/pull/10397), I decided to continue researching a push-pull based implementation in a separate project. Our end goal is to implement fully incremental AST parsing and virtual code generation in Vue language tools, based on alien-signals.
30 |
31 | ## Other Language Implementations
32 |
33 | - **Dart:** [medz/alien-signals-dart](https://github.com/medz/alien-signals-dart) 2.0.5
34 | - **Lua:** [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua) 2.0.5
35 | - **Lua 5.4:** [xuhuanzy/alien-signals-lua](https://github.com/xuhuanzy/alien-signals-lua) 2.0.5
36 | - **Luau:** [Nicell/alien-signals-luau](https://github.com/Nicell/alien-signals-luau) 1.0.13
37 | - **Java:** [CTRL-Neo-Studios/java-alien-signals](https://github.com/CTRL-Neo-Studios/java-alien-signals) 1.0.13
38 | - **C#:** [CTRL-Neo-Studios/csharp-alien-signals](https://github.com/CTRL-Neo-Studios/csharp-alien-signals) 1.0.13
39 | - **Go:** [delaneyj/alien-signals-go](https://github.com/delaneyj/alien-signals-go) 1.0.7
40 |
41 | ## Derived Projects
42 |
43 | - [Rajaniraiyn/react-alien-signals](https://github.com/Rajaniraiyn/react-alien-signals): React bindings for the alien-signals API
44 | - [CCherry07/alien-deepsignals](https://github.com/CCherry07/alien-deepsignals): Use alien-signals with the interface of a plain JavaScript object
45 | - [hunghg255/reactjs-signal](https://github.com/hunghg255/reactjs-signal): Share Store State with Signal Pattern
46 | - [gn8-ai/universe-alien-signals](https://github.com/gn8-ai/universe-alien-signals): Enables simple use of the Alien Signals state management system in modern frontend frameworks
47 | - [WebReflection/alien-signals](https://github.com/WebReflection/alien-signals): Preact signals like API and a class based approach for easy brand check
48 | - [@lift-html/alien](https://github.com/JLarky/lift-html/tree/main/packages/alien): Integrating alien-signals into lift-html
49 |
50 | ## Adoption
51 |
52 | - [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to v3.6 (PR: https://github.com/vuejs/core/pull/12349)
53 | - [statelyai/xstate](https://github.com/statelyai/xstate): The core algorithm has been ported to implement the atom architecture (PR: https://github.com/statelyai/xstate/pull/5250)
54 | - [flamrdevs/xignal](https://github.com/flamrdevs/xignal): Infrastructure for the reactive system
55 | - [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation
56 | - [unuse](https://github.com/un-ts/unuse): A framework-agnostic `use` library inspired by `VueUse`
57 |
58 | ## Usage
59 |
60 | #### Basic APIs
61 |
62 | ```ts
63 | import { signal, computed, effect } from 'alien-signals';
64 |
65 | const count = signal(1);
66 | const doubleCount = computed(() => count() * 2);
67 |
68 | effect(() => {
69 | console.log(`Count is: ${count()}`);
70 | }); // Console: Count is: 1
71 |
72 | console.log(doubleCount()); // 2
73 |
74 | count(2); // Console: Count is: 2
75 |
76 | console.log(doubleCount()); // 4
77 | ```
78 |
79 | #### Effect Scope
80 |
81 | ```ts
82 | import { signal, effect, effectScope } from 'alien-signals';
83 |
84 | const count = signal(1);
85 |
86 | const stopScope = effectScope(() => {
87 | effect(() => {
88 | console.log(`Count in scope: ${count()}`);
89 | }); // Console: Count in scope: 1
90 | });
91 |
92 | count(2); // Console: Count in scope: 2
93 |
94 | stopScope();
95 |
96 | count(3); // No console output
97 | ```
98 |
99 | #### Creating Your Own Surface API
100 |
101 | You can reuse alien-signals’ core algorithm via `createReactiveSystem()` to build your own signal API. For implementation examples, see:
102 |
103 | - [Starter template](https://github.com/johnsoncodehk/alien-signals-starter) (implements `.get()` & `.set()` methods like the [Signals proposal](https://github.com/tc39/proposal-signals))
104 | - [stackblitz/alien-signals/src/index.ts](https://github.com/stackblitz/alien-signals/blob/master/src/index.ts)
105 | - [proposal-signals/signal-polyfill#44](https://github.com/proposal-signals/signal-polyfill/pull/44)
106 |
107 |
108 | ## About `propagate` and `checkDirty` functions
109 |
110 | In order to eliminate recursive calls and improve performance, we record the last link node of the previous loop in `propagate` and `checkDirty` functions, and implement the rollback logic to return to this node.
111 |
112 | This results in code that is difficult to understand, and you don't necessarily get the same performance improvements in other languages, so we record the original implementation without eliminating recursive calls here for reference.
113 |
114 | #### `propagate`
115 |
116 | ```ts
117 | function propagate(link: Link): void {
118 | do {
119 | const sub = link.sub;
120 |
121 | let flags = sub.flags;
122 |
123 | if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) {
124 | sub.flags = flags | ReactiveFlags.Pending;
125 | } else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) {
126 | flags = ReactiveFlags.None;
127 | } else if (!(flags & ReactiveFlags.RecursedCheck)) {
128 | sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending;
129 | } else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) {
130 | sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending;
131 | flags &= ReactiveFlags.Mutable;
132 | } else {
133 | flags = ReactiveFlags.None;
134 | }
135 |
136 | if (flags & ReactiveFlags.Watching) {
137 | notify(sub);
138 | }
139 |
140 | if (flags & ReactiveFlags.Mutable) {
141 | const subSubs = sub.subs;
142 | if (subSubs !== undefined) {
143 | propagate(subSubs);
144 | }
145 | }
146 |
147 | link = link.nextSub!;
148 | } while (link !== undefined);
149 | }
150 | ```
151 |
152 | #### `checkDirty`
153 |
154 | ```ts
155 | function checkDirty(link: Link, sub: ReactiveNode): boolean {
156 | do {
157 | const dep = link.dep;
158 | const depFlags = dep.flags;
159 |
160 | if (sub.flags & ReactiveFlags.Dirty) {
161 | return true;
162 | } else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) {
163 | if (update(dep)) {
164 | const subs = dep.subs!;
165 | if (subs.nextSub !== undefined) {
166 | shallowPropagate(subs);
167 | }
168 | return true;
169 | }
170 | } else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) {
171 | if (checkDirty(dep.deps!, dep)) {
172 | if (update(dep)) {
173 | const subs = dep.subs!;
174 | if (subs.nextSub !== undefined) {
175 | shallowPropagate(subs);
176 | }
177 | return true;
178 | }
179 | } else {
180 | dep.flags = depFlags & ~ReactiveFlags.Pending;
181 | }
182 | }
183 |
184 | link = link.nextDep!;
185 | } while (link !== undefined);
186 |
187 | return false;
188 | }
189 | ```
190 |
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackblitz/alien-signals/master/assets/logo.png
--------------------------------------------------------------------------------
/benchs/complex.mjs:
--------------------------------------------------------------------------------
1 | import { run, bench, boxplot } from 'mitata';
2 | import { computed, effect, signal } from '../esm/index.mjs';
3 |
4 | boxplot(() => {
5 | bench('complex: $w * $h', function* (state) {
6 | const w = state.get('w');
7 | const h = state.get('h');
8 | const src = signal({ w, h });
9 | for (let i = 0; i < w; i++) {
10 | let last = src;
11 | for (let j = 0; j < h; j++) {
12 | const prev = last;
13 | last = computed(() => ({ [`${i}-${j}`]: prev() }));
14 | }
15 | effect(() => last());
16 | }
17 |
18 | yield () => src({ upstream: src() });
19 | })
20 | .args('h', [1, 10, 100])
21 | .args('w', [1, 10, 100]);
22 | });
23 |
24 | run({ format: 'markdown' });
25 |
--------------------------------------------------------------------------------
/benchs/memoryUsage.mjs:
--------------------------------------------------------------------------------
1 | import { computed, effect, signal } from '../esm/index.mjs';
2 |
3 | globalThis.gc();
4 | let start = process.memoryUsage().heapUsed;
5 |
6 | const signals = Array.from({ length: 10000 }, () => signal(0));
7 |
8 | globalThis.gc();
9 | let end = process.memoryUsage().heapUsed;
10 |
11 | console.log(`signal: ${((end - start) / 1024).toFixed(2)} KB`);
12 |
13 | start = end;
14 |
15 | const computeds = Array.from({ length: 10000 }, (_, i) => computed(() => signals[i]() + 1));
16 |
17 | globalThis.gc();
18 | end = process.memoryUsage().heapUsed;
19 |
20 | console.log(`computed: ${((end - start) / 1024).toFixed(2)} KB`);
21 |
22 | start = end;
23 |
24 | Array.from({ length: 10000 }, (_, i) => effect(() => computeds[i]()));
25 |
26 | globalThis.gc();
27 | end = process.memoryUsage().heapUsed;
28 |
29 | console.log(`effect: ${((end - start) / 1024).toFixed(2)} KB`);
30 |
31 | start = end;
32 |
33 | const w = 100;
34 | const h = 100;
35 | const src = signal(1);
36 |
37 | for (let i = 0; i < w; i++) {
38 | let last = src;
39 | for (let j = 0; j < h; j++) {
40 | const prev = last;
41 | last = computed(() => prev() + 1);
42 | effect(() => last());
43 | }
44 | }
45 |
46 | src(src() + 1);
47 |
48 | globalThis.gc();
49 | end = process.memoryUsage().heapUsed;
50 |
51 | console.log(`tree: ${((end - start) / 1024).toFixed(2)} KB`);
52 |
--------------------------------------------------------------------------------
/benchs/propagate.mjs:
--------------------------------------------------------------------------------
1 | import { bench, boxplot, run } from 'mitata';
2 | import { computed, effect, signal } from '../esm/index.mjs';
3 |
4 | boxplot(() => {
5 | bench('propagate: $w * $h', function* (state) {
6 | const w = state.get('w');
7 | const h = state.get('h');
8 | const src = signal(1);
9 | for (let i = 0; i < w; i++) {
10 | let last = src;
11 | for (let j = 0; j < h; j++) {
12 | const prev = last;
13 | last = computed(() => prev() + 1);
14 | }
15 | effect(() => last());
16 | }
17 | yield () => src(src() + 1);
18 | })
19 | .args('h', [1, 10, 100])
20 | .args('w', [1, 10, 100]);
21 | });
22 |
23 | run({ format: 'markdown' });
24 |
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const ts = require('typescript');
3 | const config = ts.getParsedCommandLineOfConfigFile(
4 | path.join(__dirname, 'tsconfig.json'),
5 | undefined,
6 | {
7 | ...ts.sys,
8 | onUnRecoverableConfigFileDiagnostic: () => { },
9 | }
10 | );
11 |
12 | if (config === undefined) {
13 | console.error('Failed to parse tsconfig.json');
14 | process.exit(1);
15 | }
16 |
17 | const typesProgram = ts.createProgram({
18 | rootNames: config.fileNames,
19 | configFileParsingDiagnostics: config.errors,
20 | options: {
21 | ...config.options,
22 | outDir: 'types',
23 | declaration: true,
24 | emitDeclarationOnly: true,
25 | },
26 | });
27 | const cjsProgram = ts.createProgram({
28 | rootNames: config.fileNames,
29 | configFileParsingDiagnostics: config.errors,
30 | options: {
31 | ...config.options,
32 | outDir: 'cjs',
33 | removeComments: true,
34 | module: ts.ModuleKind.CommonJS,
35 | },
36 | });
37 | const esmProgram = ts.createProgram({
38 | rootNames: config.fileNames,
39 | configFileParsingDiagnostics: config.errors,
40 | options: {
41 | ...config.options,
42 | outDir: 'esm',
43 | removeComments: true,
44 | module: ts.ModuleKind.ESNext,
45 | },
46 | });
47 |
48 | typesProgram.emit(undefined, ts.sys.writeFile);
49 | cjsProgram.emit(undefined, (fileName, text) => {
50 | fileName = fileName.slice(0, -'.js'.length) + '.cjs';
51 | text = text.replace(/\.\/system\.js/g, './system.cjs');
52 | ts.sys.writeFile(fileName, text);
53 | });
54 | esmProgram.emit(undefined, (fileName, text) => {
55 | fileName = fileName.slice(0, -'.js'.length) + '.mjs';
56 | text = text.replace(/\.\/system\.js/g, './system.mjs');
57 | ts.sys.writeFile(fileName, text);
58 | });
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alien-signals",
3 | "version": "2.0.6",
4 | "license": "MIT",
5 | "description": "The lightest signal library.",
6 | "packageManager": "pnpm@9.12.0",
7 | "types": "./types/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "types": "./types/index.d.ts",
11 | "import": "./esm/index.mjs",
12 | "require": "./cjs/index.cjs"
13 | },
14 | "./cjs": {
15 | "types": "./types/index.d.ts",
16 | "import": "./cjs/index.cjs",
17 | "require": "./cjs/index.cjs"
18 | },
19 | "./esm": {
20 | "types": "./types/index.d.ts",
21 | "import": "./esm/index.mjs",
22 | "require": "./esm/index.mjs"
23 | },
24 | "./system": {
25 | "types": "./types/system.d.ts",
26 | "import": "./esm/system.mjs",
27 | "require": "./cjs/system.cjs"
28 | },
29 | "./cjs/system": {
30 | "types": "./types/system.d.ts",
31 | "import": "./cjs/system.cjs",
32 | "require": "./cjs/system.cjs"
33 | },
34 | "./esm/system": {
35 | "types": "./types/system.d.ts",
36 | "import": "./esm/system.mjs",
37 | "require": "./esm/system.mjs"
38 | }
39 | },
40 | "files": [
41 | "cjs/*.cjs",
42 | "esm/*.mjs",
43 | "types/*.d.ts"
44 | ],
45 | "repository": {
46 | "type": "git",
47 | "url": "git+https://github.com/johnsoncodehk/signals.git"
48 | },
49 | "scripts": {
50 | "prepublishOnly": "npm run check && npm run test",
51 | "check": "tsslint --project tsconfig.json",
52 | "build": "node ./build.js",
53 | "test": "npm run build && vitest run",
54 | "bench": "npm run build && node --jitless --expose-gc benchs/propagate.mjs",
55 | "memory": "npm run build && node --expose-gc benchs/memoryUsage.mjs"
56 | },
57 | "devDependencies": {
58 | "@tsslint/cli": "latest",
59 | "@tsslint/config": "latest",
60 | "mitata": "latest",
61 | "typescript": "latest",
62 | "vitest": "latest",
63 | "jest-extended": "latest"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | devDependencies:
11 | '@tsslint/cli':
12 | specifier: latest
13 | version: 2.0.1(typescript@5.9.2)
14 | '@tsslint/config':
15 | specifier: latest
16 | version: 2.0.1(typescript@5.9.2)
17 | jest-extended:
18 | specifier: latest
19 | version: 6.0.0(typescript@5.9.2)
20 | mitata:
21 | specifier: latest
22 | version: 1.0.34
23 | typescript:
24 | specifier: latest
25 | version: 5.9.2
26 | vitest:
27 | specifier: latest
28 | version: 3.2.4
29 |
30 | packages:
31 |
32 | '@clack/core@0.3.5':
33 | resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==}
34 |
35 | '@clack/prompts@0.8.2':
36 | resolution: {integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==}
37 |
38 | '@esbuild/aix-ppc64@0.25.9':
39 | resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
40 | engines: {node: '>=18'}
41 | cpu: [ppc64]
42 | os: [aix]
43 |
44 | '@esbuild/android-arm64@0.25.9':
45 | resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==}
46 | engines: {node: '>=18'}
47 | cpu: [arm64]
48 | os: [android]
49 |
50 | '@esbuild/android-arm@0.25.9':
51 | resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==}
52 | engines: {node: '>=18'}
53 | cpu: [arm]
54 | os: [android]
55 |
56 | '@esbuild/android-x64@0.25.9':
57 | resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==}
58 | engines: {node: '>=18'}
59 | cpu: [x64]
60 | os: [android]
61 |
62 | '@esbuild/darwin-arm64@0.25.9':
63 | resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==}
64 | engines: {node: '>=18'}
65 | cpu: [arm64]
66 | os: [darwin]
67 |
68 | '@esbuild/darwin-x64@0.25.9':
69 | resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==}
70 | engines: {node: '>=18'}
71 | cpu: [x64]
72 | os: [darwin]
73 |
74 | '@esbuild/freebsd-arm64@0.25.9':
75 | resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==}
76 | engines: {node: '>=18'}
77 | cpu: [arm64]
78 | os: [freebsd]
79 |
80 | '@esbuild/freebsd-x64@0.25.9':
81 | resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==}
82 | engines: {node: '>=18'}
83 | cpu: [x64]
84 | os: [freebsd]
85 |
86 | '@esbuild/linux-arm64@0.25.9':
87 | resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==}
88 | engines: {node: '>=18'}
89 | cpu: [arm64]
90 | os: [linux]
91 |
92 | '@esbuild/linux-arm@0.25.9':
93 | resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==}
94 | engines: {node: '>=18'}
95 | cpu: [arm]
96 | os: [linux]
97 |
98 | '@esbuild/linux-ia32@0.25.9':
99 | resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==}
100 | engines: {node: '>=18'}
101 | cpu: [ia32]
102 | os: [linux]
103 |
104 | '@esbuild/linux-loong64@0.25.9':
105 | resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==}
106 | engines: {node: '>=18'}
107 | cpu: [loong64]
108 | os: [linux]
109 |
110 | '@esbuild/linux-mips64el@0.25.9':
111 | resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==}
112 | engines: {node: '>=18'}
113 | cpu: [mips64el]
114 | os: [linux]
115 |
116 | '@esbuild/linux-ppc64@0.25.9':
117 | resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==}
118 | engines: {node: '>=18'}
119 | cpu: [ppc64]
120 | os: [linux]
121 |
122 | '@esbuild/linux-riscv64@0.25.9':
123 | resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==}
124 | engines: {node: '>=18'}
125 | cpu: [riscv64]
126 | os: [linux]
127 |
128 | '@esbuild/linux-s390x@0.25.9':
129 | resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==}
130 | engines: {node: '>=18'}
131 | cpu: [s390x]
132 | os: [linux]
133 |
134 | '@esbuild/linux-x64@0.25.9':
135 | resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==}
136 | engines: {node: '>=18'}
137 | cpu: [x64]
138 | os: [linux]
139 |
140 | '@esbuild/netbsd-arm64@0.25.9':
141 | resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==}
142 | engines: {node: '>=18'}
143 | cpu: [arm64]
144 | os: [netbsd]
145 |
146 | '@esbuild/netbsd-x64@0.25.9':
147 | resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==}
148 | engines: {node: '>=18'}
149 | cpu: [x64]
150 | os: [netbsd]
151 |
152 | '@esbuild/openbsd-arm64@0.25.9':
153 | resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==}
154 | engines: {node: '>=18'}
155 | cpu: [arm64]
156 | os: [openbsd]
157 |
158 | '@esbuild/openbsd-x64@0.25.9':
159 | resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==}
160 | engines: {node: '>=18'}
161 | cpu: [x64]
162 | os: [openbsd]
163 |
164 | '@esbuild/openharmony-arm64@0.25.9':
165 | resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==}
166 | engines: {node: '>=18'}
167 | cpu: [arm64]
168 | os: [openharmony]
169 |
170 | '@esbuild/sunos-x64@0.25.9':
171 | resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==}
172 | engines: {node: '>=18'}
173 | cpu: [x64]
174 | os: [sunos]
175 |
176 | '@esbuild/win32-arm64@0.25.9':
177 | resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==}
178 | engines: {node: '>=18'}
179 | cpu: [arm64]
180 | os: [win32]
181 |
182 | '@esbuild/win32-ia32@0.25.9':
183 | resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==}
184 | engines: {node: '>=18'}
185 | cpu: [ia32]
186 | os: [win32]
187 |
188 | '@esbuild/win32-x64@0.25.9':
189 | resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==}
190 | engines: {node: '>=18'}
191 | cpu: [x64]
192 | os: [win32]
193 |
194 | '@isaacs/balanced-match@4.0.1':
195 | resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
196 | engines: {node: 20 || >=22}
197 |
198 | '@isaacs/brace-expansion@5.0.0':
199 | resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
200 | engines: {node: 20 || >=22}
201 |
202 | '@isaacs/cliui@8.0.2':
203 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
204 | engines: {node: '>=12'}
205 |
206 | '@jest/schemas@29.6.3':
207 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
208 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
209 |
210 | '@jridgewell/sourcemap-codec@1.5.5':
211 | resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
212 |
213 | '@pkgjs/parseargs@0.11.0':
214 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
215 | engines: {node: '>=14'}
216 |
217 | '@rollup/rollup-android-arm-eabi@4.46.2':
218 | resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
219 | cpu: [arm]
220 | os: [android]
221 |
222 | '@rollup/rollup-android-arm64@4.46.2':
223 | resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==}
224 | cpu: [arm64]
225 | os: [android]
226 |
227 | '@rollup/rollup-darwin-arm64@4.46.2':
228 | resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==}
229 | cpu: [arm64]
230 | os: [darwin]
231 |
232 | '@rollup/rollup-darwin-x64@4.46.2':
233 | resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==}
234 | cpu: [x64]
235 | os: [darwin]
236 |
237 | '@rollup/rollup-freebsd-arm64@4.46.2':
238 | resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==}
239 | cpu: [arm64]
240 | os: [freebsd]
241 |
242 | '@rollup/rollup-freebsd-x64@4.46.2':
243 | resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==}
244 | cpu: [x64]
245 | os: [freebsd]
246 |
247 | '@rollup/rollup-linux-arm-gnueabihf@4.46.2':
248 | resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
249 | cpu: [arm]
250 | os: [linux]
251 |
252 | '@rollup/rollup-linux-arm-musleabihf@4.46.2':
253 | resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
254 | cpu: [arm]
255 | os: [linux]
256 |
257 | '@rollup/rollup-linux-arm64-gnu@4.46.2':
258 | resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
259 | cpu: [arm64]
260 | os: [linux]
261 |
262 | '@rollup/rollup-linux-arm64-musl@4.46.2':
263 | resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
264 | cpu: [arm64]
265 | os: [linux]
266 |
267 | '@rollup/rollup-linux-loongarch64-gnu@4.46.2':
268 | resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
269 | cpu: [loong64]
270 | os: [linux]
271 |
272 | '@rollup/rollup-linux-ppc64-gnu@4.46.2':
273 | resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
274 | cpu: [ppc64]
275 | os: [linux]
276 |
277 | '@rollup/rollup-linux-riscv64-gnu@4.46.2':
278 | resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
279 | cpu: [riscv64]
280 | os: [linux]
281 |
282 | '@rollup/rollup-linux-riscv64-musl@4.46.2':
283 | resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
284 | cpu: [riscv64]
285 | os: [linux]
286 |
287 | '@rollup/rollup-linux-s390x-gnu@4.46.2':
288 | resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
289 | cpu: [s390x]
290 | os: [linux]
291 |
292 | '@rollup/rollup-linux-x64-gnu@4.46.2':
293 | resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
294 | cpu: [x64]
295 | os: [linux]
296 |
297 | '@rollup/rollup-linux-x64-musl@4.46.2':
298 | resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
299 | cpu: [x64]
300 | os: [linux]
301 |
302 | '@rollup/rollup-win32-arm64-msvc@4.46.2':
303 | resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
304 | cpu: [arm64]
305 | os: [win32]
306 |
307 | '@rollup/rollup-win32-ia32-msvc@4.46.2':
308 | resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==}
309 | cpu: [ia32]
310 | os: [win32]
311 |
312 | '@rollup/rollup-win32-x64-msvc@4.46.2':
313 | resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==}
314 | cpu: [x64]
315 | os: [win32]
316 |
317 | '@sinclair/typebox@0.27.8':
318 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
319 |
320 | '@tsslint/cli@2.0.1':
321 | resolution: {integrity: sha512-7g+jVQrs9d3D9kMpT1TBlLBQhmQriDGekyywwvrBnmocA1KJvVskms7t3xHxkGupNrF5dH+TZaQVacwCKw93Sg==}
322 | hasBin: true
323 | peerDependencies:
324 | typescript: '*'
325 |
326 | '@tsslint/config@2.0.1':
327 | resolution: {integrity: sha512-al2vj06aenhqMs2/Vm3p2gVNAFCbpuw7C3E+DFWXVpKH3+g48cJaPymRI3Ma4mQqmvSMIPF5VFN/H1SSYzdwNw==}
328 |
329 | '@tsslint/core@2.0.1':
330 | resolution: {integrity: sha512-Freonkuz+cuIyMahmDgz3xLxfgKON79Y/K7LcKexJVqRnIYMIQcA0wxRKjk0eCNOj59PlzV8RYwYGvFaxDd8Qw==}
331 |
332 | '@tsslint/types@2.0.1':
333 | resolution: {integrity: sha512-0XllhTjC3eVmbVNPAIAGlmAsIOymKbi5IzAk5QnreBDBxBA9jCPGl1NB0ijtBMrUai6zWiRB3/hPvikeEGs3hQ==}
334 |
335 | '@types/chai@5.2.2':
336 | resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
337 |
338 | '@types/deep-eql@4.0.2':
339 | resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
340 |
341 | '@types/estree@1.0.8':
342 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
343 |
344 | '@vitest/expect@3.2.4':
345 | resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
346 |
347 | '@vitest/mocker@3.2.4':
348 | resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
349 | peerDependencies:
350 | msw: ^2.4.9
351 | vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
352 | peerDependenciesMeta:
353 | msw:
354 | optional: true
355 | vite:
356 | optional: true
357 |
358 | '@vitest/pretty-format@3.2.4':
359 | resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
360 |
361 | '@vitest/runner@3.2.4':
362 | resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
363 |
364 | '@vitest/snapshot@3.2.4':
365 | resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
366 |
367 | '@vitest/spy@3.2.4':
368 | resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
369 |
370 | '@vitest/utils@3.2.4':
371 | resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
372 |
373 | '@volar/language-core@2.4.23':
374 | resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
375 |
376 | '@volar/language-hub@0.0.1':
377 | resolution: {integrity: sha512-2eOUnlMKTyjtlXIVd+6pfAtcuVugxCOgpNgcLWmlPuncQTG5C1E5mTDL/PUMw7aEnLySUOtMTIp8lT3vk/7w6Q==}
378 |
379 | '@volar/source-map@2.4.23':
380 | resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==}
381 |
382 | '@volar/typescript@2.4.23':
383 | resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==}
384 |
385 | ansi-regex@5.0.1:
386 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
387 | engines: {node: '>=8'}
388 |
389 | ansi-regex@6.1.0:
390 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
391 | engines: {node: '>=12'}
392 |
393 | ansi-styles@4.3.0:
394 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
395 | engines: {node: '>=8'}
396 |
397 | ansi-styles@5.2.0:
398 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
399 | engines: {node: '>=10'}
400 |
401 | ansi-styles@6.2.1:
402 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
403 | engines: {node: '>=12'}
404 |
405 | assertion-error@2.0.1:
406 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
407 | engines: {node: '>=12'}
408 |
409 | balanced-match@1.0.2:
410 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
411 |
412 | brace-expansion@2.0.2:
413 | resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
414 |
415 | cac@6.7.14:
416 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
417 | engines: {node: '>=8'}
418 |
419 | chai@5.2.1:
420 | resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==}
421 | engines: {node: '>=18'}
422 |
423 | chalk@4.1.2:
424 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
425 | engines: {node: '>=10'}
426 |
427 | check-error@2.1.1:
428 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
429 | engines: {node: '>= 16'}
430 |
431 | color-convert@2.0.1:
432 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
433 | engines: {node: '>=7.0.0'}
434 |
435 | color-name@1.1.4:
436 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
437 |
438 | cross-spawn@7.0.6:
439 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
440 | engines: {node: '>= 8'}
441 |
442 | debug@4.4.1:
443 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
444 | engines: {node: '>=6.0'}
445 | peerDependencies:
446 | supports-color: '*'
447 | peerDependenciesMeta:
448 | supports-color:
449 | optional: true
450 |
451 | deep-eql@5.0.2:
452 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
453 | engines: {node: '>=6'}
454 |
455 | diff-sequences@29.6.3:
456 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
457 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
458 |
459 | eastasianwidth@0.2.0:
460 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
461 |
462 | emoji-regex@8.0.0:
463 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
464 |
465 | emoji-regex@9.2.2:
466 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
467 |
468 | es-module-lexer@1.7.0:
469 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
470 |
471 | esbuild@0.25.9:
472 | resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
473 | engines: {node: '>=18'}
474 | hasBin: true
475 |
476 | estree-walker@3.0.3:
477 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
478 |
479 | expect-type@1.2.2:
480 | resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
481 | engines: {node: '>=12.0.0'}
482 |
483 | fdir@6.5.0:
484 | resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
485 | engines: {node: '>=12.0.0'}
486 | peerDependencies:
487 | picomatch: ^3 || ^4
488 | peerDependenciesMeta:
489 | picomatch:
490 | optional: true
491 |
492 | foreground-child@3.3.1:
493 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
494 | engines: {node: '>=14'}
495 |
496 | fsevents@2.3.3:
497 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
498 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
499 | os: [darwin]
500 |
501 | glob@10.4.5:
502 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
503 | hasBin: true
504 |
505 | has-flag@4.0.0:
506 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
507 | engines: {node: '>=8'}
508 |
509 | is-fullwidth-code-point@3.0.0:
510 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
511 | engines: {node: '>=8'}
512 |
513 | isexe@2.0.0:
514 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
515 |
516 | jackspeak@3.4.3:
517 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
518 |
519 | jest-diff@29.7.0:
520 | resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
521 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
522 |
523 | jest-extended@6.0.0:
524 | resolution: {integrity: sha512-SM249N/q33YQ9XE8E06qZSnFuuV4GQFx7WrrmIj4wQUAP43jAo6budLT482jdBhf8ASwUiEEfJNjej0UusYs5A==}
525 | engines: {node: ^18.12.0 || ^20.9.0 || ^22.11.0 || >=23.0.0}
526 | peerDependencies:
527 | jest: '>=27.2.5'
528 | typescript: '>=5.0.0'
529 | peerDependenciesMeta:
530 | jest:
531 | optional: true
532 |
533 | jest-get-type@29.6.3:
534 | resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
535 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
536 |
537 | js-tokens@9.0.1:
538 | resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
539 |
540 | json5@2.2.3:
541 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
542 | engines: {node: '>=6'}
543 | hasBin: true
544 |
545 | loupe@3.2.0:
546 | resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==}
547 |
548 | lru-cache@10.4.3:
549 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
550 |
551 | magic-string@0.30.17:
552 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
553 |
554 | minimatch@10.0.3:
555 | resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
556 | engines: {node: 20 || >=22}
557 |
558 | minimatch@9.0.5:
559 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
560 | engines: {node: '>=16 || 14 >=14.17'}
561 |
562 | minipass@7.1.2:
563 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
564 | engines: {node: '>=16 || 14 >=14.17'}
565 |
566 | mitata@1.0.34:
567 | resolution: {integrity: sha512-Mc3zrtNBKIMeHSCQ0XqRLo1vbdIx1wvFV9c8NJAiyho6AjNfMY8bVhbS12bwciUdd1t4rj8099CH3N3NFahaUA==}
568 |
569 | ms@2.1.3:
570 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
571 |
572 | nanoid@3.3.11:
573 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
574 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
575 | hasBin: true
576 |
577 | package-json-from-dist@1.0.1:
578 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
579 |
580 | path-browserify@1.0.1:
581 | resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
582 |
583 | path-key@3.1.1:
584 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
585 | engines: {node: '>=8'}
586 |
587 | path-scurry@1.11.1:
588 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
589 | engines: {node: '>=16 || 14 >=14.18'}
590 |
591 | pathe@2.0.3:
592 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
593 |
594 | pathval@2.0.1:
595 | resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
596 | engines: {node: '>= 14.16'}
597 |
598 | picocolors@1.1.1:
599 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
600 |
601 | picomatch@4.0.3:
602 | resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
603 | engines: {node: '>=12'}
604 |
605 | postcss@8.5.6:
606 | resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
607 | engines: {node: ^10 || ^12 || >=14}
608 |
609 | pretty-format@29.7.0:
610 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
611 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
612 |
613 | react-is@18.3.1:
614 | resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
615 |
616 | rollup@4.46.2:
617 | resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
618 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
619 | hasBin: true
620 |
621 | shebang-command@2.0.0:
622 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
623 | engines: {node: '>=8'}
624 |
625 | shebang-regex@3.0.0:
626 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
627 | engines: {node: '>=8'}
628 |
629 | siginfo@2.0.0:
630 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
631 |
632 | signal-exit@4.1.0:
633 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
634 | engines: {node: '>=14'}
635 |
636 | sisteransi@1.0.5:
637 | resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
638 |
639 | source-map-js@1.2.1:
640 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
641 | engines: {node: '>=0.10.0'}
642 |
643 | stackback@0.0.2:
644 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
645 |
646 | std-env@3.9.0:
647 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
648 |
649 | string-width@4.2.3:
650 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
651 | engines: {node: '>=8'}
652 |
653 | string-width@5.1.2:
654 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
655 | engines: {node: '>=12'}
656 |
657 | strip-ansi@6.0.1:
658 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
659 | engines: {node: '>=8'}
660 |
661 | strip-ansi@7.1.0:
662 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
663 | engines: {node: '>=12'}
664 |
665 | strip-literal@3.0.0:
666 | resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
667 |
668 | supports-color@7.2.0:
669 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
670 | engines: {node: '>=8'}
671 |
672 | tinybench@2.9.0:
673 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
674 |
675 | tinyexec@0.3.2:
676 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
677 |
678 | tinyglobby@0.2.14:
679 | resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
680 | engines: {node: '>=12.0.0'}
681 |
682 | tinypool@1.1.1:
683 | resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
684 | engines: {node: ^18.0.0 || >=20.0.0}
685 |
686 | tinyrainbow@2.0.0:
687 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
688 | engines: {node: '>=14.0.0'}
689 |
690 | tinyspy@4.0.3:
691 | resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
692 | engines: {node: '>=14.0.0'}
693 |
694 | ts-api-utils@2.1.0:
695 | resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
696 | engines: {node: '>=18.12'}
697 | peerDependencies:
698 | typescript: '>=4.8.4'
699 |
700 | typescript@5.9.2:
701 | resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
702 | engines: {node: '>=14.17'}
703 | hasBin: true
704 |
705 | vite-node@3.2.4:
706 | resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
707 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
708 | hasBin: true
709 |
710 | vite@7.1.2:
711 | resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==}
712 | engines: {node: ^20.19.0 || >=22.12.0}
713 | hasBin: true
714 | peerDependencies:
715 | '@types/node': ^20.19.0 || >=22.12.0
716 | jiti: '>=1.21.0'
717 | less: ^4.0.0
718 | lightningcss: ^1.21.0
719 | sass: ^1.70.0
720 | sass-embedded: ^1.70.0
721 | stylus: '>=0.54.8'
722 | sugarss: ^5.0.0
723 | terser: ^5.16.0
724 | tsx: ^4.8.1
725 | yaml: ^2.4.2
726 | peerDependenciesMeta:
727 | '@types/node':
728 | optional: true
729 | jiti:
730 | optional: true
731 | less:
732 | optional: true
733 | lightningcss:
734 | optional: true
735 | sass:
736 | optional: true
737 | sass-embedded:
738 | optional: true
739 | stylus:
740 | optional: true
741 | sugarss:
742 | optional: true
743 | terser:
744 | optional: true
745 | tsx:
746 | optional: true
747 | yaml:
748 | optional: true
749 |
750 | vitest@3.2.4:
751 | resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
752 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
753 | hasBin: true
754 | peerDependencies:
755 | '@edge-runtime/vm': '*'
756 | '@types/debug': ^4.1.12
757 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
758 | '@vitest/browser': 3.2.4
759 | '@vitest/ui': 3.2.4
760 | happy-dom: '*'
761 | jsdom: '*'
762 | peerDependenciesMeta:
763 | '@edge-runtime/vm':
764 | optional: true
765 | '@types/debug':
766 | optional: true
767 | '@types/node':
768 | optional: true
769 | '@vitest/browser':
770 | optional: true
771 | '@vitest/ui':
772 | optional: true
773 | happy-dom:
774 | optional: true
775 | jsdom:
776 | optional: true
777 |
778 | vscode-uri@3.1.0:
779 | resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
780 |
781 | which@2.0.2:
782 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
783 | engines: {node: '>= 8'}
784 | hasBin: true
785 |
786 | why-is-node-running@2.3.0:
787 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
788 | engines: {node: '>=8'}
789 | hasBin: true
790 |
791 | wrap-ansi@7.0.0:
792 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
793 | engines: {node: '>=10'}
794 |
795 | wrap-ansi@8.1.0:
796 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
797 | engines: {node: '>=12'}
798 |
799 | snapshots:
800 |
801 | '@clack/core@0.3.5':
802 | dependencies:
803 | picocolors: 1.1.1
804 | sisteransi: 1.0.5
805 |
806 | '@clack/prompts@0.8.2':
807 | dependencies:
808 | '@clack/core': 0.3.5
809 | picocolors: 1.1.1
810 | sisteransi: 1.0.5
811 |
812 | '@esbuild/aix-ppc64@0.25.9':
813 | optional: true
814 |
815 | '@esbuild/android-arm64@0.25.9':
816 | optional: true
817 |
818 | '@esbuild/android-arm@0.25.9':
819 | optional: true
820 |
821 | '@esbuild/android-x64@0.25.9':
822 | optional: true
823 |
824 | '@esbuild/darwin-arm64@0.25.9':
825 | optional: true
826 |
827 | '@esbuild/darwin-x64@0.25.9':
828 | optional: true
829 |
830 | '@esbuild/freebsd-arm64@0.25.9':
831 | optional: true
832 |
833 | '@esbuild/freebsd-x64@0.25.9':
834 | optional: true
835 |
836 | '@esbuild/linux-arm64@0.25.9':
837 | optional: true
838 |
839 | '@esbuild/linux-arm@0.25.9':
840 | optional: true
841 |
842 | '@esbuild/linux-ia32@0.25.9':
843 | optional: true
844 |
845 | '@esbuild/linux-loong64@0.25.9':
846 | optional: true
847 |
848 | '@esbuild/linux-mips64el@0.25.9':
849 | optional: true
850 |
851 | '@esbuild/linux-ppc64@0.25.9':
852 | optional: true
853 |
854 | '@esbuild/linux-riscv64@0.25.9':
855 | optional: true
856 |
857 | '@esbuild/linux-s390x@0.25.9':
858 | optional: true
859 |
860 | '@esbuild/linux-x64@0.25.9':
861 | optional: true
862 |
863 | '@esbuild/netbsd-arm64@0.25.9':
864 | optional: true
865 |
866 | '@esbuild/netbsd-x64@0.25.9':
867 | optional: true
868 |
869 | '@esbuild/openbsd-arm64@0.25.9':
870 | optional: true
871 |
872 | '@esbuild/openbsd-x64@0.25.9':
873 | optional: true
874 |
875 | '@esbuild/openharmony-arm64@0.25.9':
876 | optional: true
877 |
878 | '@esbuild/sunos-x64@0.25.9':
879 | optional: true
880 |
881 | '@esbuild/win32-arm64@0.25.9':
882 | optional: true
883 |
884 | '@esbuild/win32-ia32@0.25.9':
885 | optional: true
886 |
887 | '@esbuild/win32-x64@0.25.9':
888 | optional: true
889 |
890 | '@isaacs/balanced-match@4.0.1': {}
891 |
892 | '@isaacs/brace-expansion@5.0.0':
893 | dependencies:
894 | '@isaacs/balanced-match': 4.0.1
895 |
896 | '@isaacs/cliui@8.0.2':
897 | dependencies:
898 | string-width: 5.1.2
899 | string-width-cjs: string-width@4.2.3
900 | strip-ansi: 7.1.0
901 | strip-ansi-cjs: strip-ansi@6.0.1
902 | wrap-ansi: 8.1.0
903 | wrap-ansi-cjs: wrap-ansi@7.0.0
904 |
905 | '@jest/schemas@29.6.3':
906 | dependencies:
907 | '@sinclair/typebox': 0.27.8
908 |
909 | '@jridgewell/sourcemap-codec@1.5.5': {}
910 |
911 | '@pkgjs/parseargs@0.11.0':
912 | optional: true
913 |
914 | '@rollup/rollup-android-arm-eabi@4.46.2':
915 | optional: true
916 |
917 | '@rollup/rollup-android-arm64@4.46.2':
918 | optional: true
919 |
920 | '@rollup/rollup-darwin-arm64@4.46.2':
921 | optional: true
922 |
923 | '@rollup/rollup-darwin-x64@4.46.2':
924 | optional: true
925 |
926 | '@rollup/rollup-freebsd-arm64@4.46.2':
927 | optional: true
928 |
929 | '@rollup/rollup-freebsd-x64@4.46.2':
930 | optional: true
931 |
932 | '@rollup/rollup-linux-arm-gnueabihf@4.46.2':
933 | optional: true
934 |
935 | '@rollup/rollup-linux-arm-musleabihf@4.46.2':
936 | optional: true
937 |
938 | '@rollup/rollup-linux-arm64-gnu@4.46.2':
939 | optional: true
940 |
941 | '@rollup/rollup-linux-arm64-musl@4.46.2':
942 | optional: true
943 |
944 | '@rollup/rollup-linux-loongarch64-gnu@4.46.2':
945 | optional: true
946 |
947 | '@rollup/rollup-linux-ppc64-gnu@4.46.2':
948 | optional: true
949 |
950 | '@rollup/rollup-linux-riscv64-gnu@4.46.2':
951 | optional: true
952 |
953 | '@rollup/rollup-linux-riscv64-musl@4.46.2':
954 | optional: true
955 |
956 | '@rollup/rollup-linux-s390x-gnu@4.46.2':
957 | optional: true
958 |
959 | '@rollup/rollup-linux-x64-gnu@4.46.2':
960 | optional: true
961 |
962 | '@rollup/rollup-linux-x64-musl@4.46.2':
963 | optional: true
964 |
965 | '@rollup/rollup-win32-arm64-msvc@4.46.2':
966 | optional: true
967 |
968 | '@rollup/rollup-win32-ia32-msvc@4.46.2':
969 | optional: true
970 |
971 | '@rollup/rollup-win32-x64-msvc@4.46.2':
972 | optional: true
973 |
974 | '@sinclair/typebox@0.27.8': {}
975 |
976 | '@tsslint/cli@2.0.1(typescript@5.9.2)':
977 | dependencies:
978 | '@clack/prompts': 0.8.2
979 | '@tsslint/config': 2.0.1(typescript@5.9.2)
980 | '@tsslint/core': 2.0.1
981 | '@volar/language-core': 2.4.23
982 | '@volar/language-hub': 0.0.1
983 | '@volar/typescript': 2.4.23
984 | glob: 10.4.5
985 | json5: 2.2.3
986 | typescript: 5.9.2
987 |
988 | '@tsslint/config@2.0.1(typescript@5.9.2)':
989 | dependencies:
990 | '@tsslint/types': 2.0.1
991 | ts-api-utils: 2.1.0(typescript@5.9.2)
992 | transitivePeerDependencies:
993 | - typescript
994 |
995 | '@tsslint/core@2.0.1':
996 | dependencies:
997 | '@tsslint/types': 2.0.1
998 | esbuild: 0.25.9
999 | minimatch: 10.0.3
1000 |
1001 | '@tsslint/types@2.0.1': {}
1002 |
1003 | '@types/chai@5.2.2':
1004 | dependencies:
1005 | '@types/deep-eql': 4.0.2
1006 |
1007 | '@types/deep-eql@4.0.2': {}
1008 |
1009 | '@types/estree@1.0.8': {}
1010 |
1011 | '@vitest/expect@3.2.4':
1012 | dependencies:
1013 | '@types/chai': 5.2.2
1014 | '@vitest/spy': 3.2.4
1015 | '@vitest/utils': 3.2.4
1016 | chai: 5.2.1
1017 | tinyrainbow: 2.0.0
1018 |
1019 | '@vitest/mocker@3.2.4(vite@7.1.2)':
1020 | dependencies:
1021 | '@vitest/spy': 3.2.4
1022 | estree-walker: 3.0.3
1023 | magic-string: 0.30.17
1024 | optionalDependencies:
1025 | vite: 7.1.2
1026 |
1027 | '@vitest/pretty-format@3.2.4':
1028 | dependencies:
1029 | tinyrainbow: 2.0.0
1030 |
1031 | '@vitest/runner@3.2.4':
1032 | dependencies:
1033 | '@vitest/utils': 3.2.4
1034 | pathe: 2.0.3
1035 | strip-literal: 3.0.0
1036 |
1037 | '@vitest/snapshot@3.2.4':
1038 | dependencies:
1039 | '@vitest/pretty-format': 3.2.4
1040 | magic-string: 0.30.17
1041 | pathe: 2.0.3
1042 |
1043 | '@vitest/spy@3.2.4':
1044 | dependencies:
1045 | tinyspy: 4.0.3
1046 |
1047 | '@vitest/utils@3.2.4':
1048 | dependencies:
1049 | '@vitest/pretty-format': 3.2.4
1050 | loupe: 3.2.0
1051 | tinyrainbow: 2.0.0
1052 |
1053 | '@volar/language-core@2.4.23':
1054 | dependencies:
1055 | '@volar/source-map': 2.4.23
1056 |
1057 | '@volar/language-hub@0.0.1': {}
1058 |
1059 | '@volar/source-map@2.4.23': {}
1060 |
1061 | '@volar/typescript@2.4.23':
1062 | dependencies:
1063 | '@volar/language-core': 2.4.23
1064 | path-browserify: 1.0.1
1065 | vscode-uri: 3.1.0
1066 |
1067 | ansi-regex@5.0.1: {}
1068 |
1069 | ansi-regex@6.1.0: {}
1070 |
1071 | ansi-styles@4.3.0:
1072 | dependencies:
1073 | color-convert: 2.0.1
1074 |
1075 | ansi-styles@5.2.0: {}
1076 |
1077 | ansi-styles@6.2.1: {}
1078 |
1079 | assertion-error@2.0.1: {}
1080 |
1081 | balanced-match@1.0.2: {}
1082 |
1083 | brace-expansion@2.0.2:
1084 | dependencies:
1085 | balanced-match: 1.0.2
1086 |
1087 | cac@6.7.14: {}
1088 |
1089 | chai@5.2.1:
1090 | dependencies:
1091 | assertion-error: 2.0.1
1092 | check-error: 2.1.1
1093 | deep-eql: 5.0.2
1094 | loupe: 3.2.0
1095 | pathval: 2.0.1
1096 |
1097 | chalk@4.1.2:
1098 | dependencies:
1099 | ansi-styles: 4.3.0
1100 | supports-color: 7.2.0
1101 |
1102 | check-error@2.1.1: {}
1103 |
1104 | color-convert@2.0.1:
1105 | dependencies:
1106 | color-name: 1.1.4
1107 |
1108 | color-name@1.1.4: {}
1109 |
1110 | cross-spawn@7.0.6:
1111 | dependencies:
1112 | path-key: 3.1.1
1113 | shebang-command: 2.0.0
1114 | which: 2.0.2
1115 |
1116 | debug@4.4.1:
1117 | dependencies:
1118 | ms: 2.1.3
1119 |
1120 | deep-eql@5.0.2: {}
1121 |
1122 | diff-sequences@29.6.3: {}
1123 |
1124 | eastasianwidth@0.2.0: {}
1125 |
1126 | emoji-regex@8.0.0: {}
1127 |
1128 | emoji-regex@9.2.2: {}
1129 |
1130 | es-module-lexer@1.7.0: {}
1131 |
1132 | esbuild@0.25.9:
1133 | optionalDependencies:
1134 | '@esbuild/aix-ppc64': 0.25.9
1135 | '@esbuild/android-arm': 0.25.9
1136 | '@esbuild/android-arm64': 0.25.9
1137 | '@esbuild/android-x64': 0.25.9
1138 | '@esbuild/darwin-arm64': 0.25.9
1139 | '@esbuild/darwin-x64': 0.25.9
1140 | '@esbuild/freebsd-arm64': 0.25.9
1141 | '@esbuild/freebsd-x64': 0.25.9
1142 | '@esbuild/linux-arm': 0.25.9
1143 | '@esbuild/linux-arm64': 0.25.9
1144 | '@esbuild/linux-ia32': 0.25.9
1145 | '@esbuild/linux-loong64': 0.25.9
1146 | '@esbuild/linux-mips64el': 0.25.9
1147 | '@esbuild/linux-ppc64': 0.25.9
1148 | '@esbuild/linux-riscv64': 0.25.9
1149 | '@esbuild/linux-s390x': 0.25.9
1150 | '@esbuild/linux-x64': 0.25.9
1151 | '@esbuild/netbsd-arm64': 0.25.9
1152 | '@esbuild/netbsd-x64': 0.25.9
1153 | '@esbuild/openbsd-arm64': 0.25.9
1154 | '@esbuild/openbsd-x64': 0.25.9
1155 | '@esbuild/openharmony-arm64': 0.25.9
1156 | '@esbuild/sunos-x64': 0.25.9
1157 | '@esbuild/win32-arm64': 0.25.9
1158 | '@esbuild/win32-ia32': 0.25.9
1159 | '@esbuild/win32-x64': 0.25.9
1160 |
1161 | estree-walker@3.0.3:
1162 | dependencies:
1163 | '@types/estree': 1.0.8
1164 |
1165 | expect-type@1.2.2: {}
1166 |
1167 | fdir@6.5.0(picomatch@4.0.3):
1168 | optionalDependencies:
1169 | picomatch: 4.0.3
1170 |
1171 | foreground-child@3.3.1:
1172 | dependencies:
1173 | cross-spawn: 7.0.6
1174 | signal-exit: 4.1.0
1175 |
1176 | fsevents@2.3.3:
1177 | optional: true
1178 |
1179 | glob@10.4.5:
1180 | dependencies:
1181 | foreground-child: 3.3.1
1182 | jackspeak: 3.4.3
1183 | minimatch: 9.0.5
1184 | minipass: 7.1.2
1185 | package-json-from-dist: 1.0.1
1186 | path-scurry: 1.11.1
1187 |
1188 | has-flag@4.0.0: {}
1189 |
1190 | is-fullwidth-code-point@3.0.0: {}
1191 |
1192 | isexe@2.0.0: {}
1193 |
1194 | jackspeak@3.4.3:
1195 | dependencies:
1196 | '@isaacs/cliui': 8.0.2
1197 | optionalDependencies:
1198 | '@pkgjs/parseargs': 0.11.0
1199 |
1200 | jest-diff@29.7.0:
1201 | dependencies:
1202 | chalk: 4.1.2
1203 | diff-sequences: 29.6.3
1204 | jest-get-type: 29.6.3
1205 | pretty-format: 29.7.0
1206 |
1207 | jest-extended@6.0.0(typescript@5.9.2):
1208 | dependencies:
1209 | jest-diff: 29.7.0
1210 | typescript: 5.9.2
1211 |
1212 | jest-get-type@29.6.3: {}
1213 |
1214 | js-tokens@9.0.1: {}
1215 |
1216 | json5@2.2.3: {}
1217 |
1218 | loupe@3.2.0: {}
1219 |
1220 | lru-cache@10.4.3: {}
1221 |
1222 | magic-string@0.30.17:
1223 | dependencies:
1224 | '@jridgewell/sourcemap-codec': 1.5.5
1225 |
1226 | minimatch@10.0.3:
1227 | dependencies:
1228 | '@isaacs/brace-expansion': 5.0.0
1229 |
1230 | minimatch@9.0.5:
1231 | dependencies:
1232 | brace-expansion: 2.0.2
1233 |
1234 | minipass@7.1.2: {}
1235 |
1236 | mitata@1.0.34: {}
1237 |
1238 | ms@2.1.3: {}
1239 |
1240 | nanoid@3.3.11: {}
1241 |
1242 | package-json-from-dist@1.0.1: {}
1243 |
1244 | path-browserify@1.0.1: {}
1245 |
1246 | path-key@3.1.1: {}
1247 |
1248 | path-scurry@1.11.1:
1249 | dependencies:
1250 | lru-cache: 10.4.3
1251 | minipass: 7.1.2
1252 |
1253 | pathe@2.0.3: {}
1254 |
1255 | pathval@2.0.1: {}
1256 |
1257 | picocolors@1.1.1: {}
1258 |
1259 | picomatch@4.0.3: {}
1260 |
1261 | postcss@8.5.6:
1262 | dependencies:
1263 | nanoid: 3.3.11
1264 | picocolors: 1.1.1
1265 | source-map-js: 1.2.1
1266 |
1267 | pretty-format@29.7.0:
1268 | dependencies:
1269 | '@jest/schemas': 29.6.3
1270 | ansi-styles: 5.2.0
1271 | react-is: 18.3.1
1272 |
1273 | react-is@18.3.1: {}
1274 |
1275 | rollup@4.46.2:
1276 | dependencies:
1277 | '@types/estree': 1.0.8
1278 | optionalDependencies:
1279 | '@rollup/rollup-android-arm-eabi': 4.46.2
1280 | '@rollup/rollup-android-arm64': 4.46.2
1281 | '@rollup/rollup-darwin-arm64': 4.46.2
1282 | '@rollup/rollup-darwin-x64': 4.46.2
1283 | '@rollup/rollup-freebsd-arm64': 4.46.2
1284 | '@rollup/rollup-freebsd-x64': 4.46.2
1285 | '@rollup/rollup-linux-arm-gnueabihf': 4.46.2
1286 | '@rollup/rollup-linux-arm-musleabihf': 4.46.2
1287 | '@rollup/rollup-linux-arm64-gnu': 4.46.2
1288 | '@rollup/rollup-linux-arm64-musl': 4.46.2
1289 | '@rollup/rollup-linux-loongarch64-gnu': 4.46.2
1290 | '@rollup/rollup-linux-ppc64-gnu': 4.46.2
1291 | '@rollup/rollup-linux-riscv64-gnu': 4.46.2
1292 | '@rollup/rollup-linux-riscv64-musl': 4.46.2
1293 | '@rollup/rollup-linux-s390x-gnu': 4.46.2
1294 | '@rollup/rollup-linux-x64-gnu': 4.46.2
1295 | '@rollup/rollup-linux-x64-musl': 4.46.2
1296 | '@rollup/rollup-win32-arm64-msvc': 4.46.2
1297 | '@rollup/rollup-win32-ia32-msvc': 4.46.2
1298 | '@rollup/rollup-win32-x64-msvc': 4.46.2
1299 | fsevents: 2.3.3
1300 |
1301 | shebang-command@2.0.0:
1302 | dependencies:
1303 | shebang-regex: 3.0.0
1304 |
1305 | shebang-regex@3.0.0: {}
1306 |
1307 | siginfo@2.0.0: {}
1308 |
1309 | signal-exit@4.1.0: {}
1310 |
1311 | sisteransi@1.0.5: {}
1312 |
1313 | source-map-js@1.2.1: {}
1314 |
1315 | stackback@0.0.2: {}
1316 |
1317 | std-env@3.9.0: {}
1318 |
1319 | string-width@4.2.3:
1320 | dependencies:
1321 | emoji-regex: 8.0.0
1322 | is-fullwidth-code-point: 3.0.0
1323 | strip-ansi: 6.0.1
1324 |
1325 | string-width@5.1.2:
1326 | dependencies:
1327 | eastasianwidth: 0.2.0
1328 | emoji-regex: 9.2.2
1329 | strip-ansi: 7.1.0
1330 |
1331 | strip-ansi@6.0.1:
1332 | dependencies:
1333 | ansi-regex: 5.0.1
1334 |
1335 | strip-ansi@7.1.0:
1336 | dependencies:
1337 | ansi-regex: 6.1.0
1338 |
1339 | strip-literal@3.0.0:
1340 | dependencies:
1341 | js-tokens: 9.0.1
1342 |
1343 | supports-color@7.2.0:
1344 | dependencies:
1345 | has-flag: 4.0.0
1346 |
1347 | tinybench@2.9.0: {}
1348 |
1349 | tinyexec@0.3.2: {}
1350 |
1351 | tinyglobby@0.2.14:
1352 | dependencies:
1353 | fdir: 6.5.0(picomatch@4.0.3)
1354 | picomatch: 4.0.3
1355 |
1356 | tinypool@1.1.1: {}
1357 |
1358 | tinyrainbow@2.0.0: {}
1359 |
1360 | tinyspy@4.0.3: {}
1361 |
1362 | ts-api-utils@2.1.0(typescript@5.9.2):
1363 | dependencies:
1364 | typescript: 5.9.2
1365 |
1366 | typescript@5.9.2: {}
1367 |
1368 | vite-node@3.2.4:
1369 | dependencies:
1370 | cac: 6.7.14
1371 | debug: 4.4.1
1372 | es-module-lexer: 1.7.0
1373 | pathe: 2.0.3
1374 | vite: 7.1.2
1375 | transitivePeerDependencies:
1376 | - '@types/node'
1377 | - jiti
1378 | - less
1379 | - lightningcss
1380 | - sass
1381 | - sass-embedded
1382 | - stylus
1383 | - sugarss
1384 | - supports-color
1385 | - terser
1386 | - tsx
1387 | - yaml
1388 |
1389 | vite@7.1.2:
1390 | dependencies:
1391 | esbuild: 0.25.9
1392 | fdir: 6.5.0(picomatch@4.0.3)
1393 | picomatch: 4.0.3
1394 | postcss: 8.5.6
1395 | rollup: 4.46.2
1396 | tinyglobby: 0.2.14
1397 | optionalDependencies:
1398 | fsevents: 2.3.3
1399 |
1400 | vitest@3.2.4:
1401 | dependencies:
1402 | '@types/chai': 5.2.2
1403 | '@vitest/expect': 3.2.4
1404 | '@vitest/mocker': 3.2.4(vite@7.1.2)
1405 | '@vitest/pretty-format': 3.2.4
1406 | '@vitest/runner': 3.2.4
1407 | '@vitest/snapshot': 3.2.4
1408 | '@vitest/spy': 3.2.4
1409 | '@vitest/utils': 3.2.4
1410 | chai: 5.2.1
1411 | debug: 4.4.1
1412 | expect-type: 1.2.2
1413 | magic-string: 0.30.17
1414 | pathe: 2.0.3
1415 | picomatch: 4.0.3
1416 | std-env: 3.9.0
1417 | tinybench: 2.9.0
1418 | tinyexec: 0.3.2
1419 | tinyglobby: 0.2.14
1420 | tinypool: 1.1.1
1421 | tinyrainbow: 2.0.0
1422 | vite: 7.1.2
1423 | vite-node: 3.2.4
1424 | why-is-node-running: 2.3.0
1425 | transitivePeerDependencies:
1426 | - jiti
1427 | - less
1428 | - lightningcss
1429 | - msw
1430 | - sass
1431 | - sass-embedded
1432 | - stylus
1433 | - sugarss
1434 | - supports-color
1435 | - terser
1436 | - tsx
1437 | - yaml
1438 |
1439 | vscode-uri@3.1.0: {}
1440 |
1441 | which@2.0.2:
1442 | dependencies:
1443 | isexe: 2.0.0
1444 |
1445 | why-is-node-running@2.3.0:
1446 | dependencies:
1447 | siginfo: 2.0.0
1448 | stackback: 0.0.2
1449 |
1450 | wrap-ansi@7.0.0:
1451 | dependencies:
1452 | ansi-styles: 4.3.0
1453 | string-width: 4.2.3
1454 | strip-ansi: 6.0.1
1455 |
1456 | wrap-ansi@8.1.0:
1457 | dependencies:
1458 | ansi-styles: 6.2.1
1459 | string-width: 5.1.2
1460 | strip-ansi: 7.1.0
1461 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './system.js';
2 |
3 | import { createReactiveSystem, type ReactiveNode, type ReactiveFlags } from './system.js';
4 |
5 | const enum EffectFlags {
6 | Queued = 1 << 6,
7 | }
8 |
9 | interface EffectScope extends ReactiveNode { }
10 |
11 | interface Effect extends ReactiveNode {
12 | fn(): void;
13 | }
14 |
15 | interface Computed extends ReactiveNode {
16 | value: T | undefined;
17 | getter: (previousValue?: T) => T;
18 | }
19 |
20 | interface Signal extends ReactiveNode {
21 | previousValue: T;
22 | value: T;
23 | }
24 |
25 | const pauseStack: (ReactiveNode | undefined)[] = [];
26 | const queuedEffects: (Effect | EffectScope | undefined)[] = [];
27 | const {
28 | link,
29 | unlink,
30 | propagate,
31 | checkDirty,
32 | endTracking,
33 | startTracking,
34 | shallowPropagate,
35 | } = createReactiveSystem({
36 | update(signal: Signal | Computed): boolean {
37 | if ('getter' in signal) {
38 | return updateComputed(signal);
39 | } else {
40 | return updateSignal(signal, signal.value);
41 | }
42 | },
43 | notify,
44 | unwatched(node: Signal | Computed | Effect | EffectScope) {
45 | if ('getter' in node) {
46 | let toRemove = node.deps;
47 | if (toRemove !== undefined) {
48 | node.flags = 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty;
49 | do {
50 | toRemove = unlink(toRemove, node);
51 | } while (toRemove !== undefined);
52 | }
53 | } else if (!('previousValue' in node)) {
54 | effectOper.call(node);
55 | }
56 | },
57 | });
58 |
59 | export let batchDepth = 0;
60 |
61 | let notifyIndex = 0;
62 | let queuedEffectsLength = 0;
63 | let activeSub: ReactiveNode | undefined;
64 | let activeScope: EffectScope | undefined;
65 |
66 | export function getCurrentSub(): ReactiveNode | undefined {
67 | return activeSub;
68 | }
69 |
70 | export function setCurrentSub(sub: ReactiveNode | undefined) {
71 | const prevSub = activeSub;
72 | activeSub = sub;
73 | return prevSub;
74 | }
75 |
76 | export function getCurrentScope(): EffectScope | undefined {
77 | return activeScope;
78 | }
79 |
80 | export function setCurrentScope(scope: EffectScope | undefined) {
81 | const prevScope = activeScope;
82 | activeScope = scope;
83 | return prevScope;
84 | }
85 |
86 | export function startBatch() {
87 | ++batchDepth;
88 | }
89 |
90 | export function endBatch() {
91 | if (!--batchDepth) {
92 | flush();
93 | }
94 | }
95 |
96 | /**
97 | * @deprecated Will be removed in the next major version. Use `const pausedSub = setCurrentSub(undefined)` instead for better performance.
98 | */
99 | export function pauseTracking() {
100 | pauseStack.push(setCurrentSub(undefined));
101 | }
102 |
103 | /**
104 | * @deprecated Will be removed in the next major version. Use `setCurrentSub(pausedSub)` instead for better performance.
105 | */
106 | export function resumeTracking() {
107 | setCurrentSub(pauseStack.pop());
108 | }
109 |
110 | export function signal(): {
111 | (): T | undefined;
112 | (value: T | undefined): void;
113 | };
114 | export function signal(initialValue: T): {
115 | (): T;
116 | (value: T): void;
117 | };
118 | export function signal(initialValue?: T): {
119 | (): T | undefined;
120 | (value: T | undefined): void;
121 | } {
122 | return signalOper.bind({
123 | previousValue: initialValue,
124 | value: initialValue,
125 | subs: undefined,
126 | subsTail: undefined,
127 | flags: 1 satisfies ReactiveFlags.Mutable,
128 | }) as () => T | undefined;
129 | }
130 |
131 | export function computed(getter: (previousValue?: T) => T): () => T {
132 | return computedOper.bind({
133 | value: undefined,
134 | subs: undefined,
135 | subsTail: undefined,
136 | deps: undefined,
137 | depsTail: undefined,
138 | flags: 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty,
139 | getter: getter as (previousValue?: unknown) => unknown,
140 | }) as () => T;
141 | }
142 |
143 | export function effect(fn: () => void): () => void {
144 | const e: Effect = {
145 | fn,
146 | subs: undefined,
147 | subsTail: undefined,
148 | deps: undefined,
149 | depsTail: undefined,
150 | flags: 2 satisfies ReactiveFlags.Watching,
151 | };
152 | if (activeSub !== undefined) {
153 | link(e, activeSub);
154 | } else if (activeScope !== undefined) {
155 | link(e, activeScope);
156 | }
157 | const prev = setCurrentSub(e);
158 | try {
159 | e.fn();
160 | } finally {
161 | setCurrentSub(prev);
162 | }
163 | return effectOper.bind(e);
164 | }
165 |
166 | export function effectScope(fn: () => void): () => void {
167 | const e: EffectScope = {
168 | deps: undefined,
169 | depsTail: undefined,
170 | subs: undefined,
171 | subsTail: undefined,
172 | flags: 0 satisfies ReactiveFlags.None,
173 | };
174 | if (activeScope !== undefined) {
175 | link(e, activeScope);
176 | }
177 | const prevSub = setCurrentSub(undefined);
178 | const prevScope = setCurrentScope(e);
179 | try {
180 | fn();
181 | } finally {
182 | setCurrentScope(prevScope);
183 | setCurrentSub(prevSub);
184 | }
185 | return effectOper.bind(e);
186 | }
187 |
188 | function updateComputed(c: Computed): boolean {
189 | const prevSub = setCurrentSub(c);
190 | startTracking(c);
191 | try {
192 | const oldValue = c.value;
193 | return oldValue !== (c.value = c.getter(oldValue));
194 | } finally {
195 | setCurrentSub(prevSub);
196 | endTracking(c);
197 | }
198 | }
199 |
200 | function updateSignal(s: Signal, value: any): boolean {
201 | s.flags = 1 satisfies ReactiveFlags.Mutable;
202 | return s.previousValue !== (s.previousValue = value);
203 | }
204 |
205 | function notify(e: Effect | EffectScope) {
206 | const flags = e.flags;
207 | if (!(flags & EffectFlags.Queued)) {
208 | e.flags = flags | EffectFlags.Queued;
209 | const subs = e.subs;
210 | if (subs !== undefined) {
211 | notify(subs.sub as Effect | EffectScope);
212 | } else {
213 | queuedEffects[queuedEffectsLength++] = e;
214 | }
215 | }
216 | }
217 |
218 | function run(e: Effect | EffectScope, flags: ReactiveFlags): void {
219 | if (
220 | flags & 16 satisfies ReactiveFlags.Dirty
221 | || (flags & 32 satisfies ReactiveFlags.Pending && checkDirty(e.deps!, e))
222 | ) {
223 | const prev = setCurrentSub(e);
224 | startTracking(e);
225 | try {
226 | (e as Effect).fn();
227 | } finally {
228 | setCurrentSub(prev);
229 | endTracking(e);
230 | }
231 | return;
232 | } else if (flags & 32 satisfies ReactiveFlags.Pending) {
233 | e.flags = flags & ~(32 satisfies ReactiveFlags.Pending);
234 | }
235 | let link = e.deps;
236 | while (link !== undefined) {
237 | const dep = link.dep;
238 | const depFlags = dep.flags;
239 | if (depFlags & EffectFlags.Queued) {
240 | run(dep, dep.flags = depFlags & ~EffectFlags.Queued);
241 | }
242 | link = link.nextDep;
243 | }
244 | }
245 |
246 | function flush(): void {
247 | while (notifyIndex < queuedEffectsLength) {
248 | const effect = queuedEffects[notifyIndex]!;
249 | queuedEffects[notifyIndex++] = undefined;
250 | run(effect, effect.flags &= ~EffectFlags.Queued);
251 | }
252 | notifyIndex = 0;
253 | queuedEffectsLength = 0;
254 | }
255 |
256 | function computedOper(this: Computed): T {
257 | const flags = this.flags;
258 | if (
259 | flags & 16 satisfies ReactiveFlags.Dirty
260 | || (flags & 32 satisfies ReactiveFlags.Pending && checkDirty(this.deps!, this))
261 | ) {
262 | if (updateComputed(this)) {
263 | const subs = this.subs;
264 | if (subs !== undefined) {
265 | shallowPropagate(subs);
266 | }
267 | }
268 | } else if (flags & 32 satisfies ReactiveFlags.Pending) {
269 | this.flags = flags & ~(32 satisfies ReactiveFlags.Pending);
270 | }
271 | if (activeSub !== undefined) {
272 | link(this, activeSub);
273 | } else if (activeScope !== undefined) {
274 | link(this, activeScope);
275 | }
276 | return this.value!;
277 | }
278 |
279 | function signalOper(this: Signal, ...value: [T]): T | void {
280 | if (value.length) {
281 | if (this.value !== (this.value = value[0])) {
282 | this.flags = 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty;
283 | const subs = this.subs;
284 | if (subs !== undefined) {
285 | propagate(subs);
286 | if (!batchDepth) {
287 | flush();
288 | }
289 | }
290 | }
291 | } else {
292 | const value = this.value;
293 | if (this.flags & 16 satisfies ReactiveFlags.Dirty) {
294 | if (updateSignal(this, value)) {
295 | const subs = this.subs;
296 | if (subs !== undefined) {
297 | shallowPropagate(subs);
298 | }
299 | }
300 | }
301 | if (activeSub !== undefined) {
302 | link(this, activeSub);
303 | }
304 | return value;
305 | }
306 | }
307 |
308 | function effectOper(this: Effect | EffectScope): void {
309 | let dep = this.deps;
310 | while (dep !== undefined) {
311 | dep = unlink(dep, this);
312 | }
313 | const sub = this.subs;
314 | if (sub !== undefined) {
315 | unlink(sub);
316 | }
317 | this.flags = 0 satisfies ReactiveFlags.None;
318 | }
319 |
--------------------------------------------------------------------------------
/src/system.ts:
--------------------------------------------------------------------------------
1 | export interface ReactiveNode {
2 | deps?: Link;
3 | depsTail?: Link;
4 | subs?: Link;
5 | subsTail?: Link;
6 | flags: ReactiveFlags;
7 | }
8 |
9 | export interface Link {
10 | version: number;
11 | dep: ReactiveNode;
12 | sub: ReactiveNode;
13 | prevSub: Link | undefined;
14 | nextSub: Link | undefined;
15 | prevDep: Link | undefined;
16 | nextDep: Link | undefined;
17 | }
18 |
19 | interface Stack {
20 | value: T;
21 | prev: Stack | undefined;
22 | }
23 |
24 | export enum ReactiveFlags {
25 | None = 0,
26 | Mutable = 1 << 0,
27 | Watching = 1 << 1,
28 | RecursedCheck = 1 << 2,
29 | Recursed = 1 << 3,
30 | Dirty = 1 << 4,
31 | Pending = 1 << 5,
32 | }
33 |
34 | export function createReactiveSystem({
35 | update,
36 | notify,
37 | unwatched,
38 | }: {
39 | update(sub: ReactiveNode): boolean;
40 | notify(sub: ReactiveNode): void;
41 | unwatched(sub: ReactiveNode): void;
42 | }) {
43 | let currentVersion = 0;
44 | return {
45 | link,
46 | unlink,
47 | propagate,
48 | checkDirty,
49 | endTracking,
50 | startTracking,
51 | shallowPropagate,
52 | };
53 |
54 | function link(dep: ReactiveNode, sub: ReactiveNode): void {
55 | const prevDep = sub.depsTail;
56 | if (prevDep !== undefined && prevDep.dep === dep) {
57 | return;
58 | }
59 | const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
60 | if (nextDep !== undefined && nextDep.dep === dep) {
61 | nextDep.version = currentVersion;
62 | sub.depsTail = nextDep;
63 | return;
64 | }
65 | const prevSub = dep.subsTail;
66 | if (prevSub !== undefined && prevSub.version === currentVersion && prevSub.sub === sub) {
67 | return;
68 | }
69 | const newLink
70 | = sub.depsTail
71 | = dep.subsTail
72 | = {
73 | version: currentVersion,
74 | dep,
75 | sub,
76 | prevDep,
77 | nextDep,
78 | prevSub,
79 | nextSub: undefined,
80 | };
81 | if (nextDep !== undefined) {
82 | nextDep.prevDep = newLink;
83 | }
84 | if (prevDep !== undefined) {
85 | prevDep.nextDep = newLink;
86 | } else {
87 | sub.deps = newLink;
88 | }
89 | if (prevSub !== undefined) {
90 | prevSub.nextSub = newLink;
91 | } else {
92 | dep.subs = newLink;
93 | }
94 | }
95 |
96 | function unlink(link: Link, sub = link.sub): Link | undefined {
97 | const dep = link.dep;
98 | const prevDep = link.prevDep;
99 | const nextDep = link.nextDep;
100 | const nextSub = link.nextSub;
101 | const prevSub = link.prevSub;
102 | if (nextDep !== undefined) {
103 | nextDep.prevDep = prevDep;
104 | } else {
105 | sub.depsTail = prevDep;
106 | }
107 | if (prevDep !== undefined) {
108 | prevDep.nextDep = nextDep;
109 | } else {
110 | sub.deps = nextDep;
111 | }
112 | if (nextSub !== undefined) {
113 | nextSub.prevSub = prevSub;
114 | } else {
115 | dep.subsTail = prevSub;
116 | }
117 | if (prevSub !== undefined) {
118 | prevSub.nextSub = nextSub;
119 | } else if ((dep.subs = nextSub) === undefined) {
120 | unwatched(dep);
121 | }
122 | return nextDep;
123 | }
124 |
125 | function propagate(link: Link): void {
126 | let next = link.nextSub;
127 | let stack: Stack | undefined;
128 |
129 | top: do {
130 | const sub = link.sub;
131 | let flags = sub.flags;
132 |
133 | if (!(flags & 60 as ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending)) {
134 | sub.flags = flags | 32 satisfies ReactiveFlags.Pending;
135 | } else if (!(flags & 12 as ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed)) {
136 | flags = 0 satisfies ReactiveFlags.None;
137 | } else if (!(flags & 4 satisfies ReactiveFlags.RecursedCheck)) {
138 | sub.flags = (flags & ~(8 satisfies ReactiveFlags.Recursed)) | 32 satisfies ReactiveFlags.Pending;
139 | } else if (!(flags & 48 as ReactiveFlags.Dirty | ReactiveFlags.Pending) && isValidLink(link, sub)) {
140 | sub.flags = flags | 40 as ReactiveFlags.Recursed | ReactiveFlags.Pending;
141 | flags &= 1 satisfies ReactiveFlags.Mutable;
142 | } else {
143 | flags = 0 satisfies ReactiveFlags.None;
144 | }
145 |
146 | if (flags & 2 satisfies ReactiveFlags.Watching) {
147 | notify(sub);
148 | }
149 |
150 | if (flags & 1 satisfies ReactiveFlags.Mutable) {
151 | const subSubs = sub.subs;
152 | if (subSubs !== undefined) {
153 | const nextSub = (link = subSubs).nextSub;
154 | if (nextSub !== undefined) {
155 | stack = { value: next, prev: stack };
156 | next = nextSub;
157 | }
158 | continue;
159 | }
160 | }
161 |
162 | if ((link = next!) !== undefined) {
163 | next = link.nextSub;
164 | continue;
165 | }
166 |
167 | while (stack !== undefined) {
168 | link = stack.value!;
169 | stack = stack.prev;
170 | if (link !== undefined) {
171 | next = link.nextSub;
172 | continue top;
173 | }
174 | }
175 |
176 | break;
177 | } while (true);
178 | }
179 |
180 | function startTracking(sub: ReactiveNode): void {
181 | ++currentVersion;
182 | sub.depsTail = undefined;
183 | sub.flags = (sub.flags & ~(56 as ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending)) | 4 satisfies ReactiveFlags.RecursedCheck;
184 | }
185 |
186 | function endTracking(sub: ReactiveNode): void {
187 | const depsTail = sub.depsTail;
188 | let toRemove = depsTail !== undefined ? depsTail.nextDep : sub.deps;
189 | while (toRemove !== undefined) {
190 | toRemove = unlink(toRemove, sub);
191 | }
192 | sub.flags &= ~(4 satisfies ReactiveFlags.RecursedCheck);
193 | }
194 |
195 | function checkDirty(link: Link, sub: ReactiveNode): boolean {
196 | let stack: Stack | undefined;
197 | let checkDepth = 0;
198 |
199 | top: do {
200 | const dep = link.dep;
201 | const flags = dep.flags;
202 | let dirty = false;
203 |
204 | if (sub.flags & 16 satisfies ReactiveFlags.Dirty) {
205 | dirty = true;
206 | } else if ((flags & 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty) === 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty) {
207 | if (update(dep)) {
208 | const subs = dep.subs!;
209 | if (subs.nextSub !== undefined) {
210 | shallowPropagate(subs);
211 | }
212 | dirty = true;
213 | }
214 | } else if ((flags & 33 as ReactiveFlags.Mutable | ReactiveFlags.Pending) === 33 as ReactiveFlags.Mutable | ReactiveFlags.Pending) {
215 | if (link.nextSub !== undefined || link.prevSub !== undefined) {
216 | stack = { value: link, prev: stack };
217 | }
218 | link = dep.deps!;
219 | sub = dep;
220 | ++checkDepth;
221 | continue;
222 | }
223 |
224 | if (!dirty) {
225 | const nextDep = link.nextDep;
226 | if (nextDep !== undefined) {
227 | link = nextDep;
228 | continue;
229 | }
230 | }
231 |
232 | while (checkDepth--) {
233 | const firstSub = sub.subs!;
234 | const hasMultipleSubs = firstSub.nextSub !== undefined;
235 | if (hasMultipleSubs) {
236 | link = stack!.value;
237 | stack = stack!.prev;
238 | } else {
239 | link = firstSub;
240 | }
241 | if (dirty) {
242 | if (update(sub)) {
243 | if (hasMultipleSubs) {
244 | shallowPropagate(firstSub);
245 | }
246 | sub = link.sub;
247 | continue;
248 | }
249 | } else {
250 | sub.flags &= ~(32 satisfies ReactiveFlags.Pending);
251 | }
252 | sub = link.sub;
253 | if (link.nextDep !== undefined) {
254 | link = link.nextDep;
255 | continue top;
256 | }
257 | dirty = false;
258 | }
259 |
260 | return dirty;
261 | } while (true);
262 | }
263 |
264 | function shallowPropagate(link: Link): void {
265 | do {
266 | const sub = link.sub;
267 | const flags = sub.flags;
268 | if ((flags & 48 as ReactiveFlags.Pending | ReactiveFlags.Dirty) === 32 satisfies ReactiveFlags.Pending) {
269 | sub.flags = flags | 16 satisfies ReactiveFlags.Dirty;
270 | if (flags & 2 satisfies ReactiveFlags.Watching) {
271 | notify(sub);
272 | }
273 | }
274 | } while ((link = link.nextSub!) !== undefined);
275 | }
276 |
277 | function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
278 | const depsTail = sub.depsTail;
279 | if (depsTail !== undefined) {
280 | let link = sub.deps!;
281 | do {
282 | if (link === checkLink) {
283 | return true;
284 | }
285 | if (link === depsTail) {
286 | break;
287 | }
288 | } while ((link = link.nextDep!) !== undefined);
289 | }
290 | return false;
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/tests/build.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'vitest';
2 | import { test } from 'vitest';
3 |
4 | declare function require(module: string): any;
5 |
6 | test('build: cjs', () => {
7 | const index = require('../cjs/index.cjs');
8 | const system = require('../cjs/system.cjs');
9 |
10 | expect(typeof index.createReactiveSystem).toBe('function');
11 | expect(typeof system.createReactiveSystem).toBe('function');
12 | });
13 |
14 | test('build: esm', async () => {
15 | const index = await import('../esm/index.mjs');
16 | const system = await import('../esm/system.mjs');
17 |
18 | expect(typeof index.createReactiveSystem).toBe('function');
19 | expect(typeof system.createReactiveSystem).toBe('function');
20 | });
21 |
--------------------------------------------------------------------------------
/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { computed, signal } from '../src';
3 |
4 | test('should correctly propagate changes through computed signals', () => {
5 | const src = signal(0);
6 | const c1 = computed(() => src() % 2);
7 | const c2 = computed(() => c1());
8 | const c3 = computed(() => c2());
9 |
10 | c3();
11 | src(1); // c1 -> dirty, c2 -> toCheckDirty, c3 -> toCheckDirty
12 | c2(); // c1 -> none, c2 -> none
13 | src(3); // c1 -> dirty, c2 -> toCheckDirty
14 |
15 | expect(c3()).toBe(1);
16 | });
17 |
18 | test('should propagate updated source value through chained computations', () => {
19 | const src = signal(0);
20 | const a = computed(() => src());
21 | const b = computed(() => a() % 2);
22 | const c = computed(() => src());
23 | const d = computed(() => b() + c());
24 |
25 | expect(d()).toBe(0);
26 | src(2);
27 | expect(d()).toBe(2);
28 | });
29 |
30 | test('should handle flags are indirectly updated during checkDirty', () => {
31 | const a = signal(false);
32 | const b = computed(() => a());
33 | const c = computed(() => {
34 | b();
35 | return 0;
36 | });
37 | const d = computed(() => {
38 | c();
39 | return b();
40 | });
41 |
42 | expect(d()).toBe(false);
43 | a(true);
44 | expect(d()).toBe(true);
45 | });
46 |
47 | test('should not update if the signal value is reverted', () => {
48 | let times = 0;
49 |
50 | const src = signal(0);
51 | const c1 = computed(() => {
52 | times++;
53 | return src();
54 | });
55 | c1();
56 | expect(times).toBe(1);
57 | src(1);
58 | src(0);
59 | c1();
60 | expect(times).toBe(1);
61 | });
62 |
--------------------------------------------------------------------------------
/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { computed, effect, effectScope, endBatch, setCurrentSub, signal, startBatch } from '../src';
3 |
4 | test('should clear subscriptions when untracked by all subscribers', () => {
5 | let bRunTimes = 0;
6 |
7 | const a = signal(1);
8 | const b = computed(() => {
9 | bRunTimes++;
10 | return a() * 2;
11 | });
12 | const stopEffect = effect(() => {
13 | b();
14 | });
15 |
16 | expect(bRunTimes).toBe(1);
17 | a(2);
18 | expect(bRunTimes).toBe(2);
19 | stopEffect();
20 | a(3);
21 | expect(bRunTimes).toBe(2);
22 | });
23 |
24 | test('should not run untracked inner effect', () => {
25 | const a = signal(3);
26 | const b = computed(() => a() > 0);
27 |
28 | effect(() => {
29 | if (b()) {
30 | effect(() => {
31 | if (a() == 0) {
32 | throw new Error("bad");
33 | }
34 | });
35 | }
36 | });
37 |
38 | a(2);
39 | a(1);
40 | a(0);
41 | });
42 |
43 | test('should run outer effect first', () => {
44 | const a = signal(1);
45 | const b = signal(1);
46 |
47 | effect(() => {
48 | if (a()) {
49 | effect(() => {
50 | b();
51 | if (a() == 0) {
52 | throw new Error("bad");
53 | }
54 | });
55 | } else {
56 | }
57 | });
58 |
59 | startBatch();
60 | b(0);
61 | a(0);
62 | endBatch();
63 | });
64 |
65 | test('should not trigger inner effect when resolve maybe dirty', () => {
66 | const a = signal(0);
67 | const b = computed(() => a() % 2);
68 |
69 | let innerTriggerTimes = 0;
70 |
71 | effect(() => {
72 | effect(() => {
73 | b();
74 | innerTriggerTimes++;
75 | if (innerTriggerTimes >= 2) {
76 | throw new Error("bad");
77 | }
78 | });
79 | });
80 |
81 | a(2);
82 | });
83 |
84 | test('should trigger inner effects in sequence', () => {
85 | const a = signal(0);
86 | const b = signal(0);
87 | const c = computed(() => a() - b());
88 | const order: string[] = [];
89 |
90 | effect(() => {
91 | c();
92 |
93 | effect(() => {
94 | order.push('first inner');
95 | a();
96 | });
97 |
98 | effect(() => {
99 | order.push('last inner');
100 | a();
101 | b();
102 | });
103 | });
104 |
105 | order.length = 0;
106 |
107 | startBatch();
108 | b(1);
109 | a(1);
110 | endBatch();
111 |
112 | expect(order).toEqual(['first inner', 'last inner']);
113 | });
114 |
115 | test('should trigger inner effects in sequence in effect scope', () => {
116 | const a = signal(0);
117 | const b = signal(0);
118 | const order: string[] = [];
119 |
120 | effectScope(() => {
121 |
122 | effect(() => {
123 | order.push('first inner');
124 | a();
125 | });
126 |
127 | effect(() => {
128 | order.push('last inner');
129 | a();
130 | b();
131 | });
132 | });
133 |
134 | order.length = 0;
135 |
136 | startBatch();
137 | b(1);
138 | a(1);
139 | endBatch();
140 |
141 | expect(order).toEqual(['first inner', 'last inner']);
142 | });
143 |
144 | test('should custom effect support batch', () => {
145 | function batchEffect(fn: () => void) {
146 | return effect(() => {
147 | startBatch();
148 | try {
149 | return fn();
150 | } finally {
151 | endBatch();
152 | }
153 | });
154 | }
155 |
156 | const logs: string[] = [];
157 | const a = signal(0);
158 | const b = signal(0);
159 |
160 | const aa = computed(() => {
161 | logs.push('aa-0');
162 | if (!a()) {
163 | b(1);
164 | }
165 | logs.push('aa-1');
166 | });
167 |
168 | const bb = computed(() => {
169 | logs.push('bb');
170 | return b();
171 | });
172 |
173 | batchEffect(() => {
174 | bb();
175 | });
176 | batchEffect(() => {
177 | aa();
178 | });
179 |
180 | expect(logs).toEqual(['bb', 'aa-0', 'aa-1', 'bb']);
181 | });
182 |
183 | test('should duplicate subscribers do not affect the notify order', () => {
184 | const src1 = signal(0);
185 | const src2 = signal(0);
186 | const order: string[] = [];
187 |
188 | effect(() => {
189 | order.push('a');
190 | const currentSub = setCurrentSub(undefined);
191 | const isOne = src2() === 1;
192 | setCurrentSub(currentSub);
193 | if (isOne) {
194 | src1();
195 | }
196 | src2();
197 | src1();
198 | });
199 | effect(() => {
200 | order.push('b');
201 | src1();
202 | });
203 | src2(1); // src1.subs: a -> b -> a
204 |
205 | order.length = 0;
206 | src1(src1() + 1);
207 |
208 | expect(order).toEqual(['a', 'b']);
209 | });
210 |
211 | test('should handle side effect with inner effects', () => {
212 | const a = signal(0);
213 | const b = signal(0);
214 | const order: string[] = [];
215 |
216 | effect(() => {
217 | effect(() => {
218 | a();
219 | order.push('a');
220 | });
221 | effect(() => {
222 | b();
223 | order.push('b');
224 | });
225 | expect(order).toEqual(['a', 'b']);
226 |
227 | order.length = 0;
228 | b(1);
229 | a(1);
230 | expect(order).toEqual(['b', 'a']);
231 | });
232 | });
233 |
234 | test('should handle flags are indirectly updated during checkDirty', () => {
235 | const a = signal(false);
236 | const b = computed(() => a());
237 | const c = computed(() => {
238 | b();
239 | return 0;
240 | });
241 | const d = computed(() => {
242 | c();
243 | return b();
244 | });
245 |
246 | let triggers = 0;
247 |
248 | effect(() => {
249 | d();
250 | triggers++;
251 | });
252 | expect(triggers).toBe(1);
253 | a(true);
254 | expect(triggers).toBe(2);
255 | });
256 |
--------------------------------------------------------------------------------
/tests/effectScope.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { effect, effectScope, signal } from '../src';
3 |
4 | test('should not trigger after stop', () => {
5 | const count = signal(1);
6 |
7 | let triggers = 0;
8 | let effect1;
9 |
10 | const stopScope = effectScope(() => {
11 | effect1 = effect(() => {
12 | triggers++;
13 | count();
14 | });
15 | expect(triggers).toBe(1);
16 |
17 | count(2);
18 | expect(triggers).toBe(2);
19 | });
20 |
21 | count(3);
22 | expect(triggers).toBe(3);
23 | stopScope();
24 | count(4);
25 | expect(triggers).toBe(3);
26 | });
27 |
28 | test('should dispose inner effects if created in an effect', () => {
29 | const source = signal(1);
30 |
31 | let triggers = 0;
32 |
33 | effect(() => {
34 | const dispose = effectScope(() => {
35 | effect(() => {
36 | source();
37 | triggers++;
38 | });
39 | });
40 | expect(triggers).toBe(1);
41 |
42 | source(2);
43 | expect(triggers).toBe(2);
44 | dispose();
45 | source(3);
46 | expect(triggers).toBe(2);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/issue_48.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from 'vitest';
2 | import { computed, effect, setCurrentSub, signal } from '../src';
3 |
4 | test('#48', () => {
5 | const source = signal(0);
6 | let disposeInner: () => void;
7 |
8 | reaction(
9 | () => source(),
10 | (val) => {
11 | if (val === 1) {
12 | disposeInner = reaction(
13 | () => source(),
14 | () => { }
15 | );
16 | } else if (val === 2) {
17 | disposeInner!();
18 | }
19 | }
20 | );
21 |
22 | source(1);
23 | source(2);
24 | source(3);
25 | });
26 |
27 | interface ReactionOptions {
28 | fireImmediately?: F;
29 | equals?: F extends true
30 | ? (a: T, b: T | undefined) => boolean
31 | : (a: T, b: T) => boolean;
32 | onError?: (error: unknown) => void;
33 | scheduler?: (fn: () => void) => void;
34 | once?: boolean;
35 | }
36 |
37 | function reaction(
38 | dataFn: () => T,
39 | effectFn: (newValue: T, oldValue: T | undefined) => void,
40 | options: ReactionOptions = {}
41 | ): () => void {
42 | const {
43 | scheduler = (fn) => fn(),
44 | equals = Object.is,
45 | onError,
46 | once = false,
47 | fireImmediately = false,
48 | } = options;
49 |
50 | let prevValue: T | undefined;
51 | let version = 0;
52 |
53 | const tracked = computed(() => {
54 | try {
55 | return dataFn();
56 | } catch (error) {
57 | untracked(() => onError?.(error));
58 | return prevValue!;
59 | }
60 | });
61 |
62 | const dispose = effect(() => {
63 | const current = tracked();
64 | if (!fireImmediately && !version) {
65 | prevValue = current;
66 | }
67 | version++;
68 | if (equals(current, prevValue!)) return;
69 | const oldValue = prevValue;
70 | prevValue = current;
71 | untracked(() =>
72 | scheduler(() => {
73 | try {
74 | effectFn(current, oldValue);
75 | } catch (error) {
76 | onError?.(error);
77 | } finally {
78 | if (once) {
79 | if (fireImmediately && version > 1) dispose();
80 | else if (!fireImmediately && version > 0) dispose();
81 | }
82 | }
83 | })
84 | );
85 | });
86 |
87 | return dispose;
88 | }
89 |
90 | function untracked(callback: () => T): T {
91 | const currentSub = setCurrentSub(undefined);
92 | try {
93 | return callback();
94 | } finally {
95 | setCurrentSub(currentSub);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/topology.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, vi, describe } from 'vitest';
2 | import {computed, effect, signal} from '../src';
3 |
4 | // To give access to .toHaveBeenCalledBefore()
5 | import * as matchers from 'jest-extended';
6 |
7 | expect.extend(matchers);
8 |
9 | /** Tests adopted with thanks from preact-signals implementation at
10 | * https://github.com/preactjs/signals/blob/main/packages/core/test/signal.test.tsx
11 | *
12 | * The MIT License (MIT)
13 | *
14 | * Copyright (c) 2022-present Preact Team
15 | *
16 | * Permission is hereby granted, free of charge, to any person obtaining a copy
17 | * of this software and associated documentation files (the "Software"), to deal
18 | * in the Software without restriction, including without limitation the rights
19 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20 | * copies of the Software, and to permit persons to whom the Software is
21 | * furnished to do so, subject to the following conditions:
22 | *
23 | * The above copyright notice and this permission notice shall be included in all
24 | * copies or substantial portions of the Software.
25 | *
26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 | * SOFTWARE
33 | */
34 |
35 | describe("graph updates", () => {
36 |
37 | test('should drop A->B->A updates', () => {
38 | // A
39 | // / |
40 | // B | <- Looks like a flag doesn't it? :D
41 | // \ |
42 | // C
43 | // |
44 | // D
45 | const a = signal(2);
46 |
47 | const b = computed(() => a() - 1);
48 | const c = computed(() => a() + b());
49 |
50 | const compute = vi.fn(() => "d: " + c());
51 | const d = computed(compute);
52 |
53 | // Trigger read
54 | expect(d()).toBe("d: 3");
55 | expect(compute).toHaveBeenCalledOnce();
56 | compute.mockClear();
57 |
58 | a(4);
59 | d();
60 | expect(compute).toHaveBeenCalledOnce();
61 | });
62 |
63 | test('should only update every signal once (diamond graph)', () => {
64 | // In this scenario "D" should only update once when "A" receives
65 | // an update. This is sometimes referred to as the "diamond" scenario.
66 | // A
67 | // / \
68 | // B C
69 | // \ /
70 | // D
71 |
72 | const a = signal("a");
73 | const b = computed(() => a());
74 | const c = computed(() => a());
75 |
76 | const spy = vi.fn(() => b() + " " + c());
77 | const d = computed(spy);
78 |
79 | expect(d()).toBe("a a");
80 | expect(spy).toHaveBeenCalledOnce();
81 |
82 | a("aa");
83 | expect(d()).toBe("aa aa");
84 | expect(spy).toHaveBeenCalledTimes(2);
85 | });
86 |
87 | test('should only update every signal once (diamond graph + tail)', () => {
88 | // "E" will be likely updated twice if our mark+sweep logic is buggy.
89 | // A
90 | // / \
91 | // B C
92 | // \ /
93 | // D
94 | // |
95 | // E
96 |
97 | const a = signal("a");
98 | const b = computed(() => a());
99 | const c = computed(() => a());
100 |
101 | const d = computed(() => b() + " " + c());
102 |
103 | const spy = vi.fn(() => d());
104 | const e = computed(spy);
105 |
106 | expect(e()).toBe("a a");
107 | expect(spy).toHaveBeenCalledOnce();
108 |
109 | a("aa");
110 | expect(e()).toBe("aa aa");
111 | expect(spy).toHaveBeenCalledTimes(2);
112 | });
113 |
114 | test('should bail out if result is the same', () => {
115 | // Bail out if value of "B" never changes
116 | // A->B->C
117 | const a = signal("a");
118 | const b = computed(() => {
119 | a();
120 | return "foo";
121 | });
122 |
123 | const spy = vi.fn(() => b());
124 | const c = computed(spy);
125 |
126 | expect(c()).toBe("foo");
127 | expect(spy).toHaveBeenCalledOnce();
128 |
129 | a("aa");
130 | expect(c()).toBe("foo");
131 | expect(spy).toHaveBeenCalledOnce();
132 | });
133 |
134 | test('should only update every signal once (jagged diamond graph + tails)', () => {
135 | // "F" and "G" will be likely updated twice if our mark+sweep logic is buggy.
136 | // A
137 | // / \
138 | // B C
139 | // | |
140 | // | D
141 | // \ /
142 | // E
143 | // / \
144 | // F G
145 | const a = signal("a");
146 |
147 | const b = computed(() => a());
148 | const c = computed(() => a());
149 |
150 | const d = computed(() => c());
151 |
152 | const eSpy = vi.fn(() => b() + " " + d());
153 | const e = computed(eSpy);
154 |
155 | const fSpy = vi.fn(() => e());
156 | const f = computed(fSpy);
157 | const gSpy = vi.fn(() => e());
158 | const g = computed(gSpy);
159 |
160 | expect(f()).toBe("a a");
161 | expect(fSpy).toHaveBeenCalledTimes(1);
162 |
163 | expect(g()).toBe("a a");
164 | expect(gSpy).toHaveBeenCalledTimes(1);
165 |
166 | eSpy.mockClear();
167 | fSpy.mockClear();
168 | gSpy.mockClear();
169 |
170 | a("b");
171 |
172 | expect(e()).toBe("b b");
173 | expect(eSpy).toHaveBeenCalledTimes(1);
174 |
175 | expect(f()).toBe("b b");
176 | expect(fSpy).toHaveBeenCalledTimes(1);
177 |
178 | expect(g()).toBe("b b");
179 | expect(gSpy).toHaveBeenCalledTimes(1);
180 |
181 | eSpy.mockClear();
182 | fSpy.mockClear();
183 | gSpy.mockClear();
184 |
185 | a("c");
186 |
187 | expect(e()).toBe("c c");
188 | expect(eSpy).toHaveBeenCalledTimes(1);
189 |
190 | expect(f()).toBe("c c");
191 | expect(fSpy).toHaveBeenCalledTimes(1);
192 |
193 | expect(g()).toBe("c c");
194 | expect(gSpy).toHaveBeenCalledTimes(1);
195 |
196 | // top to bottom
197 | expect(eSpy).toHaveBeenCalledBefore(fSpy);
198 | // left to right
199 | expect(fSpy).toHaveBeenCalledBefore(gSpy);
200 | });
201 |
202 | test('should only subscribe to signals listened to', () => {
203 | // *A
204 | // / \
205 | // *B C <- we don't listen to C
206 | const a = signal("a");
207 |
208 | const b = computed(() => a());
209 | const spy = vi.fn(() => a());
210 | computed(spy);
211 |
212 | expect(b()).toBe("a");
213 | expect(spy).not.toHaveBeenCalled();
214 |
215 | a("aa");
216 | expect(b()).toBe("aa");
217 | expect(spy).not.toHaveBeenCalled();
218 | });
219 |
220 | test('should only subscribe to signals listened to II', () => {
221 | // Here both "B" and "C" are active in the beginning, but
222 | // "B" becomes inactive later. At that point it should
223 | // not receive any updates anymore.
224 | // *A
225 | // / \
226 | // *B D <- we don't listen to C
227 | // |
228 | // *C
229 | const a = signal("a");
230 | const spyB = vi.fn(() => a());
231 | const b = computed(spyB);
232 |
233 | const spyC = vi.fn(() => b());
234 | const c = computed(spyC);
235 |
236 | const d = computed(() => a());
237 |
238 | let result = "";
239 | const unsub = effect(() => {
240 | result = c();
241 | });
242 |
243 | expect(result).toBe("a");
244 | expect(d()).toBe("a");
245 |
246 | spyB.mockClear();
247 | spyC.mockClear();
248 | unsub();
249 |
250 | a("aa");
251 |
252 | expect(spyB).not.toHaveBeenCalled();
253 | expect(spyC).not.toHaveBeenCalled();
254 | expect(d()).toBe("aa");
255 | });
256 |
257 | test('should ensure subs update even if one dep unmarks it', () => {
258 | // In this scenario "C" always returns the same value. When "A"
259 | // changes, "B" will update, then "C" at which point its update
260 | // to "D" will be unmarked. But "D" must still update because
261 | // "B" marked it. If "D" isn't updated, then we have a bug.
262 | // A
263 | // / \
264 | // B *C <- returns same value every time
265 | // \ /
266 | // D
267 | const a = signal("a");
268 | const b = computed(() => a());
269 | const c = computed(() => {
270 | a();
271 | return "c";
272 | });
273 | const spy = vi.fn(() => b() + " " + c());
274 | const d = computed(spy);
275 |
276 | expect(d()).toBe("a c");
277 | spy.mockClear();
278 |
279 | a("aa");
280 | d();
281 | expect(spy).toHaveReturnedWith("aa c");
282 | });
283 |
284 | test('should ensure subs update even if two deps unmark it', () => {
285 | // In this scenario both "C" and "D" always return the same
286 | // value. But "E" must still update because "A" marked it.
287 | // If "E" isn't updated, then we have a bug.
288 | // A
289 | // / | \
290 | // B *C *D
291 | // \ | /
292 | // E
293 | const a = signal("a");
294 | const b = computed(() => a());
295 | const c = computed(() => {
296 | a();
297 | return "c";
298 | });
299 | const d = computed(() => {
300 | a();
301 | return "d";
302 | });
303 | const spy = vi.fn(() => b() + " " + c() + " " + d());
304 | const e = computed(spy);
305 |
306 | expect(e()).toBe("a c d");
307 | spy.mockClear();
308 |
309 | a("aa");
310 | e();
311 | expect(spy).toHaveReturnedWith("aa c d");
312 | });
313 |
314 | test('should support lazy branches', () => {
315 | const a = signal(0);
316 | const b = computed(() => a());
317 | const c = computed(() => (a() > 0 ? a() : b()));
318 |
319 | expect(c()).toBe(0);
320 | a(1);
321 | expect(c()).toBe(1);
322 |
323 | a(0);
324 | expect(c()).toBe(0);
325 | });
326 |
327 | test('should not update a sub if all deps unmark it', () => {
328 | // In this scenario "B" and "C" always return the same value. When "A"
329 | // changes, "D" should not update.
330 | // A
331 | // / \
332 | // *B *C
333 | // \ /
334 | // D
335 | const a = signal("a");
336 | const b = computed(() => {
337 | a();
338 | return "b";
339 | });
340 | const c = computed(() => {
341 | a();
342 | return "c";
343 | });
344 | const spy = vi.fn(() => b() + " " + c());
345 | const d = computed(spy);
346 |
347 | expect(d()).toBe("b c");
348 | spy.mockClear();
349 |
350 | a("aa");
351 | expect(spy).not.toHaveBeenCalled();
352 | });
353 |
354 | });
355 |
356 | describe("error handling", () => {
357 |
358 | test('should keep graph consistent on errors during activation', () => {
359 | const a = signal(0);
360 | const b = computed(() => {
361 | throw new Error("fail");
362 | });
363 | const c = computed(() => a());
364 |
365 | expect(() => b()).toThrow("fail");
366 |
367 | a(1);
368 | expect(c()).toBe(1);
369 | });
370 |
371 | test('should keep graph consistent on errors in computeds', () => {
372 | const a = signal(0);
373 | const b = computed(() => {
374 | if (a() === 1) throw new Error("fail");
375 | return a();
376 | });
377 | const c = computed(() => b());
378 |
379 | expect(c()).toBe(0);
380 |
381 | a(1);
382 | expect(() => b()).toThrow("fail");
383 |
384 | a(2);
385 | expect(c()).toBe(2);
386 | });
387 |
388 | });
389 |
--------------------------------------------------------------------------------
/tests/untrack.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { computed, effect, effectScope, setCurrentSub, signal } from '../src';
3 |
4 | test('should pause tracking in computed', () => {
5 | const src = signal(0);
6 |
7 | let computedTriggerTimes = 0;
8 | const c = computed(() => {
9 | computedTriggerTimes++;
10 | const currentSub = setCurrentSub(undefined);
11 | const value = src();
12 | setCurrentSub(currentSub);
13 | return value;
14 | });
15 |
16 | expect(c()).toBe(0);
17 | expect(computedTriggerTimes).toBe(1);
18 |
19 | src(1), src(2), src(3);
20 | expect(c()).toBe(0);
21 | expect(computedTriggerTimes).toBe(1);
22 | });
23 |
24 | test('should pause tracking in effect', () => {
25 | const src = signal(0);
26 | const is = signal(0);
27 |
28 | let effectTriggerTimes = 0;
29 | effect(() => {
30 | effectTriggerTimes++;
31 | if (is()) {
32 | const currentSub = setCurrentSub(undefined);
33 | src();
34 | setCurrentSub(currentSub);
35 | }
36 | });
37 |
38 | expect(effectTriggerTimes).toBe(1);
39 |
40 | is(1);
41 | expect(effectTriggerTimes).toBe(2);
42 |
43 | src(1), src(2), src(3);
44 | expect(effectTriggerTimes).toBe(2);
45 |
46 | is(2);
47 | expect(effectTriggerTimes).toBe(3);
48 |
49 | src(4), src(5), src(6);
50 | expect(effectTriggerTimes).toBe(3);
51 |
52 | is(0);
53 | expect(effectTriggerTimes).toBe(4);
54 |
55 | src(7), src(8), src(9);
56 | expect(effectTriggerTimes).toBe(4);
57 | });
58 |
59 | test('should pause tracking in effect scope', () => {
60 | const src = signal(0);
61 |
62 | let effectTriggerTimes = 0;
63 | effectScope(() => {
64 | effect(() => {
65 | effectTriggerTimes++;
66 | const currentSub = setCurrentSub(undefined);
67 | src();
68 | setCurrentSub(currentSub);
69 | });
70 | });
71 |
72 | expect(effectTriggerTimes).toBe(1);
73 |
74 | src(1), src(2), src(3);
75 | expect(effectTriggerTimes).toBe(1);
76 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "strict": true,
5 | "noUnusedLocals": true,
6 | "noUnusedParameters": true,
7 | "skipLibCheck": true,
8 | "rootDir": "src",
9 | },
10 | "include": [ "src" ],
11 | }
12 |
--------------------------------------------------------------------------------
/tsslint.config.ts:
--------------------------------------------------------------------------------
1 | import { createDiagnosticsPlugin, defineConfig, isCLI } from '@tsslint/config';
2 | import type * as ts from 'typescript';
3 |
4 | export default defineConfig({
5 | plugins: isCLI()
6 | ? [createDiagnosticsPlugin()]
7 | : [],
8 | rules: {
9 | 'number-equality'({
10 | typescript: ts,
11 | file,
12 | program,
13 | report,
14 | }) {
15 | const checker = program.getTypeChecker();
16 | ts.forEachChild(file, function visit(node) {
17 | if (
18 | ts.isBinaryExpression(node) &&
19 | node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken &&
20 | ts.isNumericLiteral(node.right) &&
21 | node.right.text === '0'
22 | ) {
23 | const type = checker.getTypeAtLocation(node.left);
24 | if (type.flags & ts.TypeFlags.Number) {
25 | report(
26 | `Replace "x === 0" with "!x" for numeric variables to clarify boolean usage.`,
27 | node.getStart(file),
28 | node.getEnd(),
29 | ).withFix('Use exclamation instead', () => [
30 | {
31 | fileName: file.fileName,
32 | textChanges: [
33 | {
34 | newText: `!(${node.left.getText(file)})`,
35 | span: {
36 | start: node.getStart(file),
37 | length: node.getWidth(),
38 | },
39 | },
40 | ],
41 | },
42 | ]);
43 | }
44 | }
45 | ts.forEachChild(node, visit);
46 | });
47 | },
48 | 'object-equality'({
49 | typescript: ts,
50 | file,
51 | program,
52 | report,
53 | }) {
54 | const checker = program.getTypeChecker();
55 | const checkFlags = [ts.TypeFlags.Undefined, ts.TypeFlags.Null];
56 | ts.forEachChild(file, function visit(node) {
57 | if (
58 | ts.isPrefixUnaryExpression(node) &&
59 | node.operator === ts.SyntaxKind.ExclamationToken
60 | ) {
61 | const type = checker.getTypeAtLocation(node.operand);
62 | for (const checkFlag of checkFlags) {
63 | if (isObjectOrNullableUnion(ts, type, checkFlag)) {
64 | const flagText =
65 | checkFlag === ts.TypeFlags.Undefined ? 'undefined' : 'null';
66 | if (
67 | ts.isPrefixUnaryExpression(node.parent) &&
68 | node.parent.operator === ts.SyntaxKind.ExclamationToken
69 | ) {
70 | report(
71 | `Do not use "!!" for a variable of type "object | ${flagText}". Replace with "!== ${flagText}" for clarity.`,
72 | node.parent.getStart(file),
73 | node.getEnd(),
74 | ).withFix(`Replace with !== ${flagText}`, () => [
75 | {
76 | fileName: file.fileName,
77 | textChanges: [
78 | {
79 | newText: `${node.operand.getText(file)} !== ${flagText}`,
80 | span: {
81 | start: node.parent.getStart(file),
82 | length:
83 | node.getEnd() - node.parent.getStart(file),
84 | },
85 | },
86 | ],
87 | },
88 | ]);
89 | } else {
90 | report(
91 | `Do not use "!" for a variable of type "object | ${flagText}". Replace with "=== ${flagText}" for clarity.`,
92 | node.getStart(file),
93 | node.getEnd(),
94 | ).withFix(`Replace with === ${flagText}`, () => [
95 | {
96 | fileName: file.fileName,
97 | textChanges: [
98 | {
99 | newText: `${node.operand.getText(file)} === ${flagText}`,
100 | span: {
101 | start: node.getStart(file),
102 | length: node.getWidth(),
103 | },
104 | },
105 | ],
106 | },
107 | ]);
108 | }
109 | }
110 | }
111 | }
112 | ts.forEachChild(node, visit);
113 | });
114 | },
115 | },
116 | });
117 |
118 | function isObjectOrNullableUnion(
119 | ts: typeof import('typescript'),
120 | type: ts.Type,
121 | nullableFlag: ts.TypeFlags,
122 | ) {
123 | if (!(type.flags & ts.TypeFlags.Union)) return false;
124 | const unionType = type;
125 | let hasObject = false;
126 | let hasNullable = false;
127 | for (const sub of (unionType as ts.UnionType).types) {
128 | if (sub.flags & nullableFlag) {
129 | hasNullable = true;
130 | } else if (sub.flags & ts.TypeFlags.Object) {
131 | hasObject = true;
132 | } else {
133 | return false;
134 | }
135 | }
136 | return hasObject && hasNullable;
137 | }
138 |
--------------------------------------------------------------------------------