Defjs

Fault variants and factory helpers.

Errors

Execute returns a discriminated Fault in the tuple's first slot — not a thrown exception for declared failures.

FaultCode

type FaultCode =
  | "CAP_BUFFER_EXCEEDED"
  | "CAP_QUEUE_OVERFLOW"
  | "ENV_UNSUPPORTED"
  | "EXT_HOOK_FAILED"
  | "EXT_INTERCEPTOR_FAILED"
  | "EXT_OBSERVER_FAILED"
  | "HTTP_STATUS"
  | "NET_ABORTED"
  | "NET_BODY_INCOMPLETE"
  | "NET_TIMEOUT"
  | "NET_UNREACHABLE"
  | "REQ_BUILD_FAILED"
  | "REQ_INPUT_INVALID"
  | "REQ_OPTIONS_INVALID"
  | "RES_DECODE_FAILED"
  | "RES_MEDIA_TYPE_INVALID"
  | "RES_STRUCT_MISMATCH";

One closed set. The segment before the first _ is the class; there is no separate kind field. The set stays closed so that switch (fault.code) is exhaustive — extensions report their own detail through cause, not through a new code.

FaultClass

type FaultClass<C extends string> = C extends `${infer TClass}_${string}`
  ? TClass
  : never;

FaultClass<FaultCode> is 'CAP' | 'ENV' | 'EXT' | 'HTTP' | 'NET' | 'REQ' | 'RES'.

Fault

type Fault<TErr extends AnyStruct | undefined = undefined> = FaultOf<
  ErrorBody<TErr>
>;

Switch on fault.code.

FaultOf

type FaultOf<TData = undefined> =
  DecodeFault | HttpStatusFaultOf<TData> | PreflightFault;

type Fault<TErr extends AnyStruct | undefined = undefined> = FaultOf<
  ErrorBody<TErr>
>;

FaultOf is Fault expressed by the decoded error-body type instead of the struct that produced it. HttpAwaitResult and the fault factories work in decoded values, so they use this form.

Every variant is a native Error named DefjsFault, so String(fault) produces DefjsFault: <message>. code and the variant metadata — status, response, data — are enumerable own properties. The native cause chain is non-enumerable.

Default Struct messages say received <kind>, never the received value, for example Expected number at email, received string. A JSON SyntaxError behind RES_DECODE_FAILED gives the message Response body could not be decoded. Issue paths are structural and retain record keys: struct.record(...) can produce Expected number at alice@example.com, received string, so keys may still be sensitive in default messages, prettify(), and String(fault). Raw StructIssue.received and parser causes remain sensitive; custom errorMap and extension messages are used verbatim and need review before logging.

import { StructError, type Fault } from "@defjs/core";

function logFault(fault: Fault): void {
  console.error(String(fault), { code: fault.code });
  if (fault.cause instanceof StructError) {
    console.error(fault.cause.prettify());
  }
}

Call format(), flatten(), or prettify() only after narrowing fault.cause to StructError; those helpers are not copied onto the fault.

HttpStatusFault

type HttpStatusFaultOf<TData> = Error & {
  code: "HTTP_STATUS";
  data: TData;
  response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>;
  status: number;
};

type HttpStatusFault<TErr extends AnyStruct | undefined = undefined> =
  HttpStatusFaultOf<ErrorBody<TErr>>;

Any non-2xx status. When the endpoint declared error and the body decoded, data is that body and response carries it. When error was omitted the body is never read, which leaves data as undefined and response as metadata alone.

DecodeFault

type DecodeFaultCode =
  "RES_DECODE_FAILED" | "RES_MEDIA_TYPE_INVALID" | "RES_STRUCT_MISMATCH";

type DecodeFault = Error & {
  cause: unknown;
  code: DecodeFaultCode;
  response: HttpMeta;
  status: number;
};

A response arrived but could not be read as declared. response is metadata only: a body is a decoded value, and decoding is what failed, so there is no body field to reach for. The offending detail lives on cause — a StructError for a struct mismatch, the parser failure for an unreadable representation.

PreflightFault

type PreflightFaultCode = Exclude<
  FaultCode,
  DecodeFaultCode | "HTTP_STATUS"
>;

type PreflightFault = Error & {
  cause?: unknown;
  code: PreflightFaultCode;
  response?: HttpMeta;
};

Everything that may have failed before a response existed: REQ_*, NET_*, EXT_*, CAP_*, ENV_UNSUPPORTED. response is present only where the transport already had metadata to report, such as a body that truncated mid-download.

AnyFault

type AnyFault =
  | DecodeFault
  | HttpStatusFaultOf<undefined>
  | HttpStatusFaultOf<unknown>
  | PreflightFault;

Any fault regardless of its decoded error-body type. Use it for handlers that classify faults without caring which endpoint produced them; prefer Fault<typeof yourErrorStruct> where the body type matters.

Factories

createHttpStatusFault()

createUndecodedHttpStatusFault()

createDecodeFault()

createNetworkFault()

createPreflightFault()

function createHttpStatusFault<TData>(
  response: DecodedResponse<TData>,
): HttpStatusFaultOf<TData>;

function createUndecodedHttpStatusFault(
  response: HttpMeta,
): HttpStatusFaultOf<undefined>;

function createDecodeFault(
  code: DecodeFaultCode,
  cause: unknown,
  response: HttpMeta,
): DecodeFault;

function createNetworkFault(
  cause: unknown,
  response?: HttpMeta,
): PreflightFault;

function createPreflightFault(
  code: PreflightFaultCode,
  cause?: unknown,
  response?: HttpMeta,
): PreflightFault;

createHttpStatusFault takes a response that already carries the decoded body; createUndecodedHttpStatusFault takes metadata alone, for an endpoint that declared no error.

createNetworkFault uses isAbortCause for NET_ABORTED and isTimeoutCause for NET_TIMEOUT: these recognize branded sentinels from any Core copy, a DOMException named AbortError, and a DOMException or Error named TimeoutError, respectively. Everything else maps to NET_UNREACHABLE. createPreflightFault takes the code directly; with no cause, the code becomes the message.

All factories return native Error instances with the structured fields above; they do not create plain object errors and need no adapter for String(fault).

Sentinels

ERR_ABORTED

ERR_TIMEOUT

const ERR_ABORTED: Error;

const ERR_TIMEOUT: Error;

Shared Error sentinel objects used as cause for abort and timeout. Throwing one from an HTTP interceptor resolves the call with NET_ABORTED or NET_TIMEOUT. SSE and WebSocket interceptors report any throw, these included, as EXT_INTERCEPTOR_FAILED.

The sentinels carry non-enumerable Symbol.for('defjs.cause.aborted') and Symbol.for('defjs.cause.timeout') brands. Classification does not depend on the sentinel's object identity or unrelated earlier executions.

Cause checks

function isAbortCause(cause: unknown): boolean;

function isTimeoutCause(cause: unknown): boolean;

isAbortCause recognizes an abort-branded Error or a DOMException named AbortError. isTimeoutCause recognizes a timeout-branded Error or a DOMException / Error named TimeoutError. Pass fault.cause to classify the underlying value; these functions do not unwrap a fault. They work with sentinels from another Core copy.

See Errors guide.

Edit this page on GitHub
Released under the MIT License. · Copyright © 2026 MunMunMiao

On this page