effect-error-handling
/effect-error-handling
Obsługuje typowane błędy w Effect v4: TaggedError, catchTag, Cause, ErrorReporter i strategie odzyskiwania po porażce.
- Autor
- mpsuesser ↗
- Repozytorium
- opencode-effect-enforcer ↗
- Licencja
- MIT
- Ostatnia zmiana repozytorium
- 26.10.2026
Ocena i bezpieczeństwo
- Pliki w paczce
- 1
- Skrypty
- 0
- Komentarze HTML
- 34
- Adresy URL w SKILL.md
- 0
Brak sygnałów. Repozytorium zmienione 26.10.2026, bez odwołań do wycofanych modeli i API.
MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.
- SKILL.md zawiera komentarze HTML (34). Nie widać ich w podglądzie Markdown, a model je czyta.
Co robi i kiedy
Obsługuje typowane błędy w Effect v4: TaggedError, catchTag, Cause, ErrorReporter i strategie odzyskiwania po porażce.
Oryginalny opis (EN)
Implement typed error handling in Effect v4 using Schema.TaggedError, catchTag/catchTags, catchReason/catchReasons, Cause, ErrorReporter, and recovery patterns. Use this skill when working with Effect error channels, handling expected failures, or designing error recovery strategies.
Jak działa
- Effect Source Reference
- v3 to v4 Error API Changes
- Core Error Handling Philosophy
- Error Class Decision Tree
- Creating Tagged Errors
- Error Wrapping Conventions
- Yieldable Errors
- Handling Errors by Tag
- Handling Nested Error Reasons (v4)
- Catching by Filter (v4)
Podgląd SKILL.md
---
name: effect-error-handling
description: Implement typed error handling in Effect v4 using Schema.TaggedError, catchTag/catchTags, catchReason/catchReasons, Cause, ErrorReporter, and recovery patterns. Use this skill when working with Effect error channels, handling expected failures, or designing error recovery strategies.
---
You are an Effect TypeScript expert specializing in typed error handling, recovery patterns, and error channel management in **Effect v4**.
## Effect Source Reference
The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
Browse and read files there directly to look up APIs, types, and implementations.
Reference this for:
- Schema.TaggedError and error class creation
- Error handling combinators (catchTag, catchTags, catch, catchReason, catchReasons)
- Error transformation and recovery patterns
- Cause structure and inspection
- ErrorReporter module
- Defects vs error channel distinction
## v3 to v4 Error API Changes
**This table is authoritative. Never use the v3 names.**
### Effect Catch Combinators
| v3 (DO NOT USE) | v4 (USE THIS) | Notes |
| --------------------------- | --------------------------- | -------------------------------------------------- |
| `Effect.catchAll` | `Effect.catch` | Renamed |
| `Effect.catchAllCause` | `Effect.catchCause` | Renamed |
| `Effect.catchAllDefect` | `Effect.catchDefect` | Renamed |
| `Effect.catchSome` | `Effect.catchFilter` | Uses `Filter` module instead of `Option` |
| `Effect.catchSomeCause` | `Effect.catchCauseFilter` | Uses `Filter` module instead of `Option` |
| `Effect.catchSomeDefect` | Removed | No replacement |
| `Effect.optionFromOptional` | `Effect.catchNoSuchElement` | Renamed |
| `Effect.catchTag` | `Effect.catchTag` | Enhanced: accepts array of tags, optional `orElse` |
| `Effect.catchTags` | `Effect.catchTags` | Enhanced: optional `orElse` fallback |
| `Effect.catchIf` | `Effect.catchIf` | Enhanced: optional `orElse` fallback |
| (none) | `Effect.catchReason` | NEW: catch nested reason within tagged error |
| (none) | `Effect.catchReasons` | NEW: catch multiple nested reasons |
| (none) | `Effect.unwrapReason` | NEW: promote nested reasons to error channel |
| (none) | `Effect.catchEager` | NEW: synchronous recovery optimization |
| (none) | `Effect.withErrorReporting` | NEW: report errors to registered ErrorReporters |
### Cause Structure
| v3 (DO NOT USE) | v4 (USE THIS) | Notes |
| -------------------------------- | ------------------------------------------ | ----------------------------- |
| 6-variant recursive tree | `{ reasons: ReadonlyArray<Reason<E>> }` | Flattened |
| `Cause.sequential(l, r)` | `Cause.combine(l, r)` | Concatenates reasons arrays |
| `Cause.parallel(l, r)` | `Cause.combine(l, r)` | Same as sequential |
| `Cause.isFailType(cause)` | `Cause.isFailReason(reason)` | Operates on Reason, not Cause |
| `Cause.isDieType(cause)` | `Cause.isDieReason(reason)` | Operates on Reason, not Cause |
| `Cause.isInterruptType(cause)` | `Cause.isInterruptReason(reason)` | Operates on Reason, not Cause |
| `Cause.isFailure(cause)` | `Cause.hasFails(cause)` | Renamed |
| `Cause.isDie(cause)` | `Cause.hasDies(cause)` | Renamed |
| `Cause.isInterrupted(cause)` | `Cause.hasInterrupts(cause)` | Renamed |
| `Cause.isInterruptedOnly(cause)` | `Cause.hasInterruptsOnly(cause)` | Renamed |
| `Cause.failureOption(cause)` | `Cause.findErrorOption(cause)` | Renamed |
| `Cause.failureOrCause(cause)` | `Cause.findError(cause)` | Returns `Result.Result` now |
| `Cause.dieOption(cause)` | `Cause.findDefect(cause)` | Returns `Result.Result` now |
| `Cause.interruptOption(cause)` | `Cause.findInterrupt(cause)` | Returns `Result.Result` now |
| `Cause.failures(cause)` | `cause.reasons.filter(Cause.isFailReason)` | Use array filter |
| `Cause.defects(cause)` | `cause.reasons.filter(Cause.isDieReason)` | Use array filter |
### Error Class Renames (`*Exception` to `*Error`)
| v3 (DO NOT USE) | v4 (USE THIS) |
| -------------------------------------- | ----------------------------- |
| `Cause.NoSuchElementException` | `Cause.NoSuchElementError` |
| `Cause.TimeoutException` | `Cause.TimeoutError` |
| `Cause.IllegalArgumentException` | `Cause.IllegalArgumentError` |
| `Cause.ExceededCapacityException` | `Cause.ExceededCapacityError` |
| `Cause.UnknownException` | `Cause.UnknownError` |
| `Cause.RuntimeException` | Removed |
| `Cause.InterruptedException` | Removed |
| `Cause.InvalidPubSubCapacityException` | Removed |
### Schema Error Renames
| Old API (DO NOT USE) | Effect v4 API (USE THIS) |
| --------------------------------- | ------------------------------ |
| `Schema.TaggedErrorClass` | `Schema.TaggedError` |
| `Schema.ErrorClass` | `Schema.Error` |
| `Schema.Error` (instance schema) | `Schema.ErrorInstance` |
| `Schema.ErrorReviver` | `SchemaRepresentation.ErrorInstanceReviver` |
| `ParseError` | `Schema.SchemaError` |
## Core Error Handling Philosophy
Effect distinguishes expected errors from defects:
1. **Expected Errors (Error Channel)** - Business logic failures that should be handled
- Type-safe and tracked in the effect signature: `Effect<A, E, R>`
- Represented by the `E` type parameter
- Handle with catchTag, catchTags, catch, catchReason, catchReasons
2. **Unexpected Errors (Defects)** - Programming errors that indicate bugs
- Not tracked in the type system
- Result from programming mistakes (null refs, unhandled cases, assertions)
- Usually should NOT be caught; use catchDefect only at boundaries
Interruption is cancellation, a separate `Cause` reason rather than a typed
business error. A cause can retain several reasons: if work fails and its
finalizer throws, both the original failure and the finalizer defect remain.
Inspect the full `Cause` at supervision boundaries rather than assuming one
reason or discarding cancellation during cleanup.
### Runtime Adapter Boundaries and Invariants
Do not force every impossible or adapter-internal failure into a tagged error just to satisfy a blanket rule.
Use typed errors for:
- caller-actionable failures
- business or protocol failures that the next layer can recover from
- public service contracts
Use defects or `Effect.orDie` for:
- impossible branches and invariant violations
- runtime-adapter internals where no caller can recover meaningfully
- collapsing noisy upstream error surfaces at a boundary that should not leak them further
`new Error(...)` is acceptable inside `Effect.die(...)`, invariant branches, or adapter-only defect paths. It is not acceptable as the public error model for recoverable domain behavior.
### When to Use Error Channel vs Defects
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const findUser: (userId: string) => Effect.Effect<User, UserNotFound>;
declare const validatePassword: (
user: User,
password: string
) => Effect.Effect<boolean, InvalidCredentials>;
declare const database: {
query: (
sql: string,
...params: ReadonlyArray<unknown>
) => Effect.Effect<unknown>;
};
interface User {
readonly id: string;
readonly name: string;
}
// CORRECT - Expected business failures in error channel
class UserNotFound extends Schema.TaggedError<UserNotFound>()(
'UserNotFound',
{
userId: Schema.String,
message: Schema.String
}
) {}
class InvalidCredentials extends Schema.TaggedError<InvalidCredentials>()(
'InvalidCredentials',
{ reason: Schema.String, message: Schema.String }
) {}
const authenticateUser = (
userId: string,
password: string
): Effect.Effect<User, UserNotFound | InvalidCredentials> =>
Effect.gen(function* () {
const user = yield* findUser(userId); // Can fail with UserNotFound
const valid = yield* validatePassword(user, password); // Can fail with InvalidCredentials
return user;
});
// CORRECT - Programmer errors as defects (use Effect.die)
const assertPositive = (n: number): Effect.Effect<number> =>
n > 0
? Effect.succeed(n)
: Effect.die(new Error(`Expected positive number, got ${n}`));
// WRONG - Business failure as defect
const findUserWrong = (userId: string): Effect.Effect<User> =>
Effect.gen(function* () {
const user = yield* database.query(
'SELECT * FROM users WHERE id = ?',
userId
);
if (!user) {
yield* Effect.die(new Error('User not found')); // Should be in error channel!
}
return user as User;
});
```
## Error Class Decision Tree
Effect v4 provides three ways to define error classes. Choose based on context:
### `Schema.TaggedError` — Primary choice for domain errors
Schema-validated, automatically tagged with `_tag`, catchable via `catchTag`. Use for all cross-module and public API errors.
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
class NotFound extends Schema.TaggedError<NotFound>()(
'NotFound',
{ id: Schema.String, message: Schema.String },
{ description: 'Entity was not found.' }
) {}
// Constructed with schema validation
const error = new NotFound({ id: '123', message: 'User not found' });
error._tag; // "NotFound"
```
### `Schema.Error` — For manual tag control
Schema-validated but no automatic `_tag`. Use when you need a custom discriminator field (e.g., HttpApiError types use `_tag: Schema.tag("NotFound")` manually).
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
class NotFound extends Schema.Error<NotFound>('NotFound')({
_tag: Schema.tag('NotFound'),
message: Schema.String
}) {}
```
### `Data.TaggedError` — Lightweight, no schema validation
No schema validation overhead. Use for module-internal errors or hot paths where schema decoding cost is unwanted.
```typescript
import * as Data from 'effect/Data';
class InternalError extends Data.TaggedError('InternalError')<{
readonly message: string;
}> {}
// Still catchable via catchTag
const program = Effect.fail(new InternalError({ message: 'oops' })).pipe(
Effect.catchTag('InternalError', (e) => Effect.succeed(e.message))
);
```
### Decision summary
| Scenario | Use |
| ------------------------------------------- | ------------------------- |
| Cross-module / public API errors | `Schema.TaggedError` |
| Errors that need `httpApiStatus` annotation | `Schema.TaggedError` |
| Errors with a `reason` union field | `Schema.TaggedError` |
| Custom discriminator field (not `_tag`) | `Schema.Error` |
| Module-internal, no serialization needed | `Data.TaggedError` |
## Creating Tagged Errors
Always use `Schema.TaggedError` for domain errors with a `message` field.
### Basic Tagged Error
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
// Simple error with message only
export class NetworkError extends Schema.TaggedError<NetworkError>()(
'NetworkError',
{ message: Schema.String },
{ description: 'Network request failed.' }
) {}
// Error with rich context
export class ValidationError extends Schema.TaggedError<ValidationError>()(
'ValidationError',
{
field: Schema.String,
message: Schema.String,
value: Schema.optional(Schema.Unknown)
},
{ description: 'Input validation failed for a specific field.' }
) {}
// Usage
const error = new ValidationError({
field: 'email',
message: 'Invalid email format',
value: 'not-an-email'
});
```
### Error with Reason Discriminator
For bindings that wrap a single external system, use a `reason` literal union to keep the error surface compact while remaining precise:
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class ApiError extends Schema.TaggedError<ApiError>()(
'ApiError',
{
reason: Schema.Literals([
'BadRequest',
'Unauthorized',
'NotFound',
'RateLimited',
'ServerError',
'Timeout'
]),
message: Schema.String,
statusCode: Schema.optional(Schema.Number),
details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
},
{ description: 'Failure from an external API operation.' }
) {}
```
### Error with Nested Reason Types (for `catchReason`/`catchReasons`)
When reason variants carry distinct payloads, model each as a separate `TaggedError` and compose with `Schema.Union`. This enables v4's `catchReason` and `catchReasons`:
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class RateLimitError extends Schema.TaggedError<RateLimitError>()(
'RateLimitError',
{ retryAfter: Schema.Number },
{ description: 'Rate limit exceeded.' }
) {}
export class QuotaExceededError extends Schema.TaggedError<QuotaExceededError>()(
'QuotaExceededError',
{ limit: Schema.Number },
{ description: 'Quota exhausted.' }
) {}
export class SafetyBlockedError extends Schema.TaggedError<SafetyBlockedError>()(
'SafetyBlockedError',
{ category: Schema.String },
{ description: 'Blocked by safety filter.' }
) {}
export class AiError extends Schema.TaggedError<AiError>()(
'AiError',
{
reason: Schema.Union([
RateLimitError,
QuotaExceededError,
SafetyBlockedError
])
},
{ description: 'Failure from an AI model call.' }
) {}
```
### Error with HTTP Status Annotation
For errors that map to HTTP responses, use the `httpApiStatus` annotation:
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class Unauthorized extends Schema.TaggedError<Unauthorized>()(
'Unauthorized',
{ message: Schema.String },
{
httpApiStatus: 401,
description: 'Request lacks valid authentication credentials.'
}
) {}
export class EntityNotFound extends Schema.TaggedError<EntityNotFound>()(
'EntityNotFound',
{ entityType: Schema.String, id: Schema.String, message: Schema.String },
{
httpApiStatus: 404,
description: 'Requested entity does not exist.'
}
) {}
```
### Error with Custom Properties
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class HttpError extends Schema.TaggedError<HttpError>()(
'HttpError',
{
status: Schema.Number,
body: Schema.String,
message: Schema.String
},
{ description: 'HTTP response error.' }
) {
get isClientError() {
return this.status >= 400 && this.status < 500;
}
get isServerError() {
return this.status >= 500;
}
}
```
## Error Wrapping Conventions
When wrapping upstream errors in domain error classes, choose the `cause` field schema based on intent:
| `cause` Schema | When to use | Example |
| ---------------- | -------------------------------------------------------------- | -------------------------------- |
| `Schema.Defect()` | Wrapping unknown/untyped upstream errors (throwables, defects) | `DevToolsError`, `DatabaseError` |
| `Schema.Unknown` | Preserving full upstream error structure for debugging | `SubstackFetchError` |
| `Schema.String` | Message-only wrapping where structure is irrelevant | `AuthError` |
| Omitted | When the error tag + fields fully describe the failure | `UserNotFound`, `Unauthorized` |
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
// Defect-style: wraps throwables and unknown failures
export class DatabaseError extends Schema.TaggedError<DatabaseError>()(
'DatabaseError',
{
operation: Schema.String,
message: Schema.String,
cause: Schema.Defect()
},
{ description: 'Database operation failed.' }
) {}
// Unknown-style: preserves full upstream error
export class FetchError extends Schema.TaggedError<FetchError>()(
'FetchError',
{
url: Schema.String,
message: Schema.String,
cause: Schema.Unknown
},
{ description: 'HTTP fetch operation failed.' }
) {}
// String-style: message-only wrapper
export class AuthError extends Schema.TaggedError<AuthError>()(
'AuthError',
{
cause: Schema.String
},
{ description: 'Authentication backend failure.' }
) {}
```
## Yieldable Errors
In `Effect.gen` blocks, tagged error instances can be yielded directly as a shorthand for `yield* Effect.fail(...)`. This works because `Schema.TaggedError`, `Schema.Error`, and `Data.TaggedError` all extend `Cause.YieldableError`.
<!-- typecheck -->
```typescript
import { Effect } from 'effect';
import * as Schema from 'effect/Schema';
class NotFound extends Schema.TaggedError<NotFound>()('NotFound', {
id: Schema.String,
message: Schema.String
}) {}
// These two are equivalent:
const explicit = Effect.gen(function* () {
return yield* Effect.fail(
new NotFound({ id: '123', message: 'User not found' })
);
});
const shorthand = Effect.gen(function* () {
return yield* new NotFound({ id: '123', message: 'User not found' });
});
```
The shorthand form is idiomatic and preferred in Effect v4 generators. It reads naturally as "yield this error" and reduces noise.
## Handling Errors by Tag
### catchTag - Single Error Type
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const createGuestUser: (id: string) => User;
interface User {
readonly id: string;
readonly name: string;
}
class NotFound extends Schema.TaggedError<NotFound>()('NotFound', {
id: Schema.String,
message: Schema.String
}) {}
class Unauthorized extends Schema.TaggedError<Unauthorized>()(
'Unauthorized',
{
message: Schema.String
}
) {}
// Effect<User, NotFound | Unauthorized, Dependencies>
// v
const getUser = (id: string): Effect.Effect<User, NotFound | Unauthorized> =>
Effect.fail(new NotFound({ id, message: `User ${id} not found` }));
// Handle single error type
// Effect<User, Unauthorized, Dependencies>
// v
const program = getUser('123').pipe(
Effect.catchTag('NotFound', (error) =>
// Return default user when not found
Effect.succeed(createGuestUser(error.id))
)
);
```
### catchTag - Array Form (v4)
In Effect v4, `catchTag` accepts an array of tags to handle multiple error types with a single handler:
<!-- typecheck -->
```typescript
import { Effect, Schema } from 'effect';
class ParseError extends Schema.TaggedError<ParseError>()('ParseError', {
input: Schema.String,
message: Schema.String
}) {}
class ReservedPortError extends Schema.TaggedError<ReservedPortError>()(
'ReservedPortError',
{
port: Schema.Number
}
) {}
declare const loadPort: (
input: string
) => Effect.Effect<number, ParseError | ReservedPortError>;
// Catch multiple tags with one handler - the error is typed as the union
const program = loadPort('80').pipe(
Effect.catchTag(['ParseError', 'ReservedPortError'], (_) =>
Effect.succeed(3000)
)
);
```
### catchTag / catchTags - Optional `orElse` Fallback (v4)
In v4, `catchTag`, `catchTags`, and `catchIf` accept an optional trailing `orElse` parameter for unmatched errors:
<!-- typecheck -->
```typescript
import { Effect, Schema } from 'effect';
class NotFound extends Schema.TaggedError<NotFound>()('NotFound', {
message: Schema.String
}) {}
class Forbidden extends Schema.TaggedError<Forbidden>()('Forbidden', {
message: Schema.String
}) {}
class ServerError extends Schema.TaggedError<ServerError>()(
'ServerError',
{
message: Schema.String
}
) {}
declare const riskyOp: () => Effect.Effect<
string,
NotFound | Forbidden | ServerError
>;
// The third argument is the orElse handler for unmatched errors
const program = riskyOp().pipe(
Effect.catchTag(
'NotFound',
(e) => Effect.succeed('default'),
(unmatched) => Effect.die(unmatched) // Forbidden | ServerError
)
);
// Works with catchTags too
const program2 = riskyOp().pipe(
Effect.catchTags(
{
NotFound: (e) => Effect.succeed('default'),
Forbidden: (e) => Effect.succeed('forbidden')
},
(unmatched) => Effect.die(unmatched) // ServerError
)
);
```
> **Type preservation:** When you omit `orElse`, unhandled tags stay in the error channel — `catchTag(['NotFound'], ...)` on `Effect<string, NotFound | Forbidden | ServerError>` yields `Effect<string, Forbidden | ServerError>`. Supplying `orElse` handles the remaining variants, so the resulting error channel reflects what the fallback produces.
### catchTags - Multiple Error Types
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
interface Data {
readonly data: ReadonlyArray<unknown>;
readonly cached?: boolean;
readonly timeout?: boolean;
readonly parseError?: boolean;
}
class NetworkError extends Schema.TaggedError<NetworkError>()(
'NetworkError',
{
message: Schema.String
}
) {}
class TimeoutError extends Schema.TaggedError<TimeoutError>()(
'TimeoutError',
{
message: Schema.String
}
) {}
class ParseError extends Schema.TaggedError<ParseError>()('ParseError', {
input: Schema.String,
message: Schema.String
}) {}
// Effect<Data, NetworkError | TimeoutError | ParseError, Dependencies>
// v
const fetchData = (): Effect.Effect<
Data,
NetworkError | TimeoutError | ParseError
> => Effect.fail(new NetworkError({ message: 'Connection refused' }));
// Handle multiple error types at once
// Effect<Data, never, Dependencies>
// v
const program = fetchData().pipe(
Effect.catchTags({
NetworkError: (_error) => Effect.succeed({ data: [], cached: true }),
TimeoutError: (_error) => Effect.succeed({ data: [], timeout: true }),
ParseError: (error) =>
// Access error-specific fields
Effect.logError(`Failed to parse: ${error.input}`).pipe(
Effect.as({ data: [], parseError: true })
)
})
);
```
### catch - Handle All Errors
`Effect.catch` (renamed from `catchAll` in v3) handles all errors with a single handler:
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const getDefaultResult: () => Result;
interface Result {
readonly value: string;
}
class InvalidInput extends Schema.TaggedError<InvalidInput>()(
'InvalidInput',
{
message: Schema.String
}
) {}
class ProcessingError extends Schema.TaggedError<ProcessingError>()(
'ProcessingError',
{
message: Schema.String
}
) {}
const process = (): Effect.Effect<Result, InvalidInput | ProcessingError> =>
Effect.fail(new InvalidInput({ message: 'Bad input' }));
const program = process().pipe(
Effect.catch((error) =>
// error is typed as: InvalidInput | ProcessingError
Effect.logError(`Operation failed: ${error._tag}`).pipe(
Effect.as(getDefaultResult())
)
)
);
```
**Migration note:** When replacing `Effect.promise(() => Service.method())` with direct service yields (`yield* service.method()`), errors that previously flowed as defects (untyped Promise rejections) become typed channel errors. Existing `catchDefect` handlers must be replaced with `catch` or `catchTag` to match the now-typed error channel.
### catchEager - Synchronous Recovery (v4)
`Effect.catchEager` is an optimization of `catch` that evaluates synchronous recovery effects immediately rather than suspending. Use for lightweight wrapping where the recovery handler is always synchronous:
```typescript
import { Effect, Schema } from 'effect';
class CliConfigError extends Schema.TaggedError<CliConfigError>()(
'CliConfigError',
{
message: Schema.String
}
) {}
const loadCredentials = Effect.tryPromise({
try: () => readFile('~/.config/myapp/credentials.json'),
catch: (cause) => cause
}).pipe(
Effect.catchEager((cause) =>
Effect.fail(
new CliConfigError({
message: `Failed to load credentials: ${cause}`
})
)
)
);
```
### catchNoSuchElement - Convert NoSuchElementError to Option (v4)
`Effect.catchNoSuchElement` (renamed from `optionFromOptional` in v3) catches `Cause.NoSuchElementError` and converts the result to `Option`:
```typescript
import { Effect } from 'effect';
import * as Option from 'effect/Option';
declare const maybeFindItem: () => Effect.Effect<
string,
Cause.NoSuchElementError
>;
// Effect<Option<string>, never>
const program = Effect.catchNoSuchElement(maybeFindItem());
```
## Handling Nested Error Reasons (v4)
When errors use a `reason` field containing a tagged union (see "Error with Nested Reason Types" above), v4 provides three purpose-built APIs.
### catchReason - Catch One Specific Reason
Catches a specific `reason` variant within a tagged error without removing the parent error from the error channel:
```typescript
import { Effect } from 'effect';
declare const callModel: Effect.Effect<string, AiError>;
// Catch only RateLimitError reason within AiError
const program = callModel.pipe(
Effect.catchReason(
'AiError', // parent error _tag
'RateLimitError', // reason _tag to catch
(reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`)
)
);
// With optional orElse for uncaught reasons
const withFallback = callModel.pipe(
Effect.catchReason(
'AiError',
'RateLimitError',
(reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`),
(reason) => Effect.succeed(`Model call failed: ${reason._tag}`) // QuotaExceeded | SafetyBlocked
)
);
```
### catchReasons - Catch Multiple Reasons
Handle multiple reason variants at once via an object of handlers:
```typescript
import { Effect } from 'effect';
declare const callModel: Effect.Effect<string, AiError>;
const program = callModel.pipe(
Effect.catchReasons('AiError', {
RateLimitError: (reason) =>
Effect.succeed(`Retry after ${reason.retryAfter} seconds`),
QuotaExceededError: (reason) =>
Effect.succeed(`Quota exceeded at ${reason.limit} tokens`)
})
// SafetyBlockedError remains unhandled in the error channel
);
```
### unwrapReason - Promote Reasons to Error Channel
Unwraps the `reason` field, replacing the parent error with its reason variants in the error channel. Useful when you want to `catchTags` the individual reasons directly:
```typescript
import { Effect } from 'effect';
declare const callModel: Effect.Effect<string, AiError>;
const program = callModel.pipe(
Effect.unwrapReason('AiError'),
// Error channel is now: RateLimitError | QuotaExceededError | SafetyBlockedError
Effect.catchTags({
RateLimitError: (r) => Effect.succeed(`Back off for ${r.retryAfter}s`),
QuotaExceededError: (r) =>
Effect.succeed(`Increase quota beyond ${r.limit}`),
SafetyBlockedError: (r) => Effect.succeed(`Blocked: ${r.category}`)
})
);
```
## Catching by Filter (v4)
`Effect.catchFilter` replaces v3's `Effect.catchSome` (which used `Option`). It uses the `Filter` module:
<!-- typecheck -->
```typescript
import { Effect, Filter } from 'effect';
// v3 (DO NOT USE):
// Effect.catchSome((error) =>
// error === 42 ? Option.some(Effect.succeed("caught")) : Option.none()
// )
// v4:
const program = Effect.fail(42).pipe(
Effect.catchFilter(
Filter.fromPredicate((error: number) => error === 42),
(error) => Effect.succeed('caught')
)
);
```
`Effect.catchCauseFilter` is the Cause-level equivalent (replaces `catchSomeCause`).
## Cause Structure (v4)
In v4, `Cause<E>` is a flat wrapper around an array of reasons — **not** a recursive tree.
Runtime JSON/inspection uses the `reasons` property in 4.0.6 (not `failures`).
This display correction does not change schema-backed cause encodings.
```typescript
interface Cause<E> {
readonly reasons: ReadonlyArray<Reason<E>>;
}
type Reason<E> = Fail<E> | Die | Interrupt;
```
There are only three reason variants:
- `Fail<E>` — `{ readonly error: E }` — expected typed failures
- `Die` — `{ readonly defect: unknown }` — unexpected defects
- `Interrupt` — `{ readonly fiberId: number | undefined }` — fiber interruptions
An empty cause is `cause.reasons.length === 0`. The `Empty`, `Sequential`, and `Parallel` variants from v3 no longer exist.
### Inspecting Causes
<!-- typecheck -->
```typescript
import { Cause } from 'effect';
const inspectCause = <E>(cause: Cause.Cause<E>) => {
// Iterate over the flat reasons array
for (const reason of cause.reasons) {
if (Cause.isFailReason(reason)) {
console.log('Expected error:', reason.error);
} else if (Cause.isDieReason(reason)) {
console.log('Defect:', reason.defect);
} else if (Cause.isInterruptReason(reason)) {
console.log('Interrupted by fiber:', reason.fiberId);
}
}
};
```
### Cause Extractors
<!-- typecheck -->
```typescript
import { Cause } from 'effect';
import * as Option from 'effect/Option';
declare const cause: Cause.Cause<string>;
// Extract first error as Option
const errorOpt: Option.Option<string> = Cause.findErrorOption(cause);
// Extract first error as Result (Result.Result<E, Cause<never>>)
const errorResult = Cause.findError(cause);
// Extract first defect as Result
const defectResult = Cause.findDefect(cause);
// Check what a cause contains
Cause.hasFails(cause); // has at least one Fail reason
Cause.hasDies(cause); // has at least one Die reason
Cause.hasInterrupts(cause); // has at least one Interrupt reason
Cause.hasInterruptsOnly(cause); // only Interrupt reasons, no Fail/Die
// Human-readable rendering
const pretty: string = Cause.pretty(cause);
```
### Cause Constructors (v4)
```typescript
import { Cause } from 'effect';
Cause.empty; // empty cause (no reasons)
Cause.fail(error); // single Fail reason
Cause.die(defect); // single Die reason
Cause.interrupt(fiberId); // single Interrupt reason
Cause.combine(left, right); // concatenate two causes' reasons
Cause.fromReasons(reasons); // construct from array of Reason values
Cause.makeFailReason(error); // construct a Fail reason
Cause.makeDieReason(defect); // construct a Die reason
Cause.makeInterruptReason(fiberId); // construct an Interrupt reason
Cause.annotate(cause, annotations); // attach metadata
```
### Cause.Done - Graceful Completion Signal (v4)
`Cause.Done<A>` is a graceful completion value used by queues, pulls, and streams. It travels through the typed error channel, but consumers interpret it as successful end-of-input rather than an operational failure; its `value` can carry a final leftover payload.
```typescript
import { Cause } from 'effect';
Cause.Done(); // create a Done<void> signal
Cause.Done('leftover'); // create Done<string> with a final payload
Cause.done('leftover'); // Effect<never, Cause.Done<string>>
Cause.isDone(value); // type guard
```
A `Fail` reason carrying `Done` can be combined with a real failure, for example when stream completion and resource finalization both settle unsuccessfully. Pull/stream completion handlers remove the `Done` signal but preserve any other merged failure reasons; do not treat the presence of `Done` as permission to discard the whole cause.
## Exhaustive Error Handling with Match
Use Match for exhaustive error handling with compile-time guarantees:
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Match from 'effect/Match';
import * as Schema from 'effect/Schema';
declare const dangerousOperation: () => Effect.Effect<string, AppError>;
class ConnectionError extends Schema.TaggedError<ConnectionError>()(
'ConnectionError',
{
message: Schema.String
}
) {}
class AuthError extends Schema.TaggedError<AuthError>()('AuthError', {
message: Schema.String
}) {}
class DataError extends Schema.TaggedError<DataError>()('DataError', {
message: Schema.String
}) {}
type AppError = ConnectionError | AuthError | DataError;
const handleError = (error: AppError): Effect.Effect<string> =>
Match.value(error).pipe(
Match.tag('ConnectionError', () =>
Effect.succeed('Please check your network connection')
),
Match.tag('AuthError', () => Effect.succeed('Authentication required')),
Match.tag('DataError', (err) =>
Effect.succeed(`Data error: ${err.message}`)
),
Match.exhaustive // Compiler ensures all cases handled
);
const program = dangerousOperation().pipe(Effect.catch(handleError));
```
## Error Transformation
### Mixed causes in effect@4.0.6
Inspect exact release source with `git show effect@4.0.6:packages/effect/src/Effect.ts`
and `internal/effect.ts`; the 4.0.6 rule supersedes earlier patch-release retry behavior.
Typed recovery (`catch`, tag/reason/filter catches, fallbacks, `match`, `result`,
`option`, `ignore`, and typed retry/repeat handling) gives defects precedence:
- With any `Die` reason, skip typed recovery and convert **every** `Fail(e)` to
`Die(e)`, preserving reason order and all interrupts.
- With a typed failure and interruption but **no defect**, recovery is allowed;
a successful handler can discard the interruption too.
- Pure interruption has no typed error to recover and propagates.
This is not a blanket guarantee that typed recovery preserves cancellation in
mixed causes. At an explicit supervision boundary that must preserve every
interrupt, inspect the cause and re-propagate it before typed recovery.
<!-- typecheck -->
```ts
import { Cause, Effect } from 'effect';
const mixed = Cause.combine(Cause.fail('expected'), Cause.die('bug'));
const recovered = Effect.failCause(mixed).pipe(
Effect.catch(() => Effect.succeed('not reached'))
);
const exit = Effect.runSyncExit(recovered);
// Failure cause contains Die('expected'), Die('bug'), in that order.
```
### mapError - Transform Error Type
`mapError` / `mapBoth` (including eager variants), `Exit.mapError`, and
`Stream.mapError` map **every** typed error in place, preserving defects,
interrupts, and reason order. Unlike recovery, mapping does not promote errors
to defects when a defect is also present.
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const fetchFromDatabase: () => Effect.Effect<Data, InfrastructureError>;
interface Data {
readonly value: string;
}
class DomainError extends Schema.TaggedError<DomainError>()(
'DomainError',
{
message: Schema.String
}
) {}
class InfrastructureError extends Schema.TaggedError<InfrastructureError>()(
'InfrastructureError',
{ message: Schema.String, cause: Schema.optional(Schema.Unknown) }
) {}
// Transform infrastructure errors to domain errors
const program = fetchFromDatabase().pipe(
Effect.mapError(
(infraError: InfrastructureError) =>
new DomainError({
message: `Database operation failed: ${infraError.message}`
})
)
);
```
### Idempotent Error Wrapping
When writing reusable error-mapping combinators shared across multiple call sites, guard against double-wrapping:
<!-- typecheck -->
```typescript
import { Effect, Schema } from 'effect';
class ServiceError extends Schema.TaggedError<ServiceError>()(
'ServiceError',
{
message: Schema.String,
cause: Schema.optional(Schema.Unknown)
}
) {}
const mapServiceError =
(message = 'Service operation failed') =>
<A, E, R>(
effect: Effect.Effect<A, E, R>
): Effect.Effect<A, ServiceError, R> =>
effect.pipe(
Effect.mapError((cause) =>
cause instanceof ServiceError
? cause
: new ServiceError({ message, cause })
)
);
```
The `instanceof` guard prevents double-wrapping when an upstream operation already returns the target error type.
## Error Recovery Patterns
For per-item typed outcomes, `Effect.partition(items, work, { concurrency })`
returns `[successes, failures]`, in that order. Both arrays preserve input order.
`Effect.all(..., { mode: 'result' })` instead preserves the input structure with
one `Result` per slot. Their `never` error channel means typed failures are
collected; mixed causes follow the rule above, while defects and pure interruption
still propagate. Use `Effect.validate` when
any item failure must fail the entire batch with all typed errors.
Retry timing and recurrence design belong in the dedicated effect-scheduling skill. This skill determines which failures are typed and recoverable; scheduling determines whether, when, and how often an idempotent operation is retried.
### Translate at Service Boundaries
Translate infrastructure errors into the service's public error vocabulary at the boundary that owns the abstraction, using `mapError`, `catchTag`, or `catchTags`. Preserve useful context in the translated error and avoid repeatedly wrapping an error already in the target vocabulary.
Avoid blanket `catchCause`, `ignoreCause`, or cause-to-domain-error conversion in
ordinary services. Cause-level recovery belongs at an explicit supervision/runtime
boundary; detect and re-propagate interruption rather than logging it as an
operational error and continuing. Typed recovery has the mixed-cause caveats above.
### Fallback with orElse
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
interface Data {
readonly value: string;
}
class PrimaryServiceError extends Schema.TaggedError<PrimaryServiceError>()(
'PrimaryServiceError',
{ message: Schema.String }
) {}
class SecondaryServiceError extends Schema.TaggedError<SecondaryServiceError>()(
'SecondaryServiceError',
{ message: Schema.String }
) {}
const primaryService: Effect.Effect<Data, PrimaryServiceError> = Effect.fail(
new PrimaryServiceError({ message: 'Primary down' })
);
const secondaryService: Effect.Effect<Data, SecondaryServiceError> =
Effect.fail(new SecondaryServiceError({ message: 'Secondary down' }));
// Try primary, fallback to secondary
// Effect<Data, SecondaryServiceError, Dependencies>
const program = primaryService.pipe(Effect.catch(() => secondaryService));
```
### Retry with Schedule
For policy selection, bounds, backoff, jitter, rate-limit delays, polling, and idempotency requirements, see the effect-scheduling skill.
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schedule from 'effect/Schedule';
import * as Schema from 'effect/Schema';
interface Data {
readonly value: string;
}
class TransientError extends Schema.TaggedError<TransientError>()(
'TransientError',
{
message: Schema.String
}
) {}
const unreliableOperation: Effect.Effect<Data, TransientError> = Effect.fail(
new TransientError({ message: 'Temporary failure' })
);
// Retry with exponential backoff
const program = unreliableOperation.pipe(
Effect.retry(
Schedule.exponential('100 millis').pipe(
Schedule.upTo({ times: 5 })
) // Max 5 retries
)
);
```
### Provide Default Value
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const getDefaultConfig: () => Config;
interface Config {
readonly port: number;
readonly host: string;
}
class FetchError extends Schema.TaggedError<FetchError>()('FetchError', {
message: Schema.String
}) {}
const fetchConfig: Effect.Effect<Config, FetchError> = Effect.fail(
new FetchError({ message: 'Config not available' })
);
// Provide default on failure
const program = fetchConfig.pipe(
Effect.orElseSucceed(() => getDefaultConfig())
);
```
### Convert Error to Option
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
interface Item {
readonly id: string;
readonly name: string;
}
class NotFoundError extends Schema.TaggedError<NotFoundError>()(
'NotFoundError',
{
message: Schema.String
}
) {}
const findItem: Effect.Effect<Item, NotFoundError> = Effect.fail(
new NotFoundError({ message: 'Not found' })
);
// Convert to Option (None if error)
// Effect<Option<Item>, never, Dependencies>
const program = findItem.pipe(Effect.option);
```
## Error Channel vs Defect Operators
### Converting Errors to Defects
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
interface Config {
readonly port: number;
readonly host: string;
}
class ConfigError extends Schema.TaggedError<ConfigError>()(
'ConfigError',
{
message: Schema.String
}
) {}
const loadConfig: Effect.Effect<Config, ConfigError> = Effect.fail(
new ConfigError({ message: 'Missing config' })
);
// Convert error to defect (terminates fiber)
const program = loadConfig.pipe(
Effect.orDie // Error becomes a defect
);
// With custom defect message
const program2 = loadConfig.pipe(
Effect.mapError(
(error) =>
new Error(`Fatal: Configuration failed to load: ${error._tag}`)
),
Effect.orDie
);
```
### Handling Defects (Boundary Only)
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
declare const dangerousPlugin: () => Effect.Effect<unknown>;
declare const getDefaultPluginBehavior: () => unknown;
// NOTE: ONLY use at application boundaries
const safeProgram = dangerousPlugin().pipe(
Effect.catchDefect((defect) =>
Effect.logError(`Plugin crashed: ${defect}`).pipe(
Effect.as(getDefaultPluginBehavior())
)
)
);
```
## ErrorReporter (v4)
The `ErrorReporter` module is new in v4. It provides pluggable, structured error reporting with severity levels and metadata.
### Defining a Reporter
<!-- typecheck -->
```typescript
import { ErrorReporter } from 'effect';
// Create a custom reporter — the callback receives a single options object
const myReporter = ErrorReporter.make(({ cause, error, severity, attributes }) => {
console.error(`[${severity}]`, error.message, attributes);
});
// Register reporters via Layer
const ReporterLayer = ErrorReporter.layer([myReporter]);
```
### Reporting Errors
```typescript
import { Effect, ErrorReporter } from 'effect';
// Automatically report errors from an effect
const program = riskyOperation.pipe(Effect.withErrorReporting);
// Or, to report defects only:
const defectsOnly = riskyOperation.pipe(
Effect.withErrorReporting({ defectsOnly: true })
);
```
### Per-Error Annotations
Error objects can carry reporting annotations as string-keyed properties (the keys are namespaced strings such as `"~effect/ErrorReporter/severity"`):
<!-- typecheck -->
```typescript
import { ErrorReporter, Schema } from 'effect';
class MyError extends Schema.TaggedError<MyError>()('MyError', {
message: Schema.String
}) {}
const error = new MyError({ message: 'something went wrong' });
// Mark an error to be ignored by reporters
ErrorReporter.ignore; // string key — set to true to skip reporting
// Override severity (defaults to "Info" when unset or invalid)
ErrorReporter.severity; // string key — "Trace" | "Debug" | "Info" | "Warn" | "Error" | "Fatal"
// Attach extra structured metadata
ErrorReporter.attributes; // string key — Record<string, unknown>
// Guards
ErrorReporter.isIgnored(error); // check if ignored
ErrorReporter.getSeverity(error); // read severity
ErrorReporter.getAttributes(error); // read attributes
```
## Layered Error Handling
Structure error handling in layers from specific to general:
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const validateUserData: (
data: UserData
) => Effect.Effect<ValidatedUserData, ValidationError>;
declare const saveToDatabase: (
data: ValidatedUserData
) => Effect.Effect<string, DatabaseError>;
declare const notifyUserCreated: (
userId: string
) => Effect.Effect<void, NetworkError>;
interface UserData {
readonly name: string;
readonly email: string;
}
interface ValidatedUserData {
readonly name: string;
readonly email: string;
}
class ValidationError extends Schema.TaggedError<ValidationError>()(
'ValidationError',
{
message: Schema.String
}
) {}
class DatabaseError extends Schema.TaggedError<DatabaseError>()(
'DatabaseError',
{
message: Schema.String
}
) {}
class NetworkError extends Schema.TaggedError<NetworkError>()(
'NetworkError',
{
message: Schema.String
}
) {}
class UnknownError extends Schema.TaggedError<UnknownError>()(
'UnknownError',
{
message: Schema.String,
cause: Schema.optional(Schema.Unknown)
}
) {}
const createUser = (data: UserData) =>
Effect.gen(function* () {
// Layer 1: Validate input
const validated = yield* validateUserData(data).pipe(
Effect.catchTag('ValidationError', (error) =>
Effect.fail(
new UnknownError({ message: error.message, cause: error })
)
)
);
// Layer 2: Database operation
const userId = yield* saveToDatabase(validated).pipe(
Effect.catchTag('DatabaseError', (error) =>
Effect.fail(
new UnknownError({ message: error.message, cause: error })
)
)
);
// Layer 3: Network notification
yield* notifyUserCreated(userId).pipe(
Effect.catchTag('NetworkError', (error) =>
// Non-critical: log but don't fail
Effect.logWarning(`Failed to notify: ${error._tag}`)
)
);
return userId;
});
```
## Domain-Specific Error Patterns
### HTTP Response Discrimination
Model ambiguous HTTP responses (where the body structure differs for success vs error) as a `Schema.Union` of `Schema.Class` types:
```typescript
import { Effect, Schema } from 'effect';
import { HttpClientResponse } from 'effect/http';
class TokenSuccess extends Schema.Class<TokenSuccess>('TokenSuccess')({
access_token: AccessToken,
expires_in: Schema.Number
}) {}
class TokenError extends Schema.Class<TokenError>('TokenError')({
error: Schema.String,
error_description: Schema.optional(Schema.String)
}) {}
const TokenResponse = Schema.Union([TokenSuccess, TokenError]);
// Decode and discriminate
const response = yield* HttpClientResponse.schemaBodyJson(TokenResponse)(res);
if (response instanceof TokenError) {
return (
yield*
new AuthError({
message: response.error_description ?? response.error
})
);
}
```
This replaces ad-hoc optional-field checking (`if (!body.access_token)`) with compile-time-safe discrimination via `instanceof`.
### Repository Errors
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class EntityNotFound extends Schema.TaggedError<EntityNotFound>()(
'EntityNotFound',
{
entityType: Schema.String,
id: Schema.String,
message: Schema.String
},
{ httpApiStatus: 404, description: 'Requested entity does not exist.' }
) {}
export class DuplicateEntity extends Schema.TaggedError<DuplicateEntity>()(
'DuplicateEntity',
{
entityType: Schema.String,
id: Schema.String,
message: Schema.String
},
{ httpApiStatus: 409, description: 'Entity already exists.' }
) {}
export class QueryError extends Schema.TaggedError<QueryError>()(
'QueryError',
{
query: Schema.String,
message: Schema.String,
cause: Schema.optional(Schema.Unknown)
},
{ description: 'Database query failed.' }
) {}
export type RepositoryError = EntityNotFound | DuplicateEntity | QueryError;
```
### Service Errors
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
export class ServiceUnavailable extends Schema.TaggedError<ServiceUnavailable>()(
'ServiceUnavailable',
{
service: Schema.String,
message: Schema.String,
retryAfter: Schema.optional(Schema.Number)
},
{ httpApiStatus: 503, description: 'Upstream service is unavailable.' }
) {}
export class ServiceTimeout extends Schema.TaggedError<ServiceTimeout>()(
'ServiceTimeout',
{
service: Schema.String,
message: Schema.String,
timeoutMs: Schema.Number
},
{ httpApiStatus: 504, description: 'Upstream service timed out.' }
) {}
export class InvalidResponse extends Schema.TaggedError<InvalidResponse>()(
'InvalidResponse',
{
service: Schema.String,
message: Schema.String,
response: Schema.optional(Schema.Unknown)
},
{ description: 'Upstream service returned an unexpected response.' }
) {}
export type ServiceError =
| ServiceUnavailable
| ServiceTimeout
| InvalidResponse;
```
### Error Boundaries
<!-- typecheck -->
```typescript
import * as Effect from 'effect/Effect';
import * as Schema from 'effect/Schema';
declare const processRequest: (
request: Request
) => Effect.Effect<Response, ValidationError | NotFoundError | DatabaseError>;
declare const HttpResponse: {
badRequest: (message: string) => Response;
notFound: () => Response;
internalServerError: () => Response;
};
interface Request {
readonly url: string;
}
interface Response {
readonly status: number;
}
class ValidationError extends Schema.TaggedError<ValidationError>()(
'ValidationError',
{
message: Schema.String
}
) {}
class NotFoundError extends Schema.TaggedError<NotFoundError>()(
'NotFoundError',
{
message: Schema.String
}
) {}
class DatabaseError extends Schema.TaggedError<DatabaseError>()(
'DatabaseError',
{
message: Schema.String
}
) {}
// Define clear boundaries where errors are handled
const apiEndpoint = (request: Request) =>
Effect.gen(function* () {
const result = yield* processRequest(request);
return result;
}).pipe(
// Error boundary: convert all errors to HTTP responses
Effect.catchTags({
ValidationError: (error) =>
Effect.succeed(HttpResponse.badRequest(error.message)),
NotFoundError: () => Effect.succeed(HttpResponse.notFound()),
DatabaseError: (error) =>
Effect.logError(error).pipe(
Effect.as(HttpResponse.internalServerError())
)
})
);
```
## Quality Checklist
Before completing error handling implementation:
- [ ] All domain errors use `Schema.TaggedError` with a `message` field
- [ ] Error types have meaningful, specific names and `description` annotation
- [ ] Errors include relevant context (ids, values, reasons)
- [ ] Business failures in error channel, programmer errors as defects
- [ ] catchTag/catchTags used for specific error handling
- [ ] catch (NOT catchAll) only when handling truly all error types
- [ ] Error transformations preserve important context
- [ ] Recovery strategies match business requirements
- [ ] Defect handling only at application boundaries
- [ ] Error types exported from domain modules
- [ ] Tests cover error scenarios
- [ ] Type signatures accurately reflect error channel
- [ ] No v3 API names used (catchAll, catchSome, \*Exception, etc.)
- [ ] Errors with `reason` union consider `catchReason`/`catchReasons`/`unwrapReason`
- [ ] HTTP-facing errors carry `httpApiStatus` annotation
- [ ] Error wrapping uses appropriate `cause` field schema (`Schema.Defect()`/`Schema.Unknown`/`Schema.String`)
Your error handling implementations should be type-safe, exhaustive, and maintain clear separation between expected failures and programmer errors. Always use v4 API names.
Wersje
| Repozytorium | Wynik | Licencja |
|---|---|---|
| Ta strona · mpsuesser/opencode-effect-enforcer | 91 | MIT |
| TheBushidoCollective/han | 95 | Apache-2.0 |
Migawka bazy z 28.10.2026 · wersja paczki 49007106b730 (skrót SHA-256 całej paczki)
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 100effect-v4
Pisze, przegląda i aktualizuje kod Effect v4 w pakietach Composio CLI, cli-keyring i json-schema-to-effect-schema przypiętych do effect@4.0.3: Context.Service, warstwy, Schema.TaggedError i effect/cli. - 95effect-v4-idioms
Pisze podstawowy kod Effect v4: generatory, obsługę błędów, operacje na plikach, włókna i konfigurację wejść. - 100effect-ts
Wspiera pracę z biblioteką Effect-TS w TypeScripcie: Effect.gen, Schema.Struct i Schema.Class, Layer, Context.Tag, Data.TaggedError, Ref, Queue, PubSub, Match, Config i typowany kanał błędów. - 95effect-best-practices
Pilnuje wzorców Effect-TS dla serwisów, błędów, warstw i atomów, przy pisaniu Effect.Service i komponentów React. - 100effect-review
Przegląda kod pod kątem konwencji Effect-TS, typów markowanych, obserwowalności, obsługi błędów, pokrycia testami i jakości interfejsu użytkownika. - 100effect-ts
Wspiera nietrywialną pracę z Effect 4: serwisy i warstwy, typowane błędy, Schema, Config, współbieżność, @effect/vitest, effect/ai, effect/sql, Effect Atom i @prb/effect-next.