Files
skills/docs/foundation/flow/undo/README.md
2026-05-29 08:20:56 +01:00

98 lines
10 KiB
Markdown

# @suaveplan/undo - Enterprise-grade undo/redo engine with branching history, async commands, history compression, selective undo, and command merging
## Summary
Enterprise-grade undo/redo engine with branching history, async commands, history compression, selective undo, and command merging. Built for collaborative editors, document tools, and any application requiring robust, reversible state management.
<!-- begin:auto-docs -->
## Documentation tree
- [async-command](./src/async/async-command.md) — Provides factory functions and execution helpers for asynchronous commands in `@suaveplan/undo`. Async commands follow…
- [async-stack](./src/async/async-stack.md) — Implements `AsyncUndoStack<T>`, the asynchronous counterpart to `UndoStack<T>`. All mutation methods (`push`, `undo`, `…
- [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [compression](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [history-compressor](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [undo-error](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [merge](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [command-merger](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [undo.schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [selective](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [selective-undo](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [compression](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [history-compressor](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo-error](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [merge](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [command-merger](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo.schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective-undo](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [compression](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [history-compressor](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo-error](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [merge](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [command-merger](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo.schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective-undo](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [compression](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [history-compressor](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo-error](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [merge](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [command-merger](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [undo.schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective-undo](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_- [branch](./src/branch/branch.md) — <!-- begin:auto-docs --> _(no submodules)_- [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_ - [command](./src/command/command.md) — <!-- begin:auto-docs --> _(no submodules)_- [compression](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_ - [history-compressor](./src/compression/history-compressor.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [undo-error](./src/errors/undo-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [merge](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_ - [command-merger](./src/merge/command-merger.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ - [undo.schemas](./src/schemas/undo.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [selective](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_ - [selective-undo](./src/selective/selective-undo.md) — <!-- begin:auto-docs --> _(no submodules)_- [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_ - [stack](./src/stack/stack.md) — <!-- begin:auto-docs --> _(no submodules)_
## Install
```bash
bun add @suaveplan/undo
```
## Quick Start
```ts
import { UndoStack, createCommand } from "@suaveplan/undo";
interface Counter { value: number }
const stack = new UndoStack<Counter>({ value: 0 });
const inc = createCommand(
"Increment",
(s) => ({ value: s.value + 1 }),
(s) => ({ value: s.value - 1 }),
);
stack.push(inc);
console.log(stack.current.value); // 1
stack.undo();
console.log(stack.current.value); // 0
stack.redo();
console.log(stack.current.value); // 1
```
### Branching History
```ts
import { BranchingHistory, createCommand } from "@suaveplan/undo";
const h = new BranchingHistory({ value: 0 });
h.push(createCommand("A", (s) => ({ value: s.value + 1 }), (s) => ({ value: s.value - 1 })));
h.undo();
h.push(createCommand("B", (s) => ({ value: s.value + 2 }), (s) => ({ value: s.value - 2 }))); // forks
console.log(h.branches.length); // 2
```
## Modules
| Module | Description |
|--------|-------------|
| [`src/command/command.md`](src/command/command.md) | Command factories and execution helpers |
| [`src/stack/stack.md`](src/stack/stack.md) | Synchronous linear undo/redo stack |
| [`src/branch/branch.md`](src/branch/branch.md) | Branching history manager |
| [`src/async/async-command.md`](src/async/async-command.md) | Async command factories |
| [`src/async/async-stack.md`](src/async/async-stack.md) | Async undo/redo stack |
| [`src/compression/history-compressor.md`](src/compression/history-compressor.md) | History compression |
| [`src/selective/selective-undo.md`](src/selective/selective-undo.md) | Selective undo by command ID |
| [`src/merge/command-merger.md`](src/merge/command-merger.md) | Command merging / coalescing |
| [`src/types/undo.types.md`](src/types/undo.types.md) | All types and type guards |
| [`src/schemas/undo.schemas.md`](src/schemas/undo.schemas.md) | Zod option schemas |
| [`src/errors/undo-error.md`](src/errors/undo-error.md) | Structured errors and codes |
## Benchmarks
Measured on an AMD Ryzen 5 (WSL2), 1000 iterations:
| Operation | Throughput |
|-----------|-----------|
| UndoStack push/undo/redo (closure) | ~250k ops/s |
| UndoStack push/undo (patch, 50 fields) | ~15k ops/s |
| compressHistory (1000 entries, depth 100) | ~2k ops/s |
> A >10% regression from baseline fails the CI performance gate.