01-best-practices
Effect-TS 4 best practices — Effect.fn, Effect.fnUntraced, Context.Service, Layer conventions, and FP style
- License 许可
- MIT
- 最近更新
- 2026-05-31
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- 01-best-practices
- license
- MIT
- description
- Effect-TS 4 best practices — Effect.fn, Effect.fnUntraced, Context.Service, Layer conventions, and FP style
- compatibility
- opencode
03 SKILL.md 正文
Effect-TS Best Practices
Effect.fn — all exported named functions
Every exported function that returns an Effect must use Effect.fn.
It adds span tracing, stack frames, and the name appears in traces.
export const processItem = Effect.fn("processItem")(function* (id: string) {
yield* Effect.log("processing:", id);
return yield* doWork(id);
});
Extra operators go as additional arguments to Effect.fn, not in .pipe:
// ✅ correct — operators receive the function's arguments too
export const processItem = Effect.fn("processItem")(
function* (id: string) {
return yield* doWork(id)
},
Effect.withSpan("processItem"),
Effect.annotateLogs({ component: "worker" }),
)
// ❌ wrong — .pipe after Effect.fn loses the span context
export const processItem = Effect.fn("processItem")(function* () { ... })
.pipe(Effect.withSpan("processItem"))
Effect.fnUntraced — internal helpers
For private/internal functions where tracing adds no value.
// ✅ simpler — no name string needed
const buildPayload = Effect.fnUntraced(function* (id: string) {
const data = yield* loadData(id);
return { id, data };
});
Context.Service — the canonical service definition
Always use Context.Service, not ServiceMap.Service.
The reference codebase (effect-smol) uses only Context.Service.
import { Context, Effect, Layer } from "effect";
export class MyService extends Context.Service<
MyService,
{
doThing(input: string): Effect.Effect<Result, MyError>;
}
>()("myapp/MyService") {
static readonly layer = Layer.effect(
MyService,
Effect.gen(function* () {
const doThing = Effect.fn("MyService.doThing")(function* (input: string) {
return yield* compute(input);
});
return MyService.of({ doThing });
}),
);
}
Service key naming
Use "package/path/Name" convention:
"myapp/db/Database"
"@repo/domain/BenchmarkService"
"effect/cluster/Entity/EntityAddress"
Services return .of(...) — never plain objects
return MyService.of({ doThing }); // ✅ preserves prototype chain
return { doThing }; // ❌ breaks Context.Service
No let reassignment inside Effect.gen
Extract each branch as a helper Effect; bind once with const.
// ❌ let reassignment obscures which branch ran
const program = Effect.gen(function* () {
let result: string;
if (condition) result = yield* branchA();
else result = yield* branchB();
return result;
});
// ✅ const, no mutation
const resolveResult = (condition: boolean) => (condition ? branchA() : branchB());
const program = Effect.gen(function* () {
const result = yield* resolveResult(condition);
return result;
});
No try/catch, no async/await inside Effect.gen
// ❌ escapes the error channel
Effect.gen(function* () {
try { return yield* risky() } catch (e) { return default }
})
// ✅ use Effect combinators
risky().pipe(
Effect.catchTag("MyError", (_) => Effect.succeed(default)),
)
// ❌ await mixes runtimes
Effect.gen(function* () {
const data = await fetch(url).then(r => r.json())
})
// ✅ Effect.tryPromise
Effect.gen(function* () {
const data = yield* Effect.tryPromise({
try: () => fetch(url).then(r => r.json()),
catch: (e) => new FetchError({ cause: e }),
})
})
Schema.Record — positional args only
Schema.Record(Schema.String, Schema.Number) // ✅ Effect 4
Schema.Record({ key: Schema.String, value: ... }) // ❌ Effect 3 — compile error
Domain package — no platform imports
// ✅ domain package
import { Effect, Schema } from "effect";
// ❌ domain package — platform-specific, must stay in apps/
import { HttpClient } from "effect/unstable/http";
Bun script entrypoint pattern
if (import.meta.main) {
Effect.runPromise(program).catch((e: unknown) => {
process.stderr.write(String(e) + "\n");
process.exit(1);
});
}
04 同仓库更多 Skills
tmux-workflow
Development workflow with tmux — run dev servers, Storybook, and background processes in persistent sessions with live logs, while keeping OpenCode free for chat
04-anti-patterns
React anti-patterns to avoid — manual JSX repetition, useEffect misuse, reference instability, prop drilling, conditional hooks, and common performance traps
03-state-management
React state management patterns — lifting state, colocation, URL state, server state with TanStack Query, local state with useState/useReducer
02-component-composition
React component composition patterns — render props, compound components, list rendering with map, slot patterns, and layout primitives
01-fp-style
React functional programming style — pure components, hooks conventions, Effect Atom integration, and Tailwind patterns
react-animation
React animation with react/motion (Framer Motion) — declarative animations, gestures, layout animations, and performance best practices