effect-ts
Write idiomatic Effect v4 TypeScript following official best practices from effect-solutions and the Effect source. Use when writing, reviewing, or refactoring Effect code: services (ServiceMap.Service), layers and dependency injection, error handling (Schema.TaggedErrorClass), data modeling (Schema.Class, branded types, variants), testing (@effect/vitest), HTTP clients (effect/unstable/http), CLI tools (effect/unstable/cli), config, observability, and project setup. Triggers on: 'Effect', 'effect-ts', '@effect/', 'Schema', 'ServiceMap', 'Layer', 'Effect.gen', 'Effect.fn', 'TaggedError', 'branded types', or any Effect-TS related code.
How do I install this agent skill?
npx skills add https://github.com/joelhooks/effectts-skills --skill effect-tsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides comprehensive guidelines and patterns for developing applications with Effect v4. It includes setup instructions, data modeling best practices, and testing strategies. It facilitates referencing the official Effect source code by cloning it locally, which is a standard developer workflow for providing context to an AI agent. No security issues were detected.
- Socketwarn
1 alert: gptSecurity
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Effect-TS (v4)
Patterns from effect-solutions and the Effect source. This covers the latest v4 APIs.
Source-First Rule
When working in any repo that uses Effect (effect or @effect/* in package/dependency files), reference the official Effect source before writing, reviewing, or refactoring Effect code. Do not rely on stale memory, blog posts, or high-level docs alone.
- If the
effect_sourcetool is available, use it forstatus,hydrate, andsearchinstead of hand-rolled shell commands. - First check for a repo-local shallow source mirror at
.agent-sources/effect/. - If it is missing, create it before doing Effect work:
mkdir -p .agent-sources && git clone --depth 1 --filter=blob:none https://github.com/effect-ts/effect.git .agent-sources/effect - Keep the mirror out of product commits. If needed, add
.agent-sources/to.git/info/exclude, not the project.gitignore, unless Joel explicitly wants it committed. - Search the mirror for current patterns and APIs, especially under
packages/effect/src/and package tests/examples, before calling something an Effect best practice.
Local Source References
- repo-local Effect source mirror (canonical for current work):
.agent-sources/effect/ - effect-solutions (best practices, docs, examples):
~/Code/kitlangton/effect-solutions/ - fallback global Effect monorepo:
~/Code/effect-ts/effect/ - Search source for implementations:
grep -r "pattern" .agent-sources/effect/packages/effect/src/
Effect.gen and Effect.fn
Effect.gen provides sequential, readable composition (like async/await for Effect):
import { Effect } from "effect"
const program = Effect.gen(function* () {
const data = yield* fetchData
yield* Effect.logInfo(`Processing: ${data}`)
return yield* processData(data)
})
Effect.fn adds call-site tracing and named spans. Use for all service methods:
const processUser = Effect.fn("processUser")(function* (userId: string) {
yield* Effect.logInfo(`Processing user ${userId}`)
const user = yield* getUser(userId)
return yield* processData(user)
})
// Second argument for cross-cutting concerns (retry, timeout)
const fetchWithRetry = Effect.fn("fetchWithRetry")(
function* (url: string) {
const data = yield* fetchData(url)
return yield* processData(data)
},
flow(
Effect.retry(Schedule.recurs(3)),
Effect.timeout("5 seconds")
)
)
ServiceMap.Service
Define services as classes with a unique tag and typed interface:
import { Effect, ServiceMap } from "effect"
class Database extends ServiceMap.Service<
Database,
{
readonly query: (sql: string) => Effect.Effect<unknown[]>
readonly execute: (sql: string) => Effect.Effect<void>
}
>()("@app/Database") {}
Implement with Layer.effect or Layer.sync, using Effect.fn for all methods:
import { Effect, Layer } from "effect"
class Users extends ServiceMap.Service<
Users,
{
readonly findById: (id: UserId) => Effect.Effect<User, UserNotFoundError>
readonly all: () => Effect.Effect<readonly User[]>
}
>()("@app/Users") {
static readonly layer = Layer.effect(
Users,
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const findById = Effect.fn("Users.findById")(function* (id: UserId) {
const response = yield* http.get(`/users/${id}`)
return yield* HttpClientResponse.schemaBodyJson(User)(response)
})
const all = Effect.fn("Users.all")(function* () {
const response = yield* http.get("/users")
return yield* HttpClientResponse.schemaBodyJson(Schema.Array(User))(response)
})
return { findById, all }
})
)
}
Rules:
- Tag identifiers must be unique. Use
@app/ServiceNamepattern - Service methods should have
R = never(dependencies via Layer, not method signatures) - Use
readonlyproperties
See references/services-and-layers.md for service-driven development, test layers, layer memoization, and full composition patterns.
Schema.Class and Branded Types
Use Schema.Class for domain records. Brand all entity IDs and domain primitives:
import { Schema } from "effect"
const UserId = Schema.String.pipe(Schema.brand("UserId"))
type UserId = typeof UserId.Type
const Email = Schema.String.pipe(Schema.brand("Email"))
type Email = typeof Email.Type
class User extends Schema.Class("User")({
id: UserId,
name: Schema.String,
email: Email,
createdAt: Schema.Date,
}) {
get displayName() { return `${this.name} (${this.email})` }
}
// Construct with makeUnsafe for brands
const userId = UserId.makeUnsafe("user-123")
Use Schema.TaggedClass + Schema.Union for variants (OR types):
import { Match, Schema } from "effect"
class Success extends Schema.TaggedClass("Success")("Success", {
value: Schema.Number,
}) {}
class Failure extends Schema.TaggedClass("Failure")("Failure", {
error: Schema.String,
}) {}
const Result = Schema.Union([Success, Failure])
type Result = typeof Result.Type
// Exhaustive pattern matching
const render = (r: Result) => Match.valueTags(r, {
Success: ({ value }) => `Got: ${value}`,
Failure: ({ error }) => `Error: ${error}`,
})
See references/data-modeling.md for JSON encoding, Schema.Literals, validation, and full patterns.
Schema.TaggedErrorClass
Define domain errors with Schema.TaggedErrorClass. They are yieldable (no Effect.fail needed):
import { Schema } from "effect"
class UserNotFoundError extends Schema.TaggedErrorClass("UserNotFoundError")(
"UserNotFoundError",
{ userId: UserId, message: Schema.String }
) {}
// Yieldable: yield directly in generators
const getUser = Effect.fn("getUser")(function* (id: UserId) {
const user = yield* findUser(id)
if (!user) yield* new UserNotFoundError({ userId: id, message: "Not found" })
return user
})
Recover with catchTag / catchTags:
// Single tag
const recovered = program.pipe(
Effect.catchTag("UserNotFoundError", (e) =>
Effect.succeed(`User ${e.userId} missing`)
)
)
// Multiple tags
const recovered2 = program.pipe(
Effect.catchTags({
UserNotFoundError: (e) => Effect.succeed("not found"),
ValidationError: (e) => Effect.succeed("invalid"),
})
)
See references/error-handling.md for defects, Schema.Defect, and recovery patterns.
Layer Composition
Compose layers with Layer.provideMerge (incremental, flat types) and Layer.merge (parallel):
import { Effect, Layer } from "effect"
// Compose layers for the app
const appLayer = UserService.layer.pipe(
Layer.provideMerge(DatabaseLayer),
Layer.provideMerge(LoggerLayer),
Layer.provideMerge(ConfigLayer),
)
// Provide once at the entry point
const main = program.pipe(Effect.provide(appLayer))
Effect.runPromise(main)
Key rules:
- Store parameterized layers in constants (layer memoization by reference identity)
- Provide once at app entry, not scattered throughout code
- Use
Layer.syncfor synchronous implementations,Layer.effectfor effectful ones
Testing Quick Start
import { describe, expect, it } from "@effect/vitest"
import { Effect, Layer } from "effect"
it.effect("queries database", () =>
Effect.gen(function* () {
const db = yield* Database
const results = yield* db.query("SELECT *")
expect(results.length).toBe(2)
}).pipe(Effect.provide(Database.testLayer))
)
- Use
it.effectfor Effect-based tests (provides TestContext with TestClock) - Use
it.livefor real time / real clock - Provide fresh layers per test to prevent state leakage
- Use
it.layeronly when sharing expensive resources across a suite
See references/testing.md for the full worked example and advanced patterns.
Pipe for Instrumentation
const program = fetchData.pipe(
Effect.timeout("5 seconds"),
Effect.retry(Schedule.exponential("100 millis").pipe(
Schedule.compose(Schedule.recurs(3))
)),
Effect.tap((data) => Effect.logInfo(`Fetched: ${data}`)),
Effect.withSpan("fetchData"),
)
Anti-Patterns
| Do Not | Do Instead |
|---|---|
console.log(...) | Effect.log(...) with structured data |
process.env.KEY | Config.string("KEY") or Config.redacted("KEY") |
throw new Error() inside Effect.gen | yield* new TaggedError({...}) or Effect.fail(...) |
Effect.runSync(...) inside services | Keep everything effectful |
Effect.catchAll(() => ...) losing type info | Effect.catchTag / Effect.catchTags |
null / undefined in domain types | Option<T> with Option.match |
Option.getOrThrow(...) | Option.match({ onNone, onSome }) or Option.getOrElse |
Effect.Service (v3) | ServiceMap.Service (v4) |
Schema.TaggedError<T>() (v3) | Schema.TaggedErrorClass("Tag")("Tag", {...}) (v4) |
Scatter Effect.provide calls | Provide once at app entry |
| Call parameterized layer constructors inline | Store layers in constants (memoization) |
Reference Files
Load these as needed for deeper patterns:
- Services & Layers: ServiceMap.Service, service-driven development, test layers, layer memoization, provide vs provideMerge
- Data Modeling: Schema.Class, branded types, variants, Match.valueTags, JSON encoding
- Schema Decisions: Schema.Class vs Struct vs TaggedClass decision flowchart, migration patterns
- Error Handling: Schema.TaggedErrorClass, catch/catchTag/catchTags, defects, Schema.Defect, TypeId/refail patterns
- Testing: @effect/vitest setup, it.effect/it.live/it.layer, TestClock, Effect.flip, FiberRef isolation, worked example
- HTTP Clients: HttpClient, request building, response decoding, middleware, retries, typed API service
- CLI: Command.make, Arguments, Flags, subcommands, worked task manager example
- Config: Config module, schema validation, ConfigProvider, Redacted, config layers
- Processes & Scopes: Fork types, Scope.extend, Command for child processes, killable background tasks
- Setup: tsconfig, Effect Language Service, project structure, module settings
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/joelhooks/effectts-skills/effect-ts">View effect-ts on skillZs</a>