Reactive State Management for TypeScript
川流不息,唯取一瓢
6 primitives. 70+ operators. Diamond-safe. Framework-agnostic.
From atoms to streams in one library.
Two-phase push guarantees glitch-free derived values. When A → B, A → C, B+C → D — D computes exactly once.
LLM chunks, WebSocket, SSE — all first-class. switchMap auto-cancels. scan accumulates. retry recovers.
switchMap, debounce, retry, scan, merge, combine, groupBy, window, and more — tree-shakeable.
Every node observable via Inspector. Names, edges, dirty/resolved phases — zero runtime cost in production.
No ceremony. No providers. No boilerplate. Every store is a callbag source.
state()—writable storestate(0)derived()—computed valuederived([a, b], fn)dynamicDerived()—runtime-tracked deriveddynamicDerived((get) => get(flag) ? get(a) : get(b))effect()—side effectseffect([dep], fn)producer()—async sourceproducer(({ emit }) => ...)operator()—custom transformoperator([dep], init, handler)import { state, derived, effect } from 'callbag-recharge'
// Create reactive atoms
const count = state(0)
const doubled = derived([count], () => count.get() * 2)
// React to changes
effect([doubled], () => {
console.log('doubled:', doubled.get())
})
count.set(1) // logs: "doubled: 2"import { chatStream } from 'callbag-recharge/patterns/chatStream'
// AI chat with history, cancel, retry
const chat = chatStream({ endpoint: '/api/chat' })
chat.send('Explain diamond resolution')
chat.partial.get() // accumulating response text
chat.streaming.get() // true while streaming
chat.stop() // cancel in-flight request
chat.retry() // retry last failed message| callbag-recharge | Zustand | Jotai | Nanostores | Signals | |
|---|---|---|---|---|---|
| Diamond-safe | ✓ | ✕ | ✕ | ✕ | ✕ |
| Stream operators | ✓ 70+ | ✕ | ✕ | ✕ | ✕ |
| Framework-agnostic | ✓ | ~ | ✕ | ✓ | ✓ |
| Async/streaming | ✓ native | ~ | ~ | ✕ | ✕ |
| Inspectable graph | ✓ | ✕ | ~ | ✕ | ✕ |
| Zero deps | ✓ | ✓ | ✓ | ✓ | ✓ |