Files
suaveplan/packages/foundation/flow/undo/src/errors/undo-error.md
2026-05-27 23:47:53 +01:00

4.1 KiB

undo-error

(no submodules)

Purpose

Provides the structured error class UndoError and its stable code registry UNDO_CODES for @suaveplan/undo. All errors thrown by this package are instances of UndoError, enabling callers to discriminate failures precisely via error.code without relying on string-matching error messages.

The codes are registered via defineCodes from @suaveplan/error, which guarantees namespace-scoped uniqueness, prevents accidental collisions with other packages, and produces stable string identifiers (e.g., "UNDO.EMPTY_STACK") that survive minification and serialization.

Features

  • Six error codes covering every failure scenario in the package
  • Typed UndoErrorContext carrying optional commandId, branchId, and conflictIds
  • instanceof UndoError discrimination across module boundaries
  • error.code discrimination without instanceof (safe for cross-realm use)
  • Extends SuaveplanError for full cause-chain traversal and OTel span-event adapter support

Usage: Basic Example

import { UndoError, UNDO_CODES } from "@suaveplan/undo/errors/undo-error";
import { UndoStack } from "@suaveplan/undo/stack/stack";

const stack = new UndoStack(0);

try {
  stack.undo(); // nothing to undo
} catch (err) {
  if (err instanceof UndoError && err.code === UNDO_CODES.EMPTY_STACK) {
    console.warn("Nothing to undo");
  }
}

Usage: Advanced Example

import { UndoError, UNDO_CODES } from "@suaveplan/undo/errors/undo-error";
import { SelectiveUndoStack } from "@suaveplan/undo/selective/selective-undo";

const stack = new SelectiveUndoStack({ x: 0 }, { throwOnConflict: true });

// Simulate two dependent commands
const cmd1 = { id: "c1", label: "Set x", /* ... */ };
const cmd2 = { id: "c2", label: "Double x", metadata: { dependsOn: "c1" } /* ... */ };

try {
  stack.undoById("c1");
} catch (err) {
  if (err instanceof UndoError && err.code === UNDO_CODES.DEPENDENCY_CONFLICT) {
    const ctx = err.context as { conflictIds?: string[] };
    console.error("Conflict with:", ctx.conflictIds?.join(", "));
  }
}

// Transaction guard
try {
  stack.beginTransaction();
  stack.beginTransaction(); // second call throws
} catch (err) {
  if (err instanceof UndoError && err.code === UNDO_CODES.TRANSACTION_ACTIVE) {
    console.error("Transaction already in progress");
  }
}

API Reference

UNDO_CODES

Code Value Thrown By
UNDO_CODES.EMPTY_STACK "UNDO.EMPTY_STACK" undo() / redo() when nothing available
UNDO_CODES.AT_BRANCH_ROOT "UNDO.AT_BRANCH_ROOT" BranchingHistory.switchBranch for unknown branch
UNDO_CODES.DEPENDENCY_CONFLICT "UNDO.DEPENDENCY_CONFLICT" SelectiveUndoStack.undoById when throwOnConflict is set
UNDO_CODES.COMMAND_FAILED "UNDO.COMMAND_FAILED" AsyncUndoStack when async command rejects
UNDO_CODES.TRANSACTION_ACTIVE "UNDO.TRANSACTION_ACTIVE" beginTransaction() when one is already open
UNDO_CODES.TRANSACTION_INACTIVE "UNDO.TRANSACTION_INACTIVE" handle.add/commit/rollback after close

UndoError

new UndoError(code: UndoCode, message: string, options?: {
  cause?: Error;
  context?: UndoErrorContext;
})
Property Type Description
code UndoCode Stable string code
message string Human-readable description
context UndoErrorContext | undefined Structured context data
cause Error | undefined Original error (for wrapping)
name "UndoError" Always set

UndoErrorContext

interface UndoErrorContext {
  commandId?: string;
  branchId?: string;
  conflictIds?: string[];
}

Implementation Notes

defineCodes ensures each code string is globally unique per prefix within the process lifetime. The UNDO_CODES object is frozen and the union type UndoCode is derived via typeof UNDO_CODES[keyof typeof UNDO_CODES], making exhaustive switch-case type-safe. Context fields are typed as UndoErrorContext but passed to the base class as Record<string, unknown> to satisfy the generic base signature without a breaking cast.