# stack _(no submodules)_ ## Purpose Implements `UndoStack`, a synchronous linear undo/redo stack with configurable memory limits, atomic transaction support, and `EventBusLike` integration for reactive applications. It is the core data structure used by most higher-level modules in `@suaveplan/undo`. Commands are stored as `StackEntry` pairs capturing both the command and the state immediately before execution, enabling exact rollback without re-replaying history. All mutations respect `maxEntries` and `maxMemoryBytes` limits, trimming the oldest entries and calling an `onTruncate` callback when limits are exceeded. ## Features - `push(command)` — executes command, pushes to undo stack, clears redo stack - `pushState(newState)` — pushes a state snapshot without an explicit command - `undo()` — reverses the most recent command; throws `UndoError(UNDO.EMPTY_STACK)` when empty - `redo()` — re-applies the most recently undone command; throws `UndoError(UNDO.EMPTY_STACK)` when empty - `beginTransaction()` — returns a `TransactionHandle` for atomic multi-command batching - `clear()` — discards all history, keeps current state; emits `undo:clear` - `reset(newInitial)` — resets state and clears history - `snapshot()` — returns a serializable `UndoStackState` - `getUndoStack()` / `getRedoStack()` — copies of internal stacks - Getters: `current`, `canUndo`, `canRedo`, `undoSize`, `redoSize`, `activeBranchId` - `EventBusLike` integration — emits `undo:push`, `undo:undo`, `undo:redo`, `undo:clear` - OTel counters (`undo.push.total`, `undo.undo.total`, `undo.redo.total`) and `undo.stack.depth` histogram - Injectable `clock` and `idGenerator` via `UndoStackOptions` ## Usage: Basic Example ```ts import { UndoStack } from "@suaveplan/undo/stack/stack"; import { createCommand } from "@suaveplan/undo/command/command"; interface Counter { value: number } const stack = new UndoStack({ value: 0 }); const inc = createCommand("Inc", (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 ``` ## Usage: Advanced Example ```ts import { UndoStack } from "@suaveplan/undo/stack/stack"; import { createCommand } from "@suaveplan/undo/command/command"; import { EventBus } from "@suaveplan/events"; import type { UndoEventMap } from "@suaveplan/undo/types/undo.types"; interface Doc { title: string } const bus = new EventBus(); bus.on("undo:push", ({ commandId, undoSize }) => { console.log(`Pushed ${commandId}, depth=${undoSize}`); }); const stack = new UndoStack( { title: "Draft" }, { maxEntries: 10, onTruncate: (removed) => console.log("Dropped", removed.length) }, bus ); // Transaction: batch two commands into one atomic undo unit const tx = stack.beginTransaction(); tx.add(createCommand("Title", (d) => ({ ...d, title: "Final" }), (d) => ({ ...d, title: "Draft" }))); tx.add(createCommand("Suffix", (d) => ({ ...d, title: d.title + "!" }), (d) => ({ ...d, title: d.title.slice(0, -1) }))); tx.commit(); // pushes both atomically console.log(stack.current.title); // "Final!" stack.undo(); // undoes "Suffix" stack.undo(); // undoes "Title" console.log(stack.current.title); // "Draft" ``` ## API Reference ### `new UndoStack(initial, options?, bus?)` | Param | Type | Description | |---|---|---| | `initial` | `T` | Initial state | | `options` | `UndoStackOptions` | Optional limits and injections | | `bus` | `EventBusLike` | Optional event bus | Options are validated with `UndoStackOptionsSchema`. Throws `ZodError` on invalid options. ### `push(command)` → `T` Executes command and stores it. Clears redo stack. ### `pushState(newState)` → `T` Pushes a synthetic command wrapping the state change. ### `undo()` → `T` Pops from undo stack, applies inverse. Throws `UndoError(UNDO.EMPTY_STACK)` if empty. ### `redo()` → `T` Pops from redo stack, re-applies command. Throws `UndoError(UNDO.EMPTY_STACK)` if empty. ### `beginTransaction()` → `TransactionHandle` Opens a transaction. Throws `UndoError(UNDO.TRANSACTION_ACTIVE)` if one is already open. Handle methods throw `UndoError(UNDO.TRANSACTION_INACTIVE)` after commit/rollback. ### `snapshot()` → `UndoStackState` Returns `{ current, undoStack, redoStack, undoSize, redoSize }`. ## Implementation Notes The undo stack stores `stateBefore` in each entry rather than re-executing the full history chain, making `undo()` O(1). Trimming removes the oldest entries (front of array) to preserve the most recent history. Transactions use a simple "pending commands" buffer flushed to `push()` on commit. All class members use `protected` rather than `private` to enable `SelectiveUndoStack` and other subclasses to access internals without re-implementing the whole class.