Skip to content
ledger logoLedgerUtilities
Command-pattern undo/redo with async operations, Ripple signals for reactive state, and composable commands.
v1.1.03.5 KB gzipBrowser · Node.js · SSR · Deno
createLedgercompose

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.

FeatureRoll your ownLedger
Bundle size0 B3.5 KB
Async commandsManual promise chaining serialised queue
Race preventionManual locks built-in queue
Reactive canUndoPoll or manual eventsComputed<boolean> from Ripple
Composable commandsCustom wrapper compose()
History capArray slicemaxHistory option
DisposableManualdispose() + 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/ledger
sh
npm install @vielzeug/ledger
sh
yarn add @vielzeug/ledger

Quick 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, historySnapshot are Ripple Computed values
  • compose() — Group multiple commands into one atomic undo step; partial failure rolls back already-executed sub-commands; sub-rollback errors reach onRollbackError
  • maxHistory — Cap the undo stack; oldest entries evicted automatically
  • Async-safe — execute(), rollback(), and clear() are fully serialised through the queue
  • Typed history — Command.data stores custom metadata; historySnapshot.value[n].data is typed to TData
  • Error-safe rollback — failed rollback() warns via dev console; optional onRollbackError callback for UI integration
  • Cancellable — execute/rollback receive an AbortSignal, merged from a caller-supplied signal and the ledger's own disposalSignal
  • Disposable — dispose() + [Symbol.dispose] for using declarations

Documentation

See Also

  • RipplecanUndo, canRedo, isProcessing are Ripple Computed values; use effect() or bind directly to templates
  • Keymap — Wire ctrl+z / ctrl+shift+z to ledger.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