AGENT SKILL

03-error-model

Effect-TS 4 error model — Schema.TaggedErrorClass, typed defects, union errors, and error channel semantics

License 许可
MIT
最近更新
2026-05-31
Stars
★ 0
Forks
0

01 前言 Front Matter

name
03-error-model
license
MIT
description
Effect-TS 4 error model — Schema.TaggedErrorClass, typed defects, union errors, and error channel semantics
compatibility
opencode

03 SKILL.md 正文

Error Model

Defining errors

Schema.TaggedErrorClass — primary pattern

import { Schema } from "effect";

// With fields
export class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
  input: Schema.String,
  message: Schema.String,
}) {}

// With unknown cause (from catch blocks)
export class FetchError extends Schema.TaggedErrorClass<FetchError>()("FetchError", {
  url: Schema.String,
  cause: Schema.Defect, // Schema.Defect = unknown
}) {}

// No fields
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("NotFoundError", {}) {}

Plain class — for simple non-serializable errors

Use when serialization / Schema isn't needed (e.g. internal service boundary).

export class LlmError {
  readonly _tag = "LlmError";
  constructor(readonly message: string) {}
}

Reason errors — nested tagged union

export class RateLimitError extends Schema.TaggedErrorClass<RateLimitError>()("RateLimitError", {
  retryAfter: Schema.Number,
}) {}

export class AiError extends Schema.TaggedErrorClass<AiError>()("AiError", {
  reason: Schema.Union([RateLimitError, QuotaExceededError]),
}) {}

Catching errors

// Single tag
program.pipe(Effect.catchTag("ParseError", (e) => Effect.succeed(`fallback: ${e.message}`)));

// Multiple tags — same handler
program.pipe(Effect.catchTag(["ParseError", "NetworkError"], (_) => Effect.succeed(0)));

// Multiple tags — individual handlers
program.pipe(
  Effect.catchTags({
    ParseError: (e) => Effect.succeed(`parse: ${e.message}`),
    NetworkError: (e) => Effect.succeed(`net: ${e.statusCode}`),
  }),
);

// All typed errors — Effect.catch (NOT catchAll — that doesn't exist)
program.pipe(Effect.catch((_e) => Effect.succeed(defaultValue)));

Catching reason errors

// One specific reason
program.pipe(
  Effect.catchReason("AiError", "RateLimitError",
    (r) => Effect.succeed(`retry after ${r.retryAfter}s`),
  ),
)

// Unwrap into error channel for catchTags
program.pipe(
  Effect.unwrapReason("AiError"),
  Effect.catchTags({
    RateLimitError:    (r) => ...,
    QuotaExceededError:(r) => ...,
  }),
)

Catching defects (unexpected errors)

import { Cause } from "effect";

program.pipe(
  Effect.catchCause(
    (cause) => (Cause.isFailure(cause) ? Effect.succeed("recovered from typed error") : Effect.failCause(cause)), // re-raise defects
  ),
);

Wrapping at service boundaries

const findById = Effect.fn("Repo.findById")(function* (id: string) {
  return yield* sql`SELECT * FROM users WHERE id = ${id}`.pipe(
    Effect.mapError((reason) => new UserRepoError({ reason })),
  );
});

Return before yield* error

// ✅ return ensures TS knows execution stops
export const load = Effect.fn("load")(function* (id: string) {
  if (!id) return yield* new NotFoundError();
  return yield* fetchById(id);
});

04 同仓库更多 Skills