Repo of the Day
mutativejs/travels: A fast, framework-agnostic undo/redo core powered by Mutative JSON Patch
Published: Mar 31, 2026
Open repository ↗A fast, framework-agnostic undo/redo core powered by Mutative JSON Patch - mutativejs/travels
Summary
Travels is a TypeScript undo/redo library that stores history as JSON Patches (RFC 6902) rather than full state snapshots, making it well suited to applications with large state and small updates. It is framework-agnostic and documented to work with React, Vue, Zustand, MobX, Pinia, or vanilla JavaScript, using Mutative for the underlying immutable updates. The project is MIT-licensed and published as the travels package on npm.
What it is useful for
Travels targets use cases where history size, persistence cost, and replayable operation logs matter. A text editor, drawing app, or form builder with a multi-megabyte state tree can keep many small edits in memory and serialize them cheaply, because each history entry is a patch instead of a full clone. The README is explicit that snapshot-based libraries such as Redux-undo or Zundo are a better fit when state is small and history is short and local-only, and that Travels alone does not solve collaborative editing, which still needs CRDT or OT. Documented state requirements include plain objects, dense arrays, strings, finite non--0 numbers, booleans, and null; bigint, NaN, infinities, -0, Map, Set, Date, class instances, null-prototype objects, DOM nodes, refs, and functions are not supported as durable data. Updater callbacks must be synchronous, so async or Promise-returning updaters are rejected. Patch generation also adds overhead, so for tiny state and lowest setState latency, snapshot stacks may win.
How engineers can use it
Install Travels and its peer dependency:
npm install travels mutative
A basic instance exposes setState, getState, back, forward, go, reset, and rebase:
import { createTravels } from 'travels';
const travels = createTravels({ count: 0 });
travels.subscribe(({ type, state, position, historyLength }) => {
console.log(type, state, position, historyLength);
});
travels.setState((draft) => { draft.count += 1; });
travels.back();
travels.forward();
Three setState styles are documented: passing a new value, returning a value from a function, or mutating a draft. Optional metadata such as label, source, and timestamp can be attached to entries for product UIs like undo menus. History can be persisted with serialize() and restored with Travels.deserialize(), or via initialPatches and initialPosition on creation. maxHistory (default 10) caps retained entries, and a mutable: true option mutates state in place for reactive stores that need identity stability. Companion packages zustand-travel and use-travel wrap Travels for Zustand and React. Before adopting, normalize any Map, Set, or class instances to plain objects or arrays and review the State Requirements section in the README for the full compatibility rules.