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
development
Deep review of branch diffs, working trees, or GitHub PRs at any size. Use when the user asks for CodeRabbit-grade review, an incremental re-review after new pushes, publication of findings to a PR, a cross-LLM peer-review verdict round, or conformance review against spec artifacts. Don't use for applying fixes, reviewing specs or PRDs as documents, or quick single-file feedback.
tools
Orchestrate Claude and Codex worker TUIs from a controller agent through herdr panes and the herdr socket CLI. Use when delegating bounded tasks to herdr worker panes, running user-activated plan-first delegations (Claude Code plan mode, Codex Plan mode), waiting on native agent status (idle, working, blocked, done), or verifying worker reports. Workers launch as interactive TUIs via herdr agent start — never through headless runners (compozy exec, claude -p, codex exec). Not for cmux workspaces (see cmux-orchestration) and not for end-user herdr control.
tools
TanStack Query, Router, and Form patterns for React. Use when writing useQuery/queryOptions, mutations, caching, file-based routes, search params, loaders, or TanStack Form validation. Don't use for TanStack Start, TanStack DB/collections, Zustand client state, or non-TanStack routing.
development
Use when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a frontend interface. Covers websites, landing pages, dashboards, product UI, app shells, components, forms, settings, onboarding, and empty states. Handles UX review, visual hierarchy, information architecture, cognitive load, accessibility, performance, responsive behavior, theming, anti-patterns, typography, fonts, spacing, layout, alignment, color, motion, micro-interactions, UX copy, error states, edge cases, i18n, and reusable design systems or tokens. Also use for bland designs that need to become bolder or more delightful, loud designs that should become quieter, live browser iteration on UI elements, or ambitious visual effects that should feel technically extraordinary. Not for backend-only or non-UI tasks.