Why Ledger?
Undo/redo is deceptively complex: you need to handle async side-effects, prevent concurrent mutations from racing, cap history size, and keep UI buttons reactive. Ledger solves all of this with a clean command-pattern API and Ripple signals.
| Feature | Roll your own | Ledger |
|---|---|---|
| Bundle size | 0 B | 3.5 KB |
| Async commands | Manual promise chaining | |
| Race prevention | Manual locks | |
Reactive canUndo | Poll or manual events | Computed<boolean> from Ripple |
| Composable commands | Custom wrapper | compose() |
| History cap | Array slice | maxHistory option |
| Disposable | Manual | dispose() + using |
Use Ledger when you need undo/redo for editors, design tools, form state, or any app with reversible mutations — especially with async side-effects like server persistence.
Consider a simpler approach when you only need client-side state undo with a single synchronous mutation and no UI reactivity — @vielzeug/ripple's storeWithHistory may be enough.
Installation
sh
pnpm add @vielzeug/ledgersh
npm install @vielzeug/ledgersh
yarn add @vielzeug/ledgerQuick Start
ts
import { createLedger } from '@vielzeug/ledger';
const ledger = createLedger({ maxHistory: 50 });
// Execute a reversible command
await ledger.do({
execute: async () => { item.name = newName; },
rollback: async () => { item.name = oldName; },
label: 'Rename item',
});
await ledger.undo(); // runs rollback
await ledger.redo(); // runs execute again
ledger.dispose(); // or: using ledger = createLedger()Features
createLedger<TData>()— Creates an async command stack; operations are serialised to prevent races- Reactive state —
canUndo,canRedo,historySize,isProcessing,pendingCount,historySnapshotare RippleComputedvalues compose()— Group multiple commands into one atomic undo step; partial failure rolls back already-executed sub-commands; sub-rollback errors reachonRollbackErrormaxHistory— Cap the undo stack; oldest entries evicted automatically- Async-safe —
execute(),rollback(), andclear()are fully serialised through the queue - Typed history —
Command.datastores custom metadata;historySnapshot.value[n].datais typed toTData - Error-safe rollback — failed
rollback()warns via dev console; optionalonRollbackErrorcallback for UI integration - Cancellable —
execute/rollbackreceive anAbortSignal, merged from a caller-supplied signal and the ledger's owndisposalSignal - Disposable —
dispose()+[Symbol.dispose]forusingdeclarations
Documentation
See Also
- Ripple —
canUndo,canRedo,isProcessingare RippleComputedvalues; useeffect()or bind directly to templates - Keymap — Wire
ctrl+z/ctrl+shift+ztoledger.undo()/ledger.redo()with zero boilerplate - Forge — Combine Ledger with Forge for reversible form mutations
- Vault — Persist undo history across sessions by storing commands in IndexedDB