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

4.0 KiB

stream-error

(no submodules)

Purpose

Defines the structured error hierarchy for the @suaveplan/streams package. Every runtime error emitted by stream operations is an instance of StreamError (which extends SuaveplanError) and carries a stable, dot-namespaced error code from the STREAM.* registry. This design means callers can narrow errors programmatically — error.code === STREAM_CODES.ABORTED — without string-matching unstructured messages, and ensures that OTel integrations, log pipelines, and dead-letter queues can route stream errors reliably.

The codes are registered once via defineCodes("STREAM", [...]) and reused across every throw site. Codes are intentionally coarse-grained: they represent the semantic category of failure, not the specific call stack location, so they remain stable across refactors.

Features

  • Six concrete error subclasses covering all stream failure modes
  • Stable STREAM.* error code registry via defineCodes
  • Exported StreamErrorCode union type for exhaustive switch statements
  • Each subclass propagates cause when it is an Error instance
  • All classes extend StreamError which extends SuaveplanError, compatible with the project error wire format

Usage: Basic Example

import { StreamAbortedError, STREAM_CODES } from "@suaveplan/streams/errors/stream-error";

function checkAborted(signal?: AbortSignal): void {
  if (signal?.aborted) {
    throw new StreamAbortedError({ cause: signal.reason });
  }
}

try {
  checkAborted(AbortSignal.abort());
} catch (err) {
  if (err instanceof StreamAbortedError) {
    console.log(err.code); // "STREAM.ABORTED"
  }
}

Usage: Advanced Example

import {
  StreamTransformFailedError,
  StreamClosedError,
  StreamReadFailedError,
  STREAM_CODES,
} from "@suaveplan/streams/errors/stream-error";

function handleStreamError(err: unknown): never {
  if (!(err instanceof Error)) {
    throw new StreamReadFailedError({ message: "Unknown read failure" });
  }
  switch ((err as { code?: string }).code) {
    case STREAM_CODES.CLOSED:
      throw new StreamClosedError({ message: "Cannot read from closed stream" });
    case STREAM_CODES.TRANSFORM_FAILED:
      throw new StreamTransformFailedError({ cause: err });
    default:
      throw new StreamReadFailedError({ cause: err });
  }
}

API Reference

STREAM_CODES

Record<string, string> — the stable code map. Keys: ABORTED, BACKPRESSURE_EXCEEDED, CHUNK_INVALID, PIPE_FAILED, TRANSFORM_FAILED, SOURCE_EXHAUSTED, READ_FAILED, WRITE_FAILED, CLOSED. Values are dot-namespaced strings like "STREAM.ABORTED".

StreamError

Base class. Extends SuaveplanError<StreamErrorCode>. All concrete subclasses inherit from this.

StreamAbortedError

Thrown when an AbortSignal fires mid-operation. Constructor: { cause?: unknown; message?: string }.

StreamTransformFailedError

Thrown when a user-supplied transform function throws. Constructor: { cause?: unknown; message?: string }.

StreamClosedError

Thrown when an operation is attempted on a closed stream. Constructor: { message?: string }.

StreamReadFailedError

Thrown on unrecoverable read failures. Constructor: { cause?: unknown; message?: string }.

StreamWriteFailedError

Thrown on unrecoverable write failures. Constructor: { cause?: unknown; message?: string }.

StreamPipeFailedError

Thrown when a pipe or pipeline composition fails. Constructor: { cause?: unknown; message?: string }.

Implementation Notes

defineCodes generates stable string codes by joining the namespace prefix and the key with a dot. The generated values are frozen at module-init time and never change, which means error codes survive across serialization round-trips (they appear verbatim in JSON error envelopes). Cause propagation is conditional: we only set cause when the original value instanceof Error to avoid accidentally serializing non-Error objects into the error chain.