npx skills add ...
npx skills add pedronauck/skills --skill effect-ts
Expert 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.
npx skills add pedronauck/skills --skill effect-ts
Guidelines, patterns, and best practices for Effect-TS in this project.
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 |
Effect for any fallible operationData, Chunk, HashSet)any or unknown in error channelsSchema with branded typesMatch for all branching over tagged unions (never switch/if-else on _tag)Option (not null), Either (not ad-hoc), Duration (not raw ms), DateTime (not Date), BigDecimal (not floats), Redacted (for secrets). See references/data-types.mdData.TaggedError for in-process discrimination, Schema.TaggedError for serializable errors. See references/error-handling-patterns.md.
Always use Match — Match.exhaustive catches missing cases at compile time. See references/pattern-matching.md.
See references/class-patterns.md for full pattern with factory methods and layers.
CRITICAL: Use assert from @effect/vitest for it.effect. Never expect with it.effect. See references/testing-patterns.md.
import * as Module from "effect/Module"Effect.gen for complex logic, pipe for linear, Effect.fn for public APIMatch for all _tag branching with Match.exhaustiveData.TaggedError (discrimination) or Schema.TaggedError (serializable)any/unknown in error channels, no type assertionsEffect.gen — use Effect.exitreturn yield* for terminal effects (Effect.fail, Effect.interrupt)Context.Tag with static factory methods and Effect.fn tracingLayer.merge/Layer.provide, parameterized layers in constantsEffect.acquireRelease or Effect.scopedOption for nullable values, Either for sync success/failureDuration for time values, DateTime for dates (not Date)BigDecimal for financial/precise math, Redacted for secretsData.struct/Data.Class for structural equality, HashSet for setsClock.currentTimeMillis instead of Date.now()assert from @effect/vitest (not expect) with it.effectpnpm run typecheck and pnpm run testSee /packages/looper/src/data/api-client/api-client.ts for Context.Tag service pattern.
// 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);
});export class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));// 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}`,
});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) });
}));
}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))
);// 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 insteadpnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keyword