Effect-TS Core Library
Effect-TS is a robust, type-safe functional programming library for TypeScript, providing a "missing standard library" for building highly concurrent, resilient, and performant applications. It centers around the `Effect` data type, which represents a description of a computation that may require resources (R), may fail with an error (E), and may succeed with a value (A). The library promotes a functional-first approach, emphasizing immutability, explicit error handling, and structured concurrency, leveraging TypeScript's type system to ensure correctness at compile-time. Currently stable at version 3.x (e.g., 3.21.1), `effect` maintains a regular release cadence with frequent patch and minor updates, reflecting active development and continuous improvement. It differentiates itself through its comprehensive ecosystem of modules (e.g., `Effect.Layer`, `Effect.Schema`, `Effect.Stream`) that integrate seamlessly, offering a complete solution for complex application logic, asynchronous operations, and resource management without runtime exceptions.
Common errors
-
TypeError: Effect.runPromise is not a function
cause Attempting to call `runPromise` (or other runners) on a non-Effect value, or `Effect` itself, rather than an instance.fixEnsure you are calling `Effect.runPromise` on an actual `Effect` instance, e.g., `Effect.runPromise(myEffect)`. Also, check your imports for `Effect` to ensure it's not a `require` import if your project is ESM-first. -
Type 'Effect<unknown, never, any>' is not assignable to type 'Effect<R, E, A>'
cause Often arises from type inference issues, particularly when an effect is created without explicitly defining its R, E, or A parameters, or when composing effects with incompatible types.fixExplicitly specify the type parameters (`Effect<A, E, R>`) for effects, especially when defining functions that return them. Use `Effect.mapError`, `Effect.provide`, `Effect.catchAll` to adjust error and requirement types. When composing, ensure the types align or are handled. -
TS2345: Argument of type 'Promise<any>' is not assignable to parameter of type 'Effect<any, never, never>'
cause Mixing `async/await` (Promises) directly with `Effect` values without proper conversion.fixConvert Promises to Effects using `Effect.promise`, `Effect.tryPromise`, or `Effect.async` (for more complex async operations). Do not `await` Effect values directly; use `yield*` within `Effect.gen`. -
TS2349: This expression is not callable.
cause Attempting to call a method like `Effect.succeed(value)()` or using `pipe` incorrectly, e.g., `Effect.pipe(...)` instead of `value.pipe(Effect.method(...))` or `pipe(value, Effect.method(...))`.fixEnsure methods are called correctly. If using the fluent `.pipe()` syntax, ensure it's on an Effect instance (`myEffect.pipe(Effect.map(...))`). If using the global `pipe` function, ensure it takes the value as its first argument (`pipe(myEffect, Effect.map(...))`).
Warnings
- breaking Major versions (e.g., v2 to v3) introduce significant breaking changes, including module reorganizations, API renames (e.g., `Either` to `Result`), and changes to `Layer` and `Service` definitions.
- gotcha Effects are 'cold' and lazy; they describe a computation but do not execute until explicitly 'run' using functions like `Effect.runPromise`, `Effect.runSync`, or `Effect.runFork`. Forgetting to run an effect means its encapsulated logic (e.g., logging, side effects) will never occur.
- gotcha Understanding the `Effect<A, E, R>` type parameters (Success, Error, Requirements) is crucial for type safety, especially 'R' (Requirements/Context). Incorrectly managing or providing dependencies (Services/Layers) can lead to compile-time type errors related to missing `R` or runtime failures if services are not provided.
- gotcha When using `Effect.gen`, it's critical to use `yield*` (yield-star) for yielding other Effect values, not `await`. Using `await` directly within `Effect.gen` will often lead to unexpected behavior or type errors because it does not properly unwrap the Effect context.
- gotcha A recently fixed defect in `RequestResolver.makeBatched` could cause consumer fibers to hang indefinitely if the resolver died with a defect, because cleanup logic was not correctly handling failures.
Install
-
npm install effect -
yarn add effect -
pnpm add effect
Imports
- Effect
const { Effect } = require('effect')import { Effect } from 'effect' - Layer
import { Layer } from '@effect/data/Layer'import { Layer } from 'effect' - pipe
import { pipe } from '@fp-ts/core'import { pipe } from 'effect' - Effect.gen
const program = Effect.gen(async function () { /* ... */ });import { Effect } from 'effect'; const program = Effect.gen(function* () { /* ... */ });
Quickstart
import { Effect, Console, pipe, Duration, Schedule } from 'effect';
interface User {
id: number;
name: string;
email: string;
}
// Define a custom error type for better type safety
class UserNotFoundError extends Effect.Error('UserNotFoundError')<{ userId: number }> {}
class DatabaseConnectionError extends Effect.Error('DatabaseConnectionError')<{ message: string }> {}
// Simulate fetching a user from a database
const fetchUserFromDB = (userId: number): Effect.Effect<User, UserNotFoundError | DatabaseConnectionError, never> =>
Effect.sync(() => {
if (userId === 1) {
return { id: 1, name: 'Alice', email: 'alice@example.com' };
} else if (userId === 99) {
throw new Error('Failed to connect to DB'); // Simulate a defect
}
return Effect.fail(new UserNotFoundError({ userId }));
}).pipe(
Effect.catchAllDefect((error) =>
Effect.fail(new DatabaseConnectionError({ message: String(error) }))
)
);
const program = pipe(
fetchUserFromDB(1), // Try to fetch user with ID 1
Effect.tap((user) => Console.log(`Fetched user: ${user.name}`)),
Effect.flatMap(() => fetchUserFromDB(2)), // Try to fetch user with ID 2 (will fail)
Effect.tapError((error) =>
error._tag === 'UserNotFoundError'
? Console.error(`Error: User with ID ${error.userId} not found.`)
: Console.error(`Critical Error: ${error.message}`)
),
Effect.retry(Schedule.exponential(Duration.seconds(1), 3)), // Retry failed operations with exponential backoff
Effect.matchEffect({ // Handle both success and failure paths explicitly
onFailure: (error) => Console.error(`Final failure: ${error._tag}`),
onSuccess: (user) => Console.log(`Final success (should not happen for user 2, unless retries succeed): ${user.name}`),
})
);
// Run the Effect program
Effect.runPromise(program).then(() => Console.log('Program finished.')).catch(console.error);