effect-ts
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.
- License 许可
- MIT
- 所属仓库
- dibstern/conduit
- 最近更新
- 2026-10-29
- Stars
- ★ 15
- Forks
- 2
01 前言 Front Matter
- 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.
02 资源文件 12
- 参考文档references/anti-patterns.md4.6 KB
- 参考文档references/concurrency.md5.1 KB
- 参考文档references/conduit-migration.md7.6 KB
- 参考文档references/config.md3.9 KB
- 参考文档references/critical-rules.md3.8 KB
- 参考文档references/error-handling.md6.7 KB
- 参考文档references/layers.md5.4 KB
- 参考文档references/option-null.md2.4 KB
- 参考文档references/schema.md5.7 KB
- 参考文档references/streams.md5.3 KB
- 参考文档references/svelte-effect.md7.8 KB
- 参考文档references/testing.md7.0 KB
03 SKILL.md 正文
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.TaggedErrororSchema.TaggedError(serializable) - Use
Effect.fail,Effect.catchTag,Effect.catchAllfor error control flow - Categorize: expected rejections, domain errors, defects, interruptions, unknown/foreign
- See
./references/critical-rules.mdfor forbidden patterns - See
./references/error-handling.mdfor comprehensive patterns
Dependency Injection
- Implement DI using Services and Layers
- Define services with
Context.TagorEffect.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.mdfor 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.genfor readable sequential code (preferred) - Use
pipefor 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.
// WRONG — catches nothing
Effect.gen(function* () {
try { const r = yield* someEffect } catch (e) { /* never runs */ }
})
// RIGHT
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>
// ^ ^ ^
// | | └── Services/dependencies needed (Context)
// | └────────── Typed error channel
// └─────────────────── Success value type
Creating Effects
Effect.succeed(value) // Wrap success value
Effect.fail(error) // Create failed effect
Effect.tryPromise(fn) // Wrap promise-returning function (rejects → typed error)
Effect.try(fn) // Wrap synchronous throwing function
Effect.sync(fn) // Wrap synchronous non-throwing function
Effect.promise(fn) // Wrap promise (rejects → defect — prefer tryPromise)
Composing Effects
Effect.flatMap(effect, fn) // Chain effects
Effect.map(effect, fn) // Transform success value
Effect.tap(effect, fn) // Side effect without changing value
Effect.all([...effects]) // Run effects (concurrency configurable)
Effect.forEach(items, fn) // Map over items with effects
// Collect ALL errors (not just first)
Effect.all([e1, e2, e3], { mode: "validate" })
// Partial success handling
Effect.partition([e1, e2, e3]) // Returns [failures, successes]
Error Handling
// Define typed errors with Data.TaggedError (preferred)
class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{
userId: string
}> {}
// Direct yield of errors in gen (no Effect.fail wrapper needed)
Effect.gen(function* () {
if (!user) return yield* new UserNotFoundError({ userId })
})
Effect.catchTag(effect, tag, fn) // Handle specific error tag
Effect.catchTags(effect, { ... }) // Handle multiple tags
Effect.catchAll(effect, fn) // Handle all errors
Effect.result(effect) // Convert to Exit value
Effect.either(effect) // Convert to Either<E, A>
Effect.orElse(effect, alt) // Fallback effect
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"
// Type-safe exhaustive matching on tagged errors
const handleError = Match.type<AppError>().pipe(
Match.tag("UserCancelledError", () => null),
Match.tag("ValidationError", (e) => e.message),
Match.tag("NetworkError", () => "Connection failed"),
Match.exhaustive // Compile error if case missing
)
// Cleaner than chained catchTag
Effect.catchAll(effect, (error) =>
Match.value(error).pipe(
Match.tag("A", handleA),
Match.tag("B", handleB),
Match.exhaustive
)
)
Services and Layers
// Pattern 1: Context.Tag (implementation provided separately via Layer)
class MyService extends Context.Tag("MyService")<MyService, { ... }>() {}
const MyServiceLive = Layer.succeed(MyService, { ... })
// Pattern 2: Effect.Service (default implementation bundled)
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 // Auto-generate method accessors
}) {}
// Pattern 3: Context.Reference (defaultable tags — 3.11.0+)
class SpecialNumber extends Context.Reference<SpecialNumber>()(
"SpecialNumber",
{ defaultValue: () => 2048 }
) {}
Generator Pattern
// Effect.gen — recommended for sequential code
Effect.gen(function* () {
const a = yield* effectA
const b = yield* effectB
return result
})
// Effect.fn — automatic tracing/telemetry (preferred for named functions)
const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
const db = yield* Database
return yield* db.query(id)
})
Running Effects
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)
await runtime.dispose() // Cleanup
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") // 300000
Duration.toMillis("100 millis") // 100
// Units: nanos, micros, millis, seconds, minutes, hours, days, weeks
Scheduling
Effect.retry(effect, Schedule.exponential("100 millis"))
Effect.repeat(effect, Schedule.fixed("1 second"))
Schedule.compose(s1, s2)
Concurrency
Effect.fork(effect) // Run in background fiber
Fiber.join(fiber) // Wait for result
Fiber.interrupt(fiber) // Cancel fiber
Effect.race(e1, e2) // First to complete wins
Effect.all([...], { concurrency: 5 }) // Bounded parallel
Configuration
const port = Config.integer("PORT")
const host = Config.withDefault(Config.string("HOST"), "localhost")
const secret = Config.redacted("API_KEY") // Masked in logs
const db = Config.nested("DATABASE")(Config.all({ host: Config.string("HOST"), port: Config.integer("PORT") }))
State Management
Ref.make(initial) // Mutable reference (atomic)
Ref.get(ref) / Ref.set(ref, v)
Queue.bounded<T>(n) // Producer/consumer
Deferred.make<E, A>() // One-shot signal
Effect.makeSemaphore(n) // Concurrency limiter
Array Operations
import { Array as Arr, Order } from "effect"
Arr.sort([3, 1, 2], Order.number) // [1, 2, 3]
Arr.sortWith(users, (u) => u.age, Order.number) // Sort by field
Arr.sortBy(users, Order.mapInput(Order.number, (u: User) => u.age)) // Multi-criteria
Deprecations
BigDecimal.fromNumber→ useBigDecimal.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/vitestforit.effect,it.scoped,it.live - TestClock is active in
it.effect— advance withTestClock.adjust() - See
./references/testing.mdfor comprehensive patterns
Verification Path
pnpm check # tsgo type checking
pnpm lint # biome
pnpm test:unit # unit tests
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/vitestpatterns./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
04 同仓库更多 Skills
release-notes
Drafts release notes. Fixture skill for conduit's recorded skill-loads-turn trace.
changelog-style
House style for changelog entries. Fixture skill for conduit's recorded skill-loads-turn trace.
typescript-advanced-types
Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.
sveltekit-svelte5-tailwind-skill
Comprehensive integration skill for building sites with SvelteKit 2, Svelte 5, and Tailwind CSS v4
svelte5-inspect
暂无简介
playwright-cli
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.