effect-patterns
SKILL.md
Effect TypeScript Patterns
Type-safe error handling and composable services using Effect as the core type system for Node.js/Bun backends.
Core Stack
| Component | Library | Purpose |
|---|---|---|
| Runtime | Bun 1.1+ | Fastest Node.js-compatible runtime |
| Framework | Elysia 1.x | End-to-end type safety |
| Type System | Effect 3.x | TypeScript's missing standard library |
| Validation | Effect Schema | Type-safe validation (replaces Zod) |
| HTTP Client | @effect/platform | HTTP operations |
| Database | Drizzle ORM | TypeScript ORM |
| Testing | Vitest, bun:test | Test framework |
Context7 Library IDs
Query these libraries for current best practices:
- Effect:
/effect-ts/effect - Elysia:
elysiajs - Drizzle:
/drizzle-team/drizzle-orm
Effect Documentation
Before implementing Effect code, consult:
- AI Documentation:
https://effect.website/llms.txt - Main Docs:
https://effect.website/docs
Typed Errors
Define errors as classes extending Schema.TaggedError:
import { Schema } from "effect"
class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>("UserNotFoundError")({
userId: Schema.String,
}) {}
class ValidationError extends Schema.TaggedError<ValidationError>("ValidationError")({
message: Schema.String,
field: Schema.String,
}) {}
class DatabaseError extends Schema.TaggedError<DatabaseError>("DatabaseError")({
message: Schema.String,
}) {}
Services with Context.Tag
Define services with typed interfaces:
import { Effect, Context, Layer } from "effect"
class UserRepository extends Context.Tag("UserRepository")<
UserRepository,
{
findById: (id: string) => Effect.Effect<User, UserNotFoundError>
create: (data: CreateUser) => Effect.Effect<User, ValidationError>
}
>() {}
class DatabaseService extends Context.Tag("DatabaseService")<
DatabaseService,
{ query: <T>(sql: string) => Effect.Effect<T[], DatabaseError> }
>() {}
Layer Implementation
Implement services with Layer:
const UserRepositoryLive = Layer.succeed(
UserRepository,
UserRepository.of({
findById: (id) => Effect.tryPromise({
try: () => db.findUser(id),
catch: () => new UserNotFoundError({ userId: id })
}),
create: (data) => Effect.tryPromise({
try: () => db.createUser(data),
catch: (e) => new ValidationError({ message: String(e), field: "unknown" })
})
})
)
const DatabaseServiceLive = Layer.succeed(
DatabaseService,
DatabaseService.of({
query: (sql) => Effect.tryPromise({
try: () => db.query(sql),
catch: (e) => new DatabaseError({ message: String(e) })
})
})
)
Effect.gen for Composition
Use generator syntax for composable logic:
const getUser = (id: string) =>
Effect.gen(function* () {
const repo = yield* UserRepository
const user = yield* repo.findById(id)
return user
})
const createUserWithAudit = (data: CreateUser) =>
Effect.gen(function* () {
const repo = yield* UserRepository
const user = yield* repo.create(data)
yield* Effect.log(`Created user: ${user.id}`)
return user
})
Schema Validation
Use Effect Schema for validation:
import { Schema } from "effect"
const CreateUserSchema = Schema.Struct({
name: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100)),
email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+\.[^@]+$/)),
age: Schema.optional(Schema.Number.pipe(Schema.int(), Schema.positive())),
})
type CreateUser = Schema.Schema.Type<typeof CreateUserSchema>
// Validate unknown data
const validateUser = Schema.decodeUnknown(CreateUserSchema)
Error Handling with catchTags
Pattern match on typed errors:
const result = await Effect.runPromise(
program.pipe(
Effect.catchTags({
UserNotFoundError: (e) => Effect.succeed({ error: `User ${e.userId} not found` }),
ValidationError: (e) => Effect.succeed({ error: `Invalid ${e.field}: ${e.message}` }),
DatabaseError: (e) => Effect.succeed({ error: `Database error: ${e.message}` }),
})
)
)
Retry with Schedule
import { Effect, Schedule } from "effect"
const fetchWithRetry = Effect.retry(
fetchExternalApi,
Schedule.exponential("1 second").pipe(Schedule.compose(Schedule.recurs(3)))
)
Elysia + Effect Integration
import { Elysia, t } from "elysia"
import { Effect } from "effect"
const app = new Elysia()
.post("/api/users", async ({ body }) => {
const program = Effect.gen(function* () {
const validated = yield* Schema.decodeUnknown(CreateUserSchema)(body)
const repo = yield* UserRepository
return yield* repo.create(validated)
})
return Effect.runPromise(
program.pipe(
Effect.provide(UserRepositoryLive),
Effect.catchAll((e) => Effect.succeed({ error: e.message }))
)
)
}, {
body: t.Object({
name: t.String(),
email: t.String({ format: "email" }),
})
})
Testing with Effect
import { Effect, Layer } from "effect"
const TestUserRepository = Layer.succeed(
UserRepository,
UserRepository.of({
findById: (id) =>
id === "123"
? Effect.succeed({ id: "123", name: "Test User" })
: Effect.fail(new UserNotFoundError({ userId: id })),
create: (data) => Effect.succeed({ id: "new", ...data })
})
)
// In tests
const result = await Effect.runPromise(
getUser("123").pipe(Effect.provide(TestUserRepository))
)
Validation Commands
bun tsc --noEmit
bun eslint src/
bun test
bun build src/index.ts --outdir=dist
Guidelines
- Always define typed errors with Schema.TaggedError
- Use Context.Tag for all services
- Implement services with Layer.succeed
- Compose logic with Effect.gen
- Use Schema for all validation
- Pattern match errors with catchTags
- Provide layers at the edge of the application
Weekly Installs
3
Repository
5dlabs/ctoFirst Seen
Jan 24, 2026
Installed on
claude-code2
windsurf1
trae1
opencode1
codex1
antigravity1