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
tools
Plans real-user QA deliverables: personas, journey maps, exploratory charters, persona/journey/tour/CFR test cases, regression suites, Figma validation checks, automation intent, and user-impact bug reports. Writes artifacts under <qa-output-path>/qa/ for qa-execution to consume. Use when planning QA before execution, documenting journey-driven test strategy, marking flows that need E2E follow-up, or filing structured bug reports. Do not use for live execution, AI implementation audits, CI gate ownership, or technical integration/security/performance suites; use qa-execution or agent-output-audit instead.
development
Executes real-user QA sessions through public interfaces using personas, journeys, exploratory charters, test tours, edge-case probes, CFR checks, and browser evidence. Reads qa-report artifacts from <qa-output-path>/qa/ when present, captures issues/screenshots/reports under the same output tree, and classifies bugs by user impact. Use when validating a release candidate, migration, refactor, or user-facing change against production-like behavior. Do not use for AI implementation audits, task-status reconciliation, CI gate runs, integration/security/performance templates, or flaky-test triage; use agent-output-audit for those.
development
Transform outside-of-diff review files into properly formatted issue files for a given PR. Use when converting review files from ai-docs/reviews-pr-<PR>/outside/ into issue format in ai-docs/reviews-pr-<PR>/issues/. Automatically determines starting issue number and preserves all metadata (file path, date, status) from original review files. Don't use for inline-diff review files, non-PR review artifacts, or creating GitHub issues directly.
development
Enforce root-cause fixes over workarounds, hacks, and symptom patches in all software engineering tasks. Use when debugging issues, fixing bugs, resolving test failures, planning solutions, making architectural decisions, or reviewing code changes. Activates gate functions that detect and reject common workaround patterns such as type assertions, lint suppressions, error swallowing, timing hacks, and monkey patches. Don't use for trivial formatting changes or documentation-only edits.