Effect-TS Developer Guide
SkillDev toolsExpert guide for writing Effect-TS code, including project setup, core principles, data modeling with Schema, error handling, and the Context.Tag service pattern. Use when writing, refactoring, or analyzing TypeScript code using the Effect library.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Effect-TS Developer Guide skill
What this skill tells your AI
The instructions your AI receives, as published by pedronauck/skills in skills/curated/effect-ts/SKILL.md and read by ahel’s review.
Guidelines, patterns, and best practices for Effect-TS in this project.
Reference Documents
Read the relevant reference before writing code. references/core-patterns.md is the master index.
| Reference | Topics |
|---|---|
references/foundations.md | Setup, imports, TypeScript config |
references/construction-and-style.md | Effect.gen, pipe, Effect.fn, Effect.fnUntraced |
references/schema-errors-config.md | Schema modeling, errors, config, retry |
references/pattern-matching.md | Match.type, Match.value, Match.tag, Match.exhaustive — mandatory for tagged unions |
references/control-flow-and-runtime.md | Effect.if, Effect.when, loops, runSync, runPromise, ManagedRuntime |
references/data-types.md | All data types: Option, Either, Data, Exit, Cause, Duration, DateTime, BigDecimal, Chunk, HashSet, Redacted |
references/data-and-testing.md | Option/Either/Array quick ref, @effect/vitest setup |
references/concurrency-and-resources.md | Concurrency, Scope, finalizers, resources |
references/streams-deep-dive.md | Creating, operations, grouping, partitioning, broadcasting, buffering, throttling, error handling |
references/sink.md | Sink constructors, collecting, folding, operations, concurrency, leftovers, Stream.transduce |
references/batching-and-caching.md | Request batching (RequestResolver), cachedWithTTL |
references/schema-transforms-and-filters.md | Schema.transform, Schema.filter, refinements |
references/api-platform-observability.md | HttpApi, logging, tracing, spans |
references/class-patterns.md | Context.Tag service pattern, layers, memoization, testing |
references/error-handling-patterns.md | Data.TaggedError, Schema.TaggedError, error composition, recovery |
references/library-development-patterns.md | Forbidden patterns, Effect.fn vs Effect.fnUntraced, resource management |
references/testing-patterns.md | @effect/vitest with assert, TestClock, service mocking |
references/quality-tooling-and-resources.md | Anti-patterns, validation checklist, packages |
Core Principles
- Effect is not just for async — Use
Effectfor any fallible operation - Immutability — Use Effect's immutable data structures (
Data,Chunk,HashSet) - Type Safety — Track errors in types. No
anyorunknownin error channels - Composition — Build programs by composing small Effects
- Schema-First — Define data models using
Schemawith branded types - Pattern Matching — Use
Matchfor all branching over tagged unions (neverswitch/if-elseon_tag) - Effect Data Types — Use
Option(not null),Either(not ad-hoc),Duration(not raw ms),DateTime(not Date),BigDecimal(not floats),Redacted(for secrets). Seereferences/data-types.md
Quick Reference
Import Convention
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as Match from "effect/Match";
import * as Option from "effect/Option";
import * as Either from "effect/Either";
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import { pipe } from "effect/Function";
// Also: DateTime, BigDecimal, Chunk, HashSet, Exit, Cause, Redacted
Effect.gen vs pipe vs Effect.fn
// Effect.gen — complex logic with branching
Effect.gen(function* () {
const user = yield* fetchUser(id);
if (user.isAdmin) yield* logAdminAccess(user);
return user;
});
// pipe — linear transformations
pipe(fetchData(), Effect.map(transform), Effect.flatMap(save));
// Effect.fn — traced reusable functions (public API)
const processUser = Effect.fn("processUser")(function* (userId: string) {
const user = yield* getUser(userId);
return yield* processData(user);
});
Error Handling
Data.TaggedError for in-process discrimination, Schema.TaggedError for serializable errors. See references/error-handling-patterns.md.
export class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));
Pattern Matching (Mandatory for Tagged Unions)
Always use Match — Match.exhaustive catches missing cases at compile time. See references/pattern-matching.md.
// Match.type — reusable matcher function
const handle = Match.type<Status>().pipe(
Match.tag("Pending", (s) => `Pending since ${s.requestedAt}`),
Match.tag("Approved", (s) => `Approved by ${s.approvedBy}`),
Match.exhaustive // Compile error if any variant is missing
);
// Match.valueTags — shorthand for immediate matching
Match.valueTags(status, {
Pending: (s) => `Pending since ${s.requestedAt}`,
Approved: (s) => `Approved by ${s.approvedBy}`,
});
Service Pattern (Context.Tag)
See references/class-patterns.md for full pattern with factory methods and layers.
export class MyService extends Context.Tag("@myapp/MyService")<
MyService,
{ readonly find: (id: string) => Effect.Effect<Result, NotFoundError> }
>() {
static readonly layer = Layer.effect(MyService, Effect.gen(function* () {
const db = yield* Database;
return MyService.of({ find: MyService.createFind(db) });
}));
}
Testing
CRITICAL: Use assert from @effect/vitest for it.effect. Never expect with it.effect. See references/testing-patterns.md.
import { assert, describe, it } from "@effect/vitest";
it.effect("processes data", () =>
Effect.gen(function* () {
const result = yield* processData("input");
assert.strictEqual(result, "expected");
}).pipe(Effect.provide(MyService.testLayer))
);
Forbidden Patterns
// NEVER: try-catch in Effect.gen — use Effect.exit instead
Effect.gen(function* () {
try { yield* someEffect } catch (e) { } // WRONG — will never catch
});
// NEVER: Type assertions
const value = something as any; // FORBIDDEN
const value = something as never; // FORBIDDEN
// NEVER: Missing return on terminal yield
Effect.gen(function* () {
if (bad) { yield* Effect.fail("err") } // Missing return!
});
// NEVER: switch/if-else on _tag — use Match instead
switch (status._tag) { /* no exhaustiveness checking! */ }
// NEVER: Effect.runSync inside Effects
Effect.gen(function* () { Effect.runSync(sideEffect) }); // Loses error tracking
// NEVER: Native JS where Effect data types exist
const x: string | null = null; // Use Option<string>
const delay = 5000; // Use Duration.seconds(5)
const now = new Date(); // Use DateTime.now or DateTime.unsafeNow()
const price = 0.1 + 0.2; // Use BigDecimal for precision
const secret = "sk-1234"; // Use Redacted.make("sk-1234")
// NEVER: expect with it.effect
it.effect("test", () => Effect.gen(function* () {
expect(result).toBe(value) // WRONG — use assert.strictEqual
}));
// NEVER: Inline layers (breaks memoization)
Layer.provide(Postgres.layer({ url })) // Store in constant instead
Validation Checklist
- Imports use
import * as Module from "effect/Module" -
Effect.genfor complex logic,pipefor linear,Effect.fnfor public API -
Matchfor all_tagbranching withMatch.exhaustive - Branded types for domain primitives (IDs, Emails)
- Errors:
Data.TaggedError(discrimination) orSchema.TaggedError(serializable) - No
any/unknownin error channels, no type assertions - No try-catch in
Effect.gen— useEffect.exit -
return yield*for terminal effects (Effect.fail,Effect.interrupt) - Services:
Context.Tagwith static factory methods andEffect.fntracing - Layers:
Layer.merge/Layer.provide, parameterized layers in constants - Resources:
Effect.acquireReleaseorEffect.scoped -
Optionfor nullable values,Eitherfor sync success/failure -
Durationfor time values,DateTimefor dates (notDate) -
BigDecimalfor financial/precise math,Redactedfor secrets -
Data.struct/Data.Classfor structural equality,HashSetfor sets -
Clock.currentTimeMillisinstead ofDate.now() - Tests:
assertfrom@effect/vitest(notexpect) withit.effect - Run
pnpm run typecheckandpnpm run test
Reference Implementation
See /packages/looper/src/data/api-client/api-client.ts for Context.Tag service pattern.
Effect Solutions CLI
pnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keyword
Signals
- GitHub stars
- 611
- Forks
- 88
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
effect-ts-pedronauck- Source
- github.com/pedronauck/skills