| name | Effect-TS Expert |
| description | This skill should be used when the user is working with Effect-TS, asks to "write Effect code", "use Effect", "functional TypeScript", "handle errors with Effect", "dependency injection Effect", "Effect Layer", or needs expert-level guidance on Effect-TS patterns, error handling, concurrency, and best practices. |
| user-invocable | true |
| context | current |
| allowed-tools | ["mcp__plugin_matrix_matrix__matrix_find_callers","mcp__plugin_matrix_matrix__matrix_find_definition","mcp__plugin_matrix_matrix__matrix_search_symbols","mcp__plugin_matrix_matrix__matrix_list_exports","mcp__plugin_matrix_matrix__matrix_get_imports","mcp__plugin_matrix_matrix__matrix_index_status","mcp__plugin_matrix_matrix__matrix_recall","mcp__plugin_matrix_matrix__matrix_store","mcp__plugin_matrix_matrix__matrix_reward","mcp__plugin_matrix_matrix__matrix_failure","mcp__plugin_matrix_context7__resolve-library-id","mcp__plugin_matrix_context7__query-docs","Read","Edit","Write","Grep","Glob","Bash"] |
Effect-TS Expert
Expert-level guidance for Effect-TS functional programming with typed errors, dependency injection, concurrency, and production-ready patterns.
Core Concepts
The Effect Type
Effect<Success, Error, Requirements>
Key insight: Effects are lazy descriptions of computations. They don't execute until run.
Creating Effects
import { Effect } from "effect"
const success = Effect.succeed(42)
const failure = Effect.fail(new Error("oops"))
const sync = Effect.sync(() => JSON.parse(data))
const trySync = Effect.try({
try: () => JSON.parse(data),
catch: (e) => new ParseError(e)
})
const promise = Effect.promise(() => fetch(url))
const tryPromise = Effect.tryPromise({
try: () => fetch(url).then(r => r.json()),
catch: (e) => new FetchError(e)
})
const callback = Effect.async<string, Error>((resume) => {
someCallbackApi((err, result) => {
if (err) resume(Effect.fail(err))
else resume(Effect.succeed(result))
})
})
Running Effects
Effect.runSync(effect)
Effect.runPromise(effect)
Effect.runPromiseExit(effect)
const runtime = ManagedRuntime.make(AppLayer)
await runtime.runPromise(effect)
Building Pipelines
pipe and Effect.gen
import { Effect, pipe } from "effect"
const program = pipe(
Effect.succeed(5),
Effect.map(n => n * 2),
Effect.flatMap(n => n > 5
? Effect.succeed(n)
: Effect.fail(new Error("too small"))
),
Effect.tap(n => Effect.log(`Result: ${n}`))
)
const program = Effect.gen(function* () {
const n = yield* Effect.succeed(5)
const doubled = n * 2
if (doubled <= 5) {
return yield* Effect.fail(new Error("too small"))
}
yield* Effect.log(`Result: ${doubled}`)
return doubled
})
Recommendation: Prefer Effect.gen for readability. Use pipe for simple transformations.
Error Handling
Typed Errors vs Defects
| Type | Use Case | Recovery |
|---|
| Typed Error | Domain failures (validation, not found, permissions) | Yes - caller can handle |
| Defect | Bugs, invariant violations, unrecoverable | No - terminates fiber |
class NotFoundError extends Data.TaggedError("NotFoundError")<{
readonly id: string
}> {}
class ValidationError extends Data.TaggedError("ValidationError")<{
readonly message: string
}> {}
const findUser = (id: string): Effect.Effect<User, NotFoundError> =>
pipe(
db.query(id),
Effect.flatMap(user =>
user ? Effect.succeed(user) : Effect.fail(new NotFoundError({ id }))
)
)
const divide = (a: number, b: number): Effect.Effect<number> =>
b === 0
? Effect.die(new Error("Division by zero - this is a bug!"))
: Effect.succeed(a / b)
Error Recovery
Effect.catchAll(effect, (error) => Effect.succeed(fallback))
Effect.catchTag(effect, "NotFoundError", (e) =>
Effect.succeed(defaultUser)
)
Effect.catchTags(effect, {
NotFoundError: (e) => Effect.succeed(defaultUser),
ValidationError: (e) => Effect.fail(new HttpError(400, e.message))
})
Effect.either(effect)
Effect.retry(effect, Schedule.recurs(3))
Best Practice: Error Design
import { Schema } from "effect"
class ApiError extends Schema.TaggedError<ApiError>()("ApiError", {
status: Schema.Number,
message: Schema.String,
}) {}
Effect.fail(new Error("something went wrong"))
Effect.fail("error")
Dependency Injection
Services with Context.Tag
import { Context, Effect, Layer } from "effect"
class UserRepository extends Context.Tag("UserRepository")<
UserRepository,
{
readonly findById: (id: string) => Effect.Effect<User, NotFoundError>
readonly save: (user: User) => Effect.Effect<void>
}
>() {}
const getUser = (id: string) => Effect.gen(function* () {
const repo = yield* UserRepository
return yield* repo.findById(id)
})
const UserRepositoryLive = Layer.succeed(UserRepository, {
findById: (id) => Effect.tryPromise(() => db.users.find(id)),
save: (user) => Effect.tryPromise(() => db.users.save(user))
})
const program = getUser("123")
const runnable = Effect.provide(program, UserRepositoryLive)
Effect.Service (Simplified Pattern)
class Logger extends Effect.Service<Logger>()("Logger", {
sync: () => ({
log: (msg: string) => console.log(msg)
}),
effect: Effect.gen(function* () {
const config = yield* Config
return {
log: (msg: string) => Effect.sync(() =>
console.log(`[${config.level}] ${msg}`)
)
}
}),
dependencies: [ConfigLive]
}) {}
const program = Logger.log("Hello")
Effect.provide(program, Logger.Default)
Layer Composition
const BaseLayer = Layer.merge(ConfigLive, LoggerLive)
const DbLayer = Layer.provide(DatabaseLive, ConfigLive)
const AppLayer = pipe(
Layer.merge(ConfigLive, LoggerLive),
Layer.provideMerge(DatabaseLive),
Layer.provideMerge(UserRepositoryLive)
)
See references/layers.md for advanced patterns.
Concurrency
Fibers
const fiber = yield* Effect.fork(longRunningTask)
const result = yield* Fiber.join(fiber)
yield* Fiber.interrupt(fiber)
const fastest = yield* Effect.race(task1, task2)
const results = yield* Effect.all([task1, task2, task3])
const results = yield* Effect.all(tasks, { concurrency: 5 })
Synchronization Primitives
const counter = yield* Ref.make(0)
yield* Ref.update(counter, n => n + 1)
const value = yield* Ref.get(counter)
const queue = yield* Queue.bounded<number>(100)
yield* Queue.offer(queue, 42)
const item = yield* Queue.take(queue)
const sem = yield* Effect.makeSemaphore(3)
yield* sem.withPermits(1)(expensiveOperation)
const deferred = yield* Deferred.make<string, Error>()
yield* Deferred.succeed(deferred, "done")
const value = yield* Deferred.await(deferred)
Resource Management
Scoped Resources
const file = Effect.acquireRelease(
Effect.sync(() => fs.openSync(path, "r")),
(fd) => Effect.sync(() => fs.closeSync(fd))
)
const program = Effect.scoped(
Effect.gen(function* () {
const fd = yield* file
return yield* readFile(fd)
})
)
Finalizers
const program = Effect.gen(function* () {
yield* Effect.addFinalizer((exit) =>
Effect.log(`Cleanup: ${exit._tag}`)
)
})
const runnable = Effect.scoped(program)
Quick Reference
Common Operators
| Operator | Purpose |
|---|
Effect.map | Transform success value |
Effect.flatMap | Chain effects (monadic bind) |
Effect.tap | Side effect, keep original value |
Effect.andThen | Sequence, can be value or effect |
Effect.catchAll | Handle all errors |
Effect.catchTag | Handle specific tagged error |
Effect.provide | Inject dependencies |
Effect.retry | Retry with schedule |
Effect.timeout | Add timeout |
Effect.fork | Run concurrently |
Effect.all | Parallel execution |
When to Use What
| Scenario | Use |
|---|
| Transform value | Effect.map |
| Chain effects | Effect.flatMap or Effect.gen |
| Error recovery | Effect.catchTag / Effect.catchAll |
| Add logging | Effect.tap + Effect.log |
| Run in parallel | Effect.all with concurrency |
| Limit concurrency | Semaphore |
| Share mutable state | Ref |
| Producer/consumer | Queue |
| One-time signal | Deferred |
| Cleanup resources | Effect.acquireRelease |
Reference Documents
references/error-handling.md - Typed errors, defects, recovery patterns
references/layers.md - Dependency injection, service composition
references/concurrency.md - Fibers, synchronization, parallelism
references/streams.md - Stream, Sink, Channel patterns
references/schema.md - Validation, encoding/decoding
references/testing.md - Test layers, mocking, vitest integration
references/config.md - Configuration management
references/anti-patterns.md - Common mistakes and fixes
references/fp-ts-migration.md - Migration from fp-ts
Usage
This skill activates automatically when working with Effect-TS files or when the user mentions Effect, functional TypeScript, or typed errors.
Explicit invocation:
/effect-ts help me refactor this to use Effect
/effect-ts create a service with dependency injection
/effect-ts fix error handling in this code