Effect Services And Layers
Use this skill for replaceable dependencies and runtime wiring. Keep domain methods as effects and construction in layers.
Workflow
- Search current service/layer patterns before adding a new style.
- Inspect local Effect type declarations for unfamiliar
Context or Layer APIs.
- Decide whether the dependency is a primitive contract, infrastructure adapter, runtime service, or test seam.
- Keep service interfaces free of construction details.
Service Shape
import { Context, Effect, Layer } from "effect"
interface UsersService {
readonly find: (id: UserId) => Effect.Effect<User, UserNotFound>
}
export class Users extends Context.Service<Users, UsersService>()("Users") {}
export const UsersLive = Layer.effect(Users)(
Effect.gen(function* () {
const db = yield* Db
return Users.of({
find: (id) => db.queryUser(id),
})
}),
)
Rules:
- This repo currently prefers
Context.Service<Service, Shape>()("Name").
- Do not cargo-cult
Effect.Service unless local v4 types and repo patterns support it for the case at hand.
- Declare service requirements in return types; do not hide them with
any.
- Use
Layer.succeed(Service)(implementation) when the implementation already exists.
- Use
Layer.effect(Service)(effect) when construction needs effects or dependencies.
- Compose same-level layers with
Layer.mergeAll.
- Use
Layer.provideMerge when wiring dependencies into another layer while preserving provided services.
- Avoid repeated
Effect.provide inside hot paths; provide layers at composition boundaries.
Boundaries
- Runtime services, clocks, random/id generation, stores, language models, and mutable context should be read from the Effect environment at execution time.
- Constructors and factories may capture static configuration.
- Constructors/builders should shape data and service values only. They should not perform I/O, allocate runtime resources, read config, call providers, or start fibers.
- Runtime code should avoid ambient
Date.now(), new Date(), Math.random(), and crypto.randomUUID(). Use services/layers or explicit injected providers; boundary adapters and tests may wrap those primitives.
- Do not call
Effect.runPromise or Effect.runSync inside services. Run effects at process, test, or script edges.
- For test seams, provide alternate layers instead of conditionals inside production services.
Checks
- Is this dependency replaceable in tests?
- Does the service interface expose behavior, not its implementation backend?
- Did layer wiring preserve requirements the caller still needs?
- Did the change introduce a package dependency in the wrong direction?
1---2name: effect-services-layers3description: Use when defining or wiring Effect services in Theseus, including Context.Service, service interfaces, Layer.effect/succeed/mergeAll/provideMerge, dependency graphs, runtime construction, and test seams.4---56# Effect Services And Layers78Use this skill for replaceable dependencies and runtime wiring. Keep domain methods as effects and construction in layers.910## Workflow11121. Search current service/layer patterns before adding a new style.132. Inspect local Effect type declarations for unfamiliar `Context` or `Layer` APIs.143. Decide whether the dependency is a primitive contract, infrastructure adapter, runtime service, or test seam.154. Keep service interfaces free of construction details.1617## Service Shape1819```typescript20import { Context, Effect, Layer } from "effect"2122interface UsersService {23 readonly find: (id: UserId) => Effect.Effect<User, UserNotFound>24}2526export class Users extends Context.Service<Users, UsersService>()("Users") {}2728export const UsersLive = Layer.effect(Users)(29 Effect.gen(function* () {30 const db = yield* Db31 return Users.of({32 find: (id) => db.queryUser(id),33 })34 }),35)36```3738Rules:3940- This repo currently prefers `Context.Service<Service, Shape>()("Name")`.41- Do not cargo-cult `Effect.Service` unless local v4 types and repo patterns support it for the case at hand.42- Declare service requirements in return types; do not hide them with `any`.43- Use `Layer.succeed(Service)(implementation)` when the implementation already exists.44- Use `Layer.effect(Service)(effect)` when construction needs effects or dependencies.45- Compose same-level layers with `Layer.mergeAll`.46- Use `Layer.provideMerge` when wiring dependencies into another layer while preserving provided services.47- Avoid repeated `Effect.provide` inside hot paths; provide layers at composition boundaries.4849## Boundaries5051- Runtime services, clocks, random/id generation, stores, language models, and mutable context should be read from the Effect environment at execution time.52- Constructors and factories may capture static configuration.53- Constructors/builders should shape data and service values only. They should not perform I/O, allocate runtime resources, read config, call providers, or start fibers.54- Runtime code should avoid ambient `Date.now()`, `new Date()`, `Math.random()`, and `crypto.randomUUID()`. Use services/layers or explicit injected providers; boundary adapters and tests may wrap those primitives.55- Do not call `Effect.runPromise` or `Effect.runSync` inside services. Run effects at process, test, or script edges.56- For test seams, provide alternate layers instead of conditionals inside production services.5758## Checks5960- Is this dependency replaceable in tests?61- Does the service interface expose behavior, not its implementation backend?62- Did layer wiring preserve requirements the caller still needs?63- Did the change introduce a package dependency in the wrong direction?