name: effect-ts-patterns description: Patterns and best practices for Effect-TS in the Nuclom codebase. Use when writing API routes, services, or any code using Effect-TS. Covers Effect.gen, error handling, services, layers, and common pitfalls.
Effect-TS Patterns for Nuclom
This skill documents the Effect-TS patterns used throughout the Nuclom codebase. Effect-TS provides type-safe error handling and dependency injection.
Quick Reference
API Route Template
import { Effect } from "effect";
import type { NextRequest } from "next/server";
import { createFullLayer, handleEffectExit } from "@/lib/api-handler";
import { Auth } from "@/lib/effect/services/auth";
export async function GET(request: NextRequest) {
const effect = Effect.gen(function* () {
// 1. Authenticate
const auth = yield* Auth;
const { user } = yield* auth.getSession(request.headers);
// 2. Use repository services
const repo = yield* VideoRepository;
return yield* repo.findByUserId(user.id);
});
// 3. Provide layer and run
const runnable = Effect.provide(effect, createFullLayer());
const exit = await Effect.runPromiseExit(runnable);
// 4. Handle exit
return handleEffectExit(exit);
}
Core Patterns
1. Effect.gen for Sequential Code
Always use Effect.gen for sequential async operations:
// ✅ CORRECT: Use Effect.gen with yield*
const program = Effect.gen(function* () {
const user = yield* getUser(id);
const videos = yield* getVideosForUser(user.id);
return { user, videos };
});
// ❌ WRONG: Don't use flatMap chains
const program = getUser(id).pipe(
Effect.flatMap(user => getVideosForUser(user.id).pipe(
Effect.map(videos => ({ user, videos }))
))
);
2. Error Handling with Data.TaggedError
import { Data, Effect } from "effect";
// Define tagged errors
class NotFoundError extends Data.TaggedError("NotFoundError")<{
readonly message: string;
readonly entity: string;
}> {}
class ValidationError extends Data.TaggedError("ValidationError")<{
readonly message: string;
readonly field: string;
}> {}
// Use in effects
const findVideo = (id: string) => Effect.gen(function* () {
const video = yield* repo.findById(id);
if (!video) {
return yield* Effect.fail(new NotFoundError({
message: `Video ${id} not found`,
entity: "video"
}));
}
return video;
});
3. Error Recovery with catchTag
Handle errors close to where they occur:
const program = Effect.gen(function* () {
const video = yield* findVideo(id);
return video;
}).pipe(
Effect.catchTag("NotFoundError", () => Effect.succeed(null)),
Effect.catchTag("ValidationError", (e) =>
Effect.fail(new BadRequestError({ message: e.message }))
)
);
4. Repository Services
Always use repository services, never direct db access:
// ✅ CORRECT: Use repository service
const effect = Effect.gen(function* () {
const repo = yield* VideoRepository;
return yield* repo.findById(id);
});
// ❌ WRONG: Direct db access in route
const effect = Effect.gen(function* () {
return await db.query.videos.findFirst({...}); // Don't do this!
});
5. Layer Provision
Always provide layers before execution:
// For authenticated routes
const runnable = Effect.provide(effect, createFullLayer());
// For public routes
const runnable = Effect.provide(effect, createPublicLayer());
Common Pitfalls
❌ await inside Effect.gen
// ❌ WRONG: Never await inside Effect.gen
const program = Effect.gen(function* () {
const result = await Effect.runPromise(someEffect); // NO!
return result;
});
// ✅ CORRECT: Use yield*
const program = Effect.gen(function* () {
const result = yield* someEffect;
return result;
});
❌ Forgetting to provide layers
// ❌ WRONG: Running without layer
const result = await Effect.runPromise(effect);
// ✅ CORRECT: Provide layer first
const runnable = Effect.provide(effect, createFullLayer());
const result = await Effect.runPromise(runnable);
❌ Creating effects without executing
// ❌ WRONG: Effect created but never run
Effect.gen(function* () {
yield* doSomething();
}); // This does nothing!
// ✅ CORRECT: Execute the effect
const effect = Effect.gen(function* () {
yield* doSomething();
});
await Effect.runPromise(Effect.provide(effect, layer));
API Route Checklist
Before completing an API route, verify:
- Uses
Effect.genfor business logic - Authenticates with
Authservice (if needed) - Uses repository services (not direct
db) - Custom errors extend
Data.TaggedError - Errors handled with
catchTag - Layer provided with
createFullLayer()orcreatePublicLayer() - Exit handled with
handleEffectExit()orhandleEffectExitWithStatus() - No
await Effect.runPromise()insideEffect.gen
Service Pattern
import { Context, Effect, Layer } from "effect";
// Define service interface
interface VideoService {
findById(id: string): Effect.Effect<Video | null, NotFoundError>;
create(data: CreateVideoInput): Effect.Effect<Video, ValidationError>;
}
// Create tag
export class VideoService extends Context.Tag("VideoService")<
VideoService,
VideoService
>() {}
// Implement service
export const VideoServiceLive = Layer.succeed(VideoService, {
findById: (id) => Effect.gen(function* () {
// implementation
}),
create: (data) => Effect.gen(function* () {
// implementation
}),
});
See Also
-
CLAUDE.md- Project-wide patterns -
content/docs/internal/architecture/effect-best-practices.mdx- Full documentation -
src/lib/effect/services/- Existing service implementations
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
GitHub Stars
1
GitHub Forks
0
Created
Jan 2026
Last Updated
5个月前
tools
tools full stack
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!