{"_id":"@bundt/signals","_rev":"2-20141502e2297c0e2be16d7df3416fa1","name":"@bundt/signals","dist-tags":{"alpha":"0.1.0-alpha.0","latest":"0.1.0"},"versions":{"0.1.0-alpha.0":{"name":"@bundt/signals","version":"0.1.0-alpha.0","keywords":["signals","reactive","computed","effects","state","bun"],"license":"MIT","_id":"@bundt/signals@0.1.0-alpha.0","maintainers":[{"name":"mega-blastoise","email":"rustycloud42@protonmail.com"}],"homepage":"https://github.com/mega-blastoise/bundt/tree/main/packages/signals","bugs":{"url":"https://github.com/mega-blastoise/bundt/issues"},"dist":{"shasum":"7583619e9397a20e067c02be8f88d0c94a9ebbfe","tarball":"https://registry.npmjs.org/@bundt/signals/-/signals-0.1.0-alpha.0.tgz","fileCount":20,"integrity":"sha512-gZhdI75OXfKw5DOufBDrWLlVVDEDRgiRY2gtenyuJMrypiA2zrDIapWRUP4IoX1i5wt/MlQMUjpIg009u3Hp3Q==","signatures":[{"sig":"MEQCIG+tCcPEsJSBJKKQAAXl3XfRqjmqp+jI9nMiPspCtFN5AiA8e2RWAt5A/DvlefyrjYi2tXVSzzrjRn3eUYh8GiMMeg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30020},"type":"module","engines":{"bun":">=1.3"},"exports":{".":{"bun":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"gitHead":"2b7109209c9f3bac08a791f1825f102d17e44ec5","scripts":{"test":"bun test","build":"bun run build:bundle && bun run build:types","clean":"rm -rf dist","prebuild":"rm -rf dist","typecheck":"tsc --noEmit","build:types":"tsc -p tsconfig.build.json","build:bundle":"bun build/index.ts"},"_npmUser":{"name":"mega-blastoise","email":"rustycloud42@protonmail.com"},"repository":{"url":"git+https://github.com/mega-blastoise/bundt.git","type":"git","directory":"packages/signals"},"_npmVersion":"11.6.2","description":"Reactive signal graph abstractions — signals, computed values, and effects.","directories":{},"_nodeVersion":"24.13.0","publishConfig":{"tag":"alpha","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"latest","typescript":"^5.9.0","@bundt/internal-build-utils":"workspace:*"},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.0-alpha.0_1772823048319_0.9521628994634137","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@bundt/signals","version":"0.1.0","type":"module","description":"Reactive signal graph abstractions — signals, computed values, and effects.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/mega-blastoise/bundt.git","directory":"packages/signals"},"homepage":"https://github.com/mega-blastoise/bundt/tree/main/packages/signals","bugs":{"url":"https://github.com/mega-blastoise/bundt/issues"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","tag":"latest"},"keywords":["signals","reactive","computed","effects","state","bun"],"exports":{".":{"bun":"./src/index.ts","import":"./dist/index.js","types":"./dist/index.d.ts","default":"./dist/index.js"}},"engines":{"bun":">=1.3"},"scripts":{"prebuild":"rm -rf dist","build:bundle":"bun build/index.ts","build:types":"tsc -p tsconfig.build.json","build":"bun run build:bundle && bun run build:types","typecheck":"tsc --noEmit","test":"bun test","clean":"rm -rf dist"},"devDependencies":{"@bundt/internal-build-utils":"workspace:*","@types/bun":"latest","typescript":"^5.9.0"},"gitHead":"2b7109209c9f3bac08a791f1825f102d17e44ec5","_id":"@bundt/signals@0.1.0","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-q0poS93/YTK3mC7ZhW7KlkNnp8uNKZYUKv5lu8lnmxZauBBgsfqT/RPfwk8ZF5P/iy4U2Se38WgJK8UHVtfqGQ==","shasum":"1383266a07c3030cd4588b9d300f4a501368cb38","tarball":"https://registry.npmjs.org/@bundt/signals/-/signals-0.1.0.tgz","fileCount":21,"unpackedSize":34904,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD/Ld1TRidvKQqrFIYnr4BwSWSR2f+sytfIyUzfPkeh+wIhAKRnGvx8fNLO1uov/qQkJoM+M0RXMkXfTiEKeBG6xvbZ"}]},"_npmUser":{"name":"mega-blastoise","email":"rustycloud42@protonmail.com"},"directories":{},"maintainers":[{"name":"mega-blastoise","email":"rustycloud42@protonmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/signals_0.1.0_1772825039191_0.8414984525868432"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-06T18:50:48.185Z","modified":"2026-03-06T19:23:59.491Z","0.1.0-alpha.0":"2026-03-06T18:50:48.450Z","0.1.0":"2026-03-06T19:23:59.352Z"},"bugs":{"url":"https://github.com/mega-blastoise/bundt/issues"},"license":"MIT","homepage":"https://github.com/mega-blastoise/bundt/tree/main/packages/signals","keywords":["signals","reactive","computed","effects","state","bun"],"repository":{"type":"git","url":"git+https://github.com/mega-blastoise/bundt.git","directory":"packages/signals"},"description":"Reactive signal graph abstractions — signals, computed values, and effects.","maintainers":[{"name":"mega-blastoise","email":"rustycloud42@protonmail.com"}],"readme":"# @bundt/signals\n\nReactive signal graph abstractions — signals, computed values, and effects with automatic dependency tracking.\n\n> **Status:** Pre-release (0.1.0). All core primitives are implemented: signals, computed, effects, batching, disposal, and topological notification. API may change before 1.0.\n\n## Install\n\n```bash\nbun add @bundt/signals\n```\n\n## Usage\n\n```typescript\nimport { createSignal, createComputed, createEffect, batch } from '@bundt/signals';\n\n// Signals — reactive values\nconst [count, setCount] = createSignal(0);\nconst [name, setName] = createSignal('world');\n\n// Computed — derived values (lazy, cached, auto-tracked)\nconst greeting = createComputed(() => `Hello ${name()}, count is ${count()}`);\n\n// Effects — side effects that re-run when dependencies change\nconst dispose = createEffect(() => {\n  console.log(greeting());\n});\n// logs: \"Hello world, count is 0\"\n\nsetCount(1);\n// logs: \"Hello world, count is 1\"\n\nsetName('signals');\n// logs: \"Hello signals, count is 1\"\n\n// Updater function\nsetCount(n => n + 1);\n// logs: \"Hello signals, count is 2\"\n\n// Batch — coalesce multiple updates into one notification\nbatch(() => {\n  setCount(10);\n  setName('batch');\n  // no effects fire during the batch\n});\n// logs once: \"Hello batch, count is 10\"\n\n// Dispose — stop an effect and clean up subscriptions\ndispose();\nsetCount(99); // no log — effect is disposed\n```\n\n## API\n\n### `createSignal<T>(initialValue: T): [get, set]`\n\nCreates a reactive value. Returns a getter/setter tuple.\n\n- **Getter** `() => T` — reads the current value and tracks the dependency if called inside a computed or effect\n- **Setter** `(value: T | ((prev: T) => T)) => void` — updates the value. Accepts a direct value or an updater function. Skips update if the new value is identical (`Object.is`)\n\n```typescript\nconst [count, setCount] = createSignal(0);\ncount();           // 0 (tracked if inside computed/effect)\nsetCount(5);       // set directly\nsetCount(n => n + 1); // updater function\n```\n\n### `createComputed<T>(fn: () => T): () => T`\n\nCreates a lazily-evaluated derived value. The computation function is re-run only when its tracked dependencies change, and only when the computed value is actually read.\n\n- Dependencies are tracked automatically during execution\n- Returns a getter function `() => T`\n- Computed values can depend on signals and other computed values\n- Re-computation is deferred until the value is next read (lazy pull)\n- Old dependencies are cleared on each re-computation (dynamic tracking)\n\n```typescript\nconst [a, setA] = createSignal(1);\nconst [b, setB] = createSignal(2);\nconst sum = createComputed(() => a() + b());\n\nsum(); // 3\nsetA(10);\nsum(); // 12\n```\n\n### `createEffect(fn: () => void | (() => void)): () => void`\n\nRegisters a side effect that runs immediately and re-runs when its tracked dependencies change. Returns a dispose function.\n\n- Dependencies are tracked automatically during execution\n- The effect function may return a cleanup function, which is called before each re-run and on disposal\n- Old dependencies are cleared on each re-run (dynamic tracking)\n- Disposed effects are removed from the graph and will not re-run\n\n```typescript\nconst [count, setCount] = createSignal(0);\n\nconst dispose = createEffect(() => {\n  console.log(`count is ${count()}`);\n  return () => console.log('cleaning up');\n});\n// logs: \"count is 0\"\n\nsetCount(1);\n// logs: \"cleaning up\"\n// logs: \"count is 1\"\n\ndispose();\n// logs: \"cleaning up\"\n```\n\n### `batch(fn: () => void): void`\n\nBatches multiple signal updates so that computed values and effects only recalculate once after the batch completes. Batches can be nested — only the outermost batch triggers notifications.\n\n```typescript\nconst [a, setA] = createSignal(0);\nconst [b, setB] = createSignal(0);\n\ncreateEffect(() => console.log(a() + b()));\n// logs: 0\n\nbatch(() => {\n  setA(1);\n  setB(2);\n});\n// logs: 3 (once, not twice)\n```\n\n## Design\n\nThe signal graph uses a push/pull hybrid:\n\n1. **Push:** When a signal is set, dirty flags propagate through the dependency graph via BFS. Effects are collected for re-execution.\n2. **Pull:** Computed values are lazy — they only recompute when read and marked dirty.\n3. **Topological ordering:** Effects are scheduled after all computed values in their dependency chain are re-evaluated.\n4. **Dynamic tracking:** Dependencies are re-discovered on each execution. If a computed or effect conditionally reads different signals, the graph adapts automatically.\n\nThe implementation is ~200 lines across 4 files with no external dependencies.\n\n## Graph Internals\n\n| Node Type | Properties |\n|-----------|------------|\n| **Signal** | `value`, `subscribers` (set of computed/effect nodes) |\n| **Computed** | `fn`, `value`, `dirty` flag, `dependencies`, `subscribers` |\n| **Effect** | `fn`, `cleanup`, `dependencies`, `disposed` flag |\n\n## License\n\nMIT\n","readmeFilename":"README.md"}