Files
skills/docs/foundation/data/diff/src/errors/diff-error.md
2026-05-29 08:20:56 +01:00

3.9 KiB

diff-error

(no submodules)

Purpose

diff-error.ts defines the error substrate for the @suaveplan/diff package. Every public API surface in the package throws a DiffError rather than a plain Error, giving callers a stable, machine-readable code property drawn from the DIFF_CODES registry. This makes error handling robust: you can match on err.code without parsing human-readable message text, and you can always narrow to a DiffError with instanceof.

The file intentionally avoids inheriting from @suaveplan/error so that @suaveplan/diff remains a true leaf package with no workspace runtime dependencies.

Features

  • DIFF_CODES constant object — five stable string codes covering all failure modes
  • DiffCode union type — narrows string to only valid diff error codes
  • DiffErrorContext interface — optional path, operation, and reason fields
  • DiffError class — extends Error, sets name = "DiffError", attaches context
  • Fully typed: readonly fields prevent accidental mutation after construction

Usage: Basic Example

import { DiffError, DIFF_CODES } from "@suaveplan/diff/errors";

try {
  throw new DiffError("path not found", DIFF_CODES.PATH_INVALID, {
    path: ["a", "b"],
    operation: "remove",
  });
} catch (err) {
  if (err instanceof DiffError) {
    console.error(err.code);      // "DIFF.PATH_INVALID"
    console.error(err.path);      // ["a", "b"]
    console.error(err.operation); // "remove"
  }
}

Usage: Advanced Example

import { DiffError, DIFF_CODES, type DiffCode } from "@suaveplan/diff/errors";

function assertNoDiffError(err: unknown): asserts err is never {
  if (err instanceof DiffError) {
    const ctx = [err.operation, err.path?.join("/"), err.reason]
      .filter(Boolean)
      .join(" | ");
    throw new Error(`DiffError [${err.code}]: ${err.message} — ${ctx}`);
  }
}

// Wrapping lower-level errors
function wrapApplyError(inner: unknown, path: string[]): DiffError {
  const reason = inner instanceof Error ? inner.message : String(inner);
  return new DiffError(`apply failed at /${path.join("/")}`, DIFF_CODES.APPLY_FAILED, {
    path,
    operation: "apply",
    reason,
  });
}

API Reference

DIFF_CODES

const DIFF_CODES: {
  readonly INVALID_PATCH: "DIFF.INVALID_PATCH";
  readonly TEST_FAILED:   "DIFF.TEST_FAILED";
  readonly PATH_INVALID:  "DIFF.PATH_INVALID";
  readonly APPLY_FAILED:  "DIFF.APPLY_FAILED";
  readonly INVERT_FAILED: "DIFF.INVERT_FAILED";
};

Stable string code registry. Always import codes from this object rather than inlining string literals.

DiffCode

type DiffCode = (typeof DIFF_CODES)[keyof typeof DIFF_CODES];

Union of all valid diff error code strings.

DiffErrorContext

interface DiffErrorContext {
  path?: Path;        // segment array of the JSON Pointer that failed
  operation?: string; // RFC 6902 op name ("add", "remove", "replace", etc.)
  reason?: string;    // free-text explanation
}

DiffError

class DiffError extends Error {
  readonly code: DiffCode;
  readonly path?: Path;
  readonly operation?: string;
  readonly reason?: string;
  constructor(message: string, code: DiffCode, context?: DiffErrorContext): DiffError;
}

All context fields are readonly after construction. name is set to "DiffError" for reliable stack-trace display.

Implementation Notes

  • Inherits directly from Error (not @suaveplan/error) to keep this package dependency-free at runtime.
  • Context fields are assigned only when defined to avoid sparse objects with explicit undefined values; this keeps JSON.stringify output clean.
  • The name = "DiffError" assignment is in the constructor body (not a class field) to ensure the prototype chain is established first, which matters for instanceof checks across realm boundaries in bundled output.