├── .gitignore ├── assets └── logo.png ├── tsconfig.json ├── .vscode └── settings.json ├── benchs ├── propagate.mjs └── memoryUsage.mjs ├── tests ├── build.spec.ts ├── effectScope.spec.ts ├── trigger.spec.ts ├── computed.spec.ts ├── untrack.spec.ts ├── issue_48.spec.ts ├── effect.spec.ts └── topology.spec.ts ├── .github └── workflows │ └── test.yml ├── size.js ├── LICENSE ├── package.json ├── tsslint.config.ts ├── src ├── system.ts └── index.ts ├── README.md └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cjs 3 | esm 4 | types 5 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackblitz/alien-signals/master/assets/logo.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "strict": true, 5 | "noUnusedLocals": true, 6 | "noUnusedParameters": true, 7 | "skipLibCheck": true, 8 | "rootDir": "src", 9 | "isolatedModules": false, 10 | "preserveConstEnums": true, 11 | }, 12 | "include": [ "src" ], 13 | } 14 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.getActiveSub).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.getActiveSub).toBe('function'); 19 | expect(typeof system.createReactiveSystem).toBe('function'); 20 | }); 21 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /size.js: -------------------------------------------------------------------------------- 1 | require('./build.js'); 2 | 3 | const rolldown = require('rolldown'); 4 | 5 | rolldown.build({ 6 | input: 'esm/index.mjs', output: { minify: true }, write: false, treeshake: true 7 | }).then(built => { 8 | console.log(`esm/index.mjs: ${(built.output[0].code.length / 1024).toFixed(2)} KB`); 9 | }); 10 | rolldown.build({ 11 | input: 'esm/system.mjs', output: { minify: true }, write: false, treeshake: true 12 | }).then(built => { 13 | console.log(`esm/system.mjs: ${(built.output[0].code.length / 1024).toFixed(2)} KB`); 14 | }); 15 | rolldown.build({ 16 | input: 'cjs/index.cjs', output: { minify: true }, write: false, treeshake: true 17 | }).then(built => { 18 | console.log(`cjs/index.cjs: ${(built.output[0].code.length / 1024).toFixed(2)} KB`); 19 | }); 20 | rolldown.build({ 21 | input: 'cjs/system.cjs', output: { minify: true }, write: false, treeshake: true 22 | }).then(built => { 23 | console.log(`cjs/system.cjs: ${(built.output[0].code.length / 1024).toFixed(2)} KB`); 24 | }); 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 50 | test('should track signal updates in an inner scope when accessed by an outer effect', () => { 51 | const source = signal(1); 52 | 53 | let triggers = 0; 54 | 55 | effect(() => { 56 | effectScope(() => { 57 | source(); 58 | }); 59 | triggers++; 60 | }); 61 | 62 | expect(triggers).toBe(1); 63 | source(2); 64 | expect(triggers).toBe(2); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/trigger.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { computed, effect, trigger, signal } from '../src'; 3 | 4 | test('should not throw when triggering with no dependencies', () => { 5 | trigger(() => { }); 6 | }); 7 | 8 | test('should trigger updates for dependent computed signals', () => { 9 | const arr = signal([]); 10 | const length = computed(() => arr().length); 11 | 12 | expect(length()).toBe(0); 13 | arr().push(1); 14 | trigger(arr); 15 | expect(length()).toBe(1); 16 | }); 17 | 18 | test('should trigger updates for the second source signal', () => { 19 | const src1 = signal([]); 20 | const src2 = signal([]); 21 | const length = computed(() => src2().length); 22 | 23 | expect(length()).toBe(0); 24 | src2().push(1); 25 | trigger(() => { 26 | src1(); 27 | src2(); 28 | }); 29 | expect(length()).toBe(1); 30 | }); 31 | 32 | test('should trigger effect once', () => { 33 | const src1 = signal([]); 34 | const src2 = signal([]); 35 | 36 | let triggers = 0; 37 | 38 | effect(() => { 39 | triggers++; 40 | src1(); 41 | src2(); 42 | }); 43 | 44 | expect(triggers).toBe(1); 45 | trigger(() => { 46 | src1(); 47 | src2(); 48 | }); 49 | expect(triggers).toBe(2); 50 | }); 51 | 52 | test('should not notify the trigger function sub', () => { 53 | const src1 = signal([]); 54 | const src2 = computed(() => src1()); 55 | 56 | effect(() => { 57 | src1(); 58 | src2(); 59 | }); 60 | trigger(() => { 61 | src1(); 62 | src2(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /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/untrack.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { computed, effect, effectScope, setActiveSub, 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 = setActiveSub(); 11 | const value = src(); 12 | setActiveSub(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 = setActiveSub(); 33 | src(); 34 | setActiveSub(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 = setActiveSub(); 67 | src(); 68 | setActiveSub(currentSub); 69 | }); 70 | }); 71 | 72 | expect(effectTriggerTimes).toBe(1); 73 | 74 | src(1), src(2), src(3); 75 | expect(effectTriggerTimes).toBe(1); 76 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alien-signals", 3 | "version": "3.1.1", 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 | "size": "node ./size.js", 53 | "build": "node ./build.js", 54 | "test": "npm run build && vitest run", 55 | "bench": "npm run build && node --jitless --expose-gc benchs/propagate.mjs", 56 | "memory": "npm run build && node --expose-gc benchs/memoryUsage.mjs" 57 | }, 58 | "devDependencies": { 59 | "@tsslint/cli": "latest", 60 | "@tsslint/config": "latest", 61 | "jest-extended": "latest", 62 | "mitata": "latest", 63 | "rolldown": "latest", 64 | "typescript": "latest", 65 | "vitest": "latest" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/issue_48.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'vitest'; 2 | import { computed, effect, setActiveSub, 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 = setActiveSub(); 92 | try { 93 | return callback(); 94 | } finally { 95 | setActiveSub(currentSub); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { computed, effect, effectScope, endBatch, getActiveSub, setActiveSub, signal, startBatch } from '../src'; 3 | import { ReactiveFlags } from '../src/system'; 4 | 5 | test('should clear subscriptions when untracked by all subscribers', () => { 6 | let bRunTimes = 0; 7 | 8 | const a = signal(1); 9 | const b = computed(() => { 10 | bRunTimes++; 11 | return a() * 2; 12 | }); 13 | const stopEffect = effect(() => { 14 | b(); 15 | }); 16 | 17 | expect(bRunTimes).toBe(1); 18 | a(2); 19 | expect(bRunTimes).toBe(2); 20 | stopEffect(); 21 | a(3); 22 | expect(bRunTimes).toBe(2); 23 | }); 24 | 25 | test('should not run untracked inner effect', () => { 26 | const a = signal(3); 27 | const b = computed(() => a() > 0); 28 | 29 | effect(() => { 30 | if (b()) { 31 | effect(() => { 32 | if (a() == 0) { 33 | throw new Error("bad"); 34 | } 35 | }); 36 | } 37 | }); 38 | 39 | a(2); 40 | a(1); 41 | a(0); 42 | }); 43 | 44 | test('should run outer effect first', () => { 45 | const a = signal(1); 46 | const b = signal(1); 47 | 48 | effect(() => { 49 | if (a()) { 50 | effect(() => { 51 | b(); 52 | if (a() == 0) { 53 | throw new Error("bad"); 54 | } 55 | }); 56 | } else { 57 | } 58 | }); 59 | 60 | startBatch(); 61 | b(0); 62 | a(0); 63 | endBatch(); 64 | }); 65 | 66 | test('should not trigger inner effect when resolve maybe dirty', () => { 67 | const a = signal(0); 68 | const b = computed(() => a() % 2); 69 | 70 | let innerTriggerTimes = 0; 71 | 72 | effect(() => { 73 | effect(() => { 74 | b(); 75 | innerTriggerTimes++; 76 | if (innerTriggerTimes >= 2) { 77 | throw new Error("bad"); 78 | } 79 | }); 80 | }); 81 | 82 | a(2); 83 | }); 84 | 85 | test('should notify inner effects in the same order as non-inner effects', () => { 86 | const a = signal(0); 87 | const b = signal(0); 88 | const c = computed(() => a() - b()); 89 | const order1: string[] = []; 90 | const order2: string[] = []; 91 | const order3: string[] = []; 92 | 93 | effect(() => { 94 | order1.push('effect1'); 95 | a(); 96 | }); 97 | effect(() => { 98 | order1.push('effect2'); 99 | a(); 100 | b(); 101 | }); 102 | 103 | effect(() => { 104 | c(); 105 | effect(() => { 106 | order2.push('effect1'); 107 | a(); 108 | }); 109 | effect(() => { 110 | order2.push('effect2'); 111 | a(); 112 | b(); 113 | }); 114 | }); 115 | 116 | effectScope(() => { 117 | effect(() => { 118 | order3.push('effect1'); 119 | a(); 120 | }); 121 | effect(() => { 122 | order3.push('effect2'); 123 | a(); 124 | b(); 125 | }); 126 | }); 127 | 128 | order1.length = 0; 129 | order2.length = 0; 130 | order3.length = 0; 131 | 132 | startBatch(); 133 | b(1); 134 | a(1); 135 | endBatch(); 136 | 137 | expect(order1).toEqual(['effect2', 'effect1']); 138 | expect(order2).toEqual(order1); 139 | expect(order3).toEqual(order1); 140 | }); 141 | 142 | test('should custom effect support batch', () => { 143 | function batchEffect(fn: () => void) { 144 | return effect(() => { 145 | startBatch(); 146 | try { 147 | return fn(); 148 | } finally { 149 | endBatch(); 150 | } 151 | }); 152 | } 153 | 154 | const logs: string[] = []; 155 | const a = signal(0); 156 | const b = signal(0); 157 | 158 | const aa = computed(() => { 159 | logs.push('aa-0'); 160 | if (!a()) { 161 | b(1); 162 | } 163 | logs.push('aa-1'); 164 | }); 165 | 166 | const bb = computed(() => { 167 | logs.push('bb'); 168 | return b(); 169 | }); 170 | 171 | batchEffect(() => { 172 | bb(); 173 | }); 174 | batchEffect(() => { 175 | aa(); 176 | }); 177 | 178 | expect(logs).toEqual(['bb', 'aa-0', 'aa-1', 'bb']); 179 | }); 180 | 181 | test('should duplicate subscribers do not affect the notify order', () => { 182 | const src1 = signal(0); 183 | const src2 = signal(0); 184 | const order: string[] = []; 185 | 186 | effect(() => { 187 | order.push('a'); 188 | const currentSub = setActiveSub(); 189 | const isOne = src2() === 1; 190 | setActiveSub(currentSub); 191 | if (isOne) { 192 | src1(); 193 | } 194 | src2(); 195 | src1(); 196 | }); 197 | effect(() => { 198 | order.push('b'); 199 | src1(); 200 | }); 201 | src2(1); // src1.subs: a -> b -> a 202 | 203 | order.length = 0; 204 | src1(src1() + 1); 205 | 206 | expect(order).toEqual(['a', 'b']); 207 | }); 208 | 209 | test('should handle side effect with inner effects', () => { 210 | const a = signal(0); 211 | const b = signal(0); 212 | const order: string[] = []; 213 | 214 | effect(() => { 215 | effect(() => { 216 | a(); 217 | order.push('a'); 218 | }); 219 | effect(() => { 220 | b(); 221 | order.push('b'); 222 | }); 223 | expect(order).toEqual(['a', 'b']); 224 | 225 | order.length = 0; 226 | b(1); 227 | a(1); 228 | expect(order).toEqual(['b', 'a']); 229 | }); 230 | }); 231 | 232 | test('should handle flags are indirectly updated during checkDirty', () => { 233 | const a = signal(false); 234 | const b = computed(() => a()); 235 | const c = computed(() => { 236 | b(); 237 | return 0; 238 | }); 239 | const d = computed(() => { 240 | c(); 241 | return b(); 242 | }); 243 | 244 | let triggers = 0; 245 | 246 | effect(() => { 247 | d(); 248 | triggers++; 249 | }); 250 | expect(triggers).toBe(1); 251 | a(true); 252 | expect(triggers).toBe(2); 253 | }); 254 | 255 | test('should handle effect recursion for the first execution', () => { 256 | const src1 = signal(0); 257 | const src2 = signal(0); 258 | 259 | let triggers1 = 0; 260 | let triggers2 = 0; 261 | 262 | effect(() => { 263 | triggers1++; 264 | src1(Math.min(src1() + 1, 5)); 265 | }); 266 | effect(() => { 267 | triggers2++; 268 | src2(Math.min(src2() + 1, 5)); 269 | src2(); 270 | }); 271 | 272 | expect(triggers1).toBe(1); 273 | expect(triggers2).toBe(1); 274 | }); 275 | 276 | test('should support custom recurse effect', () => { 277 | const src = signal(0); 278 | 279 | let triggers = 0; 280 | 281 | effect(() => { 282 | getActiveSub()!.flags &= ~ReactiveFlags.RecursedCheck; 283 | triggers++; 284 | src(Math.min(src() + 1, 5)); 285 | }); 286 | 287 | expect(triggers).toBe(6); 288 | }); 289 | -------------------------------------------------------------------------------- /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 const enum ReactiveFlags { 25 | None = 0, 26 | Mutable = 1, 27 | Watching = 2, 28 | RecursedCheck = 4, 29 | Recursed = 8, 30 | Dirty = 16, 31 | Pending = 32, 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 | return { 44 | link, 45 | unlink, 46 | propagate, 47 | checkDirty, 48 | shallowPropagate, 49 | }; 50 | 51 | function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void { 52 | const prevDep = sub.depsTail; 53 | if (prevDep !== undefined && prevDep.dep === dep) { 54 | return; 55 | } 56 | const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps; 57 | if (nextDep !== undefined && nextDep.dep === dep) { 58 | nextDep.version = version; 59 | sub.depsTail = nextDep; 60 | return; 61 | } 62 | const prevSub = dep.subsTail; 63 | if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) { 64 | return; 65 | } 66 | const newLink 67 | = sub.depsTail 68 | = dep.subsTail 69 | = { 70 | version, 71 | dep, 72 | sub, 73 | prevDep, 74 | nextDep, 75 | prevSub, 76 | nextSub: undefined, 77 | }; 78 | if (nextDep !== undefined) { 79 | nextDep.prevDep = newLink; 80 | } 81 | if (prevDep !== undefined) { 82 | prevDep.nextDep = newLink; 83 | } else { 84 | sub.deps = newLink; 85 | } 86 | if (prevSub !== undefined) { 87 | prevSub.nextSub = newLink; 88 | } else { 89 | dep.subs = newLink; 90 | } 91 | } 92 | 93 | function unlink(link: Link, sub = link.sub): Link | undefined { 94 | const dep = link.dep; 95 | const prevDep = link.prevDep; 96 | const nextDep = link.nextDep; 97 | const nextSub = link.nextSub; 98 | const prevSub = link.prevSub; 99 | if (nextDep !== undefined) { 100 | nextDep.prevDep = prevDep; 101 | } else { 102 | sub.depsTail = prevDep; 103 | } 104 | if (prevDep !== undefined) { 105 | prevDep.nextDep = nextDep; 106 | } else { 107 | sub.deps = nextDep; 108 | } 109 | if (nextSub !== undefined) { 110 | nextSub.prevSub = prevSub; 111 | } else { 112 | dep.subsTail = prevSub; 113 | } 114 | if (prevSub !== undefined) { 115 | prevSub.nextSub = nextSub; 116 | } else if ((dep.subs = nextSub) === undefined) { 117 | unwatched(dep); 118 | } 119 | return nextDep; 120 | } 121 | 122 | function propagate(link: Link): void { 123 | let next = link.nextSub; 124 | let stack: Stack | undefined; 125 | 126 | top: do { 127 | const sub = link.sub; 128 | let flags = sub.flags; 129 | 130 | if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) { 131 | sub.flags = flags | ReactiveFlags.Pending; 132 | } else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) { 133 | flags = ReactiveFlags.None; 134 | } else if (!(flags & ReactiveFlags.RecursedCheck)) { 135 | sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending; 136 | } else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) { 137 | sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending); 138 | flags &= ReactiveFlags.Mutable; 139 | } else { 140 | flags = ReactiveFlags.None; 141 | } 142 | 143 | if (flags & ReactiveFlags.Watching) { 144 | notify(sub); 145 | } 146 | 147 | if (flags & ReactiveFlags.Mutable) { 148 | const subSubs = sub.subs; 149 | if (subSubs !== undefined) { 150 | const nextSub = (link = subSubs).nextSub; 151 | if (nextSub !== undefined) { 152 | stack = { value: next, prev: stack }; 153 | next = nextSub; 154 | } 155 | continue; 156 | } 157 | } 158 | 159 | if ((link = next!) !== undefined) { 160 | next = link.nextSub; 161 | continue; 162 | } 163 | 164 | while (stack !== undefined) { 165 | link = stack.value!; 166 | stack = stack.prev; 167 | if (link !== undefined) { 168 | next = link.nextSub; 169 | continue top; 170 | } 171 | } 172 | 173 | break; 174 | } while (true); 175 | } 176 | 177 | function checkDirty(link: Link, sub: ReactiveNode): boolean { 178 | let stack: Stack | undefined; 179 | let checkDepth = 0; 180 | let dirty = false; 181 | 182 | top: do { 183 | const dep = link.dep; 184 | const flags = dep.flags; 185 | 186 | if (sub.flags & ReactiveFlags.Dirty) { 187 | dirty = true; 188 | } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) { 189 | if (update(dep)) { 190 | const subs = dep.subs!; 191 | if (subs.nextSub !== undefined) { 192 | shallowPropagate(subs); 193 | } 194 | dirty = true; 195 | } 196 | } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) { 197 | if (link.nextSub !== undefined || link.prevSub !== undefined) { 198 | stack = { value: link, prev: stack }; 199 | } 200 | link = dep.deps!; 201 | sub = dep; 202 | ++checkDepth; 203 | continue; 204 | } 205 | 206 | if (!dirty) { 207 | const nextDep = link.nextDep; 208 | if (nextDep !== undefined) { 209 | link = nextDep; 210 | continue; 211 | } 212 | } 213 | 214 | while (checkDepth--) { 215 | const firstSub = sub.subs!; 216 | const hasMultipleSubs = firstSub.nextSub !== undefined; 217 | if (hasMultipleSubs) { 218 | link = stack!.value; 219 | stack = stack!.prev; 220 | } else { 221 | link = firstSub; 222 | } 223 | if (dirty) { 224 | if (update(sub)) { 225 | if (hasMultipleSubs) { 226 | shallowPropagate(firstSub); 227 | } 228 | sub = link.sub; 229 | continue; 230 | } 231 | dirty = false; 232 | } else { 233 | sub.flags &= ~ReactiveFlags.Pending; 234 | } 235 | sub = link.sub; 236 | const nextDep = link.nextDep; 237 | if (nextDep !== undefined) { 238 | link = nextDep; 239 | continue top; 240 | } 241 | } 242 | 243 | return dirty; 244 | } while (true); 245 | } 246 | 247 | function shallowPropagate(link: Link): void { 248 | do { 249 | const sub = link.sub; 250 | const flags = sub.flags; 251 | if ((flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) === ReactiveFlags.Pending) { 252 | sub.flags = flags | ReactiveFlags.Dirty; 253 | if ((flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) === ReactiveFlags.Watching) { 254 | notify(sub); 255 | } 256 | } 257 | } while ((link = link.nextSub!) !== undefined); 258 | } 259 | 260 | function isValidLink(checkLink: Link, sub: ReactiveNode): boolean { 261 | let link = sub.depsTail; 262 | while (link !== undefined) { 263 | if (link === checkLink) { 264 | return true; 265 | } 266 | link = link.prevDep; 267 | } 268 | return false; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createReactiveSystem, ReactiveFlags, type ReactiveNode } from './system.js'; 2 | 3 | interface EffectNode extends ReactiveNode { 4 | fn(): void; 5 | } 6 | 7 | interface ComputedNode extends ReactiveNode { 8 | value: T | undefined; 9 | getter: (previousValue?: T) => T; 10 | } 11 | 12 | interface SignalNode extends ReactiveNode { 13 | currentValue: T; 14 | pendingValue: T; 15 | } 16 | 17 | let cycle = 0; 18 | let batchDepth = 0; 19 | let notifyIndex = 0; 20 | let queuedLength = 0; 21 | let activeSub: ReactiveNode | undefined; 22 | 23 | const queued: (EffectNode | undefined)[] = []; 24 | const { 25 | link, 26 | unlink, 27 | propagate, 28 | checkDirty, 29 | shallowPropagate, 30 | } = createReactiveSystem({ 31 | update(node: SignalNode | ComputedNode): boolean { 32 | if (node.depsTail !== undefined) { 33 | return updateComputed(node as ComputedNode); 34 | } else { 35 | return updateSignal(node as SignalNode); 36 | } 37 | }, 38 | notify(effect: EffectNode) { 39 | let insertIndex = queuedLength; 40 | let firstInsertedIndex = insertIndex; 41 | 42 | do { 43 | effect.flags &= ~ReactiveFlags.Watching; 44 | queued[insertIndex++] = effect; 45 | effect = effect.subs?.sub as EffectNode; 46 | if (effect === undefined || !(effect.flags & ReactiveFlags.Watching)) { 47 | break; 48 | } 49 | } while (true); 50 | 51 | queuedLength = insertIndex; 52 | 53 | while (firstInsertedIndex < --insertIndex) { 54 | const left = queued[firstInsertedIndex]; 55 | queued[firstInsertedIndex++] = queued[insertIndex]; 56 | queued[insertIndex] = left; 57 | } 58 | }, 59 | unwatched(node) { 60 | if (!(node.flags & ReactiveFlags.Mutable)) { 61 | effectScopeOper.call(node); 62 | } else if (node.depsTail !== undefined) { 63 | node.depsTail = undefined; 64 | node.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty; 65 | purgeDeps(node); 66 | } 67 | }, 68 | }); 69 | 70 | export function getActiveSub(): ReactiveNode | undefined { 71 | return activeSub; 72 | } 73 | 74 | export function setActiveSub(sub?: ReactiveNode) { 75 | const prevSub = activeSub; 76 | activeSub = sub; 77 | return prevSub; 78 | } 79 | 80 | export function getBatchDepth(): number { 81 | return batchDepth; 82 | } 83 | 84 | export function startBatch() { 85 | ++batchDepth; 86 | } 87 | 88 | export function endBatch() { 89 | if (!--batchDepth) { 90 | flush(); 91 | } 92 | } 93 | 94 | export function isSignal(fn: () => void): boolean { 95 | return fn.name === 'bound ' + signalOper.name; 96 | } 97 | 98 | export function isComputed(fn: () => void): boolean { 99 | return fn.name === 'bound ' + computedOper.name; 100 | } 101 | 102 | export function isEffect(fn: () => void): boolean { 103 | return fn.name === 'bound ' + effectOper.name; 104 | } 105 | 106 | export function isEffectScope(fn: () => void): boolean { 107 | return fn.name === 'bound ' + effectScopeOper.name; 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 | currentValue: initialValue, 124 | pendingValue: initialValue, 125 | subs: undefined, 126 | subsTail: undefined, 127 | flags: 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: ReactiveFlags.None, 139 | getter: getter as (previousValue?: unknown) => unknown, 140 | }) as () => T; 141 | } 142 | 143 | export function effect(fn: () => void): () => void { 144 | const e: EffectNode = { 145 | fn, 146 | subs: undefined, 147 | subsTail: undefined, 148 | deps: undefined, 149 | depsTail: undefined, 150 | flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck, 151 | }; 152 | const prevSub = setActiveSub(e); 153 | if (prevSub !== undefined) { 154 | link(e, prevSub, 0); 155 | } 156 | try { 157 | e.fn(); 158 | } finally { 159 | activeSub = prevSub; 160 | e.flags &= ~ReactiveFlags.RecursedCheck; 161 | } 162 | return effectOper.bind(e); 163 | } 164 | 165 | export function effectScope(fn: () => void): () => void { 166 | const e: ReactiveNode = { 167 | deps: undefined, 168 | depsTail: undefined, 169 | subs: undefined, 170 | subsTail: undefined, 171 | flags: ReactiveFlags.None, 172 | }; 173 | const prevSub = setActiveSub(e); 174 | if (prevSub !== undefined) { 175 | link(e, prevSub, 0); 176 | } 177 | try { 178 | fn(); 179 | } finally { 180 | activeSub = prevSub; 181 | } 182 | return effectScopeOper.bind(e); 183 | } 184 | 185 | export function trigger(fn: () => void) { 186 | const sub: ReactiveNode = { 187 | deps: undefined, 188 | depsTail: undefined, 189 | flags: ReactiveFlags.Watching, 190 | }; 191 | const prevSub = setActiveSub(sub); 192 | try { 193 | fn(); 194 | } finally { 195 | activeSub = prevSub; 196 | let link = sub.deps; 197 | while (link !== undefined) { 198 | const dep = link.dep; 199 | link = unlink(link, sub); 200 | const subs = dep.subs; 201 | if (subs !== undefined) { 202 | sub.flags = ReactiveFlags.None; 203 | propagate(subs); 204 | shallowPropagate(subs); 205 | } 206 | } 207 | if (!batchDepth) { 208 | flush(); 209 | } 210 | } 211 | } 212 | 213 | function updateComputed(c: ComputedNode): boolean { 214 | ++cycle; 215 | c.depsTail = undefined; 216 | c.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck; 217 | const prevSub = setActiveSub(c); 218 | try { 219 | const oldValue = c.value; 220 | return oldValue !== (c.value = c.getter(oldValue)); 221 | } finally { 222 | activeSub = prevSub; 223 | c.flags &= ~ReactiveFlags.RecursedCheck; 224 | purgeDeps(c); 225 | } 226 | } 227 | 228 | function updateSignal(s: SignalNode): boolean { 229 | s.flags = ReactiveFlags.Mutable; 230 | return s.currentValue !== (s.currentValue = s.pendingValue); 231 | } 232 | 233 | function run(e: EffectNode): void { 234 | const flags = e.flags; 235 | if ( 236 | flags & ReactiveFlags.Dirty 237 | || ( 238 | flags & ReactiveFlags.Pending 239 | && checkDirty(e.deps!, e) 240 | ) 241 | ) { 242 | ++cycle; 243 | e.depsTail = undefined; 244 | e.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck; 245 | const prevSub = setActiveSub(e); 246 | try { 247 | (e as EffectNode).fn(); 248 | } finally { 249 | activeSub = prevSub; 250 | e.flags &= ~ReactiveFlags.RecursedCheck; 251 | purgeDeps(e); 252 | } 253 | } else { 254 | e.flags = ReactiveFlags.Watching; 255 | } 256 | } 257 | 258 | function flush(): void { 259 | while (notifyIndex < queuedLength) { 260 | const effect = queued[notifyIndex]!; 261 | queued[notifyIndex++] = undefined; 262 | run(effect); 263 | } 264 | notifyIndex = 0; 265 | queuedLength = 0; 266 | } 267 | 268 | function computedOper(this: ComputedNode): T { 269 | const flags = this.flags; 270 | if ( 271 | flags & ReactiveFlags.Dirty 272 | || ( 273 | flags & ReactiveFlags.Pending 274 | && ( 275 | checkDirty(this.deps!, this) 276 | || (this.flags = flags & ~ReactiveFlags.Pending, false) 277 | ) 278 | ) 279 | ) { 280 | if (updateComputed(this)) { 281 | const subs = this.subs; 282 | if (subs !== undefined) { 283 | shallowPropagate(subs); 284 | } 285 | } 286 | } else if (!flags) { 287 | this.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck; 288 | const prevSub = setActiveSub(this); 289 | try { 290 | this.value = this.getter(); 291 | } finally { 292 | activeSub = prevSub; 293 | this.flags &= ~ReactiveFlags.RecursedCheck; 294 | } 295 | } 296 | const sub = activeSub; 297 | if (sub !== undefined) { 298 | link(this, sub, cycle); 299 | } 300 | return this.value!; 301 | } 302 | 303 | function signalOper(this: SignalNode, ...value: [T]): T | void { 304 | if (value.length) { 305 | if (this.pendingValue !== (this.pendingValue = value[0])) { 306 | this.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty; 307 | const subs = this.subs; 308 | if (subs !== undefined) { 309 | propagate(subs); 310 | if (!batchDepth) { 311 | flush(); 312 | } 313 | } 314 | } 315 | } else { 316 | if (this.flags & ReactiveFlags.Dirty) { 317 | if (updateSignal(this)) { 318 | const subs = this.subs; 319 | if (subs !== undefined) { 320 | shallowPropagate(subs); 321 | } 322 | } 323 | } 324 | let sub = activeSub; 325 | while (sub !== undefined) { 326 | if (sub.flags & (ReactiveFlags.Mutable | ReactiveFlags.Watching)) { 327 | link(this, sub, cycle); 328 | break; 329 | } 330 | sub = sub.subs?.sub; 331 | } 332 | return this.currentValue; 333 | } 334 | } 335 | 336 | function effectOper(this: EffectNode): void { 337 | effectScopeOper.call(this); 338 | } 339 | 340 | function effectScopeOper(this: ReactiveNode): void { 341 | this.depsTail = undefined; 342 | this.flags = ReactiveFlags.None; 343 | purgeDeps(this); 344 | const sub = this.subs; 345 | if (sub !== undefined) { 346 | unlink(sub); 347 | } 348 | } 349 | 350 | function purgeDeps(sub: ReactiveNode) { 351 | const depsTail = sub.depsTail; 352 | let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps; 353 | while (dep !== undefined) { 354 | dep = unlink(dep, sub); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 | 5 |

6 | npm package 7 | Ask DeepWiki 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 | Image 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) 34 | - **Dart:** [void-signals/void_signals](https://github.com/void-signals/void_signals) 35 | - **Lua:** [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua) 36 | - **Lua 5.4:** [xuhuanzy/alien-signals-lua](https://github.com/xuhuanzy/alien-signals-lua) 37 | - **Luau:** [Nicell/alien-signals-luau](https://github.com/Nicell/alien-signals-luau) 38 | - **Java:** [CTRL-Neo-Studios/java-alien-signals](https://github.com/CTRL-Neo-Studios/java-alien-signals) 39 | - **C#:** [CTRL-Neo-Studios/csharp-alien-signals](https://github.com/CTRL-Neo-Studios/csharp-alien-signals) 40 | - **Go:** [delaneyj/alien-signals-go](https://github.com/delaneyj/alien-signals-go) 41 | 42 | ## Derived Projects 43 | 44 | - [Rajaniraiyn/react-alien-signals](https://github.com/Rajaniraiyn/react-alien-signals): React bindings for the alien-signals API 45 | - [CCherry07/alien-deepsignals](https://github.com/CCherry07/alien-deepsignals): Use alien-signals with the interface of a plain JavaScript object 46 | - [hunghg255/reactjs-signal](https://github.com/hunghg255/reactjs-signal): Share Store State with Signal Pattern 47 | - [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 48 | - [WebReflection/alien-signals](https://github.com/WebReflection/alien-signals): Preact signals like API and a class based approach for easy brand check 49 | - [@lift-html/alien](https://github.com/JLarky/lift-html/tree/main/packages/alien): Integrating alien-signals into lift-html 50 | 51 | ## Adoption 52 | 53 | - [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to v3.6 (PR: https://github.com/vuejs/core/pull/12349) 54 | - [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) 55 | - [flamrdevs/xignal](https://github.com/flamrdevs/xignal): Infrastructure for the reactive system 56 | - [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation 57 | - [unuse](https://github.com/un-ts/unuse): A framework-agnostic `use` library inspired by `VueUse` 58 | 59 | ## Usage 60 | 61 | #### Basic APIs 62 | 63 | ```ts 64 | import { signal, computed, effect } from 'alien-signals'; 65 | 66 | const count = signal(1); 67 | const doubleCount = computed(() => count() * 2); 68 | 69 | effect(() => { 70 | console.log(`Count is: ${count()}`); 71 | }); // Console: Count is: 1 72 | 73 | console.log(doubleCount()); // 2 74 | 75 | count(2); // Console: Count is: 2 76 | 77 | console.log(doubleCount()); // 4 78 | ``` 79 | 80 | #### Effect Scope 81 | 82 | ```ts 83 | import { signal, effect, effectScope } from 'alien-signals'; 84 | 85 | const count = signal(1); 86 | 87 | const stopScope = effectScope(() => { 88 | effect(() => { 89 | console.log(`Count in scope: ${count()}`); 90 | }); // Console: Count in scope: 1 91 | }); 92 | 93 | count(2); // Console: Count in scope: 2 94 | 95 | stopScope(); 96 | 97 | count(3); // No console output 98 | ``` 99 | 100 | #### Manual Triggering 101 | 102 | The `trigger()` function allows you to manually trigger updates for downstream dependencies when you've directly mutated a signal's value without using the signal setter: 103 | 104 | ```ts 105 | import { signal, computed, trigger } from 'alien-signals'; 106 | 107 | const arr = signal([]); 108 | const length = computed(() => arr().length); 109 | 110 | console.log(length()); // 0 111 | 112 | // Direct mutation doesn't automatically trigger updates 113 | arr().push(1); 114 | console.log(length()); // Still 0 115 | 116 | // Manually trigger updates 117 | trigger(arr); 118 | console.log(length()); // 1 119 | ``` 120 | 121 | You can also trigger multiple signals at once: 122 | 123 | ```ts 124 | import { signal, computed, trigger } from 'alien-signals'; 125 | 126 | const src1 = signal([]); 127 | const src2 = signal([]); 128 | const total = computed(() => src1().length + src2().length); 129 | 130 | src1().push(1); 131 | src2().push(2); 132 | 133 | trigger(() => { 134 | src1(); 135 | src2(); 136 | }); 137 | 138 | console.log(total()); // 2 139 | ``` 140 | 141 | #### Creating Your Own Surface API 142 | 143 | You can reuse alien-signals’ core algorithm via `createReactiveSystem()` to build your own signal API. For implementation examples, see: 144 | 145 | - [Starter template](https://github.com/johnsoncodehk/alien-signals-starter) (implements `.get()` & `.set()` methods like the [Signals proposal](https://github.com/tc39/proposal-signals)) 146 | - [stackblitz/alien-signals/src/index.ts](https://github.com/stackblitz/alien-signals/blob/master/src/index.ts) 147 | - [proposal-signals/signal-polyfill#44](https://github.com/proposal-signals/signal-polyfill/pull/44) 148 | 149 | 150 | ## About `propagate` and `checkDirty` functions 151 | 152 | 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. 153 | 154 | 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. 155 | 156 | #### `propagate` 157 | 158 | ```ts 159 | function propagate(link: Link): void { 160 | do { 161 | const sub = link.sub; 162 | 163 | let flags = sub.flags; 164 | 165 | if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) { 166 | sub.flags = flags | ReactiveFlags.Pending; 167 | } else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) { 168 | flags = ReactiveFlags.None; 169 | } else if (!(flags & ReactiveFlags.RecursedCheck)) { 170 | sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending; 171 | } else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) { 172 | sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending; 173 | flags &= ReactiveFlags.Mutable; 174 | } else { 175 | flags = ReactiveFlags.None; 176 | } 177 | 178 | if (flags & ReactiveFlags.Watching) { 179 | notify(sub); 180 | } 181 | 182 | if (flags & ReactiveFlags.Mutable) { 183 | const subSubs = sub.subs; 184 | if (subSubs !== undefined) { 185 | propagate(subSubs); 186 | } 187 | } 188 | 189 | link = link.nextSub!; 190 | } while (link !== undefined); 191 | } 192 | ``` 193 | 194 | #### `checkDirty` 195 | 196 | ```ts 197 | function checkDirty(link: Link, sub: ReactiveNode): boolean { 198 | do { 199 | const dep = link.dep; 200 | const depFlags = dep.flags; 201 | 202 | if (sub.flags & ReactiveFlags.Dirty) { 203 | return true; 204 | } else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) { 205 | if (update(dep)) { 206 | const subs = dep.subs!; 207 | if (subs.nextSub !== undefined) { 208 | shallowPropagate(subs); 209 | } 210 | return true; 211 | } 212 | } else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) { 213 | if (checkDirty(dep.deps!, dep)) { 214 | if (update(dep)) { 215 | const subs = dep.subs!; 216 | if (subs.nextSub !== undefined) { 217 | shallowPropagate(subs); 218 | } 219 | return true; 220 | } 221 | } else { 222 | dep.flags = depFlags & ~ReactiveFlags.Pending; 223 | } 224 | } 225 | 226 | link = link.nextDep!; 227 | } while (link !== undefined); 228 | 229 | return false; 230 | } 231 | ``` 232 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | rolldown: 24 | specifier: latest 25 | version: 1.0.0-beta.43 26 | typescript: 27 | specifier: latest 28 | version: 5.9.2 29 | vitest: 30 | specifier: latest 31 | version: 3.2.4 32 | 33 | packages: 34 | 35 | '@clack/core@0.3.5': 36 | resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} 37 | 38 | '@clack/prompts@0.8.2': 39 | resolution: {integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==} 40 | 41 | '@emnapi/core@1.5.0': 42 | resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} 43 | 44 | '@emnapi/runtime@1.5.0': 45 | resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} 46 | 47 | '@emnapi/wasi-threads@1.1.0': 48 | resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} 49 | 50 | '@esbuild/aix-ppc64@0.25.9': 51 | resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} 52 | engines: {node: '>=18'} 53 | cpu: [ppc64] 54 | os: [aix] 55 | 56 | '@esbuild/android-arm64@0.25.9': 57 | resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} 58 | engines: {node: '>=18'} 59 | cpu: [arm64] 60 | os: [android] 61 | 62 | '@esbuild/android-arm@0.25.9': 63 | resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} 64 | engines: {node: '>=18'} 65 | cpu: [arm] 66 | os: [android] 67 | 68 | '@esbuild/android-x64@0.25.9': 69 | resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} 70 | engines: {node: '>=18'} 71 | cpu: [x64] 72 | os: [android] 73 | 74 | '@esbuild/darwin-arm64@0.25.9': 75 | resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} 76 | engines: {node: '>=18'} 77 | cpu: [arm64] 78 | os: [darwin] 79 | 80 | '@esbuild/darwin-x64@0.25.9': 81 | resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} 82 | engines: {node: '>=18'} 83 | cpu: [x64] 84 | os: [darwin] 85 | 86 | '@esbuild/freebsd-arm64@0.25.9': 87 | resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} 88 | engines: {node: '>=18'} 89 | cpu: [arm64] 90 | os: [freebsd] 91 | 92 | '@esbuild/freebsd-x64@0.25.9': 93 | resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} 94 | engines: {node: '>=18'} 95 | cpu: [x64] 96 | os: [freebsd] 97 | 98 | '@esbuild/linux-arm64@0.25.9': 99 | resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} 100 | engines: {node: '>=18'} 101 | cpu: [arm64] 102 | os: [linux] 103 | 104 | '@esbuild/linux-arm@0.25.9': 105 | resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} 106 | engines: {node: '>=18'} 107 | cpu: [arm] 108 | os: [linux] 109 | 110 | '@esbuild/linux-ia32@0.25.9': 111 | resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} 112 | engines: {node: '>=18'} 113 | cpu: [ia32] 114 | os: [linux] 115 | 116 | '@esbuild/linux-loong64@0.25.9': 117 | resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} 118 | engines: {node: '>=18'} 119 | cpu: [loong64] 120 | os: [linux] 121 | 122 | '@esbuild/linux-mips64el@0.25.9': 123 | resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} 124 | engines: {node: '>=18'} 125 | cpu: [mips64el] 126 | os: [linux] 127 | 128 | '@esbuild/linux-ppc64@0.25.9': 129 | resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} 130 | engines: {node: '>=18'} 131 | cpu: [ppc64] 132 | os: [linux] 133 | 134 | '@esbuild/linux-riscv64@0.25.9': 135 | resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} 136 | engines: {node: '>=18'} 137 | cpu: [riscv64] 138 | os: [linux] 139 | 140 | '@esbuild/linux-s390x@0.25.9': 141 | resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} 142 | engines: {node: '>=18'} 143 | cpu: [s390x] 144 | os: [linux] 145 | 146 | '@esbuild/linux-x64@0.25.9': 147 | resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} 148 | engines: {node: '>=18'} 149 | cpu: [x64] 150 | os: [linux] 151 | 152 | '@esbuild/netbsd-arm64@0.25.9': 153 | resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} 154 | engines: {node: '>=18'} 155 | cpu: [arm64] 156 | os: [netbsd] 157 | 158 | '@esbuild/netbsd-x64@0.25.9': 159 | resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} 160 | engines: {node: '>=18'} 161 | cpu: [x64] 162 | os: [netbsd] 163 | 164 | '@esbuild/openbsd-arm64@0.25.9': 165 | resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} 166 | engines: {node: '>=18'} 167 | cpu: [arm64] 168 | os: [openbsd] 169 | 170 | '@esbuild/openbsd-x64@0.25.9': 171 | resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} 172 | engines: {node: '>=18'} 173 | cpu: [x64] 174 | os: [openbsd] 175 | 176 | '@esbuild/openharmony-arm64@0.25.9': 177 | resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} 178 | engines: {node: '>=18'} 179 | cpu: [arm64] 180 | os: [openharmony] 181 | 182 | '@esbuild/sunos-x64@0.25.9': 183 | resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} 184 | engines: {node: '>=18'} 185 | cpu: [x64] 186 | os: [sunos] 187 | 188 | '@esbuild/win32-arm64@0.25.9': 189 | resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} 190 | engines: {node: '>=18'} 191 | cpu: [arm64] 192 | os: [win32] 193 | 194 | '@esbuild/win32-ia32@0.25.9': 195 | resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} 196 | engines: {node: '>=18'} 197 | cpu: [ia32] 198 | os: [win32] 199 | 200 | '@esbuild/win32-x64@0.25.9': 201 | resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} 202 | engines: {node: '>=18'} 203 | cpu: [x64] 204 | os: [win32] 205 | 206 | '@isaacs/balanced-match@4.0.1': 207 | resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} 208 | engines: {node: 20 || >=22} 209 | 210 | '@isaacs/brace-expansion@5.0.0': 211 | resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} 212 | engines: {node: 20 || >=22} 213 | 214 | '@isaacs/cliui@8.0.2': 215 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 216 | engines: {node: '>=12'} 217 | 218 | '@jest/schemas@29.6.3': 219 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} 220 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 221 | 222 | '@jridgewell/sourcemap-codec@1.5.5': 223 | resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 224 | 225 | '@napi-rs/wasm-runtime@1.0.7': 226 | resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} 227 | 228 | '@oxc-project/types@0.94.0': 229 | resolution: {integrity: sha512-+UgQT/4o59cZfH6Cp7G0hwmqEQ0wE+AdIwhikdwnhWI9Dp8CgSY081+Q3O67/wq3VJu8mgUEB93J9EHHn70fOw==} 230 | 231 | '@pkgjs/parseargs@0.11.0': 232 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 233 | engines: {node: '>=14'} 234 | 235 | '@rolldown/binding-android-arm64@1.0.0-beta.43': 236 | resolution: {integrity: sha512-TP8bcPOb1s6UmY5syhXrDn9k0XkYcw+XaoylTN4cJxf0JOVS2j682I3aTcpfT51hOFGr2bRwNKN9RZ19XxeQbA==} 237 | engines: {node: ^20.19.0 || >=22.12.0} 238 | cpu: [arm64] 239 | os: [android] 240 | 241 | '@rolldown/binding-darwin-arm64@1.0.0-beta.43': 242 | resolution: {integrity: sha512-kuVWnZsE4vEjMF/10SbSUyzucIW2zmdsqFghYMqy+fsjXnRHg0luTU6qWF8IqJf4Cbpm9NEZRnjIEPpAbdiSNQ==} 243 | engines: {node: ^20.19.0 || >=22.12.0} 244 | cpu: [arm64] 245 | os: [darwin] 246 | 247 | '@rolldown/binding-darwin-x64@1.0.0-beta.43': 248 | resolution: {integrity: sha512-u9Ps4sh6lcmJ3vgLtyEg/x4jlhI64U0mM93Ew+tlfFdLDe7yKyA+Fe80cpr2n1mNCeZXrvTSbZluKpXQ0GxLjw==} 249 | engines: {node: ^20.19.0 || >=22.12.0} 250 | cpu: [x64] 251 | os: [darwin] 252 | 253 | '@rolldown/binding-freebsd-x64@1.0.0-beta.43': 254 | resolution: {integrity: sha512-h9lUtVtXgfbk/tnicMpbFfZ3DJvk5Zn2IvmlC1/e0+nUfwoc/TFqpfrRRqcNBXk/e+xiWMSKv6b0MF8N+Rtvlg==} 255 | engines: {node: ^20.19.0 || >=22.12.0} 256 | cpu: [x64] 257 | os: [freebsd] 258 | 259 | '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': 260 | resolution: {integrity: sha512-IX2C6bA6wM2rX/RvD75ko+ix9yxPKjKGGq7pOhB8wGI4Z4fqX5B1nDHga/qMDmAdCAR1m9ymzxkmqhm/AFYf7A==} 261 | engines: {node: ^20.19.0 || >=22.12.0} 262 | cpu: [arm] 263 | os: [linux] 264 | 265 | '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': 266 | resolution: {integrity: sha512-mcjd57vEj+CEQbZAzUiaxNzNgwwgOpFtZBWcINm8DNscvkXl5b/s622Z1dqGNWSdrZmdjdC6LWMvu8iHM6v9sQ==} 267 | engines: {node: ^20.19.0 || >=22.12.0} 268 | cpu: [arm64] 269 | os: [linux] 270 | 271 | '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': 272 | resolution: {integrity: sha512-Pa8QMwlkrztTo/1mVjZmPIQ44tCSci10TBqxzVBvXVA5CFh5EpiEi99fPSll2dHG2uT4dCOMeC6fIhyDdb0zXA==} 273 | engines: {node: ^20.19.0 || >=22.12.0} 274 | cpu: [arm64] 275 | os: [linux] 276 | 277 | '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': 278 | resolution: {integrity: sha512-BgynXKMjeaX4AfWLARhOKDetBOOghnSiVRjAHVvhiAaDXgdQN8e65mSmXRiVoVtD3cHXx/cfU8Gw0p0K+qYKVQ==} 279 | engines: {node: ^20.19.0 || >=22.12.0} 280 | cpu: [x64] 281 | os: [linux] 282 | 283 | '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': 284 | resolution: {integrity: sha512-VIsoPlOB/tDSAw9CySckBYysoIBqLeps1/umNSYUD8pMtalJyzMTneAVI1HrUdf4ceFmQ5vARoLIXSsPwVFxNg==} 285 | engines: {node: ^20.19.0 || >=22.12.0} 286 | cpu: [x64] 287 | os: [linux] 288 | 289 | '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': 290 | resolution: {integrity: sha512-YDXTxVJG67PqTQMKyjVJSddoPbSWJ4yRz/E3xzTLHqNrTDGY0UuhG8EMr8zsYnfH/0cPFJ3wjQd/hJWHuR6nkA==} 291 | engines: {node: ^20.19.0 || >=22.12.0} 292 | cpu: [arm64] 293 | os: [openharmony] 294 | 295 | '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': 296 | resolution: {integrity: sha512-3M+2DmorXvDuAIGYQ9Z93Oy1G9ETkejLwdXXb1uRTgKN9pMcu7N+KG2zDrJwqyxeeLIFE22AZGtSJm3PJbNu9Q==} 297 | engines: {node: '>=14.0.0'} 298 | cpu: [wasm32] 299 | 300 | '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': 301 | resolution: {integrity: sha512-/B1j1pJs33y9ywtslOMxryUPHq8zIGu/OGEc2gyed0slimJ8fX2uR/SaJVhB4+NEgCFIeYDR4CX6jynAkeRuCA==} 302 | engines: {node: ^20.19.0 || >=22.12.0} 303 | cpu: [arm64] 304 | os: [win32] 305 | 306 | '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': 307 | resolution: {integrity: sha512-29oG1swCz7hNP+CQYrsM4EtylsKwuYzM8ljqbqC5TsQwmKat7P8ouDpImsqg/GZxFSXcPP9ezQm0Q0wQwGM3JA==} 308 | engines: {node: ^20.19.0 || >=22.12.0} 309 | cpu: [ia32] 310 | os: [win32] 311 | 312 | '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': 313 | resolution: {integrity: sha512-eWBV1Ef3gfGNehxVGCyXs7wLayRIgCmyItuCZwYYXW5bsk4EvR4n2GP5m3ohjnx7wdiY3nLmwQfH2Knb5gbNZw==} 314 | engines: {node: ^20.19.0 || >=22.12.0} 315 | cpu: [x64] 316 | os: [win32] 317 | 318 | '@rolldown/pluginutils@1.0.0-beta.43': 319 | resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} 320 | 321 | '@rollup/rollup-android-arm-eabi@4.46.2': 322 | resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} 323 | cpu: [arm] 324 | os: [android] 325 | 326 | '@rollup/rollup-android-arm64@4.46.2': 327 | resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} 328 | cpu: [arm64] 329 | os: [android] 330 | 331 | '@rollup/rollup-darwin-arm64@4.46.2': 332 | resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} 333 | cpu: [arm64] 334 | os: [darwin] 335 | 336 | '@rollup/rollup-darwin-x64@4.46.2': 337 | resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} 338 | cpu: [x64] 339 | os: [darwin] 340 | 341 | '@rollup/rollup-freebsd-arm64@4.46.2': 342 | resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} 343 | cpu: [arm64] 344 | os: [freebsd] 345 | 346 | '@rollup/rollup-freebsd-x64@4.46.2': 347 | resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} 348 | cpu: [x64] 349 | os: [freebsd] 350 | 351 | '@rollup/rollup-linux-arm-gnueabihf@4.46.2': 352 | resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} 353 | cpu: [arm] 354 | os: [linux] 355 | 356 | '@rollup/rollup-linux-arm-musleabihf@4.46.2': 357 | resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} 358 | cpu: [arm] 359 | os: [linux] 360 | 361 | '@rollup/rollup-linux-arm64-gnu@4.46.2': 362 | resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} 363 | cpu: [arm64] 364 | os: [linux] 365 | 366 | '@rollup/rollup-linux-arm64-musl@4.46.2': 367 | resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} 368 | cpu: [arm64] 369 | os: [linux] 370 | 371 | '@rollup/rollup-linux-loongarch64-gnu@4.46.2': 372 | resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} 373 | cpu: [loong64] 374 | os: [linux] 375 | 376 | '@rollup/rollup-linux-ppc64-gnu@4.46.2': 377 | resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} 378 | cpu: [ppc64] 379 | os: [linux] 380 | 381 | '@rollup/rollup-linux-riscv64-gnu@4.46.2': 382 | resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} 383 | cpu: [riscv64] 384 | os: [linux] 385 | 386 | '@rollup/rollup-linux-riscv64-musl@4.46.2': 387 | resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} 388 | cpu: [riscv64] 389 | os: [linux] 390 | 391 | '@rollup/rollup-linux-s390x-gnu@4.46.2': 392 | resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} 393 | cpu: [s390x] 394 | os: [linux] 395 | 396 | '@rollup/rollup-linux-x64-gnu@4.46.2': 397 | resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} 398 | cpu: [x64] 399 | os: [linux] 400 | 401 | '@rollup/rollup-linux-x64-musl@4.46.2': 402 | resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} 403 | cpu: [x64] 404 | os: [linux] 405 | 406 | '@rollup/rollup-win32-arm64-msvc@4.46.2': 407 | resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} 408 | cpu: [arm64] 409 | os: [win32] 410 | 411 | '@rollup/rollup-win32-ia32-msvc@4.46.2': 412 | resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} 413 | cpu: [ia32] 414 | os: [win32] 415 | 416 | '@rollup/rollup-win32-x64-msvc@4.46.2': 417 | resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} 418 | cpu: [x64] 419 | os: [win32] 420 | 421 | '@sinclair/typebox@0.27.8': 422 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 423 | 424 | '@tsslint/cli@2.0.1': 425 | resolution: {integrity: sha512-7g+jVQrs9d3D9kMpT1TBlLBQhmQriDGekyywwvrBnmocA1KJvVskms7t3xHxkGupNrF5dH+TZaQVacwCKw93Sg==} 426 | hasBin: true 427 | peerDependencies: 428 | typescript: '*' 429 | 430 | '@tsslint/config@2.0.1': 431 | resolution: {integrity: sha512-al2vj06aenhqMs2/Vm3p2gVNAFCbpuw7C3E+DFWXVpKH3+g48cJaPymRI3Ma4mQqmvSMIPF5VFN/H1SSYzdwNw==} 432 | 433 | '@tsslint/core@2.0.1': 434 | resolution: {integrity: sha512-Freonkuz+cuIyMahmDgz3xLxfgKON79Y/K7LcKexJVqRnIYMIQcA0wxRKjk0eCNOj59PlzV8RYwYGvFaxDd8Qw==} 435 | 436 | '@tsslint/types@2.0.1': 437 | resolution: {integrity: sha512-0XllhTjC3eVmbVNPAIAGlmAsIOymKbi5IzAk5QnreBDBxBA9jCPGl1NB0ijtBMrUai6zWiRB3/hPvikeEGs3hQ==} 438 | 439 | '@tybys/wasm-util@0.10.1': 440 | resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} 441 | 442 | '@types/chai@5.2.2': 443 | resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} 444 | 445 | '@types/deep-eql@4.0.2': 446 | resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} 447 | 448 | '@types/estree@1.0.8': 449 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 450 | 451 | '@vitest/expect@3.2.4': 452 | resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} 453 | 454 | '@vitest/mocker@3.2.4': 455 | resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} 456 | peerDependencies: 457 | msw: ^2.4.9 458 | vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 459 | peerDependenciesMeta: 460 | msw: 461 | optional: true 462 | vite: 463 | optional: true 464 | 465 | '@vitest/pretty-format@3.2.4': 466 | resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} 467 | 468 | '@vitest/runner@3.2.4': 469 | resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} 470 | 471 | '@vitest/snapshot@3.2.4': 472 | resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} 473 | 474 | '@vitest/spy@3.2.4': 475 | resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} 476 | 477 | '@vitest/utils@3.2.4': 478 | resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} 479 | 480 | '@volar/language-core@2.4.23': 481 | resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} 482 | 483 | '@volar/language-hub@0.0.1': 484 | resolution: {integrity: sha512-2eOUnlMKTyjtlXIVd+6pfAtcuVugxCOgpNgcLWmlPuncQTG5C1E5mTDL/PUMw7aEnLySUOtMTIp8lT3vk/7w6Q==} 485 | 486 | '@volar/source-map@2.4.23': 487 | resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} 488 | 489 | '@volar/typescript@2.4.23': 490 | resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} 491 | 492 | ansi-regex@5.0.1: 493 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 494 | engines: {node: '>=8'} 495 | 496 | ansi-regex@6.1.0: 497 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 498 | engines: {node: '>=12'} 499 | 500 | ansi-styles@4.3.0: 501 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 502 | engines: {node: '>=8'} 503 | 504 | ansi-styles@5.2.0: 505 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 506 | engines: {node: '>=10'} 507 | 508 | ansi-styles@6.2.1: 509 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 510 | engines: {node: '>=12'} 511 | 512 | ansis@4.2.0: 513 | resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} 514 | engines: {node: '>=14'} 515 | 516 | assertion-error@2.0.1: 517 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 518 | engines: {node: '>=12'} 519 | 520 | balanced-match@1.0.2: 521 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 522 | 523 | brace-expansion@2.0.2: 524 | resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 525 | 526 | cac@6.7.14: 527 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 528 | engines: {node: '>=8'} 529 | 530 | chai@5.2.1: 531 | resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} 532 | engines: {node: '>=18'} 533 | 534 | chalk@4.1.2: 535 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 536 | engines: {node: '>=10'} 537 | 538 | check-error@2.1.1: 539 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 540 | engines: {node: '>= 16'} 541 | 542 | color-convert@2.0.1: 543 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 544 | engines: {node: '>=7.0.0'} 545 | 546 | color-name@1.1.4: 547 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 548 | 549 | cross-spawn@7.0.6: 550 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 551 | engines: {node: '>= 8'} 552 | 553 | debug@4.4.1: 554 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 555 | engines: {node: '>=6.0'} 556 | peerDependencies: 557 | supports-color: '*' 558 | peerDependenciesMeta: 559 | supports-color: 560 | optional: true 561 | 562 | deep-eql@5.0.2: 563 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 564 | engines: {node: '>=6'} 565 | 566 | diff-sequences@29.6.3: 567 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 568 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 569 | 570 | eastasianwidth@0.2.0: 571 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 572 | 573 | emoji-regex@8.0.0: 574 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 575 | 576 | emoji-regex@9.2.2: 577 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 578 | 579 | es-module-lexer@1.7.0: 580 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 581 | 582 | esbuild@0.25.9: 583 | resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} 584 | engines: {node: '>=18'} 585 | hasBin: true 586 | 587 | estree-walker@3.0.3: 588 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 589 | 590 | expect-type@1.2.2: 591 | resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} 592 | engines: {node: '>=12.0.0'} 593 | 594 | fdir@6.5.0: 595 | resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 596 | engines: {node: '>=12.0.0'} 597 | peerDependencies: 598 | picomatch: ^3 || ^4 599 | peerDependenciesMeta: 600 | picomatch: 601 | optional: true 602 | 603 | foreground-child@3.3.1: 604 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 605 | engines: {node: '>=14'} 606 | 607 | fsevents@2.3.3: 608 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 609 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 610 | os: [darwin] 611 | 612 | glob@10.4.5: 613 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 614 | hasBin: true 615 | 616 | has-flag@4.0.0: 617 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 618 | engines: {node: '>=8'} 619 | 620 | is-fullwidth-code-point@3.0.0: 621 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 622 | engines: {node: '>=8'} 623 | 624 | isexe@2.0.0: 625 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 626 | 627 | jackspeak@3.4.3: 628 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 629 | 630 | jest-diff@29.7.0: 631 | resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} 632 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 633 | 634 | jest-extended@6.0.0: 635 | resolution: {integrity: sha512-SM249N/q33YQ9XE8E06qZSnFuuV4GQFx7WrrmIj4wQUAP43jAo6budLT482jdBhf8ASwUiEEfJNjej0UusYs5A==} 636 | engines: {node: ^18.12.0 || ^20.9.0 || ^22.11.0 || >=23.0.0} 637 | peerDependencies: 638 | jest: '>=27.2.5' 639 | typescript: '>=5.0.0' 640 | peerDependenciesMeta: 641 | jest: 642 | optional: true 643 | 644 | jest-get-type@29.6.3: 645 | resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} 646 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 647 | 648 | js-tokens@9.0.1: 649 | resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} 650 | 651 | json5@2.2.3: 652 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 653 | engines: {node: '>=6'} 654 | hasBin: true 655 | 656 | loupe@3.2.0: 657 | resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} 658 | 659 | lru-cache@10.4.3: 660 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 661 | 662 | magic-string@0.30.17: 663 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 664 | 665 | minimatch@10.0.3: 666 | resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} 667 | engines: {node: 20 || >=22} 668 | 669 | minimatch@9.0.5: 670 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 671 | engines: {node: '>=16 || 14 >=14.17'} 672 | 673 | minipass@7.1.2: 674 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 675 | engines: {node: '>=16 || 14 >=14.17'} 676 | 677 | mitata@1.0.34: 678 | resolution: {integrity: sha512-Mc3zrtNBKIMeHSCQ0XqRLo1vbdIx1wvFV9c8NJAiyho6AjNfMY8bVhbS12bwciUdd1t4rj8099CH3N3NFahaUA==} 679 | 680 | ms@2.1.3: 681 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 682 | 683 | nanoid@3.3.11: 684 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 685 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 686 | hasBin: true 687 | 688 | package-json-from-dist@1.0.1: 689 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 690 | 691 | path-browserify@1.0.1: 692 | resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 693 | 694 | path-key@3.1.1: 695 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 696 | engines: {node: '>=8'} 697 | 698 | path-scurry@1.11.1: 699 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 700 | engines: {node: '>=16 || 14 >=14.18'} 701 | 702 | pathe@2.0.3: 703 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 704 | 705 | pathval@2.0.1: 706 | resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} 707 | engines: {node: '>= 14.16'} 708 | 709 | picocolors@1.1.1: 710 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 711 | 712 | picomatch@4.0.3: 713 | resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 714 | engines: {node: '>=12'} 715 | 716 | postcss@8.5.6: 717 | resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 718 | engines: {node: ^10 || ^12 || >=14} 719 | 720 | pretty-format@29.7.0: 721 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} 722 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 723 | 724 | react-is@18.3.1: 725 | resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} 726 | 727 | rolldown@1.0.0-beta.43: 728 | resolution: {integrity: sha512-6RcqyRx0tY1MlRLnjXPp/849Rl/CPFhzpGGwNPEPjKwqBMqPq/Rbbkxasa8s0x+IkUk46ty4jazb5skZ/Vgdhw==} 729 | engines: {node: ^20.19.0 || >=22.12.0} 730 | hasBin: true 731 | 732 | rollup@4.46.2: 733 | resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} 734 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 735 | hasBin: true 736 | 737 | shebang-command@2.0.0: 738 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 739 | engines: {node: '>=8'} 740 | 741 | shebang-regex@3.0.0: 742 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 743 | engines: {node: '>=8'} 744 | 745 | siginfo@2.0.0: 746 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 747 | 748 | signal-exit@4.1.0: 749 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 750 | engines: {node: '>=14'} 751 | 752 | sisteransi@1.0.5: 753 | resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 754 | 755 | source-map-js@1.2.1: 756 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 757 | engines: {node: '>=0.10.0'} 758 | 759 | stackback@0.0.2: 760 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 761 | 762 | std-env@3.9.0: 763 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} 764 | 765 | string-width@4.2.3: 766 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 767 | engines: {node: '>=8'} 768 | 769 | string-width@5.1.2: 770 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 771 | engines: {node: '>=12'} 772 | 773 | strip-ansi@6.0.1: 774 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 775 | engines: {node: '>=8'} 776 | 777 | strip-ansi@7.1.0: 778 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 779 | engines: {node: '>=12'} 780 | 781 | strip-literal@3.0.0: 782 | resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} 783 | 784 | supports-color@7.2.0: 785 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 786 | engines: {node: '>=8'} 787 | 788 | tinybench@2.9.0: 789 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 790 | 791 | tinyexec@0.3.2: 792 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 793 | 794 | tinyglobby@0.2.14: 795 | resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} 796 | engines: {node: '>=12.0.0'} 797 | 798 | tinypool@1.1.1: 799 | resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} 800 | engines: {node: ^18.0.0 || >=20.0.0} 801 | 802 | tinyrainbow@2.0.0: 803 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 804 | engines: {node: '>=14.0.0'} 805 | 806 | tinyspy@4.0.3: 807 | resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} 808 | engines: {node: '>=14.0.0'} 809 | 810 | ts-api-utils@2.1.0: 811 | resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 812 | engines: {node: '>=18.12'} 813 | peerDependencies: 814 | typescript: '>=4.8.4' 815 | 816 | tslib@2.8.1: 817 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 818 | 819 | typescript@5.9.2: 820 | resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} 821 | engines: {node: '>=14.17'} 822 | hasBin: true 823 | 824 | vite-node@3.2.4: 825 | resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} 826 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 827 | hasBin: true 828 | 829 | vite@7.1.2: 830 | resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} 831 | engines: {node: ^20.19.0 || >=22.12.0} 832 | hasBin: true 833 | peerDependencies: 834 | '@types/node': ^20.19.0 || >=22.12.0 835 | jiti: '>=1.21.0' 836 | less: ^4.0.0 837 | lightningcss: ^1.21.0 838 | sass: ^1.70.0 839 | sass-embedded: ^1.70.0 840 | stylus: '>=0.54.8' 841 | sugarss: ^5.0.0 842 | terser: ^5.16.0 843 | tsx: ^4.8.1 844 | yaml: ^2.4.2 845 | peerDependenciesMeta: 846 | '@types/node': 847 | optional: true 848 | jiti: 849 | optional: true 850 | less: 851 | optional: true 852 | lightningcss: 853 | optional: true 854 | sass: 855 | optional: true 856 | sass-embedded: 857 | optional: true 858 | stylus: 859 | optional: true 860 | sugarss: 861 | optional: true 862 | terser: 863 | optional: true 864 | tsx: 865 | optional: true 866 | yaml: 867 | optional: true 868 | 869 | vitest@3.2.4: 870 | resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} 871 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 872 | hasBin: true 873 | peerDependencies: 874 | '@edge-runtime/vm': '*' 875 | '@types/debug': ^4.1.12 876 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 877 | '@vitest/browser': 3.2.4 878 | '@vitest/ui': 3.2.4 879 | happy-dom: '*' 880 | jsdom: '*' 881 | peerDependenciesMeta: 882 | '@edge-runtime/vm': 883 | optional: true 884 | '@types/debug': 885 | optional: true 886 | '@types/node': 887 | optional: true 888 | '@vitest/browser': 889 | optional: true 890 | '@vitest/ui': 891 | optional: true 892 | happy-dom: 893 | optional: true 894 | jsdom: 895 | optional: true 896 | 897 | vscode-uri@3.1.0: 898 | resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} 899 | 900 | which@2.0.2: 901 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 902 | engines: {node: '>= 8'} 903 | hasBin: true 904 | 905 | why-is-node-running@2.3.0: 906 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 907 | engines: {node: '>=8'} 908 | hasBin: true 909 | 910 | wrap-ansi@7.0.0: 911 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 912 | engines: {node: '>=10'} 913 | 914 | wrap-ansi@8.1.0: 915 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 916 | engines: {node: '>=12'} 917 | 918 | snapshots: 919 | 920 | '@clack/core@0.3.5': 921 | dependencies: 922 | picocolors: 1.1.1 923 | sisteransi: 1.0.5 924 | 925 | '@clack/prompts@0.8.2': 926 | dependencies: 927 | '@clack/core': 0.3.5 928 | picocolors: 1.1.1 929 | sisteransi: 1.0.5 930 | 931 | '@emnapi/core@1.5.0': 932 | dependencies: 933 | '@emnapi/wasi-threads': 1.1.0 934 | tslib: 2.8.1 935 | optional: true 936 | 937 | '@emnapi/runtime@1.5.0': 938 | dependencies: 939 | tslib: 2.8.1 940 | optional: true 941 | 942 | '@emnapi/wasi-threads@1.1.0': 943 | dependencies: 944 | tslib: 2.8.1 945 | optional: true 946 | 947 | '@esbuild/aix-ppc64@0.25.9': 948 | optional: true 949 | 950 | '@esbuild/android-arm64@0.25.9': 951 | optional: true 952 | 953 | '@esbuild/android-arm@0.25.9': 954 | optional: true 955 | 956 | '@esbuild/android-x64@0.25.9': 957 | optional: true 958 | 959 | '@esbuild/darwin-arm64@0.25.9': 960 | optional: true 961 | 962 | '@esbuild/darwin-x64@0.25.9': 963 | optional: true 964 | 965 | '@esbuild/freebsd-arm64@0.25.9': 966 | optional: true 967 | 968 | '@esbuild/freebsd-x64@0.25.9': 969 | optional: true 970 | 971 | '@esbuild/linux-arm64@0.25.9': 972 | optional: true 973 | 974 | '@esbuild/linux-arm@0.25.9': 975 | optional: true 976 | 977 | '@esbuild/linux-ia32@0.25.9': 978 | optional: true 979 | 980 | '@esbuild/linux-loong64@0.25.9': 981 | optional: true 982 | 983 | '@esbuild/linux-mips64el@0.25.9': 984 | optional: true 985 | 986 | '@esbuild/linux-ppc64@0.25.9': 987 | optional: true 988 | 989 | '@esbuild/linux-riscv64@0.25.9': 990 | optional: true 991 | 992 | '@esbuild/linux-s390x@0.25.9': 993 | optional: true 994 | 995 | '@esbuild/linux-x64@0.25.9': 996 | optional: true 997 | 998 | '@esbuild/netbsd-arm64@0.25.9': 999 | optional: true 1000 | 1001 | '@esbuild/netbsd-x64@0.25.9': 1002 | optional: true 1003 | 1004 | '@esbuild/openbsd-arm64@0.25.9': 1005 | optional: true 1006 | 1007 | '@esbuild/openbsd-x64@0.25.9': 1008 | optional: true 1009 | 1010 | '@esbuild/openharmony-arm64@0.25.9': 1011 | optional: true 1012 | 1013 | '@esbuild/sunos-x64@0.25.9': 1014 | optional: true 1015 | 1016 | '@esbuild/win32-arm64@0.25.9': 1017 | optional: true 1018 | 1019 | '@esbuild/win32-ia32@0.25.9': 1020 | optional: true 1021 | 1022 | '@esbuild/win32-x64@0.25.9': 1023 | optional: true 1024 | 1025 | '@isaacs/balanced-match@4.0.1': {} 1026 | 1027 | '@isaacs/brace-expansion@5.0.0': 1028 | dependencies: 1029 | '@isaacs/balanced-match': 4.0.1 1030 | 1031 | '@isaacs/cliui@8.0.2': 1032 | dependencies: 1033 | string-width: 5.1.2 1034 | string-width-cjs: string-width@4.2.3 1035 | strip-ansi: 7.1.0 1036 | strip-ansi-cjs: strip-ansi@6.0.1 1037 | wrap-ansi: 8.1.0 1038 | wrap-ansi-cjs: wrap-ansi@7.0.0 1039 | 1040 | '@jest/schemas@29.6.3': 1041 | dependencies: 1042 | '@sinclair/typebox': 0.27.8 1043 | 1044 | '@jridgewell/sourcemap-codec@1.5.5': {} 1045 | 1046 | '@napi-rs/wasm-runtime@1.0.7': 1047 | dependencies: 1048 | '@emnapi/core': 1.5.0 1049 | '@emnapi/runtime': 1.5.0 1050 | '@tybys/wasm-util': 0.10.1 1051 | optional: true 1052 | 1053 | '@oxc-project/types@0.94.0': {} 1054 | 1055 | '@pkgjs/parseargs@0.11.0': 1056 | optional: true 1057 | 1058 | '@rolldown/binding-android-arm64@1.0.0-beta.43': 1059 | optional: true 1060 | 1061 | '@rolldown/binding-darwin-arm64@1.0.0-beta.43': 1062 | optional: true 1063 | 1064 | '@rolldown/binding-darwin-x64@1.0.0-beta.43': 1065 | optional: true 1066 | 1067 | '@rolldown/binding-freebsd-x64@1.0.0-beta.43': 1068 | optional: true 1069 | 1070 | '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': 1071 | optional: true 1072 | 1073 | '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': 1074 | optional: true 1075 | 1076 | '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': 1077 | optional: true 1078 | 1079 | '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': 1080 | optional: true 1081 | 1082 | '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': 1083 | optional: true 1084 | 1085 | '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': 1086 | optional: true 1087 | 1088 | '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': 1089 | dependencies: 1090 | '@napi-rs/wasm-runtime': 1.0.7 1091 | optional: true 1092 | 1093 | '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': 1094 | optional: true 1095 | 1096 | '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': 1097 | optional: true 1098 | 1099 | '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': 1100 | optional: true 1101 | 1102 | '@rolldown/pluginutils@1.0.0-beta.43': {} 1103 | 1104 | '@rollup/rollup-android-arm-eabi@4.46.2': 1105 | optional: true 1106 | 1107 | '@rollup/rollup-android-arm64@4.46.2': 1108 | optional: true 1109 | 1110 | '@rollup/rollup-darwin-arm64@4.46.2': 1111 | optional: true 1112 | 1113 | '@rollup/rollup-darwin-x64@4.46.2': 1114 | optional: true 1115 | 1116 | '@rollup/rollup-freebsd-arm64@4.46.2': 1117 | optional: true 1118 | 1119 | '@rollup/rollup-freebsd-x64@4.46.2': 1120 | optional: true 1121 | 1122 | '@rollup/rollup-linux-arm-gnueabihf@4.46.2': 1123 | optional: true 1124 | 1125 | '@rollup/rollup-linux-arm-musleabihf@4.46.2': 1126 | optional: true 1127 | 1128 | '@rollup/rollup-linux-arm64-gnu@4.46.2': 1129 | optional: true 1130 | 1131 | '@rollup/rollup-linux-arm64-musl@4.46.2': 1132 | optional: true 1133 | 1134 | '@rollup/rollup-linux-loongarch64-gnu@4.46.2': 1135 | optional: true 1136 | 1137 | '@rollup/rollup-linux-ppc64-gnu@4.46.2': 1138 | optional: true 1139 | 1140 | '@rollup/rollup-linux-riscv64-gnu@4.46.2': 1141 | optional: true 1142 | 1143 | '@rollup/rollup-linux-riscv64-musl@4.46.2': 1144 | optional: true 1145 | 1146 | '@rollup/rollup-linux-s390x-gnu@4.46.2': 1147 | optional: true 1148 | 1149 | '@rollup/rollup-linux-x64-gnu@4.46.2': 1150 | optional: true 1151 | 1152 | '@rollup/rollup-linux-x64-musl@4.46.2': 1153 | optional: true 1154 | 1155 | '@rollup/rollup-win32-arm64-msvc@4.46.2': 1156 | optional: true 1157 | 1158 | '@rollup/rollup-win32-ia32-msvc@4.46.2': 1159 | optional: true 1160 | 1161 | '@rollup/rollup-win32-x64-msvc@4.46.2': 1162 | optional: true 1163 | 1164 | '@sinclair/typebox@0.27.8': {} 1165 | 1166 | '@tsslint/cli@2.0.1(typescript@5.9.2)': 1167 | dependencies: 1168 | '@clack/prompts': 0.8.2 1169 | '@tsslint/config': 2.0.1(typescript@5.9.2) 1170 | '@tsslint/core': 2.0.1 1171 | '@volar/language-core': 2.4.23 1172 | '@volar/language-hub': 0.0.1 1173 | '@volar/typescript': 2.4.23 1174 | glob: 10.4.5 1175 | json5: 2.2.3 1176 | typescript: 5.9.2 1177 | 1178 | '@tsslint/config@2.0.1(typescript@5.9.2)': 1179 | dependencies: 1180 | '@tsslint/types': 2.0.1 1181 | ts-api-utils: 2.1.0(typescript@5.9.2) 1182 | transitivePeerDependencies: 1183 | - typescript 1184 | 1185 | '@tsslint/core@2.0.1': 1186 | dependencies: 1187 | '@tsslint/types': 2.0.1 1188 | esbuild: 0.25.9 1189 | minimatch: 10.0.3 1190 | 1191 | '@tsslint/types@2.0.1': {} 1192 | 1193 | '@tybys/wasm-util@0.10.1': 1194 | dependencies: 1195 | tslib: 2.8.1 1196 | optional: true 1197 | 1198 | '@types/chai@5.2.2': 1199 | dependencies: 1200 | '@types/deep-eql': 4.0.2 1201 | 1202 | '@types/deep-eql@4.0.2': {} 1203 | 1204 | '@types/estree@1.0.8': {} 1205 | 1206 | '@vitest/expect@3.2.4': 1207 | dependencies: 1208 | '@types/chai': 5.2.2 1209 | '@vitest/spy': 3.2.4 1210 | '@vitest/utils': 3.2.4 1211 | chai: 5.2.1 1212 | tinyrainbow: 2.0.0 1213 | 1214 | '@vitest/mocker@3.2.4(vite@7.1.2)': 1215 | dependencies: 1216 | '@vitest/spy': 3.2.4 1217 | estree-walker: 3.0.3 1218 | magic-string: 0.30.17 1219 | optionalDependencies: 1220 | vite: 7.1.2 1221 | 1222 | '@vitest/pretty-format@3.2.4': 1223 | dependencies: 1224 | tinyrainbow: 2.0.0 1225 | 1226 | '@vitest/runner@3.2.4': 1227 | dependencies: 1228 | '@vitest/utils': 3.2.4 1229 | pathe: 2.0.3 1230 | strip-literal: 3.0.0 1231 | 1232 | '@vitest/snapshot@3.2.4': 1233 | dependencies: 1234 | '@vitest/pretty-format': 3.2.4 1235 | magic-string: 0.30.17 1236 | pathe: 2.0.3 1237 | 1238 | '@vitest/spy@3.2.4': 1239 | dependencies: 1240 | tinyspy: 4.0.3 1241 | 1242 | '@vitest/utils@3.2.4': 1243 | dependencies: 1244 | '@vitest/pretty-format': 3.2.4 1245 | loupe: 3.2.0 1246 | tinyrainbow: 2.0.0 1247 | 1248 | '@volar/language-core@2.4.23': 1249 | dependencies: 1250 | '@volar/source-map': 2.4.23 1251 | 1252 | '@volar/language-hub@0.0.1': {} 1253 | 1254 | '@volar/source-map@2.4.23': {} 1255 | 1256 | '@volar/typescript@2.4.23': 1257 | dependencies: 1258 | '@volar/language-core': 2.4.23 1259 | path-browserify: 1.0.1 1260 | vscode-uri: 3.1.0 1261 | 1262 | ansi-regex@5.0.1: {} 1263 | 1264 | ansi-regex@6.1.0: {} 1265 | 1266 | ansi-styles@4.3.0: 1267 | dependencies: 1268 | color-convert: 2.0.1 1269 | 1270 | ansi-styles@5.2.0: {} 1271 | 1272 | ansi-styles@6.2.1: {} 1273 | 1274 | ansis@4.2.0: {} 1275 | 1276 | assertion-error@2.0.1: {} 1277 | 1278 | balanced-match@1.0.2: {} 1279 | 1280 | brace-expansion@2.0.2: 1281 | dependencies: 1282 | balanced-match: 1.0.2 1283 | 1284 | cac@6.7.14: {} 1285 | 1286 | chai@5.2.1: 1287 | dependencies: 1288 | assertion-error: 2.0.1 1289 | check-error: 2.1.1 1290 | deep-eql: 5.0.2 1291 | loupe: 3.2.0 1292 | pathval: 2.0.1 1293 | 1294 | chalk@4.1.2: 1295 | dependencies: 1296 | ansi-styles: 4.3.0 1297 | supports-color: 7.2.0 1298 | 1299 | check-error@2.1.1: {} 1300 | 1301 | color-convert@2.0.1: 1302 | dependencies: 1303 | color-name: 1.1.4 1304 | 1305 | color-name@1.1.4: {} 1306 | 1307 | cross-spawn@7.0.6: 1308 | dependencies: 1309 | path-key: 3.1.1 1310 | shebang-command: 2.0.0 1311 | which: 2.0.2 1312 | 1313 | debug@4.4.1: 1314 | dependencies: 1315 | ms: 2.1.3 1316 | 1317 | deep-eql@5.0.2: {} 1318 | 1319 | diff-sequences@29.6.3: {} 1320 | 1321 | eastasianwidth@0.2.0: {} 1322 | 1323 | emoji-regex@8.0.0: {} 1324 | 1325 | emoji-regex@9.2.2: {} 1326 | 1327 | es-module-lexer@1.7.0: {} 1328 | 1329 | esbuild@0.25.9: 1330 | optionalDependencies: 1331 | '@esbuild/aix-ppc64': 0.25.9 1332 | '@esbuild/android-arm': 0.25.9 1333 | '@esbuild/android-arm64': 0.25.9 1334 | '@esbuild/android-x64': 0.25.9 1335 | '@esbuild/darwin-arm64': 0.25.9 1336 | '@esbuild/darwin-x64': 0.25.9 1337 | '@esbuild/freebsd-arm64': 0.25.9 1338 | '@esbuild/freebsd-x64': 0.25.9 1339 | '@esbuild/linux-arm': 0.25.9 1340 | '@esbuild/linux-arm64': 0.25.9 1341 | '@esbuild/linux-ia32': 0.25.9 1342 | '@esbuild/linux-loong64': 0.25.9 1343 | '@esbuild/linux-mips64el': 0.25.9 1344 | '@esbuild/linux-ppc64': 0.25.9 1345 | '@esbuild/linux-riscv64': 0.25.9 1346 | '@esbuild/linux-s390x': 0.25.9 1347 | '@esbuild/linux-x64': 0.25.9 1348 | '@esbuild/netbsd-arm64': 0.25.9 1349 | '@esbuild/netbsd-x64': 0.25.9 1350 | '@esbuild/openbsd-arm64': 0.25.9 1351 | '@esbuild/openbsd-x64': 0.25.9 1352 | '@esbuild/openharmony-arm64': 0.25.9 1353 | '@esbuild/sunos-x64': 0.25.9 1354 | '@esbuild/win32-arm64': 0.25.9 1355 | '@esbuild/win32-ia32': 0.25.9 1356 | '@esbuild/win32-x64': 0.25.9 1357 | 1358 | estree-walker@3.0.3: 1359 | dependencies: 1360 | '@types/estree': 1.0.8 1361 | 1362 | expect-type@1.2.2: {} 1363 | 1364 | fdir@6.5.0(picomatch@4.0.3): 1365 | optionalDependencies: 1366 | picomatch: 4.0.3 1367 | 1368 | foreground-child@3.3.1: 1369 | dependencies: 1370 | cross-spawn: 7.0.6 1371 | signal-exit: 4.1.0 1372 | 1373 | fsevents@2.3.3: 1374 | optional: true 1375 | 1376 | glob@10.4.5: 1377 | dependencies: 1378 | foreground-child: 3.3.1 1379 | jackspeak: 3.4.3 1380 | minimatch: 9.0.5 1381 | minipass: 7.1.2 1382 | package-json-from-dist: 1.0.1 1383 | path-scurry: 1.11.1 1384 | 1385 | has-flag@4.0.0: {} 1386 | 1387 | is-fullwidth-code-point@3.0.0: {} 1388 | 1389 | isexe@2.0.0: {} 1390 | 1391 | jackspeak@3.4.3: 1392 | dependencies: 1393 | '@isaacs/cliui': 8.0.2 1394 | optionalDependencies: 1395 | '@pkgjs/parseargs': 0.11.0 1396 | 1397 | jest-diff@29.7.0: 1398 | dependencies: 1399 | chalk: 4.1.2 1400 | diff-sequences: 29.6.3 1401 | jest-get-type: 29.6.3 1402 | pretty-format: 29.7.0 1403 | 1404 | jest-extended@6.0.0(typescript@5.9.2): 1405 | dependencies: 1406 | jest-diff: 29.7.0 1407 | typescript: 5.9.2 1408 | 1409 | jest-get-type@29.6.3: {} 1410 | 1411 | js-tokens@9.0.1: {} 1412 | 1413 | json5@2.2.3: {} 1414 | 1415 | loupe@3.2.0: {} 1416 | 1417 | lru-cache@10.4.3: {} 1418 | 1419 | magic-string@0.30.17: 1420 | dependencies: 1421 | '@jridgewell/sourcemap-codec': 1.5.5 1422 | 1423 | minimatch@10.0.3: 1424 | dependencies: 1425 | '@isaacs/brace-expansion': 5.0.0 1426 | 1427 | minimatch@9.0.5: 1428 | dependencies: 1429 | brace-expansion: 2.0.2 1430 | 1431 | minipass@7.1.2: {} 1432 | 1433 | mitata@1.0.34: {} 1434 | 1435 | ms@2.1.3: {} 1436 | 1437 | nanoid@3.3.11: {} 1438 | 1439 | package-json-from-dist@1.0.1: {} 1440 | 1441 | path-browserify@1.0.1: {} 1442 | 1443 | path-key@3.1.1: {} 1444 | 1445 | path-scurry@1.11.1: 1446 | dependencies: 1447 | lru-cache: 10.4.3 1448 | minipass: 7.1.2 1449 | 1450 | pathe@2.0.3: {} 1451 | 1452 | pathval@2.0.1: {} 1453 | 1454 | picocolors@1.1.1: {} 1455 | 1456 | picomatch@4.0.3: {} 1457 | 1458 | postcss@8.5.6: 1459 | dependencies: 1460 | nanoid: 3.3.11 1461 | picocolors: 1.1.1 1462 | source-map-js: 1.2.1 1463 | 1464 | pretty-format@29.7.0: 1465 | dependencies: 1466 | '@jest/schemas': 29.6.3 1467 | ansi-styles: 5.2.0 1468 | react-is: 18.3.1 1469 | 1470 | react-is@18.3.1: {} 1471 | 1472 | rolldown@1.0.0-beta.43: 1473 | dependencies: 1474 | '@oxc-project/types': 0.94.0 1475 | '@rolldown/pluginutils': 1.0.0-beta.43 1476 | ansis: 4.2.0 1477 | optionalDependencies: 1478 | '@rolldown/binding-android-arm64': 1.0.0-beta.43 1479 | '@rolldown/binding-darwin-arm64': 1.0.0-beta.43 1480 | '@rolldown/binding-darwin-x64': 1.0.0-beta.43 1481 | '@rolldown/binding-freebsd-x64': 1.0.0-beta.43 1482 | '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.43 1483 | '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.43 1484 | '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.43 1485 | '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.43 1486 | '@rolldown/binding-linux-x64-musl': 1.0.0-beta.43 1487 | '@rolldown/binding-openharmony-arm64': 1.0.0-beta.43 1488 | '@rolldown/binding-wasm32-wasi': 1.0.0-beta.43 1489 | '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.43 1490 | '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.43 1491 | '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.43 1492 | 1493 | rollup@4.46.2: 1494 | dependencies: 1495 | '@types/estree': 1.0.8 1496 | optionalDependencies: 1497 | '@rollup/rollup-android-arm-eabi': 4.46.2 1498 | '@rollup/rollup-android-arm64': 4.46.2 1499 | '@rollup/rollup-darwin-arm64': 4.46.2 1500 | '@rollup/rollup-darwin-x64': 4.46.2 1501 | '@rollup/rollup-freebsd-arm64': 4.46.2 1502 | '@rollup/rollup-freebsd-x64': 4.46.2 1503 | '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 1504 | '@rollup/rollup-linux-arm-musleabihf': 4.46.2 1505 | '@rollup/rollup-linux-arm64-gnu': 4.46.2 1506 | '@rollup/rollup-linux-arm64-musl': 4.46.2 1507 | '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 1508 | '@rollup/rollup-linux-ppc64-gnu': 4.46.2 1509 | '@rollup/rollup-linux-riscv64-gnu': 4.46.2 1510 | '@rollup/rollup-linux-riscv64-musl': 4.46.2 1511 | '@rollup/rollup-linux-s390x-gnu': 4.46.2 1512 | '@rollup/rollup-linux-x64-gnu': 4.46.2 1513 | '@rollup/rollup-linux-x64-musl': 4.46.2 1514 | '@rollup/rollup-win32-arm64-msvc': 4.46.2 1515 | '@rollup/rollup-win32-ia32-msvc': 4.46.2 1516 | '@rollup/rollup-win32-x64-msvc': 4.46.2 1517 | fsevents: 2.3.3 1518 | 1519 | shebang-command@2.0.0: 1520 | dependencies: 1521 | shebang-regex: 3.0.0 1522 | 1523 | shebang-regex@3.0.0: {} 1524 | 1525 | siginfo@2.0.0: {} 1526 | 1527 | signal-exit@4.1.0: {} 1528 | 1529 | sisteransi@1.0.5: {} 1530 | 1531 | source-map-js@1.2.1: {} 1532 | 1533 | stackback@0.0.2: {} 1534 | 1535 | std-env@3.9.0: {} 1536 | 1537 | string-width@4.2.3: 1538 | dependencies: 1539 | emoji-regex: 8.0.0 1540 | is-fullwidth-code-point: 3.0.0 1541 | strip-ansi: 6.0.1 1542 | 1543 | string-width@5.1.2: 1544 | dependencies: 1545 | eastasianwidth: 0.2.0 1546 | emoji-regex: 9.2.2 1547 | strip-ansi: 7.1.0 1548 | 1549 | strip-ansi@6.0.1: 1550 | dependencies: 1551 | ansi-regex: 5.0.1 1552 | 1553 | strip-ansi@7.1.0: 1554 | dependencies: 1555 | ansi-regex: 6.1.0 1556 | 1557 | strip-literal@3.0.0: 1558 | dependencies: 1559 | js-tokens: 9.0.1 1560 | 1561 | supports-color@7.2.0: 1562 | dependencies: 1563 | has-flag: 4.0.0 1564 | 1565 | tinybench@2.9.0: {} 1566 | 1567 | tinyexec@0.3.2: {} 1568 | 1569 | tinyglobby@0.2.14: 1570 | dependencies: 1571 | fdir: 6.5.0(picomatch@4.0.3) 1572 | picomatch: 4.0.3 1573 | 1574 | tinypool@1.1.1: {} 1575 | 1576 | tinyrainbow@2.0.0: {} 1577 | 1578 | tinyspy@4.0.3: {} 1579 | 1580 | ts-api-utils@2.1.0(typescript@5.9.2): 1581 | dependencies: 1582 | typescript: 5.9.2 1583 | 1584 | tslib@2.8.1: 1585 | optional: true 1586 | 1587 | typescript@5.9.2: {} 1588 | 1589 | vite-node@3.2.4: 1590 | dependencies: 1591 | cac: 6.7.14 1592 | debug: 4.4.1 1593 | es-module-lexer: 1.7.0 1594 | pathe: 2.0.3 1595 | vite: 7.1.2 1596 | transitivePeerDependencies: 1597 | - '@types/node' 1598 | - jiti 1599 | - less 1600 | - lightningcss 1601 | - sass 1602 | - sass-embedded 1603 | - stylus 1604 | - sugarss 1605 | - supports-color 1606 | - terser 1607 | - tsx 1608 | - yaml 1609 | 1610 | vite@7.1.2: 1611 | dependencies: 1612 | esbuild: 0.25.9 1613 | fdir: 6.5.0(picomatch@4.0.3) 1614 | picomatch: 4.0.3 1615 | postcss: 8.5.6 1616 | rollup: 4.46.2 1617 | tinyglobby: 0.2.14 1618 | optionalDependencies: 1619 | fsevents: 2.3.3 1620 | 1621 | vitest@3.2.4: 1622 | dependencies: 1623 | '@types/chai': 5.2.2 1624 | '@vitest/expect': 3.2.4 1625 | '@vitest/mocker': 3.2.4(vite@7.1.2) 1626 | '@vitest/pretty-format': 3.2.4 1627 | '@vitest/runner': 3.2.4 1628 | '@vitest/snapshot': 3.2.4 1629 | '@vitest/spy': 3.2.4 1630 | '@vitest/utils': 3.2.4 1631 | chai: 5.2.1 1632 | debug: 4.4.1 1633 | expect-type: 1.2.2 1634 | magic-string: 0.30.17 1635 | pathe: 2.0.3 1636 | picomatch: 4.0.3 1637 | std-env: 3.9.0 1638 | tinybench: 2.9.0 1639 | tinyexec: 0.3.2 1640 | tinyglobby: 0.2.14 1641 | tinypool: 1.1.1 1642 | tinyrainbow: 2.0.0 1643 | vite: 7.1.2 1644 | vite-node: 3.2.4 1645 | why-is-node-running: 2.3.0 1646 | transitivePeerDependencies: 1647 | - jiti 1648 | - less 1649 | - lightningcss 1650 | - msw 1651 | - sass 1652 | - sass-embedded 1653 | - stylus 1654 | - sugarss 1655 | - supports-color 1656 | - terser 1657 | - tsx 1658 | - yaml 1659 | 1660 | vscode-uri@3.1.0: {} 1661 | 1662 | which@2.0.2: 1663 | dependencies: 1664 | isexe: 2.0.0 1665 | 1666 | why-is-node-running@2.3.0: 1667 | dependencies: 1668 | siginfo: 2.0.0 1669 | stackback: 0.0.2 1670 | 1671 | wrap-ansi@7.0.0: 1672 | dependencies: 1673 | ansi-styles: 4.3.0 1674 | string-width: 4.2.3 1675 | strip-ansi: 6.0.1 1676 | 1677 | wrap-ansi@8.1.0: 1678 | dependencies: 1679 | ansi-styles: 6.2.1 1680 | string-width: 5.1.2 1681 | strip-ansi: 7.1.0 1682 | --------------------------------------------------------------------------------