ojowwalker77-effect-ts-expert
0
总安装量
5
周安装量
安装命令
npx skills add https://smithery.ai
Agent 安装分布
codex
4
amp
3
opencode
3
kimi-cli
3
gemini-cli
3
Skill 文档
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>
// ^ ^ ^
// | | âââ Services/dependencies needed (Context)
// | âââââââââââ Typed error channel
// ââââââââââââââââââââ Success value type
Key insight: Effects are lazy descriptions of computations. They don’t execute until run.
Creating Effects
import { Effect } from "effect"
// From pure values
const success = Effect.succeed(42)
const failure = Effect.fail(new Error("oops"))
// From sync code (may throw)
const sync = Effect.sync(() => JSON.parse(data))
const trySync = Effect.try({
try: () => JSON.parse(data),
catch: (e) => new ParseError(e)
})
// From async code
const promise = Effect.promise(() => fetch(url))
const tryPromise = Effect.tryPromise({
try: () => fetch(url).then(r => r.json()),
catch: (e) => new FetchError(e)
})
// From callbacks
const callback = Effect.async<string, Error>((resume) => {
someCallbackApi((err, result) => {
if (err) resume(Effect.fail(err))
else resume(Effect.succeed(result))
})
})
Running Effects
// Development/testing
Effect.runSync(effect) // Sync, throws on async/error
Effect.runPromise(effect) // Returns Promise<A>
Effect.runPromiseExit(effect) // Returns Promise<Exit<A, E>>
// Production (with runtime)
const runtime = ManagedRuntime.make(AppLayer)
await runtime.runPromise(effect)
Building Pipelines
pipe and Effect.gen
import { Effect, pipe } from "effect"
// Using pipe (point-free style)
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}`))
)
// Using Effect.gen (generator style - RECOMMENDED)
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 |
// Typed errors - tracked in type system
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 }))
)
)
// Defects - for bugs, not domain errors
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
// Catch all errors
Effect.catchAll(effect, (error) => Effect.succeed(fallback))
// Catch specific tagged errors
Effect.catchTag(effect, "NotFoundError", (e) =>
Effect.succeed(defaultUser)
)
// Catch multiple tags
Effect.catchTags(effect, {
NotFoundError: (e) => Effect.succeed(defaultUser),
ValidationError: (e) => Effect.fail(new HttpError(400, e.message))
})
// Convert to Either (errors become Left)
Effect.either(effect) // Effect<Either<E, A>, never, R>
// Retry on failure
Effect.retry(effect, Schedule.recurs(3))
Best Practice: Error Design
// DO: Use tagged errors with Schema
import { Schema } from "effect"
class ApiError extends Schema.TaggedError<ApiError>()("ApiError", {
status: Schema.Number,
message: Schema.String,
}) {}
// DON'T: Use plain Error or strings
Effect.fail(new Error("something went wrong")) // Loses type info
Effect.fail("error") // Not an Error type
Dependency Injection
Services with Context.Tag
import { Context, Effect, Layer } from "effect"
// 1. Define service interface
class UserRepository extends Context.Tag("UserRepository")<
UserRepository,
{
readonly findById: (id: string) => Effect.Effect<User, NotFoundError>
readonly save: (user: User) => Effect.Effect<void>
}
>() {}
// 2. Use in effects
const getUser = (id: string) => Effect.gen(function* () {
const repo = yield* UserRepository
return yield* repo.findById(id)
})
// Type: Effect<User, NotFoundError, UserRepository>
// 3. Create layer implementation
const UserRepositoryLive = Layer.succeed(UserRepository, {
findById: (id) => Effect.tryPromise(() => db.users.find(id)),
save: (user) => Effect.tryPromise(() => db.users.save(user))
})
// 4. Provide to run
const program = getUser("123")
const runnable = Effect.provide(program, UserRepositoryLive)
Effect.Service (Simplified Pattern)
// Combines Tag + Layer in one declaration
class Logger extends Effect.Service<Logger>()("Logger", {
// Option 1: Sync implementation
sync: () => ({
log: (msg: string) => console.log(msg)
}),
// Option 2: Effect-based with dependencies
effect: Effect.gen(function* () {
const config = yield* Config
return {
log: (msg: string) => Effect.sync(() =>
console.log(`[${config.level}] ${msg}`)
)
}
}),
dependencies: [ConfigLive]
}) {}
// Use directly
const program = Logger.log("Hello")
// Access via Layer
Effect.provide(program, Logger.Default)
Layer Composition
// Merge independent layers
const BaseLayer = Layer.merge(ConfigLive, LoggerLive)
// Provide dependencies
const DbLayer = Layer.provide(DatabaseLive, ConfigLive)
// Full composition
const AppLayer = pipe(
Layer.merge(ConfigLive, LoggerLive),
Layer.provideMerge(DatabaseLive),
Layer.provideMerge(UserRepositoryLive)
)
See references/layers.md for advanced patterns.
Concurrency
Fibers
// Fork to run concurrently
const fiber = yield* Effect.fork(longRunningTask)
// Wait for result
const result = yield* Fiber.join(fiber)
// Interrupt
yield* Fiber.interrupt(fiber)
// Race - first to complete wins
const fastest = yield* Effect.race(task1, task2)
// All - run all, collect results
const results = yield* Effect.all([task1, task2, task3])
// All with concurrency limit
const results = yield* Effect.all(tasks, { concurrency: 5 })
Synchronization Primitives
// Ref - mutable reference
const counter = yield* Ref.make(0)
yield* Ref.update(counter, n => n + 1)
const value = yield* Ref.get(counter)
// Queue - bounded producer/consumer
const queue = yield* Queue.bounded<number>(100)
yield* Queue.offer(queue, 42)
const item = yield* Queue.take(queue)
// Semaphore - limit concurrent access
const sem = yield* Effect.makeSemaphore(3)
yield* sem.withPermits(1)(expensiveOperation)
// Deferred - one-shot signal
const deferred = yield* Deferred.make<string, Error>()
yield* Deferred.succeed(deferred, "done")
const value = yield* Deferred.await(deferred)
Resource Management
Scoped Resources
// Acquire/release pattern
const file = Effect.acquireRelease(
Effect.sync(() => fs.openSync(path, "r")), // acquire
(fd) => Effect.sync(() => fs.closeSync(fd)) // release
)
// Use with scoped
const program = Effect.scoped(
Effect.gen(function* () {
const fd = yield* file
return yield* readFile(fd)
})
)
// File automatically closed after scope
Finalizers
const program = Effect.gen(function* () {
yield* Effect.addFinalizer((exit) =>
Effect.log(`Cleanup: ${exit._tag}`)
)
// ... do work
})
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 patternsreferences/layers.md– Dependency injection, service compositionreferences/concurrency.md– Fibers, synchronization, parallelismreferences/streams.md– Stream, Sink, Channel patternsreferences/schema.md– Validation, encoding/decodingreferences/testing.md– Test layers, mocking, vitest integrationreferences/config.md– Configuration managementreferences/anti-patterns.md– Common mistakes and fixesreferences/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