TypeScript has great types but no standard library for error handling, concurrency, or dependency injection. Effect fixes all three.
What Is Effect?
Effect is a TypeScript library that gives you typed errors, structured concurrency, dependency injection, retries, timeouts, and more — all composable and type-safe.
import { Effect, Console } from "effect"
const program = Effect.gen(function* () {
yield* Console.log("Hello from Effect!")
const result = yield* Effect.succeed(42)
yield* Console.log(\`The answer is \${result}\`)
return result
})
Effect.runPromise(program)
Typed Errors: No More Try-Catch Guessing
import { Effect, Data } from "effect"
class UserNotFound extends Data.TaggedError("UserNotFound")<{
readonly userId: string
}> {}
class DatabaseError extends Data.TaggedError("DatabaseError")<{
readonly message: string
}> {}
// The type signature TELLS you what can go wrong
const getUser = (id: string): Effect.Effect<User, UserNotFound | DatabaseError> =>
Effect.gen(function* () {
const result = yield* queryDatabase(id)
if (!result) yield* Effect.fail(new UserNotFound({ userId: id }))
return result
})
// Handle errors exhaustively — compiler ensures you handle all cases
const handled = getUser("123").pipe(
Effect.catchTag("UserNotFound", (e) => Effect.succeed(defaultUser)),
Effect.catchTag("DatabaseError", (e) => Effect.die(e))
)
The function signature tells you: this can fail with UserNotFound or DatabaseError. TypeScript enforces you handle both.
Retries and Timeouts
import { Effect, Schedule } from "effect"
const resilientFetch = fetchData.pipe(
Effect.retry(Schedule.exponential("100 millis").pipe(
Schedule.compose(Schedule.recurs(3))
)),
Effect.timeout("5 seconds"),
Effect.withSpan("fetch-data") // automatic tracing
)
Dependency Injection
import { Effect, Context, Layer } from "effect"
class Database extends Context.Tag("Database")<Database, {
query: (sql: string) => Effect.Effect<any[]>
}>() {}
const program = Effect.gen(function* () {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
// Provide real implementation
const DatabaseLive = Layer.succeed(Database, {
query: (sql) => Effect.succeed([{ id: 1, name: "Alice" }])
})
Effect.runPromise(program.pipe(Effect.provide(DatabaseLive)))
Swap DatabaseLive for DatabaseTest in tests. Zero code changes.
Why Effect
- Typed errors — know what fails at compile time
- Structured concurrency — fibers, racing, interruption
- Dependency injection — built-in, type-safe
- Observability — tracing, metrics, logging built in
- Schema — runtime validation with type inference
Building robust TypeScript apps? Check out my developer tools or email spinov001@gmail.com.
Top comments (0)