effect-patterns
Effect TypeScript patterns including services, layers, schema validation, and Elysia integration.
Installation
Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.
Voir la source ↗GuidesInstructions
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
Exemples
Consultez les exemples du dépôt source.
Voir la source ↗Historique source
- Date d’indexation
- 2026-01-20
- Mise à jour de l’index
- 2026-03-03
- Branch
- main
Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!