Skip to main content

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.

소스 정보

저장소
joelhooks/effectts-skills
최근 소스 활동
2026년 5월 20일 17:05
감지된 SKILL.md 언어
영어
스타
43
포크
2

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
11 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
effect-ts
description
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.
# Effect-TS (v4) Patterns from [effect-solutions](https://effect.solutions) and the [Effect source](https://github.com/effect-ts/effect). 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_source` tool is available, use it for `status`, `hydrate`, and `search` instead 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): ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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/ServiceName` pattern - Service methods should have `R = never` (dependencies via Layer, not method signatures) - Use `readonly` properties See [references/services-and-layers.md](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: ```typescript 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): ```typescript 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](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): ```typescript 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`: ```typescript // 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](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): ```typescript 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.sync` for synchronous implementations, `Layer.effect` for effectful ones ## Testing Quick Start ```typescript 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.effect` for Effect-based tests (provides TestContext with TestClock) - Use `it.live` for real time / real clock - Provide fresh layers per test to prevent state leakage - Use `it.layer` only when sharing expensive resources across a suite See [references/testing.md](references/testing.md) for the full worked example and advanced patterns. ## Pipe for Instrumentation ```typescript 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](references/services-and-layers.md)**: ServiceMap.Service, service-driven development, test layers, layer memoization, provide vs provideMerge - **[Data Modeling](references/data-modeling.md)**: Schema.Class, branded types, variants, Match.valueTags, JSON encoding - **[Schema Decisions](references/schema-decisions.md)**: Schema.Class vs Struct vs TaggedClass decision flowchart, migration patterns - **[Error Handling](references/error-handling.md)**: Schema.TaggedErrorClass, catch/catchTag/catchTags, defects, Schema.Defect, TypeId/refail patterns - **[Testing](references/testing.md)**: @effect/vitest setup, it.effect/it.live/it.layer, TestClock, Effect.flip, FiberRef isolation, worked example - **[HTTP Clients](references/http-clients.md)**: HttpClient, request building, response decoding, middleware, retries, typed API service - **[CLI](references/cli.md)**: Command.make, Arguments, Flags, subcommands, worked task manager example - **[Config](references/config.md)**: Config module, schema validation, ConfigProvider, Redacted, config layers - **[Processes & Scopes](references/processes.md)**: Fork types, Scope.extend, Command for child processes, killable background tasks - **[Setup](references/setup.md)**: tsconfig, Effect Language Service, project structure, module settings
GitHub에서 보기