skills/curated/effect-ts/SKILL.md
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 skillsauth add pedronauck/skills effect-tsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.mdimport * 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 — 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);
});
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)));
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}`,
});
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) });
}));
}
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))
);
// 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
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.
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
testing
Assess DDD fit; discover domains with EventStorming, define language and bounded contexts, and design aggregates, value objects, events, and repositories. Excludes architecture-only audits, product specs, and CQRS/Event Sourcing catalogs.
development
Build terminal UIs with ratatui following 2026 Rust best practices. Use when: (1) Creating new TUI apps, (2) Adding widgets/layouts, (3) Keyboard navigation/state management, (4) Image integration via ratatui-image, (5) Async event handling, (6) Release optimization. Covers v0.30.0+ API, Elm Architecture, StatefulWidget, color-eyre.
development
Find 10x product opportunities and high-leverage improvements. Use when user wants strategic product thinking, mentions '10x', wants to find high-impact features, or says 'what would make this 10x better', 'product strategy', or 'what should we build next'.
development
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.