Loading...
Loading...
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 skill4agent add pedronauck/skills effect-tsreferences/core-patterns.md| Reference | Topics |
|---|---|
| Setup, imports, TypeScript config |
| |
| Schema modeling, errors, config, retry |
| |
| |
| All data types: |
| Option/Either/Array quick ref, |
| Concurrency, |
| Creating, operations, grouping, partitioning, broadcasting, buffering, throttling, error handling |
| Sink constructors, collecting, folding, operations, concurrency, leftovers, |
| Request batching ( |
| |
| |
| |
| |
| Forbidden patterns, |
| |
| Anti-patterns, validation checklist, packages |
EffectDataChunkHashSetanyunknownSchemaMatchswitchif-else_tagOptionEitherDurationDateTimeBigDecimalRedactedreferences/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.TaggedErrorSchema.TaggedErrorreferences/error-handling-patterns.mdexport class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));MatchMatch.exhaustivereferences/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}`,
});references/class-patterns.mdexport 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) });
}));
}assert@effect/vitestit.effectexpectit.effectreferences/testing-patterns.mdimport { 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 insteadimport * as Module from "effect/Module"Effect.genpipeEffect.fnMatch_tagMatch.exhaustiveData.TaggedErrorSchema.TaggedErroranyunknownEffect.genEffect.exitreturn yield*Effect.failEffect.interruptContext.TagEffect.fnLayer.mergeLayer.provideEffect.acquireReleaseEffect.scopedOptionEitherDurationDateTimeDateBigDecimalRedactedData.structData.ClassHashSetClock.currentTimeMillisDate.now()assert@effect/vitestexpectit.effectpnpm run typecheckpnpm run test/packages/looper/src/data/api-client/api-client.tspnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keyword