| name | effect-ts |
| description | Use when working with Effect-TS patterns, services, layers, schemas, error handling, streams, concurrency, or writing/refactoring code that imports from 'effect'. Also covers Svelte 5 + Effect integration and conduit's ongoing Effect migration. |
Effect-TS Expert
Expert guidance for functional programming with the Effect library โ typed errors, dependency injection, composability,
concurrency, and testing patterns. Tailored for the conduit codebase (Svelte 5 + Node.js daemon, mid-migration to Effect).
Prerequisites Check
Before starting any Effect-related work, verify the Effect-TS source code exists at ~/src/personal/.effect.
If missing, stop immediately and inform the user. Clone it before proceeding:
git clone https://github.com/Effect-TS/effect.git ~/src/personal/.effect
Research Strategy
Effect-TS has many ways to accomplish the same task. Proactively research best practices using the Agent tool to spawn
research agents when working with Effect patterns, especially for moderate to high complexity tasks.
Research Sources (Priority Order)
-
Codebase Patterns First โ Examine similar patterns in the current project before implementing. If Effect patterns
exist in the codebase, follow them for consistency. If no patterns exist, skip this step.
-
Effect Source Code โ For complex type errors, unclear behavior, or implementation details, examine the Effect
source at ~/src/personal/.effect/packages/effect/src/. This contains the core Effect logic and modules.
When to Research
HIGH Priority (Always Research):
- Implementing Services, Layers, or complex dependency injection
- Error handling with multiple error types or complex error hierarchies
- Stream-based operations and reactive patterns
- Resource management with scoped effects and cleanup
- Concurrent/parallel operations and performance-critical code
- Testing patterns, especially unfamiliar test scenarios
MEDIUM Priority (Research if Complex):
- Refactoring imperative code (try-catch, promises) to Effect patterns
- Adding new service dependencies or restructuring service layers
- Custom error types or extending existing error hierarchies
- Integrations with external systems (databases, APIs, third-party services)
Research Approach
- Spawn multiple concurrent agents when investigating multiple related patterns
- Focus on canonical, readable, and maintainable solutions over clever optimizations
- Verify against existing codebase patterns for consistency (if patterns exist)
- When multiple approaches are possible, research to find the most idiomatic Effect-TS solution
Core Principles
Error Handling
- Use Effect's typed error system instead of throwing exceptions
- Define descriptive error types with
Data.TaggedError or Schema.TaggedError (serializable)
- Use
Effect.fail, Effect.catchTag, Effect.catchAll for error control flow
- Categorize: expected rejections, domain errors, defects, interruptions, unknown/foreign
- See
./references/critical-rules.md for forbidden patterns
- See
./references/error-handling.md for comprehensive patterns
Dependency Injection
- Implement DI using Services and Layers
- Define services with
Context.Tag or Effect.Service (simplified)
- Compose layers with
Layer.merge, Layer.provide, Layer.provideMerge
- Provide layers at the composition root, not scattered through business logic
- See
./references/layers.md for full patterns
Composability
- Leverage Effect's composability for complex operations
- Use appropriate constructors:
Effect.succeed, Effect.fail, Effect.tryPromise, Effect.try
- Apply proper resource management with scoped effects
- Chain with
Effect.flatMap, Effect.map, Effect.tap
Code Quality
- Write type-safe code that leverages Effect's type system
- Use
Effect.gen for readable sequential code (preferred)
- Use
pipe for simple one-liner transformations
- Prefer
Effect.fn() for named functions โ automatic telemetry and better stack traces
- Implement proper testing patterns using
@effect/vitest
Critical Rules
Read ./references/critical-rules.md before writing any Effect code. Key rules:
INEFFECTIVE: try-catch in Effect.gen
Effect failures are returned as exits, not thrown. try-catch inside Effect.gen will NOT catch Effect failures.
Effect.gen(function* () {
try { const r = yield* someEffect } catch (e) { }
})
Effect.gen(function* () {
const result = yield* Effect.catchTag(someEffect, "MyError", (e) => Effect.succeed(fallback))
})
AVOID: Type assertions (as never, as any, as unknown)
Fix underlying type issues instead. Occasional assertions for poorly-typed external libraries are acceptable if documented.
RECOMMENDED: return yield* for errors
Makes termination explicit and prevents unreachable-code warnings:
Effect.gen(function* () {
if (bad) {
return yield* Effect.fail(new MyError({ reason: "bad" }))
}
return yield* doWork()
})
Quick Reference
The Effect Type
Effect<Success, Error, Requirements>
Creating Effects
Effect.succeed(value)
Effect.fail(error)
Effect.tryPromise(fn)
Effect.try(fn)
Effect.sync(fn)
Effect.promise(fn)
Composing Effects
Effect.flatMap(effect, fn)
Effect.map(effect, fn)
Effect.tap(effect, fn)
Effect.all([...effects])
Effect.forEach(items, fn)
Effect.all([e1, e2, e3], { mode: "validate" })
Effect.partition([e1, e2, e3])
Error Handling
class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{
userId: string
}> {}
Effect.gen(function* () {
if (!user) return yield* new UserNotFoundError({ userId })
})
Effect.catchTag(effect, tag, fn)
Effect.catchTags(effect, { ... })
Effect.catchAll(effect, fn)
Effect.result(effect)
Effect.either(effect)
Effect.orElse(effect, alt)
Error Taxonomy
| Category | Examples | Handling |
|---|
| Expected Rejections | User cancel, deny | Graceful exit, no retry |
| Domain Errors | Validation, business rules | Show to user, don't retry |
| Defects | Bugs, assertions | Log + alert, investigate |
| Interruptions | Fiber cancel, timeout | Cleanup, may retry |
| Unknown/Foreign | Thrown exceptions | Normalize at boundary |
Pattern Matching (Match Module)
import { Match } from "effect"
const handleError = Match.type<AppError>().pipe(
Match.tag("UserCancelledError", () => null),
Match.tag("ValidationError", (e) => e.message),
Match.tag("NetworkError", () => "Connection failed"),
Match.exhaustive
)
Effect.catchAll(effect, (error) =>
Match.value(error).pipe(
Match.tag("A", handleA),
Match.tag("B", handleB),
Match.exhaustive
)
)
Services and Layers
class MyService extends Context.Tag("MyService")<MyService, { ... }>() {}
const MyServiceLive = Layer.succeed(MyService, { ... })
class UserRepo extends Effect.Service<UserRepo>()("UserRepo", {
effect: Effect.gen(function* () {
const db = yield* Database
return { findAll: db.query("SELECT * FROM users") }
}),
dependencies: [Database.Default],
accessors: true
}) {}
class SpecialNumber extends Context.Reference<SpecialNumber>()(
"SpecialNumber",
{ defaultValue: () => 2048 }
) {}
Generator Pattern
Effect.gen(function* () {
const a = yield* effectA
const b = yield* effectB
return result
})
const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
const db = yield* Database
return yield* db.query(id)
})
Running Effects
Effect.runSync(effect)
Effect.runPromise(effect)
Effect.runPromiseExit(effect)
const runtime = ManagedRuntime.make(AppLayer)
await runtime.runPromise(effect)
await runtime.dispose()
Resource Management
Effect.acquireUseRelease(acquire, use, release)
Effect.scoped(effect)
Effect.addFinalizer(cleanup)
Duration
Effect accepts human-readable duration strings anywhere a DurationInput is expected:
Duration.toMillis("5 minutes")
Duration.toMillis("100 millis")
Scheduling
Effect.retry(effect, Schedule.exponential("100 millis"))
Effect.repeat(effect, Schedule.fixed("1 second"))
Schedule.compose(s1, s2)
Concurrency
Effect.fork(effect)
Fiber.join(fiber)
Fiber.interrupt(fiber)
Effect.race(e1, e2)
Effect.all([...], { concurrency: 5 })
Configuration
const port = Config.integer("PORT")
const host = Config.withDefault(Config.string("HOST"), "localhost")
const secret = Config.redacted("API_KEY")
const db = Config.nested("DATABASE")(Config.all({ host: Config.string("HOST"), port: Config.integer("PORT") }))
State Management
Ref.make(initial)
Ref.get(ref) / Ref.set(ref, v)
Queue.bounded<T>(n)
Deferred.make<E, A>()
Effect.makeSemaphore(n)
Array Operations
import { Array as Arr, Order } from "effect"
Arr.sort([3, 1, 2], Order.number)
Arr.sortWith(users, (u) => u.age, Order.number)
Arr.sortBy(users, Order.mapInput(Order.number, (u: User) => u.age))
Deprecations
BigDecimal.fromNumber โ use BigDecimal.unsafeFromNumber (3.11.0+)
Schema.annotations() now removes previously set identifier annotations (3.17.10)
Conduit Migration Conventions
Conduit is mid-migration to Effect. Read docs/plans/2026-04-23-effect-ts-migration-plan.md for the full roadmap.
Migration Layers (in order)
- Schema + Errors โ branded types, error hierarchy, RelayMessage union
- Resources โ SQLite lifecycle, connection pool
- Concurrency โ AbortSignal โ Effect.interrupt, fiber supervision
- Dependency Injection โ ServiceRegistry โ Layers
- Handlers โ request handlers as Effects
- Server โ HTTP/WS server as Effect program
- Frontend โ Svelte stores bridging to Effect runtime
Key Mappings
| Current Pattern | Effect Replacement |
|---|
AbortSignal / AbortController | Fiber.interrupt / Effect.scoped |
try-catch + async/await | Effect.gen + typed errors |
RelayError class hierarchy | Schema.TaggedError union |
ServiceRegistry + constructor injection | Context.Tag / Effect.Service + Layers |
Promise.all() / Promise.allSettled() | Effect.all() with concurrency options |
TrackedService lifecycle | Layer.scoped with acquireRelease |
| pino structured logging | Effect.log + custom logger layer |
Testing
Conduit uses Vitest with multiple config variants. When testing Effect code:
- Default unit tests:
pnpm test:unit
- Run specific:
pnpm vitest run <path>
- Use
@effect/vitest for it.effect, it.scoped, it.live
- TestClock is active in
it.effect โ advance with TestClock.adjust()
- See
./references/testing.md for comprehensive patterns
Verification Path
pnpm check
pnpm lint
pnpm test:unit
Reference Files
Read these when working on specific topics:
./references/critical-rules.md โ Forbidden patterns and mandatory conventions
./references/error-handling.md โ Typed errors, defects, recovery, retry, timeout
./references/schema.md โ Validation, encoding/decoding, branded types, class schemas
./references/layers.md โ Dependency injection, service composition, Layer construction
./references/concurrency.md โ Fibers, synchronization primitives, interruption
./references/streams.md โ Stream creation, transformation, consumption, backpressure
./references/testing.md โ TestClock, test layers, mocking, @effect/vitest patterns
./references/anti-patterns.md โ Common mistakes and how to fix them
./references/option-null.md โ Option vs null boundary patterns
./references/config.md โ Configuration management, env vars, secrets
./references/svelte-effect.md โ Svelte 5 + Effect integration patterns
./references/conduit-migration.md โ Conduit-specific migration patterns and mappings