A generic undo/redo + version-history engine — an append-only event log over
snapshots of any type T.
Undo/redo and version history are two views of one log:
- undo / redo navigate the log's tip
- restore jumps to any past event — by appending a new event, never rewinding
Nothing is ever destroyed: undo marks an event (doesn't delete it); a new edit
while events sit undone marks that redo branch abandoned (doesn't truncate);
restoreTo appends. So the full history is always recoverable, and a version
panel is just the log rendered as a timeline.
Each event stores a whole snapshot, so the "current" state is a single lookup —
no diffs, no materialized state. Every function is pure: it takes a History<T>
and returns a new one.
import {
createHistory, appendEvent, undo, redo, restoreTo, currentSnapshot,
} from 'undo-redo-versions'
let h = createHistory({ count: 0 }) // History<{count:number}>
h = appendEvent(h, { count: 1 })
h = appendEvent(h, { count: 2 })
currentSnapshot(h) // { count: 2 }
h = undo(h)
currentSnapshot(h) // { count: 1 }
h = redo(h)
currentSnapshot(h) // { count: 2 }
h = restoreTo(h, 1) // jump back to event #1 (appends a 'restore' event)
currentSnapshot(h) // { count: 0 }| function | description |
|---|---|
createHistory(snapshot, kind?, label?) |
a fresh log with one event |
appendEvent(history, snapshot, kind?, label?) |
append; abandons the redo branch |
undo(history) / redo(history) |
navigate the tip (redo is LIFO) |
restoreTo(history, id, label?) |
append a restore event for event id |
currentSnapshot(history) |
the current snapshot (topActiveEvent's) |
topActiveEvent(history) |
the current event |
canUndo(history) / canRedo(history) |
booleans |
HistoryEvent<T> carries id, ts, kind, snapshot, undoneAt,
abandonedAt, and an optional label. undoneAt / abandonedAt are monotonic
op-markers (not timestamps) — collision-free, deterministic ordering.
MIT