Claude Skill

effect-ts

Effect-TS development guide for TypeScript, focused on Effect v4 (the recommended default) with full v3 (stable) support for existing codebases. Use when building, debugging, reviewing, or generating Effect code across its error, concurrency, service, streaming, schema, and platf

LLM Mart · 0 points · 21 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download tenequm-skills-skills_effect-ts-1ff2284.zip · 71 KB
Part of tenequm/skills — 25 skills

Install

skills CLI npx skills add https://github.com/tenequm/skills/tree/main/skills/effect-ts
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tenequm-skills@llmmart
Git git clone https://github.com/tenequm/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tenequm/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Effect-TS

Effect is a TypeScript library for building production-grade software with typed errors, structured concurrency, dependency injection, and built-in observability.

Version Detection

Before writing Effect code, detect which version the user is on:

# Check installed version
cat package.json | grep '"effect"'
  • v4.x (recommended, the direction Effect is heading): Context.Service, Effect.catch, Effect.forkChild, Schema.TaggedErrorClass
  • v3.x (stable, still common in production): Context.Tag, Effect.catchAll, Effect.fork, Data.TaggedError

Note: v4 beta briefly used a ServiceMap module, renamed back to Context on 2026-04-07 (PR #1961). If you see ServiceMap.* in any doc or older beta code, it is the current Context.*. Both v3 and v4 import Context from "effect"; the exports inside differ (Context.Service in v4 vs Context.Tag in v3).

Prefer v4 for new projects - it's where Effect is going. In an existing codebase, match the installed version: don't rewrite v3 code in v4 syntax unless asked. If the version is genuinely unclear, default to v4 and say so. v4 is still in beta, so pin an exact version (4.0.0-beta.x) and expect occasional API churn.

Primary Documentation Sources

v4 (primary):

v3 (for existing codebases):

Both versions:

AI Guardrails: Critical Corrections

LLM outputs frequently contain incorrect Effect APIs. Verify every API against the reference docs before using it.

Common hallucinations (both versions):

Wrong (AI often generates) Correct
Effect.cachedWithTTL(...) Cache.make({ capacity, timeToLive, lookup })
Effect.cachedInvalidateWithTTL(...) cache.invalidate(key) / cache.invalidateAll()
Effect.mapError(effect, fn) Effect.mapError(fn) in pipe, or use Effect.catchTag
import { Schema } from "@effect/schema" import { Schema } from "effect" (v3.10+ and all v4)
import { JSONSchema } from "@effect/schema" import { JSONSchema } from "effect" (v3.10+)
JSON Schema Draft 2020-12 Effect Schema generates Draft-07
"thread-local storage" "fiber-local storage" via FiberRef (v3) / Context.Reference (v4)
fibers are "cancelled" fibers are "interrupted"
all queues have back-pressure only bounded queues; sliding/dropping do not
new MyError("message") new MyError({ message: "..." }) (Schema errors take objects)

v3-specific hallucinations:

Wrong Correct (v3)
Effect.Service (function call) class Foo extends Effect.Service<Foo>()("id", {})
Effect.match(effect, { ... }) Effect.match(effect, { onSuccess, onFailure })
Effect.provide(layer1, layer2) Effect.provide(Layer.merge(layer1, layer2))

v4-specific hallucinations (AI may mix v3/v4):

Wrong (v3 API used in v4 code) Correct (v4)
Context.Tag("X") (v3 shape) Context.Service<X>(id) or class syntax
ServiceMap.Service / ServiceMap.Reference Renamed back to Context.Service / Context.Reference on 2026-04-07
Effect.catchAll(fn) Effect.catch(fn)
Effect.fork(effect) Effect.forkChild(effect)
Effect.forkDaemon(effect) Effect.forkDetach(effect)
Data.TaggedError Schema.TaggedErrorClass
FiberRef.get(ref) yield* References.X (a Context.Reference)
yield* ref (Ref as Effect) yield* Ref.get(ref) (Ref is no longer an Effect)
yield* fiber (Fiber as Effect) yield* Fiber.join(fiber) (Fiber is no longer Effect)
Logger.Default / Logger.Live Logger.layer (v4 naming convention)
Schema.TaggedError Schema.TaggedErrorClass
Schema.makeUnsafe(input) Schema.make(input) (throws SchemaError); also instance methods schema.makeOption(...), schema.makeEffect(...)
ParseResult (from "effect") SchemaIssue module + SchemaError class; narrow with Schema.isSchemaError
HttpApiEndpoint.get(n, p).pipe(HttpApiEndpoint.setPath(...), setPayload(...), setSuccess(...)) HttpApiEndpoint.get(n, p, { params, query, payload, success, error }) (object-option form)
Otlp.layer({ url, serviceName }) OtlpTracer.layer({ url, resource: { serviceName } }) + OtlpSerialization.layerJson + FetchHttpClient.layer
import { HttpApi } from "@effect/platform" (v4) import { HttpApi } from "effect/unstable/httpapi"
HttpApi endpoint schema errors are typed errors by default In current v4 betas they default to defects unless transformed

Read references/llm-corrections.md for the exhaustive corrections table.

Progressive Disclosure

Read only the reference files relevant to your task:

  • Error modeling or typed failures → references/error-modeling.md
  • Services, DI, or Layer wiring → references/dependency-injection.md
  • Per-key dynamic layers (per-tenant resources, LayerMap) → references/dependency-injection.md
  • Bridging Effect into non-Effect frameworks (Hono/Express, ManagedRuntime) → references/dependency-injection.md
  • Retries, timeouts, or backoff → references/retry-scheduling.md
  • Fibers, forking, or parallel work → references/concurrency.md
  • Request batching, N+1 elimination, DataLoader pattern → references/concurrency.md
  • Multi-provider fallback (ExecutionPlan) → references/effect-ai.md / references/retry-scheduling.md
  • Streams, queues, or SSE → references/streams.md
  • Framing streams (NDJSON / MessagePack encode-decode) → references/streams.md
  • Running child processes / shelling out → references/concurrency.md
  • Resource lifecycle or cleanup → references/resource-management.md
  • Refreshable values (rotating credentials, polled config) → references/resource-management.md
  • Reference-counted shared resources (RcRef/RcMap) → references/resource-management.md
  • Schema validation or decoding → references/schema.md
  • Branded / nominal types (Brand) → references/schema.md
  • Logging, metrics, or tracing → references/observability.md
  • HTTP clients or API calls → references/http.md
  • HTTP API servers → references/http.md (covers both client and server)
  • File uploads / multipart form-data → references/http.md
  • LLM/AI integration → references/effect-ai.md
  • Configuration, env vars, secrets → references/configuration.md
  • SQL / database access → references/sql.md
  • Command-line apps → references/cli.md
  • Typed client/server RPC → references/rpc.md
  • Sharded entities, durable workflows, event sourcing → references/distributed.md
  • Transactional state (STM, Tx*) → references/stm.md
  • Date/time handling → references/datetime.md
  • Immutable nested updates (optics) → references/optics.md
  • Graphs, dependency ordering, shortest paths, cycle detection → references/graph.md
  • Pattern matching (Match) → references/core-patterns.md
  • Pooling resources (Pool) → references/resource-management.md
  • Fiber sets, SubscriptionRef, worker threads → references/concurrency.md
  • Testing Effect code → references/testing.md
  • Property-based testing / generating data from schemas → references/testing.md
  • Migrating from async/await → references/migration-async.md
  • Migrating from v3 to v4 → references/migration-v4.md
  • Core types, gen, pipe, running → references/core-patterns.md
  • Full wrong-vs-correct API table → references/llm-corrections.md

Core Workflow

  1. Detect version from package.json before writing any code
  2. Clarify boundaries: identify where IO happens, keep core logic as Effect values
  3. Choose style: use Effect.gen for sequential logic, pipelines for simple transforms. In v4, prefer Effect.fn("name") for named functions
  4. Model errors explicitly: type expected errors in the E channel; treat bugs as defects
  5. Model dependencies with services and layers; keep interfaces free of construction logic
  6. Manage resources with Scope when opening/closing things (files, connections, etc.)
  7. Provide layers and run effects only at program edges (NodeRuntime.runMain or ManagedRuntime)
  8. Verify APIs exist before using them - consult https://tim-smart.github.io/effect-io-ai/ or source docs

Starter Function Set

Start with these ~20 functions (the official recommended set):

Creating effects: Effect.succeed, Effect.fail, Effect.sync, Effect.tryPromise

Composition: Effect.gen (+ Effect.fn in v4), Effect.andThen, Effect.map, Effect.tap, Effect.all

Running: Effect.runPromise, NodeRuntime.runMain (preferred for entry points)

Error handling: Effect.catchTag, Effect.catch (v4) / Effect.catchAll (v3), Effect.orDie

Resources: Effect.acquireRelease, Effect.acquireUseRelease, Effect.scoped

Dependencies: Effect.provide, Effect.provideService

Key modules: Effect, Schema, Layer, Option, Result (v4) / Either (v3), Array, Match

DI (v4): Context.Service, Context.Reference, Layer.effect, Effect.fn("name") DI (v3): Context.Tag, Context.Reference

Import Patterns

Always use barrel imports from "effect":

import { Context, Effect, Schema, Layer, Option, Stream } from "effect"

For companion packages, import from the package name. v3 and v4 differ here:

// v4 (recommended) - platform transports still separate, but HttpApi / observability
// moved under effect/unstable/*
import { NodeRuntime } from "@effect/platform-node"
import { FetchHttpClient } from "effect/unstable/http"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi"
import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"

// v3 (stable) companion packages
import { NodeRuntime } from "@effect/platform-node"
import { HttpClient } from "@effect/platform"
import { NodeSdk } from "@effect/opentelemetry"

Avoid deep module imports (effect/Effect) unless your bundler requires it for tree-shaking.

Output Standards

  • Show imports in every code example
  • Prefer Effect.gen (imperative) for multi-step logic; pipelines for transforms
  • In v4, use Effect.fn("name") instead of bare Effect.gen for named functions; use Effect.fnUntraced for internal helpers that don't need a span/stack-frame
  • Never call Effect.runPromise / Effect.runSync inside library code - only at program edges
  • Use NodeRuntime.runMain for CLI/server entry points (handles SIGINT gracefully)
  • Use ManagedRuntime when integrating Effect into non-Effect frameworks (Hono, Express, etc.)
  • Always return yield* when raising an error in a generator (ensures TS understands control flow)
  • Avoid point-free/tacit usage: write Effect.map((x) => fn(x)) not Effect.map(fn) (generics get erased)
  • Keep dependency graphs explicit (services, layers, tags)
  • State the Effect<A, E, R> shape when it helps design decisions

Agent Quality Checklist

Before outputting Effect code, verify:

  • Every API exists (check against tim-smart API list or source docs)
  • Imports are from "effect" (not @effect/schema, @effect/io, etc.)
  • Version matches the user's codebase (v3 vs v4 syntax)
  • Expected errors are typed in E; unexpected failures are defects
  • run* is called only at program edges, not inside library code
  • Resources opened with acquireRelease are wrapped in Effect.scoped
  • Layers are provided before running (no missing R requirements)
  • Generator bodies use yield* (not yield without *)
  • Error raises in generators use return yield* pattern
Files (skills)
  • evals
    • evals.json 3.9 KB
      {
        "skill_name": "effect-ts",
        "evals": [
          {
            "id": 1,
            "prompt": "Write an Effect service that wraps a flaky HTTP API. It should retry on network errors with exponential backoff (max 3 retries), fail fast on auth errors, and expose a typed interface via Context.Tag. Include the layer and a usage example.",
            "expected_output": "A complete Effect service with typed errors (NetworkError, AuthError), a Context.Tag-based interface, a Layer with retry logic using Schedule.exponential + Schedule.recurs, conditional retry (only on NetworkError), and a runnable usage example.",
            "files": [],
            "expectations": [
              "Imports from 'effect' package (not invented packages)",
              "Defines at least 2 distinct typed error classes (e.g., NetworkError and AuthError)",
              "Uses Context.Tag (v3) or Context.Service (v4) for the service interface",
              "Uses Schedule.exponential for exponential backoff",
              "Limits retries to 3 (Schedule.recurs(3) or equivalent)",
              "Only retries network errors, not auth errors (conditional retry logic)",
              "Includes a Layer definition (Layer.succeed, Layer.effect, or similar)",
              "Includes a runnable usage example (Effect.runPromise or NodeRuntime.runMain)",
              "Does not use non-existent Effect APIs (e.g., Effect.retryN, Effect.cachedWithTTL)"
            ]
          },
          {
            "id": 2,
            "prompt": "I need to consume an SSE endpoint that streams JSON events and process each event through a validation schema. Show me how to do this with Effect Stream, including proper resource cleanup if the connection drops.",
            "expected_output": "An Effect Stream that connects to an SSE endpoint via HttpClient, parses SSE data lines, validates each event with Schema.decodeUnknown, handles errors, and uses Scope/acquireRelease for cleanup on disconnect.",
            "files": [],
            "expectations": [
              "Uses Effect Stream module for SSE consumption",
              "Validates events with Schema (Schema.decodeUnknown or Schema.decodeUnknownEffect)",
              "Uses acquireRelease, addFinalizer, unwrapScoped, or AbortController for resource cleanup",
              "Defines typed error classes for connection and parse failures",
              "Parses SSE format (data: lines, event separation on blank lines)",
              "Does not use non-existent APIs (e.g., Stream.fromSSE, Stream.fromEventSource, Stream.acquireRelease)",
              "Handles errors with catchTag or similar typed error handling"
            ]
          },
          {
            "id": 3,
            "prompt": "Migrate this v3 Effect code to v4:\n```typescript\nimport { Context, Data, Effect, Layer } from 'effect'\nclass AppError extends Data.TaggedError('AppError')<{ message: string }> {}\nclass Config extends Context.Tag('Config')<Config, { readonly endpoint: string }>() {}\nconst ConfigLive = Layer.succeed(Config, { endpoint: 'https://api.example.com' })\nconst program = Effect.gen(function*() {\n  const config = yield* Config\n  return yield* Effect.tryPromise({ try: () => fetch(config.endpoint), catch: () => new AppError({ message: 'failed' }) })\n}).pipe(Effect.catchAll((e) => Effect.log(e.message)))\nEffect.runPromise(program.pipe(Effect.provide(ConfigLive)))\n```",
            "expected_output": "Migrated code using Context.Service (the v4 API) instead of Context.Tag (v3), Schema.TaggedErrorClass instead of Data.TaggedError, Effect.catch instead of Effect.catchAll, and correct v4 import patterns. Should note all changes made.",
            "files": [],
            "expectations": [
              "Replaces Context.Tag (v3) with Context.Service (v4)",
              "Replaces Data.TaggedError with Schema.TaggedErrorClass (the v4 API)",
              "Replaces Effect.catchAll with Effect.catch (the v4 rename)",
              "Imports Context from 'effect'",
              "Includes a changelog or migration notes explaining what changed",
              "No v3-only APIs remain in code (Context.Tag, Data.TaggedError, Effect.catchAll)",
              "Code is logically complete and would compile with effect v4"
            ]
          }
        ]
      }
      
  • references
    • cli.md 2.3 KB
      # CLI
      
      `effect/unstable/cli` builds command-line apps where handlers are Effects — flags and arguments are parsed and typed, and the whole program runs through the Effect runtime. (Unstable module, `@since 4.0.0`.)
      
      ```typescript
      import { Command, Flag, Argument } from "effect/unstable/cli"
      ```
      
      ## A Minimal Command
      
      ```typescript
      import { Console, Effect } from "effect"
      import { Command, Flag } from "effect/unstable/cli"
      import { NodeRuntime, NodeServices } from "@effect/platform-node"
      
      const greet = Command.make(
        "greet",
        {
          name: Flag.string("name"),
          loud: Flag.boolean("loud")
        },
        (config) =>
          Effect.gen(function*() {
            const msg = `Hello, ${config.name}!`
            yield* Console.log(config.loud ? msg.toUpperCase() : msg)
          })
      )
      
      Command.run(greet, { version: "1.0.0" }).pipe(
        Effect.provide(NodeServices.layer),
        NodeRuntime.runMain
      )
      ```
      
      `Command.make(name, config?, handler?)` — `config` maps option names to `Flag.*` / `Argument.*` params (and can nest: `server: { host, port }`); the handler receives the parsed, typed values and returns an Effect.
      
      > `Command.run` takes `{ version }` only — the command's name comes from the command itself, **not** a `{ name, version }` object. Use `Command.runWith(command, { version })(argv)` to pass args explicitly.
      
      ## Flags and Arguments
      
      ```typescript
      const port = Flag.integer("port").pipe(Flag.withDefault(3000))
      const verbose = Flag.boolean("verbose").pipe(Flag.withAlias("v"))
      const config = Flag.file("config").pipe(Flag.optional)
      const env = Flag.choice("env", ["dev", "prod"])
      
      const file = Argument.string("file")
      const rest = Argument.variadic(Argument.string("paths"))
      ```
      
      `Flag.*` constructors: `string`, `boolean`, `integer`, `float`, `date`, `choice`, `path` / `file` / `directory`, `redacted`, `fileText` / `fileParse` / `fileSchema`, `keyValuePair`. Modifiers: `withDefault`, `withAlias`, `withDescription`, `optional`. `Argument.*` mirrors these and adds `Argument.variadic`. Both are built on the shared `Param` layer.
      
      ## Subcommands
      
      ```typescript
      const root = Command.make("mytool").pipe(
        Command.withSubcommands([greet, otherCommand])
      )
      ```
      
      `Command.run` / `runWith` require the `FileSystem | Path | Terminal | ...` environment, all provided by `NodeServices.layer`; finish with `NodeRuntime.runMain` so SIGINT is handled.
      
    • concurrency.md 10.6 KB
      # Concurrency
      
      Effect uses fiber-based structured concurrency. Fibers are lightweight virtual threads managed by the Effect runtime.
      
      ## Forking Fibers
      
      ### v3
      
      ```typescript
      import { Effect, Fiber } from "effect"
      
      // Fork as child (interrupted when parent ends)
      const fiber = yield* Effect.fork(myEffect)
      
      // Fork as daemon (outlives parent)
      const fiber = yield* Effect.forkDaemon(longRunning)
      
      // Fork tied to a Scope
      const fiber = yield* Effect.forkScoped(background)
      ```
      
      ### v4
      
      ```typescript
      import { Effect, Fiber } from "effect"
      
      // Fork as child
      const fiber = yield* Effect.forkChild(myEffect)
      
      // Fork detached (outlives parent)
      const fiber = yield* Effect.forkDetach(longRunning)
      
      // Fork tied to a Scope (unchanged)
      const fiber = yield* Effect.forkScoped(background)
      
      // New options
      const fiber = yield* Effect.forkChild(myEffect, {
        startImmediately: true,   // begin executing immediately
        uninterruptible: true     // cannot be interrupted
      })
      ```
      
      > **`forkDetach` parents to the global scope**, so the fiber survives even `runtime.dispose()` on a `ManagedRuntime`. For a background fiber (a poller, a subscriber) that must die when its runtime is torn down, use `Effect.forkScoped` inside the runtime's scope instead — `forkDetach` there leaks the fiber past disposal.
      
      ## Joining and Interrupting
      
      ```typescript
      // Wait for a fiber to complete
      const result = yield* Fiber.join(fiber)
      
      // Interrupt a fiber
      yield* Fiber.interrupt(fiber)
      
      // v4: Fiber is NOT an Effect - must use Fiber.join explicitly
      // v3: yield* fiber was allowed (Fiber was an Effect subtype)
      ```
      
      ## Parallel Execution
      
      ```typescript
      // Process items with bounded concurrency
      const results = yield* Effect.all(
        items.map((item) => processItem(item)),
        { concurrency: 5 }
      )
      
      // forEach variant
      const results = yield* Effect.forEach(
        items,
        (item) => processItem(item),
        { concurrency: 10 }
      )
      ```
      
      ## Racing
      
      ```typescript
      // First to succeed wins, loser is interrupted
      const result = yield* Effect.race(fetchFromCache, fetchFromDb)
      
      // Race multiple effects
      const result = yield* Effect.raceAll([
        fetchFromCdn1,
        fetchFromCdn2,
        fetchFromCdn3
      ])
      ```
      
      ## Interruption
      
      Interruption is cooperative, not preemptive. Fibers check for interruption at yield points.
      
      ```typescript
      // Register cleanup on interruption
      const withCleanup = myEffect.pipe(
        Effect.onInterrupt(() => Effect.log("Interrupted! Cleaning up..."))
      )
      
      // Make a region uninterruptible
      const critical = Effect.uninterruptible(
        Effect.gen(function*() {
          yield* beginTransaction()
          yield* doWork()
          yield* commitTransaction()
        })
      )
      
      // Interruptible region inside an uninterruptible one
      const mixed = Effect.uninterruptible(
        Effect.gen(function*() {
          yield* criticalSetup()
          yield* Effect.interruptible(longComputation)
          yield* criticalTeardown()
        })
      )
      ```
      
      ## Queue
      
      Bounded queues provide back-pressure; dropping/sliding queues do not.
      
      ```typescript
      import { Queue } from "effect"
      
      // Bounded queue (back-pressure: offer suspends when full)
      const queue = yield* Queue.bounded<string>(100)
      
      // Dropping queue (discards new items when full)
      const queue = yield* Queue.dropping<string>(100)
      
      // Sliding queue (discards oldest items when full)
      const queue = yield* Queue.sliding<string>(100)
      
      // Offer and take
      yield* Queue.offer(queue, "hello")
      const item = yield* Queue.take(queue)
      
      // Take all available
      const items = yield* Queue.takeAll(queue)
      ```
      
      ## Semaphore
      
      ```typescript
      // v3
      import { Effect } from "effect"
      
      const semaphore = yield* Effect.makeSemaphore(3)
      
      // Limit concurrency (method-style on the returned semaphore)
      const limited = semaphore.withPermits(1)(expensiveOp)
      ```
      
      ```typescript
      // v4 — Effect.makeSemaphore is removed. Use the Semaphore module.
      import { Semaphore } from "effect"
      
      const semaphore = yield* Semaphore.make(3)
      
      // withPermits is data-first in v4: pass the semaphore as the first arg.
      const limited = Semaphore.withPermits(semaphore, 1)(expensiveOp)
      ```
      
      ## Deferred (one-shot signal)
      
      ```typescript
      import { Deferred, Effect } from "effect"
      
      const deferred = yield* Deferred.make<string, never>()
      
      // Complete the deferred (can only be done once)
      yield* Deferred.succeed(deferred, "done")
      
      // Wait for completion
      // v3: yield* deferred (Deferred was an Effect subtype)
      // v4: must use Deferred.await
      const value = yield* Deferred.await(deferred)
      ```
      
      ## Structured Concurrency Pattern: Background Worker
      
      ```typescript
      const withWorker = Effect.scoped(
        Effect.gen(function*() {
          // Background fiber tied to scope - auto-interrupted on scope exit
          yield* Effect.forkScoped(
            Effect.repeat(
              processQueue,
              Schedule.spaced("100 millis")
            )
          )
          // Main work continues...
          yield* handleRequests()
        })
      )
      ```
      
      ## Managing Dynamic Sets of Fibers
      
      `FiberHandle` (one fiber), `FiberMap` (keyed) and `FiberSet` (unkeyed) manage fibers whose set changes at runtime. All three are **scoped** — closing the surrounding scope interrupts every managed fiber, and fibers self-remove on completion. `FiberMap.run(map, key, effect)` forks `effect` under `key`, interrupting any prior fiber at that key (pass `{ onlyIfMissing: true }` to keep the existing one).
      
      ```typescript
      import { Effect, FiberMap } from "effect"
      
      const program = Effect.scoped(
        Effect.gen(function*() {
          const fibers = yield* FiberMap.make<string>()
      
          // Start (or restart) a background fiber per connection id
          yield* FiberMap.run(fibers, "conn-1", handleConnection(conn1))
          yield* FiberMap.run(fibers, "conn-2", handleConnection(conn2))
      
          yield* serveRequests()
          // All managed fibers interrupted when this scope closes
        })
      )
      ```
      
      ## SubscriptionRef (Observable State)
      
      `SubscriptionRef` is a `Ref` whose updates can be observed as a `Stream`. `.changes` emits the current value first, then every subsequent update — useful for reactive state, config hot-reload, or fan-out to watchers.
      
      ```typescript
      import { Effect, Stream, SubscriptionRef } from "effect"
      
      const program = Effect.gen(function*() {
        const ref = yield* SubscriptionRef.make(0)
      
        // Subscribe in the background; receives 0, then each update
        yield* Effect.forkScoped(
          Stream.runForEach(SubscriptionRef.changes(ref), (n) =>
            Effect.log(`value is now ${n}`)
          )
        )
      
        yield* SubscriptionRef.set(ref, 1)
        yield* SubscriptionRef.update(ref, (n) => n + 1) // 2
      })
      ```
      
      ## Request Batching & Caching (Request / RequestResolver)
      
      When the same data is fetched repeatedly (the classic N+1 problem), model each fetch as a typed `Request` and resolve a batch of them through a `RequestResolver`. Effect automatically deduplicates identical requests in flight and batches those collected within the same step, then `Effect.request` reads a single result. The resolver receives all pending entries at once — issue one bulk query, then complete each entry.
      
      ```typescript
      import { Console, Effect, Exit, Request, RequestResolver } from "effect"
      
      interface GetUser extends Request.Request<string, Error> {
        readonly _tag: "GetUser"
        readonly id: number
      }
      const GetUser = Request.tagged<GetUser>("GetUser")
      
      // runAll receives the full batch of pending entries; complete each with an Exit
      const UserResolver = RequestResolver.make<GetUser>(
        Effect.fnUntraced(function*(entries) {
          const ids = entries.map((e) => e.request.id)
          const rows = yield* bulkFetchUsers(ids) // ONE query for the whole batch
          for (const entry of entries) {
            yield* Request.complete(entry, Exit.succeed(rows[entry.request.id]))
          }
        })
      )
      
      const program = Effect.gen(function*() {
        // These two run in the same step -> batched into one runAll call
        const [a, b] = yield* Effect.all(
          [Effect.request(GetUser({ id: 1 }), UserResolver),
           Effect.request(GetUser({ id: 2 }), UserResolver)],
          { concurrency: "unbounded" }
        )
        yield* Console.log([a, b])
      })
      ```
      
      `Request.tagged<T>(tag)` builds the constructor; `Request.Class` is the class form. `RequestResolver.make(runAll)` is the basic batched resolver; `RequestResolver.makeGrouped` partitions entries by a computed key, and `RequestResolver.fromEffect` lifts a per-request effect. Group several resolvers behind a service so callers just `yield* Effect.request(...)`.
      
      ## Worker Threads (`effect/unstable/workers`)
      
      There is **no `Worker.makePool` / high-level worker-pool API** in v4. The intended way to offload work to worker threads is **RPC-over-worker**: define an `RpcGroup` (see `references/rpc.md`), run an `RpcServer` inside the worker, and create a pooled client with `RpcClient.layerProtocolWorker({ size })`. Calling a typed RPC method dispatches it onto a worker in the pool.
      
      ```typescript
      import { Layer } from "effect"
      import { RpcClient } from "effect/unstable/rpc"
      import { NodeWorker } from "@effect/platform-node"
      import { Worker as NodeWorkerThread } from "node:worker_threads"
      
      // Client side: a pool of worker threads; RPC calls run on a worker
      const WorkerClientLive = MyRpcClient.layer.pipe(
        Layer.provide(RpcClient.layerProtocolWorker({ size: 4 })),
        Layer.provide(
          NodeWorker.layer(() => new NodeWorkerThread(new URL("./worker.ts", import.meta.url)))
        )
      )
      // Inside ./worker.ts: RpcServer.layerProtocolWorkerRunner + your handler layer
      // + NodeWorkerRunner.layer (see references/rpc.md for the server side).
      ```
      
      The low-level `Worker` / `WorkerRunner` modules exist but are platform primitives, not an ergonomic pool — prefer the RPC transport above.
      
      ## Child Processes (`effect/unstable/process`)
      
      Define commands with `ChildProcess.make` and run them through the `ChildProcessSpawner` service. `ChildProcess.make` supports positional args, a template-literal form, and options (`cwd`, `env`, `extendEnv`); `ChildProcess.pipeTo` composes a shell-style pipeline between two command values. Provide the spawner from your platform (`NodeServices.layer` on Node).
      
      ```typescript
      import { Effect, String } from "effect"
      import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
      
      const gitLog = Effect.gen(function*() {
        const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
      
        // Collect the whole output as a string
        const nodeVersion = yield* spawner.string(
          ChildProcess.make("node", ["--version"])
        ).pipe(Effect.map(String.trim))
      
        // Line-oriented output, with a piped command: git log ... | head -n 5
        const subjects = yield* spawner.lines(
          ChildProcess.make("git", ["log", "--pretty=format:%s", "-n", "20"]).pipe(
            ChildProcess.pipeTo(ChildProcess.make("head", ["-n", "5"]))
          )
        )
      
        return { nodeVersion, subjects }
      })
      ```
      
      Use `spawner.string` / `spawner.lines` to collect completed output; use `spawner.spawn` to get a `ChildProcessHandle` and stream stdout/stderr while the process is still running. On Node, provide `NodeServices.layer` (from `@effect/platform-node`) to satisfy the `ChildProcessSpawner` requirement.
      
    • configuration.md 3.3 KB
      # Configuration (Config / ConfigProvider)
      
      Effect's `Config` module reads configuration (env vars by default) as typed, validated values inside the type system. A `Config<T>` *is* an `Effect<T, ConfigError>`, so you consume it with `yield*` and it resolves against the `ConfigProvider` in the fiber's services. Always import from `"effect"`.
      
      ```typescript
      import { Config, Effect } from "effect"
      ```
      
      > v4 note: in v4 `Config` is schema-backed — every constructor delegates to `Config.schema`, and several constructors (`Config.schema`, `Config.int`, `Config.finite`, `Config.literals`) are `@since 4.0.0`. The consumption model (`yield* Config.string(...)`) is the same across v3 and v4.
      
      ## Reading Values
      
      ```typescript
      const program = Effect.gen(function*() {
        const host = yield* Config.string("HOST")
        const port = yield* Config.port("PORT")            // validates 1-65535
        const debug = yield* Config.boolean("DEBUG")       // "true"/"yes"/"on"/"1" -> true
        const timeout = yield* Config.duration("TIMEOUT")  // "10 seconds" -> Duration
        const apiKey = yield* Config.redacted("API_KEY")   // Redacted<string> (won't print)
        return { host, port, debug, timeout, apiKey }
      })
      ```
      
      Common constructors: `Config.string`, `Config.nonEmptyString`, `Config.number`, `Config.int`, `Config.boolean`, `Config.duration`, `Config.port`, `Config.url`, `Config.date`, `Config.literal` / `Config.literals`, `Config.logLevel`, `Config.redacted`.
      
      ## Defaults, Optional, Nesting
      
      ```typescript
      // Fall back to a default (only on MISSING data, not on validation failure)
      const port = yield* Config.port("PORT").pipe(Config.withDefault(8080))
      
      // Option<T> when absent
      const proxy = yield* Config.option(Config.string("PROXY_URL"))
      
      // Group related config and namespace it (reads DATABASE_HOST / DATABASE_PORT)
      const db = yield* Config.all({
        host: Config.string("HOST"),
        port: Config.number("PORT")
      }).pipe(Config.nested("DATABASE"))
      ```
      
      `Config.all` accepts a struct (object) or a tuple/iterable of configs and combines them. `withDefault` / `option` only recover from *missing* data — a present-but-invalid value (wrong type, out of range) still fails with a `ConfigError`.
      
      ## Schema-Validated Config
      
      `Config.schema` decodes raw config through an Effect `Schema`, giving full validation and transformation:
      
      ```typescript
      import { Config, Schema } from "effect"
      
      const AppConfig = Config.schema(
        Schema.Struct({ host: Schema.String, port: Schema.Int }),
        "APP"
      )
      ```
      
      ## Providing a ConfigProvider
      
      The default provider reads from `process.env`. Override it (tests, embedded config) by providing a `ConfigProvider` via its layer. There is **no `ConfigProvider.fromJson`** — use `ConfigProvider.fromUnknown(obj)` for an in-memory object or `ConfigProvider.fromEnv({ env })` for an explicit env map.
      
      ```typescript
      import { Config, ConfigProvider, Effect, Layer } from "effect"
      
      const TestConfig = ConfigProvider.layer(
        ConfigProvider.fromUnknown({ HOST: "localhost", PORT: "8080", DEBUG: "yes" })
      )
      
      const runnable = program.pipe(Effect.provide(TestConfig))
      ```
      
      Other provider helpers: `ConfigProvider.fromEnv`, `ConfigProvider.fromDotEnv`, `ConfigProvider.fromDir`, plus combinators `orElse`, `nested`, `constantCase`, `mapInput`. The provider is a `Context.Reference` service, so you can also set it inline with `Effect.provideService(ConfigProvider.ConfigProvider, provider)`.
      
    • core-patterns.md 5.8 KB
      # Core Patterns
      
      ## The Effect Type
      
      ```typescript
      Effect<Success, Error, Requirements>
      //      ^        ^       ^
      //      |        |       └── Dependencies needed (provided via Layers)
      //      |        └── Expected errors (typed, must be handled)
      //      └── Success value
      ```
      
      An `Effect` is a lazy description of a program. Nothing runs until you call `run*` at the edge.
      
      ## Creating Effects
      
      ```typescript
      import { Effect } from "effect"
      
      // From a pure value
      const succeed = Effect.succeed(42)
      
      // From a failure
      const fail = Effect.fail(new Error("boom"))
      
      // From synchronous code that might throw
      const sync = Effect.sync(() => JSON.parse(rawJson))
      
      // From a Promise (typed error on rejection)
      const async = Effect.tryPromise({
        try: () => fetch(url),
        catch: (err) => new FetchError({ cause: err })
      })
      
      // From a Promise that never rejects
      const safe = Effect.promise(() => fs.readFile(path))
      ```
      
      ## Composition with Effect.gen
      
      Use `Effect.gen` for imperative-style multi-step logic:
      
      ```typescript
      const program = Effect.gen(function*() {
        const user = yield* fetchUser(id)
        const posts = yield* fetchPosts(user.id)
        return { user, posts }
      })
      ```
      
      **v4: Use `Effect.fn` for named functions** (adds automatic span + better stack traces):
      
      ```typescript
      // v4 only
      const fetchUserPosts = Effect.fn("fetchUserPosts")(
        function*(id: string): Effect.fn.Return<UserPosts, ApiError> {
          const user = yield* fetchUser(id)
          const posts = yield* fetchPosts(user.id)
          return { user, posts }
        },
        Effect.catch((e) => Effect.log(`Failed: ${e}`))
      )
      ```
      
      **`Effect.fn` vs `Effect.fnUntraced`:** `Effect.fn("name")(...)` creates a tracing span and records a stack-frame boundary on every call. When you do not need spans or stack frames (most internal helpers), use `Effect.fnUntraced(function*() { ... })` instead — same generator ergonomics, no span/frame overhead. Reach for the named `Effect.fn` only where the observability is worth it (request handlers, top-level operations). Both accept extra `pipe`-style combinators after the generator body.
      
      ## Composition with Pipes
      
      Use pipes for simple linear transforms:
      
      ```typescript
      const program = fetchUser(id).pipe(
        Effect.map((user) => user.name),
        Effect.tap((name) => Effect.log(`Found: ${name}`)),
        Effect.catchTag("NotFound", () => Effect.succeed("anonymous"))
      )
      ```
      
      ## Running Effects
      
      **Entry points** - use platform-specific `runMain`:
      
      ```typescript
      import { NodeRuntime } from "@effect/platform-node"
      
      const main = Effect.gen(function*() {
        yield* Effect.log("Starting...")
        // your program here
      })
      
      NodeRuntime.runMain(main) // handles SIGINT gracefully
      ```
      
      **One-off execution** (scripts, tests):
      
      ```typescript
      await Effect.runPromise(myEffect)       // throws on failure
      const exit = await Effect.runPromiseExit(myEffect) // returns Exit
      Effect.runSync(myEffect)                // sync only, throws on async
      ```
      
      **Integrating with non-Effect frameworks** (Hono, Express, etc.) - use `ManagedRuntime`:
      
      ```typescript
      import { ManagedRuntime } from "effect"
      
      // Create once at startup
      const runtime = ManagedRuntime.make(MyAppLayer)
      
      // Use in route handlers
      app.get("/users/:id", async (c) => {
        const result = await runtime.runPromise(fetchUser(c.req.param("id")))
        return c.json(result)
      })
      ```
      
      ## Effect.all - Parallel/Sequential Collection
      
      ```typescript
      // Sequential (default)
      const results = yield* Effect.all([effectA, effectB, effectC])
      
      // Parallel (bounded)
      const results = yield* Effect.all([effectA, effectB, effectC], {
        concurrency: 5
      })
      
      // Fully parallel
      const results = yield* Effect.all([effectA, effectB, effectC], {
        concurrency: "unbounded"
      })
      
      // With error accumulation (collect all errors, not just first)
      const results = yield* Effect.all([effectA, effectB], {
        concurrency: "unbounded",
        mode: "validate"
      })
      ```
      
      ## Key Combinators
      
      | Combinator       | Purpose                                                |
      |------------------|--------------------------------------------------------|
      | `Effect.map`     | Transform the success value                            |
      | `Effect.flatMap` | Chain effects (success of first feeds into next)       |
      | `Effect.tap`     | Side-effect on success without changing the value      |
      | `Effect.andThen` | Like flatMap but also accepts plain values/functions   |
      | `Effect.all`     | Combine multiple effects (sequential or parallel)      |
      | `Effect.forEach` | Map over an iterable with an effectful function        |
      | `Effect.if`      | Branch on a boolean condition                          |
      | `Effect.match`   | Pattern match on success/failure                       |
      | `Effect.zip`     | Combine two effects into a tuple                       |
      | `Effect.orDie`   | Convert typed error to defect (unrecoverable)          |
      
      ## Pattern Matching with `Match`
      
      `Match` builds type-safe, exhaustive matchers. Start from `Match.type<T>()` (matcher over a type) or `Match.value(x)` (match a concrete value), pipe in cases, then finish with `Match.exhaustive` (compile error if a case is missed) or `Match.orElse(fallback)`. The combinators are the same in v3 and v4.
      
      ```typescript
      import { Match } from "effect"
      
      type Event =
        | { readonly _tag: "Click"; readonly x: number }
        | { readonly _tag: "Key"; readonly key: string }
      
      // Match on the discriminant tag, exhaustively
      const render = Match.type<Event>().pipe(
        Match.tag("Click", ({ x }) => `click at ${x}`),
        Match.tag("Key", ({ key }) => `key ${key}`),
        Match.exhaustive
      )
      
      render({ _tag: "Click", x: 10 }) // "click at 10"
      
      // Predicate / partial matching with a fallback
      const classify = (n: number) =>
        Match.value(n).pipe(
          Match.when((x) => x < 0, () => "negative"),
          Match.when(0, () => "zero"),
          Match.orElse(() => "positive")
        )
      ```
      
      `Match.tag` narrows on `_tag` (works with `Data.TaggedError` / `Schema.TaggedErrorClass` errors); `Match.when` accepts a literal, a predicate, or a partial-shape pattern.
      
    • datetime.md 2.6 KB
      # DateTime
      
      `DateTime` is Effect's timezone-aware date/time type. It has two variants, both carrying an absolute instant (`epochMillis`):
      
      - `DateTime.Utc` (`_tag: "Utc"`) - an instant with no zone
      - `DateTime.Zoned` (`_tag: "Zoned"`) - an instant plus a `TimeZone`
      
      ```typescript
      import { DateTime, Effect } from "effect"
      ```
      
      > v4 naming: the unsafe constructors use `Unsafe` as a **suffix** (not a v3-style `unsafe*` prefix): `DateTime.makeUnsafe`, `DateTime.nowUnsafe`, `DateTime.makeZonedUnsafe`, `DateTime.zoneMakeNamedUnsafe`, `DateTime.fromDateUnsafe`. The safe `make` / `makeZoned` / `zoneMakeNamed` return `Option`, not `Effect`.
      
      ## Getting "now"
      
      ```typescript
      const program = Effect.gen(function*() {
        const utc = yield* DateTime.now        // Effect<DateTime.Utc> (uses the Clock)
        return utc
      })
      
      // Outside Effect (impure):
      const nowImpure = DateTime.nowUnsafe()
      ```
      
      Prefer `yield* DateTime.now` inside effects — it reads the `Clock` service, so it is controllable in tests via `TestClock`.
      
      ## Constructing
      
      ```typescript
      // Safe: returns Option
      const maybe = DateTime.make("2026-06-04T12:00:00Z") // Option<Utc>
      
      // Unsafe: throws on invalid input
      const utc = DateTime.makeUnsafe("2026-06-04T12:00:00Z")
      const fromDate = DateTime.fromDateUnsafe(new Date())
      ```
      
      ## Arithmetic
      
      ```typescript
      const utc = DateTime.makeUnsafe("2026-06-04T12:00:00Z")
      
      const later = utc.pipe(DateTime.addDuration("5 minutes")) // stays Utc
      const tomorrow = utc.pipe(DateTime.add({ days: 1 }))       // calendar-aware
      const startOfDay = utc.pipe(DateTime.startOf("day"))
      ```
      
      `addDuration` / `subtractDuration` shift by a `Duration`; `add` / `subtract` take calendar parts (`{ days, months, hours, ... }`). These are variant-preserving — a `Utc` stays `Utc`.
      
      ## Time Zones
      
      ```typescript
      const utc = DateTime.makeUnsafe("2026-06-04T12:00:00Z")
      
      // Attach a named zone -> Zoned
      const london = DateTime.setZone(utc, DateTime.zoneMakeNamedUnsafe("Europe/London"))
      
      // Run an effect with an ambient current zone
      const withZone = program.pipe(
        DateTime.withCurrentZone(DateTime.zoneMakeNamedUnsafe("America/New_York"))
      )
      ```
      
      `setZone` always returns a `Zoned`. `withCurrentZone` / `nowInCurrentZone` use a `CurrentTimeZone` service (provide it once via `DateTime.layerCurrentZoneNamed("...")`).
      
      ## Formatting
      
      ```typescript
      DateTime.formatIso(utc)        // "2026-06-04T12:00:00.000Z"
      DateTime.formatIsoDate(utc)    // "2026-06-04"
      DateTime.format(london, { dateStyle: "full", timeStyle: "short" }) // Intl-based, zone-aware
      ```
      
      Other formatters: `formatLocal`, `formatUtc`, `formatIntl`, `formatIsoOffset`, `formatIsoZoned`.
      
    • dependency-injection.md 8.3 KB
      # Dependency Injection
      
      Effect's DI system tracks dependencies through the type system. The `R` parameter in `Effect<A, E, R>` lists required services. The compiler enforces that all dependencies are provided before running.
      
      ## Defining Services
      
      ### v3: Context.Tag
      
      ```typescript
      import { Context, Effect, Layer } from "effect"
      
      class Database extends Context.Tag("Database")<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>() {}
      ```
      
      ### v4: Context.Service
      
      ```typescript
      import { Context, Effect, Layer } from "effect"
      
      class Database extends Context.Service<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>()(
        "myapp/Database" // include package path for uniqueness
      ) {}
      ```
      
      Note the argument order difference: v3's `Context.Tag` takes `id` first and types second; v4's `Context.Service` takes types first and `id` on the returned constructor. The module name is the same (`Context`); only the exported factory differs.
      
      > v4 briefly exported this under a `ServiceMap` module; it was renamed back to `Context` on 2026-04-07 (PR #1961). Older beta docs or code may still say `ServiceMap.Service` / `ServiceMap.Reference` — treat as `Context.Service` / `Context.Reference`.
      
      ## Building Layers
      
      Layers construct services and wire their dependencies:
      
      ```typescript
      // Pure implementation (no dependencies)
      const DatabaseLive = Layer.succeed(Database, {
        query: (sql) => Effect.tryPromise(() => pgClient.query(sql))
      })
      
      // Effectful construction (with dependencies)
      const DatabaseLive = Layer.effect(
        Database,
        Effect.gen(function*() {
          const config = yield* AppConfig
          const pool = yield* createPool(config.dbUrl)
          return {
            query: (sql) => Effect.tryPromise(() => pool.query(sql))
          }
        })
      )
      
      // Scoped (with resource lifecycle)
      // v3
      const DatabaseLive = Layer.scoped(
        Database,
        Effect.gen(function*() {
          const pool = yield* Effect.acquireRelease(
            createPool(),
            (pool) => Effect.promise(() => pool.end())
          )
          return { query: (sql) => Effect.tryPromise(() => pool.query(sql)) }
        })
      )
      
      // v4 — Layer.scoped is removed. Use Layer.effect; it strips Scope from the
      // requirements automatically when the inner effect uses acquireRelease.
      const DatabaseLive = Layer.effect(
        Database,
        Effect.gen(function*() {
          const pool = yield* Effect.acquireRelease(
            createPool(),
            (pool) => Effect.promise(() => pool.end())
          )
          return { query: (sql) => Effect.tryPromise(() => pool.query(sql)) }
        })
      )
      ```
      
      ## v4: Context.Service with make
      
      ```typescript
      import { Context, Effect, Layer } from "effect"
      
      class Database extends Context.Service<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>()(
        "myapp/Database",
        {
          make: Effect.gen(function*() {
            const config = yield* AppConfig
            return {
              query: (sql) => Effect.tryPromise(() => pgClient.query(sql))
            }
          })
        }
      ) {
        // Build layer explicitly from make (v4 does NOT auto-generate layers)
        static readonly layer = Layer.effect(this, this.make).pipe(
          Layer.provide(AppConfig.layer)
        )
      }
      ```
      
      ## Composing Layers
      
      ```typescript
      // Merge independent layers
      const AppLayer = Layer.merge(DatabaseLive, CacheLive)
      
      // Wire dependencies between layers
      const FullLayer = Layer.provide(ServiceLayer, DatabaseLive)
      // ServiceLayer depends on Database, DatabaseLive provides it
      
      // Compose multiple with provideMerge
      const FullApp = DatabaseLive.pipe(
        Layer.provideMerge(CacheLive),
        Layer.provideMerge(LoggerLive)
      )
      ```
      
      ## Providing Dependencies
      
      ```typescript
      // Provide a full layer
      const runnable = program.pipe(Effect.provide(AppLayer))
      
      // Provide a single service inline
      const runnable = program.pipe(
        Effect.provideService(Database, { query: mockQuery })
      )
      ```
      
      ## Accessing Services
      
      ### In generators (preferred)
      
      ```typescript
      const program = Effect.gen(function*() {
        const db = yield* Database
        const results = yield* db.query("SELECT * FROM users")
        return results
      })
      ```
      
      ### v4: Service.use (one-liner access)
      
      ```typescript
      // v4 only
      const program = Database.use((db) => db.query("SELECT * FROM users"))
      ```
      
      Prefer `yield*` in generators over `.use()` because it makes dependencies explicit and avoids accidentally leaking service requirements.
      
      ## Testing with Layer Swaps
      
      ```typescript
      // Production layer
      const DatabaseLive = Layer.effect(Database, /* real implementation */)
      
      // Test layer
      const DatabaseTest = Layer.succeed(Database, {
        query: (sql) => Effect.succeed([{ id: 1, name: "test" }])
      })
      
      // In tests, provide the test layer
      const result = await Effect.runPromise(
        program.pipe(Effect.provide(DatabaseTest))
      )
      ```
      
      ## v4: References (Services with Defaults)
      
      For configuration values and feature flags that have sensible defaults:
      
      ```typescript
      // v3
      class LogLevel extends Context.Reference<LogLevel>()("LogLevel", {
        defaultValue: () => "info" as const
      }) {}
      
      // v4
      const LogLevel = Context.Reference<"info" | "warn" | "error">("LogLevel", {
        defaultValue: () => "info" as const
      })
      ```
      
      References can be `yield*`-ed like services but have a default if not provided.
      
      ## Layer Naming Convention
      
      - v3: `DatabaseLive`, `Database.Default`
      - v4: `Database.layer`, `Database.layerTest`, `Database.layerConfig`
      
      ## v4: Per-Key Dynamic Layers (`LayerMap`)
      
      When you need one instance of a service *per key* — a connection pool per tenant, a client per region — build a `LayerMap.Service`. Its `lookup` builds the layer for a key on first access, caches it, and releases it after `idleTimeToLive`. Downstream code stays key-agnostic (`yield* DatabasePool`); the correct instance is chosen by whichever `MyMap.get(key)` layer is provided.
      
      ```typescript
      import { Context, Effect, Layer, LayerMap } from "effect"
      
      class DatabasePool extends Context.Service<DatabasePool, {
        readonly query: (sql: string) => Effect.Effect<ReadonlyArray<unknown>>
      }>()("app/DatabasePool") {
        // one layer per tenant, cleaned up on scope close
        static readonly layer = (tenantId: string) =>
          Layer.effect(
            DatabasePool,
            Effect.acquireRelease(
              Effect.sync(() => DatabasePool.of({ query: (sql) => Effect.succeed([]) })),
              () => Effect.log(`Closing pool for ${tenantId}`)
            )
          )
      }
      
      class PoolMap extends LayerMap.Service<PoolMap>()("app/PoolMap", {
        lookup: (tenantId: string) => DatabasePool.layer(tenantId),
        idleTimeToLive: "1 minute"
      }) {}
      
      const queryUsers = Effect.gen(function*() {
        const pool = yield* DatabasePool // tenant-agnostic
        return yield* pool.query("SELECT id FROM users")
      })
      
      const program = queryUsers.pipe(
        Effect.provide(PoolMap.get("acme")), // builds/caches the "acme" pool
        Effect.provide(PoolMap.layer)
      )
      // PoolMap.invalidate("acme") forces a rebuild on next access.
      ```
      
      ## Bridging Effect into Non-Effect Frameworks (`ManagedRuntime`)
      
      To call Effect from an imperative framework (Hono, Express, a webhook handler), build **one** `ManagedRuntime` from your application layer at startup and reuse it — never construct a runtime per request, and never chain two `runPromise` calls where one Effect would do.
      
      Carry per-request state through a fiber-local `Context.Reference` set with `Effect.provideService` inside the run, **not** as an extra "bag" parameter threaded through every function. This keeps the request context off the `R` channel (it stays `never` at the edge) and prevents cross-request leakage. Pass a plain object into the reference — never the framework's own request/context object.
      
      ```typescript
      import { Context, Effect, Layer, ManagedRuntime } from "effect"
      
      interface RequestInfo { readonly requestId: string; readonly userId: string }
      const RequestInfo = Context.Reference<RequestInfo>("app/RequestInfo", {
        defaultValue: () => ({ requestId: "", userId: "" })
      })
      
      const runtime = ManagedRuntime.make(AppLayer) // once, at startup
      
      // In a Hono handler:
      app.post("/charge", async (c) => {
        const info: RequestInfo = { requestId: c.req.header("x-request-id") ?? "", userId: c.get("userId") }
        const result = await runtime.runPromise(
          chargeUser.pipe(Effect.provideService(RequestInfo, info))
        )
        return c.json(result)
      })
      // On shutdown: await runtime.dispose()
      ```
      
      Anti-patterns to avoid: two sequential `runPromise` calls in one handler, a `ChargeOpts`-style parameter bag instead of a `Context.Reference`, telemetry/logging buried inside a `tryPromise` thunk, and re-running `setup()` without disposing the previous runtime (leaks the old runtime and any detached fibers).
      
    • distributed.md 4.7 KB
      # Distributed & Durable Execution
      
      Effect's distributed-systems modules — Cluster (sharded stateful entities), Workflow (durable, resumable execution), and EventLog (event sourcing) — all live under `effect/unstable/*` and are explicitly **unstable** (APIs may shift between minor releases). Reach for them only when the architecture calls for it; most apps do not.
      
      All three follow the same convention: the **tag/name is the first argument** (`Entity.make(tag, ...)`, `Workflow.make(tag, ...)`, `Event.make({ tag, ... })`).
      
      ## Cluster: Sharded Entities
      
      An `Entity` is a stateful actor addressed by `entityId`; `Sharding` routes messages to the shard owning that id. Messages are typed `Rpc` definitions (see `references/rpc.md`).
      
      ```typescript
      import { Effect, Schema } from "effect"
      import { Entity, ShardingConfig } from "effect/unstable/cluster"
      import { Rpc } from "effect/unstable/rpc"
      
      class User extends Schema.Class<User>("User")({
        id: Schema.Number,
        name: Schema.String
      }) {}
      
      // Tag first, then an array of Rpc message definitions
      const UserEntity = Entity.make("UserEntity", [
        Rpc.make("GetUser", { payload: { id: Schema.Number }, success: User })
      ])
      
      const UserEntityLayer = UserEntity.toLayer({
        GetUser: (envelope) =>
          Effect.succeed(new User({ id: envelope.payload.id, name: `User ${envelope.payload.id}` }))
      })
      
      // In tests, address an entity with an in-memory client:
      const program = Effect.gen(function*() {
        const makeClient = yield* Entity.makeTestClient(UserEntity, UserEntityLayer)
        const client = yield* makeClient("user-1") // addressed by entityId
        return yield* client.GetUser({ id: 1 })     // message by rpc tag
      }).pipe(Effect.provide(ShardingConfig.layer({})))
      ```
      
      In production, provide a real `Sharding` layer + a runner backend (SQL/Http/Socket) instead of `makeTestClient`, and obtain the client via `UserEntity.client`.
      
      ## Workflow: Durable Execution
      
      A `Workflow` is execution that survives process restarts — steps (`Activity`) are checkpointed so a resumed workflow replays completed steps instead of re-running them.
      
      ```typescript
      import { Effect, Layer, Schema } from "effect"
      import { Workflow, WorkflowEngine } from "effect/unstable/workflow"
      
      const IncrementWorkflow = Workflow.make("IncrementWorkflow", {
        payload: { value: Schema.Number },
        success: Schema.Number,
        idempotencyKey: ({ value }) => String(value)
      })
      
      const IncrementLayer = IncrementWorkflow.toLayer(
        ({ value }) => Effect.succeed(value + 1)
      )
      
      const program = Effect.gen(function*() {
        return yield* IncrementWorkflow.execute({ value: 1 }) // 2
      }).pipe(
        Effect.provide(
          IncrementLayer.pipe(Layer.provideMerge(WorkflowEngine.layerMemory))
        )
      )
      ```
      
      `Workflow.make(tag, { payload, success?, error?, idempotencyKey?, ... })` returns a workflow with `.execute`, `.poll`, `.interrupt`, `.resume`, `.toLayer`, `.withCompensation`. It also supports class form: `class MyWorkflow extends Workflow.make(...) {}` (the tag-first signature landed in beta.75). Inside a workflow, wrap side-effecting steps in `Activity.make({ name, execute, ... })` and use `Activity.retry({ times })`, `DurableClock.sleep`, and `DurableDeferred` for durable waits. `DurableQueue` provides a persistent work queue. Use `WorkflowEngine.layerMemory` for dev/tests; a cluster-backed engine for production.
      
      ## EventLog: Event Sourcing
      
      `EventLog` is a full event-sourcing stack: define event groups, write events, and run handlers against a journal (in-memory, IndexedDB, or SQL), with optional encryption and remote replication.
      
      ```typescript
      import { Effect, Layer, Schema } from "effect"
      import * as EventGroup from "effect/unstable/eventlog/EventGroup"
      import * as EventJournal from "effect/unstable/eventlog/EventJournal"
      import * as EventLog from "effect/unstable/eventlog/EventLog"
      
      const UserEvents = EventGroup.empty.add({
        tag: "UserCreated",
        primaryKey: (payload) => payload.id,
        payload: Schema.Struct({ id: Schema.String })
      })
      
      const schema = EventLog.schema(UserEvents)
      
      const HandlersLive = EventLog.group(UserEvents, (handlers) =>
        handlers.handle("UserCreated", ({ payload }) =>
          Effect.log(`created ${payload.id}`)
        )
      ).pipe(Layer.provide(EventLog.layerRegistry))
      
      const program = Effect.gen(function*() {
        const log = yield* EventLog.EventLog
        yield* log.write({ schema, event: "UserCreated", payload: { id: "user-1" } })
      }).pipe(
        Effect.provide(
          EventLog.layer(schema, HandlersLive).pipe(Layer.provide(EventJournal.layerMemory))
        )
      )
      ```
      
      Build events with `EventGroup.empty.add({ tag, primaryKey, payload })` (or standalone `Event.make({ tag, ... })`), register handlers with `EventLog.group`, and assemble with `EventLog.layer(schema, handlers)` over a journal backend (`EventJournal.layerMemory` / `layerIndexedDb` / SQL).
      
    • effect-ai.md 7.4 KB
      # Effect AI
      
      The `@effect/ai` packages provide a provider-agnostic interface for language models. Write AI logic once, swap providers at runtime.
      
      **Status:** Unstable / Alpha (marked "Unstable" in official docs). APIs may change between releases.
      
      ## Packages
      
      ### v3
      
      | Package                     | Purpose                                          |
      |-----------------------------|--------------------------------------------------|
      | `@effect/ai`                | Core abstractions (LanguageModel, Tool, Toolkit, Chat, McpServer) |
      | `@effect/ai-openai`         | OpenAI provider                                  |
      | `@effect/ai-anthropic`      | Anthropic provider                               |
      | `@effect/ai-amazon-bedrock` | Amazon Bedrock provider (v3 only)                |
      | `@effect/ai-google`         | Google Gemini provider (v3 only)                 |
      | `@effect/ai-openrouter`     | OpenRouter provider                              |
      
      ### v4
      
      The core AI module is consolidated into `effect/unstable/ai` (no separate `@effect/ai` package). Provider packages still ship separately and currently include only `@effect/ai-anthropic`, `@effect/ai-openai`, `@effect/ai-openai-compat`, and `@effect/ai-openrouter` — Bedrock and Google providers are not yet ported to v4.
      
      ```typescript
      // v4 imports
      import { Chat, LanguageModel, McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai"
      ```
      
      ## Basic Text Generation
      
      ```typescript
      // v3
      import { LanguageModel } from "@effect/ai"
      import { OpenAiLanguageModel } from "@effect/ai-openai"
      
      // v4 — equivalent imports:
      //   import { LanguageModel } from "effect/unstable/ai"
      //   import { OpenAiLanguageModel } from "@effect/ai-openai"
      
      const program = Effect.gen(function*() {
        const response = yield* LanguageModel.generateText({
          prompt: "Explain Effect-TS in one sentence"
        })
        return response.text
      })
      
      // Provide the OpenAI layer
      const main = program.pipe(
        Effect.provide(OpenAiLanguageModel.layer({ model: "gpt-4o" })),
        Effect.provide(OpenAiClient.layer({ apiKey: env.OPENAI_API_KEY }))
      )
      ```
      
      ## Structured Output (Schema-Validated)
      
      ```typescript
      const SentimentResult = Schema.Struct({
        sentiment: Schema.Literal("positive", "negative", "neutral"),
        confidence: Schema.Number
      })
      
      const analyze = LanguageModel.generateObject({
        prompt: `Analyze sentiment: "${text}"`,
        schema: SentimentResult
      })
      // Returns Effect<{ sentiment, confidence }, AiError, LanguageModel>
      // Output is Schema-validated at runtime
      ```
      
      ## Streaming
      
      ```typescript
      const stream = LanguageModel.streamText({
        prompt: "Write a story about..."
      })
      // Returns Stream<TextChunk, AiError, LanguageModel>
      
      // Per-chunk processing (for token billing, SSE forwarding, etc.)
      const processed = stream.pipe(
        Stream.tap((chunk) => incrementTokenCount(chunk)),
        Stream.map((chunk) => chunk.text)
      )
      ```
      
      ## Tool Use
      
      `Tool.make` defines only the *schema* of a tool — name, description, parameters, success/failure types. The runtime *handler* is attached separately via `Toolkit.toLayer`, which produces a Layer that the LanguageModel call requires.
      
      ```typescript
      import { Effect, Schema } from "effect"
      import { Tool, Toolkit } from "effect/unstable/ai" // v4 (or "@effect/ai" for v3)
      
      // Define a tool — note: NO `handler` field on Tool.make
      const GetWeather = Tool.make("GetWeather", {
        description: "Get current weather for a location",
        parameters: Schema.Struct({ location: Schema.String }),
        success: Schema.Struct({
          temperature: Schema.Number,
          condition: Schema.String
        })
      })
      
      // Group tools into a toolkit
      const MyToolkit = Toolkit.make(GetWeather)
      
      // Attach handlers via toLayer (handler keys match the tool names)
      const MyToolkitLayer = MyToolkit.toLayer({
        GetWeather: ({ location }) => fetchWeather(location)
      })
      
      // Use with LanguageModel — provide the toolkit layer
      const program = LanguageModel.generateText({
        prompt: "What's the weather in San Francisco?",
        toolkit: MyToolkit
      }).pipe(Effect.provide(MyToolkitLayer))
      ```
      
      ## Chat (Stateful Conversations)
      
      The Chat module returns a Service whose instance carries `generateText`, `streamText`, and `generateObject` methods. It threads conversation history through a Ref automatically — there is no static `Chat.send`.
      
      ```typescript
      import { Chat } from "effect/unstable/ai" // v4 (or "@effect/ai" for v3)
      
      const program = Effect.gen(function*() {
        const chat = yield* Chat.empty // also: Chat.fromPrompt(initial), Chat.makePersisted(...)
      
        const r1 = yield* chat.generateText({ prompt: "Hello, who are you?" })
        const r2 = yield* chat.generateText({ prompt: "What did I just say?" })
        // r2 sees the prior turn — history is appended in the chat's internal state
        return [r1.text, r2.text]
      })
      ```
      
      ## MCP Server (v4)
      
      Effect v4's AI modules include built-in MCP server support:
      
      ```typescript
      // v4 only
      import { McpServer, McpSchema } from "effect/unstable/ai"
      
      // Define MCP tools using Effect's Schema and service patterns
      ```
      
      ## Provider Pattern
      
      The key benefit: write AI logic against the abstract `LanguageModel` interface, then swap providers via layers:
      
      ```typescript
      // Business logic - no provider dependency
      const summarize = (text: string) =>
        LanguageModel.generateText({
          prompt: `Summarize: ${text}`
        })
      
      // Production: OpenAI
      const prod = summarize(text).pipe(
        Effect.provide(OpenAiLanguageModel.layer({ model: "gpt-4o" }))
      )
      
      // Development: local model via OpenRouter
      const dev = summarize(text).pipe(
        Effect.provide(OpenRouterLanguageModel.layer({ model: "llama-3" }))
      )
      
      // Testing: mock
      const test = summarize(text).pipe(
        Effect.provideService(LanguageModel, {
          generateText: () => Effect.succeed({ text: "mock summary" })
        })
      )
      ```
      
      ## Provider Fallback (ExecutionPlan)
      
      `ExecutionPlan` declares an ordered sequence of attempts, each providing its own `Layer` (e.g. a different model/provider) with optional `attempts` and retry `schedule`. `Effect.withExecutionPlan` runs the effect against each step until one succeeds or the plan is exhausted — ideal for primary -> backup model fallback.
      
      ```typescript
      import { Effect, ExecutionPlan, Schedule } from "effect"
      import type { Layer } from "effect"
      import type { LanguageModel } from "effect/unstable/ai"
      
      declare const primary: Layer.Layer<LanguageModel.LanguageModel>
      declare const backup: Layer.Layer<LanguageModel.LanguageModel>
      
      const ThePlan = ExecutionPlan.make(
        // try the primary model twice, 3s apart
        { provide: primary, attempts: 2, schedule: Schedule.spaced("3 seconds") },
        // then fall back to the backup model (one attempt when attempts/schedule omitted)
        { provide: backup }
      )
      
      declare const summarize: Effect.Effect<string, never, LanguageModel.LanguageModel>
      const resilient = Effect.withExecutionPlan(summarize, ThePlan)
      ```
      
      Each step's `provide` accepts a `Layer` or `Context`; `Stream.withExecutionPlan` is the streaming equivalent. Not AI-specific — use it anywhere you need ordered, layer-swapping fallback (primary -> replica DB, region failover).
      
      ## Observability
      
      Effect AI integrates with Effect's built-in tracing. Each model call produces spans with:
      - Model name and provider
      - Input/output token counts
      - Duration
      - Error details
      
      Use `Effect.withSpan` to create parent spans for multi-step AI workflows:
      
      ```typescript
      const aiWorkflow = Effect.gen(function*() {
        const summary = yield* LanguageModel.generateText({ prompt: text })
        const analysis = yield* LanguageModel.generateObject({
          prompt: summary.text,
          schema: AnalysisSchema
        })
        return analysis
      }).pipe(Effect.withSpan("ai.analyze-document"))
      ```
      
    • error-modeling.md 5 KB
      # Error Modeling
      
      Effect has a two-tier error model. Understanding the distinction is critical for writing correct Effect code.
      
      ## Expected Errors vs Defects
      
      **Expected errors** (typed in `E` channel): recoverable failures your program anticipates. These MUST be handled before running.
      
      **Defects** (untyped): unexpected bugs, programmer mistakes, invariant violations. These crash the fiber and are NOT in the type signature.
      
      ```typescript
      // Expected error - appears in the type
      //                            v---- typed error
      const fetchUser: Effect<User, NotFoundError | NetworkError>
      
      // Defect - NOT in the type (thrown at runtime)
      const bad = Effect.sync(() => { throw new Error("bug") })
      //    ^--- Effect<never, never> - defect is invisible in types
      ```
      
      ## Defining Errors
      
      **v3: `Data.TaggedError`**
      
      ```typescript
      import { Data } from "effect"
      
      class NotFoundError extends Data.TaggedError("NotFoundError")<{
        readonly id: string
      }> {}
      
      class NetworkError extends Data.TaggedError("NetworkError")<{
        readonly cause: unknown
      }> {}
      ```
      
      **v4: `Schema.TaggedErrorClass`**
      
      ```typescript
      import { Schema } from "effect"
      
      class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("NotFoundError", {
        id: Schema.String
      }) {}
      
      class NetworkError extends Schema.TaggedErrorClass<NetworkError>()("NetworkError", {
        cause: Schema.Defect() // v4 beta.76+: Schema.Defect is a constructor function, call it
      }) {}
      ```
      
      ## Handling Errors
      
      ### catchTag - handle a specific error by its _tag
      
      ```typescript
      // v3
      const handled = program.pipe(
        Effect.catchTag("NotFoundError", (e) => Effect.succeed(defaultUser))
      )
      
      // v4 - also accepts arrays
      const handled = program.pipe(
        Effect.catchTag(["NotFoundError", "NetworkError"], (e) => Effect.succeed(fallback))
      )
      ```
      
      ### catchAll (v3) / catch (v4) - handle all errors
      
      ```typescript
      // v3
      const handled = program.pipe(
        Effect.catchAll((error) => Effect.succeed(fallback))
      )
      
      // v4
      const handled = program.pipe(
        Effect.catch((error) => Effect.succeed(fallback))
      )
      ```
      
      ### catchTags - handle multiple errors with different handlers
      
      ```typescript
      const handled = program.pipe(
        Effect.catchTags({
          NotFoundError: (e) => Effect.succeed(defaultUser),
          NetworkError: (e) => Effect.retry(program, Schedule.exponential("1 second"))
        })
      )
      ```
      
      ### orDie - convert expected error to defect
      
      Use when an error is logically impossible or unrecoverable at this point:
      
      ```typescript
      const critical = program.pipe(Effect.orDie)
      // Error channel becomes `never` - any failure is a defect
      ```
      
      ### mapError - transform errors
      
      ```typescript
      const mapped = program.pipe(
        Effect.mapError((e) => new AppError({ cause: e }))
      )
      ```
      
      ## Defect Handling (Advanced)
      
      > **`Effect.catch` / `Effect.catchTag` only handle typed `E`-channel failures.** Defects (thrown bugs, `Effect.die`) and interruptions pass straight through them — a common wrong assumption is that `catch` swallows everything. To handle defects use `Effect.catchDefect`; to handle the full `Cause` (failures + defects + interrupts) use `Effect.catchCause` (see below). Also beware `Effect.catch(() => Effect.void)`: it silently discards the typed error, which turns a real failure into an invisible no-op and is a frequent source of "it just does nothing" debugging dead-ends — log or re-raise instead of returning `void`.
      
      Defects are for debugging, not normal recovery. Use sparingly:
      
      ```typescript
      // v3
      const withDefectHandling = program.pipe(
        Effect.catchAllDefect((defect) => Effect.log(`Bug: ${defect}`))
      )
      
      // v4
      const withDefectHandling = program.pipe(
        Effect.catchDefect((defect) => Effect.log(`Bug: ${defect}`))
      )
      ```
      
      ### Sandbox - expose defects as Cause for inspection
      
      ```typescript
      import { Cause, Effect } from "effect"
      
      // v3 — uses Effect.catchAllCause
      const diagnosed = program.pipe(
        Effect.sandbox,
        Effect.catchAllCause((cause) => {
          console.log(Cause.pretty(cause))
          return Effect.succeed(fallback)
        })
      )
      
      // v4 — renamed to Effect.catchCause
      const diagnosedV4 = program.pipe(
        Effect.sandbox,
        Effect.catchCause((cause) => {
          console.log(Cause.pretty(cause))
          return Effect.succeed(fallback)
        })
      )
      ```
      
      ## v4: Reason-Based Errors
      
      v4 adds `catchReason` for errors with a tagged `reason` field:
      
      ```typescript
      // v4 only
      const handled = program.pipe(
        Effect.catchReason("AiError", "RateLimitError", (reason) =>
          Effect.retry(program, Schedule.exponential("1 second"))
        )
      )
      ```
      
      ## Pattern: Error Hierarchy Design
      
      For services with multiple error types, use a discriminated union:
      
      ```typescript
      // v3
      class ApiError extends Data.TaggedError("ApiError")<{
        readonly kind: "network" | "auth" | "not_found"
        readonly message: string
      }> {}
      
      // Better: separate tags for type-safe catchTag
      class NetworkError extends Data.TaggedError("NetworkError")<{ cause: unknown }> {}
      class AuthError extends Data.TaggedError("AuthError")<{ message: string }> {}
      class NotFoundError extends Data.TaggedError("NotFoundError")<{ id: string }> {}
      
      type ApiError = NetworkError | AuthError | NotFoundError
      ```
      
    • graph.md 5 KB
      # Graph (`effect/Graph`)
      
      `Graph` is Effect's built-in graph data structure - indexed **nodes** and **edges** that both carry your own data, in **directed** or **undirected** form. Reach for it to model dependency graphs, build/task ordering, routing and shortest paths, cycle detection, or any network of relationships without hand-rolling BFS/DFS/Dijkstra.
      
      Import from `"effect"`:
      
      ```typescript
      import { Graph } from "effect"
      ```
      
      Nodes and edges are identified by `Graph.NodeIndex` / `Graph.EdgeIndex`, which are **plain numbers** - stable identifiers, not array offsets. A removed index is not reused.
      
      ## Building a Graph
      
      Graphs are immutable. Construct one by mutating a scoped-mutable draft inside the builder callback; `addNode` returns the new node's index, which you pass to `addEdge`.
      
      ```typescript
      import { Graph } from "effect"
      
      // Directed graph of string nodes and string edge labels
      const graph = Graph.directed<string, string>((mutable) => {
        const a = Graph.addNode(mutable, "A")
        const b = Graph.addNode(mutable, "B")
        const c = Graph.addNode(mutable, "C")
        Graph.addEdge(mutable, a, b, "A->B")
        Graph.addEdge(mutable, b, c, "B->C")
      })
      
      // Undirected variant: Graph.undirected<N, E>((mutable) => { ... })
      ```
      
      Update an existing immutable graph with `Graph.mutate` (returns a new graph; the original is untouched):
      
      ```typescript
      const withExtra = Graph.mutate(graph, (mutable) => {
        const d = Graph.addNode(mutable, "D")
        Graph.addEdge(mutable, 2, d, "C->D") // 2 = index of node "C"
      })
      ```
      
      Other mutations (all inside a builder/`mutate` callback): `updateNode`, `updateEdge`, `removeNode`, `removeEdge`, `mapNodes`, `mapEdges`, `filterNodes`, `filterEdges`, `filterMapNodes`, `filterMapEdges`, `reverse`.
      
      ## Reading a Graph
      
      ```typescript
      import { Graph } from "effect"
      
      Graph.nodeCount(graph)          // number of nodes
      Graph.edgeCount(graph)          // number of edges
      Graph.getNode(graph, 0)         // Option<N> for the node at that index
      Graph.hasNode(graph, 0)         // boolean
      Graph.getEdge(graph, 0)         // Option<Edge<E>>
      Graph.findNode(graph, (n) => n === "B")   // Option<NodeIndex>
      Graph.findEdges(graph, (e) => e.startsWith("A"))
      ```
      
      `Graph.nodes(graph)` and `Graph.edges(graph)` return **walkers** you iterate with the walker helpers below. Neighbor queries: `Graph.neighbors`, `Graph.successors` (outgoing), `Graph.predecessors` (incoming). `Graph.neighborsDirected` is deprecated (beta.80) - use `successors`/`predecessors`.
      
      ## Traversal
      
      `dfs`, `bfs`, `topo` (topological sort, Kahn's algorithm), and `dfsPostOrder` return a lazy **walker**. Turn it into values with `Graph.indices` (node indices), `Graph.values` (node data), or `Graph.entries` (`[index, data]` pairs).
      
      ```typescript
      import { Graph } from "effect"
      
      // Depth-first from a start node
      for (const idx of Graph.indices(Graph.dfs(graph, { start: [0] }))) {
        console.log(idx)
      }
      
      // Breadth-first; omit start to walk all nodes
      for (const data of Graph.values(Graph.bfs(graph))) {
        console.log(data)
      }
      
      // Topological order (only valid on a DAG)
      const order = Array.from(Graph.indices(Graph.topo(graph)))
      ```
      
      `dfs`/`bfs` accept `{ start, direction }` where `direction` is `"outgoing"` (default) or `"incoming"`; `topo` accepts `{ initials }`.
      
      ## Analysis
      
      ```typescript
      import { Graph } from "effect"
      
      Graph.isAcyclic(graph)                    // boolean - true if no cycles (a DAG)
      Graph.isBipartite(graph)                  // boolean
      Graph.connectedComponents(graph)          // Array<Array<NodeIndex>>
      Graph.stronglyConnectedComponents(graph)  // Array<Array<NodeIndex>> (directed)
      ```
      
      ## Shortest Paths
      
      `dijkstra` finds the cheapest path between two nodes; `cost` maps each edge's data to a **non-negative** weight. It returns `Option<PathResult<E>>` - `Option.none()` when the target is unreachable.
      
      ```typescript
      import { Graph } from "effect"
      
      const weighted = Graph.directed<string, number>((mutable) => {
        const a = Graph.addNode(mutable, "A")
        const b = Graph.addNode(mutable, "B")
        const c = Graph.addNode(mutable, "C")
        Graph.addEdge(mutable, a, b, 3)
        Graph.addEdge(mutable, b, c, 4)
      })
      
      const result = Graph.dijkstra(weighted, {
        source: 0,
        target: 2,
        cost: (edgeData) => edgeData
      })
      
      if (result._tag === "Some") {
        console.log(result.value.path)     // [0, 1, 2]
        console.log(result.value.distance) // 7
        console.log(result.value.costs)    // [3, 4] - edge data along the path
      }
      ```
      
      Related solvers: `Graph.astar` (heuristic-guided), `Graph.bellmanFord` (allows negative edge weights), `Graph.floydWarshall` (all-pairs shortest paths).
      
      ## Visualization
      
      `Graph.toGraphViz(graph, options)` and `Graph.toMermaid(graph, options)` render the graph as DOT or Mermaid diagram source - useful for debugging or docs.
      
      ## When to Use
      
      Prefer `Graph` when relationships between entities are the core of the problem (ordering, reachability, routing, cycle detection). For a plain adjacency you only ever look up once, a `Map`/`Record` is simpler. `Graph` is a data structure, not an `Effect` - its operations are pure and synchronous; wrap them in `Effect.sync` only if you need them inside an effectful pipeline.
      
    • http.md 9.4 KB
      # HTTP Client and Server
      
      Import paths differ between versions:
      
      - **v3:** `@effect/platform` for client + API, `@effect/platform-node` for Node transports.
      - **v4:** `effect/unstable/http` for the client and transport helpers, `effect/unstable/httpapi` for the schema-first API layer. `@effect/platform-node` still provides Node-specific `NodeHttpServer` / `NodeHttpClient`.
      
      ## HTTP Client (v3 — @effect/platform)
      
      ### Making Requests
      
      ```typescript
      import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform"
      
      const program = Effect.gen(function*() {
        const client = yield* HttpClient.HttpClient
      
        // GET request
        const response = yield* client.execute(
          HttpClientRequest.get("https://api.example.com/users")
        )
      
        // POST with JSON body
        const response = yield* client.execute(
          HttpClientRequest.post("https://api.example.com/users").pipe(
            HttpClientRequest.jsonBody({ name: "Alice", age: 30 })
          )
        )
      
        // With headers
        const response = yield* client.execute(
          HttpClientRequest.get("https://api.example.com/data").pipe(
            HttpClientRequest.setHeader("Authorization", `Bearer ${token}`)
          )
        )
      })
      ```
      
      ### Response Handling
      
      ```typescript
      // Filter by status (fail on non-2xx)
      const okResponse = yield* client.execute(request).pipe(
        HttpClientResponse.filterStatusOk
      )
      
      // Parse JSON body with Schema validation
      const users = yield* client.execute(request).pipe(
        HttpClientResponse.filterStatusOk,
        HttpClientResponse.schemaBodyJson(Schema.Array(User))
      )
      
      // Get raw text
      const text = yield* response.text
      
      // Get raw JSON
      const json = yield* response.json
      ```
      
      ### Retry with HttpClient
      
      ```typescript
      // v3 — Schedule.compose still works
      const resilientClient = client.pipe(
        HttpClient.retryTransient({
          schedule: Schedule.exponential("200 millis").pipe(
            Schedule.compose(Schedule.recurs(3))
          )
        })
      )
      ```
      
      > v4 note: `Schedule.compose` is removed. Use `Schedule.take(n)` instead — see `retry-scheduling.md`.
      
      ### Platform Layer
      
      Provide the HTTP client layer for your runtime:
      
      ```typescript
      import { NodeHttpClient } from "@effect/platform-node"
      
      const main = program.pipe(
        Effect.provide(NodeHttpClient.layer)
      )
      ```
      
      ## HTTP Server (v3 — @effect/platform)
      
      ### Schema-First API Definition
      
      ```typescript
      import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "@effect/platform"
      
      // Define endpoints (chained setters - v3 style)
      const getUser = HttpApiEndpoint.get("getUser", "/users/:id").pipe(
        HttpApiEndpoint.setPath(Schema.Struct({ id: Schema.String })),
        HttpApiEndpoint.setSuccess(User)
      )
      
      const createUser = HttpApiEndpoint.post("createUser", "/users").pipe(
        HttpApiEndpoint.setPayload(CreateUserBody),
        HttpApiEndpoint.setSuccess(User)
      )
      
      // Group endpoints
      const UsersApi = HttpApiGroup.make("users").pipe(
        HttpApiGroup.add(getUser),
        HttpApiGroup.add(createUser)
      )
      
      // Build the API
      const MyApi = HttpApi.make("my-api").pipe(
        HttpApi.addGroup(UsersApi)
      )
      ```
      
      ### Implement Handlers (v3)
      
      ```typescript
      import { HttpApiBuilder } from "@effect/platform"
      
      const UsersLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
        handlers.pipe(
          HttpApiBuilder.handle("getUser", ({ path }) =>
            Effect.gen(function*() {
              const db = yield* Database
              return yield* db.findUser(path.id)
            })
          ),
          HttpApiBuilder.handle("createUser", ({ payload }) =>
            Effect.gen(function*() {
              const db = yield* Database
              return yield* db.createUser(payload)
            })
          )
        )
      )
      ```
      
      ### Serve (v3)
      
      ```typescript
      import { HttpApiBuilder, HttpMiddleware } from "@effect/platform"
      import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
      import { createServer } from "node:http"
      
      const ServerLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
        Layer.provide(HttpApiBuilder.api(MyApi)),
        Layer.provide(UsersLive),
        Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
      )
      
      NodeRuntime.runMain(Layer.launch(ServerLive))
      ```
      
      ### OpenAPI / Swagger (v3)
      
      ```typescript
      import { HttpApiSwagger } from "@effect/platform"
      
      const ServerLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
        Layer.provide(HttpApiSwagger.layer()), // adds /docs
        Layer.provide(HttpApiBuilder.api(MyApi)),
        // ...
      )
      ```
      
      ## HTTP API Server (v4 — effect/unstable/httpapi)
      
      v4 replaces the chained endpoint setters with an **object-option** form and moves all the HttpApi modules to `effect/unstable/httpapi`. Transport helpers live in `effect/unstable/http`. `HttpApiScalar` replaces `HttpApiSwagger` as the canonical docs UI (Swagger still exists).
      
      ### Define endpoints with object options
      
      ```typescript
      import { Schema } from "effect"
      import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
      
      const User = Schema.Struct({
        id: Schema.Number,
        name: Schema.String,
        email: Schema.String
      })
      
      class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()("UserNotFound", {
        id: Schema.Number
      }) {}
      
      // Endpoints take (name, path, options) — no chained setters
      const Users = HttpApiGroup.make("users")
        .add(
          HttpApiEndpoint.get("list", "/", {
            query: { search: Schema.optional(Schema.String) },
            success: Schema.Array(User)
          })
        )
        .add(
          HttpApiEndpoint.get("getById", "/:id", {
            params: { id: Schema.NumberFromString },
            success: User,
            error: UserNotFound
          })
        )
        .add(
          HttpApiEndpoint.post("create", "/", {
            payload: Schema.Struct({ name: Schema.String, email: Schema.String }),
            success: User
          })
        )
      
      export const Api = HttpApi.make("api").add(Users)
      ```
      
      ### Implement handlers with `Effect.fn`
      
      ```typescript
      import { Effect, Layer } from "effect"
      import { HttpApiBuilder } from "effect/unstable/httpapi"
      
      const UsersApiHandlers = HttpApiBuilder.group(
        Api,
        "users",
        Effect.fn(function*(handlers) {
          const db = yield* Database
          return handlers
            .handle("list", ({ urlParams }) => db.listUsers(urlParams.search))
            .handle("getById", ({ path }) => db.findUser(path.id))
            .handle("create", ({ payload }) => db.createUser(payload))
        })
      )
      ```
      
      ### Serve + docs UI
      
      ```typescript
      import { FetchHttpClient } from "effect/unstable/http"
      import { HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi"
      import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
      import { createServer } from "node:http"
      
      const DocsRoute = HttpApiScalar.layer(Api, { path: "/docs" })
      
      const ApiRoutes = HttpApiBuilder.layer(Api, {
        openapiPath: "/openapi.json"
      }).pipe(Layer.provide([UsersApiHandlers]))
      
      const ServerLive = Layer.mergeAll(ApiRoutes, DocsRoute).pipe(
        Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
      )
      
      NodeRuntime.runMain(Layer.launch(ServerLive))
      ```
      
      ### HttpApi schema errors default to defects (v4, since PR #2057)
      
      Endpoint parse/schema failures no longer appear in the typed error channel by default — they surface as defects. If you want typed error responses (e.g. `400 Bad Request` with a structured body), transform the failure through `HttpApiSchema` helpers or add an explicit `error` schema on the endpoint:
      
      ```typescript
      import { HttpApiSchema } from "effect/unstable/httpapi"
      
      class BadInput extends Schema.TaggedErrorClass<BadInput>()("BadInput", {
        message: Schema.String
      }) {}
      
      HttpApiEndpoint.post("create", "/", {
        payload: CreateUserBody.pipe(HttpApiSchema.withBadRequest(BadInput)),
        success: User,
        error: BadInput
      })
      ```
      
      Without this transform, bad payloads cause a defect (500-style) instead of a typed error. This is a deliberate 2026-04-20 change (commit `8e04bfc9`).
      
      ### File uploads (Multipart, v4)
      
      For `multipart/form-data` (file uploads), brand the endpoint payload with `HttpApiSchema.asMultipart()` and type file fields with the schemas from `effect/unstable/http/Multipart` (`SingleFileSchema`, `FilesSchema`, `PersistedFileSchema`). Files are persisted to disk and arrive as `PersistedFile` (with `.path`, `.name`, `.contentType`).
      
      ```typescript
      import { Schema } from "effect"
      import { HttpApiEndpoint, HttpApiSchema } from "effect/unstable/httpapi"
      import { Multipart } from "effect/unstable/http"
      
      const upload = HttpApiEndpoint.post("upload", "/upload", {
        payload: Schema.Struct({
          title: Schema.String,
          file: Multipart.SingleFileSchema // a single PersistedFile; FilesSchema for many
        }).pipe(HttpApiSchema.asMultipart()),
        success: Schema.Struct({ path: Schema.String })
      })
      // handler: ({ payload }) => ... payload.file.path / payload.file.name
      ```
      
      Tune limits with `Context.Reference`s from `Multipart` (`MaxFileSize`, `MaxParts`, `MaxFieldSize`, `FieldMimeTypes`) provided into the server layer. Outside HttpApi, a raw `HttpServerRequest` exposes `request.multipart` (buffered) and `request.multipartStream` (streaming), plus `HttpServerRequest.schemaBodyMultipart(schema)`. Use `HttpApiSchema.asMultipartStream()` for streaming large uploads instead of buffering.
      
      ## Integrating Effect with Hono
      
      Use `ManagedRuntime` to bridge Effect into Hono routes:
      
      ```typescript
      import { Hono } from "hono"
      import { ManagedRuntime } from "effect"
      
      // Build runtime once from your app layer
      const runtime = ManagedRuntime.make(
        Layer.mergeAll(DatabaseLive, CacheLive, LoggerLive)
      )
      
      const app = new Hono()
      
      app.get("/users/:id", async (c) => {
        const result = await runtime.runPromise(
          Effect.gen(function*() {
            const db = yield* Database
            return yield* db.findUser(c.req.param("id"))
          })
        )
        return c.json(result)
      })
      ```
      
      Layers are created once and reused across all requests. Do NOT call `Layer.provide` + `Effect.runPromise` per request - that rebuilds layers every time.
      
    • llm-corrections.md 10.7 KB
      # LLM Corrections: Wrong vs Correct Effect APIs
      
      This is the exhaustive reference for preventing hallucinated Effect code. Check this before using any API you're unsure about.
      
      ## Non-Existent APIs (frequently hallucinated)
      
      | Hallucinated API                      | What to use instead                                          |
      |---------------------------------------|--------------------------------------------------------------|
      | `Effect.cachedWithTTL`                | `Cache.make({ capacity, timeToLive, lookup })`               |
      | `Effect.cachedInvalidateWithTTL`      | `cache.invalidate(key)` / `cache.invalidateAll()`            |
      | `Effect.retryN(n)`                    | `Effect.retry(Schedule.recurs(n))`                           |
      | `Effect.retryWithBackoff`             | `Effect.retry(Schedule.exponential("100 millis"))`           |
      | `Effect.timeout(effect, ms)`          | `Effect.timeout(effect, "5 seconds")` (Duration string)      |
      | `Effect.timeoutTo`                    | `Effect.timeoutTo(effect, { duration, onTimeout })`          |
      | `Effect.parallel`                     | `Effect.all([...], { concurrency: "unbounded" })`            |
      | `Effect.race(a, b)` (array form)     | `Effect.race(a, b)` takes exactly two effects                |
      | `Effect.raceAll([...])`              | `Effect.raceAll(effects)` (single iterable argument)         |
      | `Effect.withTimeout`                  | `Effect.timeout("5 seconds")` in a pipe                      |
      | `Effect.bracket`                      | `Effect.acquireUseRelease(acquire, use, release)`            |
      | `Effect.ensuring(effect, finalizer)`  | `Effect.ensuring(finalizer)` in a pipe                       |
      | `Effect.supervised`                   | Use `Effect.forkScoped` or manual fiber management            |
      | `Effect.blocking`                     | `Effect.sync` or `Effect.tryPromise` (no separate blocking)  |
      | `Stream.fromSSE`                      | Build from `HttpClient` response + chunk parsing              |
      | `Layer.fromEffect`                    | `Layer.effect(Tag, effect)`                                   |
      | `Layer.fromFunction`                  | `Layer.succeed(Tag, implementation)`                          |
      | `Layer.fromService`                   | `Layer.effect(Tag, Effect.gen(function*() { ... }))`          |
      | `Schema.nullable`                     | `Schema.NullOr(schema)`                                      |
      | `Schema.optional` (standalone)        | `Schema.optional(schema)` for struct fields only              |
      | `Schema.makeUnsafe(input)` (v4)       | `Schema.make(input)` - throws `SchemaError` on invalid input; also instance methods `schema.makeOption(...)`, `schema.makeEffect(...)` |
      | `ServiceMap.Service` / `ServiceMap.Reference` (v4) | Renamed back to `Context.Service` / `Context.Reference` on 2026-04-07 (PR #1961). Import `Context` from `"effect"`. |
      | `Otlp.layer({ url, serviceName })` (v4) | Canonical split form: `OtlpTracer.layer({ url, resource: { serviceName } })` + `OtlpSerialization.layerJson` + `FetchHttpClient.layer`. The aggregator `Otlp.layer` exists but uses `baseUrl` + `resource.serviceName`, not `url` + `serviceName`. |
      | Chained `HttpApiEndpoint.get(n, p).pipe(HttpApiEndpoint.setPath(...), setPayload(...), setSuccess(...))` (v4) | Object-option form: `HttpApiEndpoint.get(name, path, { params, query, payload, success, error })` |
      | `HttpApiEndpoint` parse failures surface as typed errors (v4) | In current v4 betas, endpoint schema failures default to **defects** unless transformed via `HttpApiSchema` helpers. Use `HttpApiSchemaError` when you need typed error responses. |
      | `Layer.scoped(Tag, eff)` (v4) | Removed. `Layer.effect(Tag, eff)` strips `Scope` from the requirements automatically. |
      | `Effect.async((resume) => ...)` (v4) | Renamed to `Effect.callback`. Register signature is `(resume, signal: AbortSignal) => void \| Effect<void, never, R>`. |
      | `Effect.makeSemaphore(n)` (v4) | Removed from `Effect`. Use `Semaphore.make(n)` from the `Semaphore` module. `withPermits` is data-first: `Semaphore.withPermits(sem, n)(eff)`. |
      | `Effect.logSpan(label)(eff)` (v3 + v4) | Never existed. Use `Effect.withLogSpan(label)(eff)`. |
      | `Schedule.compose(other)` (v4) | Removed. Use `Schedule.take(n)` to bound by attempt count, or `Effect.retry(eff, { schedule, times })`. |
      | `Schedule.once` (v4) | Removed. Use `Schedule.recurs(0)` (run once, no retry) or `Schedule.recurs(1)`. |
      | `Tool.make("name", { ..., handler: ... })` | `Tool.make` defines schema only — there is no `handler` field. Attach handlers via `MyToolkit.toLayer({ ToolName: (params) => effect })`. |
      | `Chat.make()` / `Chat.send(session, msg)` | No such exports. Use `yield* Chat.empty` (or `Chat.fromPrompt`, `Chat.makePersisted`) and call methods on the instance: `chat.generateText({ prompt })`, `chat.streamText({ prompt })`, `chat.generateObject({ prompt, schema })`. |
      | `@effect/ai-amazon-bedrock` / `@effect/ai-google` (v4) | Not yet ported to v4. v4 ships only `@effect/ai-anthropic`, `@effect/ai-openai`, `@effect/ai-openai-compat`, `@effect/ai-openrouter`. |
      | `cause: Schema.Defect` / `error: Schema.Error` as bare field values (v4) | Since beta.76 `Schema.Defect` and `Schema.Error` are **constructor functions** — call them: `Schema.Defect()` / `Schema.Error()`. Stack control moved to an option: `Schema.Error({ includeStack: true })` (the old `Schema.ErrorWithStack` / `Schema.DefectWithStack` are removed). |
      | `Random.nextUUIDv4()` (v4) | Removed — `Random` is not cryptographically secure. Use the `Crypto` service: `yield* Crypto.Crypto` then `crypto.randomUUIDv4` / `crypto.randomUUIDv7`. |
      
      ## Wrong Import Paths
      
      | Wrong                                        | Correct                                    |
      |----------------------------------------------|--------------------------------------------|
      | `import { Schema } from "@effect/schema"`    | `import { Schema } from "effect"`          |
      | `import { JSONSchema } from "@effect/schema"`| `import { JSONSchema } from "effect"`      |
      | `import { ParseResult } from "@effect/schema"` | `import { ParseResult } from "effect"` (v3). In v4 `ParseResult` is gone — use the `SchemaIssue` module for issue inspection and the `SchemaError` class (re-exported from `Schema`) as the thrown/failed value. Narrow with `Schema.isSchemaError(e)`, then inspect `e.issue` via `SchemaIssue.makeFormatterStandardSchemaV1()(e.issue).issues`. |
      | `import { Effect } from "@effect/io"`        | `import { Effect } from "effect"`          |
      | `import { Layer } from "@effect/io"`         | `import { Layer } from "effect"`           |
      | `import { Stream } from "@effect/stream"`    | `import { Stream } from "effect"`          |
      | `import { HttpClient } from "@effect/platform"` | `import { HttpClient } from "@effect/platform"` (v3) or `"effect/unstable/http"` (v4) |
      | `import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "@effect/platform"` (v4) | `import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi"` |
      | `import { Otlp } from "effect/unstable/observability"` + single `Otlp.layer(...)` | `import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"` + `import { FetchHttpClient } from "effect/unstable/http"` |
      | `import { RateLimiter } from "effect"` (v4) | `import { RateLimiter } from "effect/unstable/persistence"`. v4 exposes a Service (`RateLimiter.layer` + `RateLimiter.layerStoreMemory`) and the `makeWithRateLimiter` accessor — there is no top-level `RateLimiter.make`/`withCost`. |
      
      ## Wrong Patterns
      
      ### Running effects inside effects (DO NOT)
      
      ```typescript
      // WRONG - calling runPromise inside an Effect pipeline
      const bad = Effect.gen(function*() {
        const result = Effect.runPromise(someEffect) // NO!
        return result
      })
      
      // CORRECT - compose effects, run only at the edge
      const good = Effect.gen(function*() {
        const result = yield* someEffect
        return result
      })
      ```
      
      ### Missing scope for acquireRelease (DO NOT)
      
      ```typescript
      // WRONG - acquireRelease without scoped
      const bad = Effect.gen(function*() {
        const conn = yield* Effect.acquireRelease(
          openConn(),
          (conn) => closeConn(conn)
        )
        return yield* conn.query("SELECT 1")
      })
      
      // CORRECT - wrap in Effect.scoped
      const good = Effect.scoped(
        Effect.gen(function*() {
          const conn = yield* Effect.acquireRelease(
            openConn(),
            (conn) => closeConn(conn)
          )
          return yield* conn.query("SELECT 1")
        })
      )
      ```
      
      ### Generator yield without star (DO NOT)
      
      ```typescript
      // WRONG - yield without *
      const bad = Effect.gen(function*() {
        const x = yield someEffect // Missing *! This yields the Effect object itself
      })
      
      // CORRECT - yield*
      const good = Effect.gen(function*() {
        const x = yield* someEffect
      })
      ```
      
      ### Not returning on error raise (DO NOT)
      
      ```typescript
      // WRONG - error doesn't stop execution in TS's view
      const bad = Effect.gen(function*() {
        if (!input) {
          yield* Effect.fail(new InputError({ message: "missing" }))
        }
        // TS thinks this code runs even after fail ^
        yield* doWork(input) // input might be undefined
      })
      
      // CORRECT - return yield* to signal control flow
      const good = Effect.gen(function*() {
        if (!input) {
          return yield* Effect.fail(new InputError({ message: "missing" }))
        }
        yield* doWork(input) // TS knows input is defined here
      })
      ```
      
      ### Point-free/tacit function passing (DO NOT)
      
      ```typescript
      // WRONG - generics get erased, overloads break
      const bad = Effect.map(myEffect, JSON.parse)
      
      // CORRECT - explicit lambda preserves types
      const good = Effect.map(myEffect, (x) => JSON.parse(x))
      ```
      
      ## Terminology Corrections
      
      | Wrong term               | Correct term                               |
      |--------------------------|--------------------------------------------|
      | "cancel a fiber"         | "interrupt a fiber"                        |
      | "thread-local storage"   | "fiber-local storage" (FiberRef / Reference) |
      | "worker pool"            | `concurrency` option on `Effect.all/forEach` |
      | "dependency container"   | "Layer"; `Context.Tag` (v3) or `Context.Service` (v4) — both live in the `Context` module |
      | "middleware" (for layers)| "Layer" (layers compose, not chain)          |
      | "async effect"           | Effects are lazy by default, both sync/async |
      | "Observable" / "RxJS"    | Effect is single-shot (like lazy Promise), not multi-shot |
      
      ## Myths to Not Repeat
      
      1. **"Effect is 500x slower"** - Only for trivial `1+1` micro-ops. For real IO-bound services, fiber overhead is unmeasurable
      2. **"Huge bundle size"** - v3 minimum ~25KB gzipped; v4 minimum ~6.3KB gzipped. Tree-shaking friendly
      3. **"Generators are slow"** - Effect internals are NOT built on generators. Generator API is equally performant to async/await
      4. **"Same as RxJS"** - Effect's base type is single-shot (like a lazy Promise), not multi-shot like Observables
      
    • migration-async.md 4 KB
      # Migrating from async/await to Effect
      
      Mechanical conversion patterns for moving Promise-based code to Effect.
      
      ## Promise -> Effect
      
      ```typescript
      // BEFORE: async/await
      async function fetchUser(id: string): Promise<User> {
        const response = await fetch(`/api/users/${id}`)
        if (!response.ok) throw new Error("Not found")
        return response.json()
      }
      
      // AFTER: Effect (v3)
      const fetchUser = (id: string): Effect.Effect<User, FetchError> =>
        Effect.tryPromise({
          try: () => fetch(`/api/users/${id}`).then((r) => {
            if (!r.ok) throw new Error("Not found")
            return r.json()
          }),
          catch: (err) => new FetchError({ cause: err })
        })
      
      // AFTER: Effect (v4 with Effect.fn)
      const fetchUser = Effect.fn("fetchUser")(
        function*(id: string): Effect.fn.Return<User, FetchError> {
          return yield* Effect.tryPromise({
            try: () => fetch(`/api/users/${id}`).then((r) => {
              if (!r.ok) throw new Error("Not found")
              return r.json()
            }),
            catch: (err) => new FetchError({ cause: err })
          })
        }
      )
      ```
      
      ## try/catch -> typed errors
      
      ```typescript
      // BEFORE
      async function processPayment(amount: number) {
        try {
          const result = await chargeCard(amount)
          return result
        } catch (err) {
          if (err instanceof InsufficientFunds) {
            return { status: "declined" }
          }
          throw err // re-throw unknown errors
        }
      }
      
      // AFTER
      const processPayment = (amount: number) =>
        chargeCard(amount).pipe(
          Effect.catchTag("InsufficientFunds", () =>
            Effect.succeed({ status: "declined" as const })
          )
          // Unknown errors become defects automatically
        )
      ```
      
      ## Promise.all -> Effect.all
      
      ```typescript
      // BEFORE
      const [user, posts] = await Promise.all([
        fetchUser(id),
        fetchPosts(id)
      ])
      
      // AFTER (parallel)
      const [user, posts] = yield* Effect.all(
        [fetchUser(id), fetchPosts(id)],
        { concurrency: "unbounded" }
      )
      
      // With bounded concurrency
      const results = yield* Effect.all(
        urls.map((url) => fetchUrl(url)),
        { concurrency: 5 }
      )
      ```
      
      ## Callback APIs
      
      ```typescript
      // BEFORE
      function readFile(path: string): Promise<string> {
        return new Promise((resolve, reject) => {
          fs.readFile(path, "utf-8", (err, data) => {
            if (err) reject(err)
            else resolve(data)
          })
        })
      }
      
      // AFTER (v3) — Effect.async
      const readFile = (path: string): Effect.Effect<string, FileError> =>
        Effect.async((resume) => {
          fs.readFile(path, "utf-8", (err, data) => {
            if (err) resume(Effect.fail(new FileError({ cause: err })))
            else resume(Effect.succeed(data))
          })
        })
      
      // AFTER (v4) — Effect.async is renamed to Effect.callback. The register
      // receives a second positional `signal: AbortSignal` argument for cancellation
      // (ignore it when not needed).
      const readFile = (path: string): Effect.Effect<string, FileError> =>
        Effect.callback((resume, signal) => {
          fs.readFile(path, "utf-8", (err, data) => {
            if (err) resume(Effect.fail(new FileError({ cause: err })))
            else resume(Effect.succeed(data))
          })
        })
      ```
      
      ## Class with state -> Service + Ref
      
      ```typescript
      // BEFORE
      class Counter {
        private count = 0
        increment() { this.count++ }
        getCount() { return this.count }
      }
      
      // AFTER
      class Counter extends Context.Tag("Counter")<Counter, {
        readonly increment: Effect.Effect<void>
        readonly getCount: Effect.Effect<number>
      }>() {}
      
      const CounterLive = Layer.effect(Counter,
        Effect.gen(function*() {
          const ref = yield* Ref.make(0)
          return {
            increment: Ref.update(ref, (n) => n + 1),
            getCount: Ref.get(ref)
          }
        })
      )
      ```
      
      ## Key Principles
      
      1. **Effects are lazy**: nothing runs until `run*` is called. Don't mix `await` and `yield*`
      2. **Errors are typed**: convert thrown exceptions to typed errors at boundaries with `Effect.tryPromise`
      3. **Dependencies are explicit**: extract shared state/clients into services with `Context.Tag` (v3) / `Context.Service` (v4)
      4. **run* at edges only**: libraries return `Effect` values. Only call `runPromise` / `runMain` at the program boundary
      5. **Incremental adoption**: you can wrap individual functions with `Effect.tryPromise` and gradually expand
      
    • migration-v4.md 18.9 KB
      # Migrating from Effect v3 to v4
      
      Effect v4 (beta, February 2026) is a major release. The core programming model (Effect, Layer, Schema, Stream) is unchanged, but naming, imports, and some APIs have changed significantly.
      
      Source: https://github.com/Effect-TS/effect-smol/blob/main/MIGRATION.md
      
      ## Installation
      
      ```bash
      npm install effect@beta
      # Companion packages must match versions:
      npm install @effect/platform-node@beta @effect/opentelemetry@beta
      ```
      
      ## Structural Changes
      
      ### Unified Versioning
      All packages share a single version (e.g., `effect@4.0.0-beta.X`, `@effect/sql-pg@4.0.0-beta.X`).
      
      ### Package Consolidation
      Many packages merged into `effect`. Remaining separate: `@effect/platform-*`, `@effect/sql-*`, `@effect/ai-*`, `@effect/opentelemetry`, `@effect/vitest`.
      
      ### Unstable Modules
      New `effect/unstable/*` paths may break in minor releases: `ai`, `cli`, `cluster`, `devtools`, `http`, `httpapi`, `observability`, `rpc`, `sql`, `workflow`, `workers`, etc.
      
      ### Bundle Size
      ~70KB (v3) -> ~20KB (v4) for Effect + Stream + Schema. Minimal: ~6.3KB gzipped.
      
      ## Services: Context.Tag -> Context.Service
      
      v4 briefly introduced a `ServiceMap` module for service definitions. On 2026-04-07 (PR #1961) it was renamed back to `Context`. Any doc or older beta code that says `ServiceMap.Service` / `ServiceMap.Reference` should be read as the current `Context.Service` / `Context.Reference`.
      
      | v3                                    | v4                                        |
      |---------------------------------------|-------------------------------------------|
      | `Context.GenericTag<T>(id)`           | `Context.Service<T>(id)`                  |
      | `Context.Tag(id)<Self, Shape>()`      | `Context.Service<Self, Shape>()(id)`      |
      | `Effect.Tag(id)<Self, Shape>()`       | `Context.Service<Self, Shape>()(id)`      |
      | `Effect.Service<Self>()(id, opts)`    | `Context.Service<Self>()(id, { make })`   |
      | `Context.Reference<Self>()(id, opts)` | `Context.Reference<T>(id, opts)`          |
      
      ```typescript
      // v3
      class Database extends Context.Tag("Database")<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>() {}
      
      // v4
      class Database extends Context.Service<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>()(
        "myapp/Database"
      ) {}
      ```
      
      **Static accessors removed.** Use `Service.use()` or `yield*` in generators:
      
      ```typescript
      // v3: Notifications.notify("hello") (proxy accessor)
      // v4: Notifications.use((n) => n.notify("hello"))
      // v4 preferred: yield* Notifications in Effect.gen
      ```
      
      **Layer naming:** `.Default` / `.Live` -> `.layer`, `.layerTest`, `.layerConfig`
      
      **No auto-generated layers in v4.** Build explicitly with `Layer.effect(this, this.make)`.
      
      ## Error Handling Renames
      
      | v3                       | v4                             |
      |--------------------------|--------------------------------|
      | `Effect.catchAll`        | `Effect.catch`                 |
      | `Effect.catchAllCause`   | `Effect.catchCause`            |
      | `Effect.catchAllDefect`  | `Effect.catchDefect`           |
      | `Effect.catchSome`       | `Effect.catchFilter`           |
      | `Effect.catchSomeCause`  | `Effect.catchCauseFilter`      |
      | `Effect.catchSomeDefect` | Removed                        |
      | `Effect.catchTag`        | Unchanged (also accepts arrays)|
      | `Effect.catchTags`       | Unchanged                      |
      
      **New in v4:** `Effect.catchReason`, `Effect.catchReasons`, `Effect.catchEager`
      
      ## Forking Renames
      
      | v3                            | v4                  |
      |-------------------------------|---------------------|
      | `Effect.fork`                 | `Effect.forkChild`  |
      | `Effect.forkDaemon`           | `Effect.forkDetach` |
      | `Effect.forkScoped`           | Unchanged           |
      | `Effect.forkIn`               | Unchanged           |
      | `Effect.forkAll`              | Removed             |
      | `Effect.forkWithErrorHandler` | Removed             |
      
      Fork options: `{ startImmediately?: boolean, uninterruptible?: boolean | "inherit" }`
      
      ## FiberRef -> Context.Reference
      
      `FiberRef`, `FiberRefs`, `FiberRefsPatch`, `Differ` are removed. Fiber-local state is now handled by `Context.Reference` — the same mechanism used for services with default values. Built-in fiber-local values are exported from the `References` namespace.
      
      | v3                              | v4                                 |
      |---------------------------------|------------------------------------|
      | `FiberRef.currentLogLevel`      | `References.CurrentLogLevel`       |
      | `FiberRef.currentConcurrency`   | `References.CurrentConcurrency`    |
      | `FiberRef.get(ref)`             | `yield* References.X`             |
      | `Effect.locally(e, ref, value)` | `Effect.provideService(e, Ref, v)` |
      
      ## Either -> Result
      
      | v3                | v4                 |
      |-------------------|--------------------|
      | `Either`          | `Result`           |
      | `Either.right(x)` | `Result.ok(x)`    |
      | `Either.left(e)`  | `Result.err(e)`   |
      | `Effect.either`   | `Effect.result`    |
      
      ## Yieldable (Types No Longer Effect Subtypes)
      
      In v3, `Ref`, `Deferred`, `Fiber`, `Option`, `Either`, `Config` etc. were structural subtypes of `Effect`. In v4, they implement `Yieldable` instead - `yield*` still works in generators, but they can't be passed directly to Effect combinators.
      
      ```typescript
      // v3: yield* ref      -> reads the ref value
      // v4: yield* Ref.get(ref)
      
      // v3: yield* fiber    -> joins the fiber
      // v4: yield* Fiber.join(fiber)
      
      // v3: yield* deferred -> awaits the deferred
      // v4: yield* Deferred.await(deferred)
      
      // v3: Effect.map(option, fn) -> worked because Option was Effect
      // v4: Effect.map(option.asEffect(), fn) -> explicit conversion needed
      ```
      
      ## Runtime<R> Removed
      
      | v3                               | v4                                    |
      |----------------------------------|---------------------------------------|
      | `Effect.runtime<R>()`           | `Effect.services<R>()`               |
      | `Runtime.runFork(runtime)(eff)` | `Effect.runForkWith(services)(eff)`   |
      
      ## Effect.fn (New in v4)
      
      Preferred way to write functions returning Effects. Adds automatic span + better stack traces:
      
      ```typescript
      const fetchUser = Effect.fn("fetchUser")(
        function*(id: string): Effect.fn.Return<User, NotFoundError> {
          const db = yield* Database
          return yield* db.findUser(id)
        },
        // Additional combinators (no .pipe needed)
        Effect.catch((e) => Effect.log(`Error: ${e}`))
      )
      ```
      
      ## Schema Changes (Major)
      
      ### Renames
      
      | v3                            | v4                                    |
      |-------------------------------|---------------------------------------|
      | `Schema.TaggedError`          | `Schema.TaggedErrorClass`             |
      | `Schema.decodeUnknown`        | `Schema.decodeUnknownEffect`          |
      | `Schema.decode`               | `Schema.decodeEffect`                 |
      | `Schema.encode`               | `Schema.encodeEffect`                 |
      | `Schema.decodeUnknownEither`  | `Schema.decodeUnknownExit`            |
      | `Schema.Literal("a","b")`    | `Schema.Literals(["a","b"])`          |
      | `Schema.Union(A, B)`         | `Schema.Union([A, B])`               |
      | `Schema.Tuple(A, B)`         | `Schema.Tuple([A, B])`               |
      | `Schema.pick("a")`           | `.mapFields(Struct.pick(["a"]))`      |
      | `Schema.omit("a")`           | `.mapFields(Struct.omit(["a"]))`      |
      | `Schema.partial`             | `.mapFields(Struct.map(Schema.optional))` |
      | `Schema.extend(B)`           | `.mapFields(Struct.assign(fieldsB))`  |
      | `Schema.filter(pred)`        | `.check(Schema.makeFilter(pred))`     |
      | `Schema.positive()`          | `Schema.isGreaterThan(0)`             |
      | `Schema.int()`               | `Schema.isInt()`                      |
      | `Schema.minLength(n)`        | `Schema.isMinLength(n)`              |
      
      ### Transform syntax change
      
      ```typescript
      // v3
      Schema.transform(FromSchema, ToSchema, { decode, encode })
      
      // v4
      FromSchema.pipe(
        Schema.decodeTo(ToSchema, SchemaTransformation.transform({ decode, encode }))
      )
      ```
      
      ### optionalWith changes
      
      | v3 options                    | v4                                                        |
      |-------------------------------|-----------------------------------------------------------|
      | `{ exact: true }`            | `Schema.optionalKey(schema)`                              |
      | `{ default }`                | `schema.pipe(Schema.withDecodingDefaultType(...))`        |
      | `{ exact: true, default }`   | `schema.pipe(Schema.withDecodingDefaultTypeKey(...))`     |
      
      > The non-`Type` variants (`withDecodingDefault`, `withDecodingDefaultKey`) also exist in v4 but apply the default to the **Encoded** side. v3's `optionalWith({ default })` applied the default on the Type (decoded) side, so the `*Type*` variants are the correct migration target.
      
      ### Equality
      
      `Equal.equals` performs deep structural comparison by default in v4. `Schema.Data` is removed (unnecessary).
      
      ## Other API Removals & Renames
      
      | v3                                       | v4                                                                                |
      |------------------------------------------|-----------------------------------------------------------------------------------|
      | `Layer.scoped(Tag, eff)`                 | `Layer.effect(Tag, eff)` — strips `Scope` from requirements automatically         |
      | `Effect.async((resume) => ...)`          | `Effect.callback((resume, signal) => ...)` — `signal: AbortSignal` is positional  |
      | `Effect.makeSemaphore(n)`                | `Semaphore.make(n)` (import `Semaphore` from `"effect"`)                          |
      | `semaphore.withPermits(n)(eff)`          | `Semaphore.withPermits(semaphore, n)(eff)` — data-first                           |
      | `Schedule.compose(Schedule.recurs(n))`   | `Schedule.take(n)` (bound by attempt count) or `Effect.retry(_, { schedule, times })` |
      | `Schedule.once`                          | `Schedule.recurs(0)`                                                              |
      | `import { RateLimiter } from "effect"`   | `import { RateLimiter } from "effect/unstable/persistence"` — Service-based API; no `withCost`, use `tokens` option |
      
      ## Quick Checklist for v3 -> v4
      
      1. Replace `Context.Tag` / `Effect.Tag` / `Effect.Service` with `Context.Service`
      2. Replace `Effect.catchAll` with `Effect.catch` (and similar renames)
      3. Replace `Effect.fork` with `Effect.forkChild`, `Effect.forkDaemon` with `Effect.forkDetach`
      4. Replace `FiberRef.*` with `Context.Reference` / `References.*`
      5. Replace `yield* ref` with `yield* Ref.get(ref)`, same for Fiber/Deferred
      6. Replace `Data.TaggedError` with `Schema.TaggedErrorClass`
      7. Update Schema API calls (variadic to array, filter renames, transform syntax)
      8. Replace `Effect.either` with `Effect.result`
      9. Update layer naming (`.Default` -> `.layer`)
      10. Use `Effect.fn("name")` for new functions
      11. Replace `Layer.scoped` with `Layer.effect`
      12. Replace `Effect.async` with `Effect.callback`
      13. Replace `Effect.makeSemaphore` with `Semaphore.make`; switch `withPermits` to data-first
      14. Replace `Schedule.compose(Schedule.recurs(n))` with `Schedule.take(n)`; replace `Schedule.once` with `Schedule.recurs(0)`
      15. Move `RateLimiter` import from `"effect"` to `"effect/unstable/persistence"` and switch to its Service-based API
      
      ## v4 beta additions (through beta.92)
      
      These landed in later betas and are worth knowing if you are currently on an older beta. The skill tracks `effect@4.0.0-beta.92`; companion packages share that single version.
      
      ### beta.55-58 (through 2026-04-28)
      
      - `Effect.abortSignal` for bridging AbortController-based APIs (beta.57, PR #2085)
      - `@effect/sql-pglite` package wrapping `@electric-sql/pglite` (beta.57, PR #2073)
      - `Effectable` module for lifting existing types into Effect (beta.55-ish)
      - `Socket.make` constructor (beta.57, PR #2078)
      - `RpcGroup.omit` for deriving subsets of RPC groups
      - `AtomRpc.query` requires an explicit serialization option for serializable atoms (PR #2040)
      - **HttpApi schema errors now default to defects** unless transformed (PR #2057, 2026-04-20). See `references/http.md` for how to surface them as typed errors via `HttpApiSchema` transforms.
      - `Schema.withDecodingDefaultType` / `...TypeKey` added alongside the Encoded-side variants (PR #2013, 2026-04-10)
      - `AsyncResult.builder` (`effect/unstable/reactivity`) gained `.onInterrupt(...)` and an `.exhaustive()` finalizer; `.onDefect` / `.onFailure` typing refined so `.exhaustive()` is only callable when every case (success, error, initial, defect, interrupt) is handled (beta.58, PR #2097, 2026-04-28). Use `.exhaustive(): Out` instead of `.render(): Out | null` when you want a non-nullable result.
      - Stream -> `Uint8Array` conversion and HTTP body consumption now use fewer buffer copies - internal perf only, no public API change (beta.58, PR #2098, 2026-04-27).
      
      ### beta.59-78 (2026-04-29 through 2026-06)
      
      Breaking / behavioral:
      
      - **`Schema.Error` / `Schema.Defect` are now constructor functions**, not constants — write `Schema.Error()` / `Schema.Defect()`. `ErrorWithStack` / `DefectWithStack` folded into `{ includeStack: true }`. `Schema.Defect()` now models defects as `unknown` with a JSON-encoded form, so non-`Error` objects no longer round-trip unchanged (beta.76, PR #2318).
      - **`Random.nextUUIDv4` removed** — `Random` is not cryptographically secure. Use the new platform-agnostic `Crypto` service's `randomUUIDv4` / `randomUUIDv7` (beta.68, PR #2180).
      - **`Effect.Yieldable` export removed** (beta.66, PR #2163). The Yieldable *concept* still applies (Ref/Deferred/Fiber/Option/Config implement it); only the re-export off `Effect` is gone.
      - `Types.MergeRecord` removed -> use `Types.MergeLeft` (beta.75, PR #2298).
      - `SchemaParser.makeUnsafe` -> `SchemaParser.make` (beta.67, PR #2172).
      - `Schema.asserts` signature changed to `asserts(schema, input)`; `Schema.Codec.ToAsserts` removed (beta.68, PR #2221).
      - `Model.Generated` -> `Model.GeneratedByDb` (beta.68, PR #2207). See `references/sql.md`.
      - `Workflow.make` now takes the tag as its **first** argument and supports `class X extends Workflow.make(...) {}` (beta.75, PR #2294). See `references/distributed.md`.
      - `Inspectable.stringifyCircular` removed (beta.60, PR #2119).
      
      Fixes / additions worth knowing:
      
      - `catch*` combinators no longer silently drop unhandled error types — the residual error channel is now preserved (beta.71, PR #2257).
      - `Effect.firstSuccessOf` ported from v3 (beta.61, PR #2120); `Effect.acquireDisposable` added (beta.63, PR #2123).
      - `Schedule.tap` added — observe full schedule metadata without altering inputs/outputs (beta.71, PR #2252).
      - `Stream.broadcastN` for fixed-size stream broadcasts (beta.68, PR #2210); `Channel.decodeText` UTF-8-across-chunk fix (beta.68, PR #2209).
      - `HttpApiTest` module added for testing HttpApi servers (beta.63, PR #2136); `HttpApiSecurity.http` for custom schemes (beta.73, PR #2291).
      - `Schema.DurationFromString` (beta.60, PR #2117); `Schema.isGUID` + RFC 9562 max-UUID support in `Schema.isUUID` (beta.76, PR #2320).
      - OTLP observability now reads `OTEL_*` environment variables and prefers them over explicit `OtlpResource.fromConfig` options (beta.77, PRs #2325/#2326).
      - `Config.literals` convenience constructor for `Schema.Literals` (beta.60, PR #2116).
      
      ### beta.79-92 (2026-06 through 2026-07)
      
      Breaking / behavioral:
      
      - **`SchemaError` now extends `Data.TaggedError`** (beta.84, PR #2407) — it is also a native `Error` with `_tag: "SchemaError"`, catchable via `Effect.catchTag`. The `SchemaParser` Promise APIs reject an `Error` whose `cause` is the `SchemaIssue.Issue`; the `is`/`asserts`/`Promise`/`Sync`/`Result`/`Option`/`make`/`makeOption` adapters now distinguish schema issues from non-schema causes.
      - **`Schema.Void` now models ignored `void` return values** (beta.89, PR #2475) — it accepts any present value and discards it as `undefined`. Use `Schema.Undefined` when you need to match `undefined` exactly.
      - **`Config.make` low-level constructor removed** (beta.84, PR #2383) — use the config constructors/combinators or `ConfigProvider.make`. `ConfigProvider.fromDir` now returns `undefined` when neither file nor dir exists, so you can chain `orElse` fallbacks.
      - **`Config.withDefault` recovery narrowed** (beta.81, PRs #2387/#2388) — it now only recovers from *missing* data for literal/union schemas; present-but-invalid values and filter failures propagate the validation error instead of falling back to the default. `Config.schema` also treats a missing array value as missing data so `withDefault` applies (beta.90, PR #2483).
      - **`Effect.try` accepts a thunk directly** (beta.84, PR #2415), matching `Effect.tryPromise`; `tryPromise` only creates an `AbortController` when the thunk declares an `AbortSignal` parameter.
      - **`RpcGroup.toHandlers` is now definition-first** (beta.84, PR #2423); RpcClient HTTP requests fail with a *defect* when the response stream closes before a terminal response (beta.86, PR #2461).
      - **Schema arbitrary-derivation metadata migrated** (beta.79, PR #2348) — custom filter annotations use `arbitrary: { constraint }` instead of `toArbitraryConstraint`, bucketed constraints are flattened (`string.minLength` -> `minLength`, `number.isInteger` -> `integer`), `ctx.constraints` -> `ctx.constraint`, and `Schema.toArbitrary(schema, { report: true })` returns `{ value, report }`. Plain `Schema.toArbitrary(schema)` (as shown in `references/testing.md`) is unaffected.
      - `keepDeclarations` option removed from `Schema.toCodecStringTree` (beta.86, PR #2452).
      - `Graph.neighborsDirected` deprecated in favor of `Graph.successors` / `Graph.predecessors` (beta.80, PR #2376).
      
      Fixes / additions worth knowing:
      
      - `Effect.transposeOption` turns `Option<Effect<A, E, R>>` into `Effect<Option<A>, E, R>` (beta.84, PR #2420); `Effect.fromOption` gained custom error callbacks (beta.89, PR #2479).
      - `Random.choice` selects a random element from an iterable (beta.85, PR #2425); `Latch.isOpen` queries latch state (beta.88, PR #2428).
      - `String.configCase` for configuration-key casing, plus a numeric-segment fix in `camelCase`/`pascalCase` (beta.91, PR #2488).
      - HTTP API streaming response support (beta.81, PR #2270); malformed JSON request bodies now return 400 (unreleased, PR #2492).
      - `Statement.valuesUnprepared` returns unprepared SQL rows as arrays (beta.86, PR #2462); `Schema.toCodecArrayFromSingle` added (beta.86, PR #2442); the original input schema is now exposed on `Schema.toType`/`toEncoded`/`toCodecJson`/`toCodecStringTree` via a `.schema` property (beta.87, PR #2468).
      - `RequestResolver` interruption fixed (beta.91, PR #2485); `Schedule.andThenResult` now emits `self` outputs as `Failure` and `other` as `Success` (beta.91, PR #2497); `Schema.toTaggedUnion(...).isAnyOf` narrowing fixed for custom discriminant keys (beta.81, PR #2386).
      - Adaptive consume + feedback operations on the unstable persistent `RateLimiterStore` API (in-memory + Redis, 429 Retry-After feedback) (beta.88, PR #2472).
      - `OtlpTracer` now renders causes in exception events (beta.89, PR #2480); excess-property handling fixed in schema-backed class constructors (beta.92, PR #2499).
      - `@effect/ai-anthropic`: non-streaming responses no longer throw on tool-call `caller` metadata (emits `null` for `caller.toolId`, beta.88, PR #2450).
      
    • observability.md 5.7 KB
      # Observability
      
      Effect has built-in structured logging, metrics, and distributed tracing. For exporting telemetry, use `effect/unstable/observability` (v4/new projects) or `@effect/opentelemetry` (v3/existing OTel setups).
      
      ## Structured Logging
      
      ```typescript
      import { Effect } from "effect"
      
      yield* Effect.log("Processing request")
      yield* Effect.logInfo("User found", userId)
      yield* Effect.logWarning("Rate limit approaching")
      yield* Effect.logError("Failed to fetch", error)
      yield* Effect.logDebug("Cache miss for key:", key)
      
      // Add context to all logs in scope
      const withContext = myEffect.pipe(
        Effect.annotateLogs("requestId", requestId),
        Effect.annotateLogs("userId", userId)
      )
      
      // Log spans (structured timing)
      const timed = Effect.withLogSpan("database.query")(queryEffect)
      ```
      
      ## Distributed Tracing
      
      ```typescript
      // Add a span to any effect
      const traced = myEffect.pipe(
        Effect.withSpan("processPayment", {
          attributes: { amount, currency }
        })
      )
      
      // Nested spans create parent-child relationships automatically
      const program = Effect.gen(function*() {
        yield* fetchUser(id).pipe(Effect.withSpan("fetch.user"))
        yield* validatePayment().pipe(Effect.withSpan("validate.payment"))
        yield* processCharge().pipe(Effect.withSpan("process.charge"))
      }).pipe(Effect.withSpan("handlePayment"))
      // Produces: handlePayment -> fetch.user, validate.payment, process.charge
      
      // Annotate spans
      const annotated = myEffect.pipe(
        Effect.annotateSpans("http.status_code", 200)
      )
      ```
      
      **v4: `Effect.fn` adds spans automatically:**
      
      ```typescript
      // v4 - the name string becomes a span
      const fetchUser = Effect.fn("fetchUser")(function*(id: string) {
        // ... automatically wrapped in a "fetchUser" span
      })
      ```
      
      ## Metrics
      
      ```typescript
      import { Metric } from "effect"
      
      // Counter
      const requestCount = Metric.counter("http.requests.total")
      yield* Metric.increment(requestCount)
      
      // Counter with tags
      const errorCount = Metric.counter("http.errors.total")
      yield* Metric.increment(errorCount).pipe(
        Effect.tagMetrics("status", "500"),
        Effect.tagMetrics("method", "POST")
      )
      
      // Gauge
      const activeConnections = Metric.gauge("connections.active")
      yield* Metric.set(activeConnections, 42)
      
      // Histogram
      const latency = Metric.histogram("http.request.duration_ms",
        Metric.Histogram.exponential({ start: 1, factor: 2, count: 10 })
      )
      yield* Metric.record(latency, durationMs)
      ```
      
      ## OpenTelemetry Export
      
      ### v3: @effect/opentelemetry
      
      ```typescript
      import { NodeSdk } from "@effect/opentelemetry"
      import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
      import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"
      import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"
      
      const OtelLayer = NodeSdk.layer(() => ({
        resource: { serviceName: "my-service" },
        spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter({
          url: "http://localhost:4318/v1/traces"
        })),
        metricReader: new PeriodicExportingMetricReader({
          exporter: new OTLPMetricExporter({
            url: "http://localhost:4318/v1/metrics"
          })
        })
      }))
      
      // Provide to your program
      const main = program.pipe(Effect.provide(OtelLayer))
      ```
      
      ### v4: effect/unstable/observability (fetch-based, no OTel SDK dependency)
      
      v4 ships its own lightweight OTLP exporters under `effect/unstable/observability`. The canonical setup is to compose split modules — `OtlpTracer.layer`, `OtlpLogger.layer` — with an `OtlpSerialization` encoding layer and an `HttpClient` transport. The whole stack depends on `FetchHttpClient.layer` from `effect/unstable/http`; there is no OpenTelemetry SDK dependency.
      
      ```typescript
      import { Layer } from "effect"
      import { FetchHttpClient } from "effect/unstable/http"
      import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"
      
      const Tracing = OtlpTracer.layer({
        url: "http://localhost:4318/v1/traces",
        resource: { serviceName: "my-service", serviceVersion: "1.0.0" }
      })
      
      const Logging = OtlpLogger.layer({
        url: "http://localhost:4318/v1/logs",
        resource: { serviceName: "my-service" }
      })
      
      export const Observability = Layer.merge(Tracing, Logging).pipe(
        Layer.provide(OtlpSerialization.layerJson), // or layerProtobuf
        Layer.provide(FetchHttpClient.layer)
      )
      
      const main = program.pipe(Effect.provide(Observability))
      ```
      
      An aggregator `Otlp.layer` also exists that bundles tracer + logger + metrics, but note its argument shape:
      
      ```typescript
      import { Otlp } from "effect/unstable/observability"
      
      // aggregator form - baseUrl, NOT url; serviceName under resource, NOT top-level
      const All = Otlp.layer({
        baseUrl: "http://localhost:4318",
        resource: { serviceName: "my-service" }
      }).pipe(
        Layer.provide(OtlpSerialization.layerJson),
        Layer.provide(FetchHttpClient.layer)
      )
      ```
      
      Do NOT use `Otlp.layer({ url, serviceName })` — neither field exists in that shape. Prefer the split-module form above; it is the pattern in `ai-docs/src/08_observability/20_otlp-tracing.ts`.
      
      The v3 `@effect/opentelemetry` NodeSdk path is not available in v4 — if you need the OpenTelemetry SDK stack you should stay on v3.
      
      ## Testing Observability
      
      ```typescript
      import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"
      
      const TestOtelLayer = NodeSdk.layer(() => ({
        resource: { serviceName: "test" },
        spanProcessor: new SimpleSpanProcessor(new InMemorySpanExporter())
      }))
      ```
      
      ## Log Level Configuration
      
      ```typescript
      import { Logger, LogLevel } from "effect"
      
      // Set minimum log level
      const withLogLevel = program.pipe(
        Logger.withMinimumLogLevel(LogLevel.Info)
      )
      
      // Custom logger
      const jsonLogger = Logger.make(({ logLevel, message, annotations }) => {
        console.log(JSON.stringify({ level: logLevel.label, message, ...annotations }))
      })
      
      const withCustomLogger = program.pipe(
        Effect.provide(Logger.replace(Logger.defaultLogger, jsonLogger))
      )
      ```
      
    • optics.md 1.8 KB
      # Optics
      
      `Optic` provides composable, type-safe access into immutable nested data — read, replace, and modify deeply nested fields without hand-written spreads. The v4 module is **instance/method-chain based** (a departure from v3's standalone-combinator `@fp-ts/optic` style): build an optic from `Optic.id<S>()` and chain `.key(...)` / `.at(...)`.
      
      ```typescript
      import { Optic } from "effect"
      ```
      
      ## Building and Using an Optic
      
      ```typescript
      interface State {
        readonly user: { readonly profile: { readonly age: number } }
      }
      
      // Focus a deeply nested field
      const ageOptic = Optic.id<State>().key("user").key("profile").key("age")
      
      const s: State = { user: { profile: { age: 30 } } }
      
      ageOptic.get(s)                 // 30
      const s2 = ageOptic.replace(31, s)        // { user: { profile: { age: 31 } } }
      const s3 = ageOptic.modify((n) => n + 1)(s) // modify returns (s) => s
      ```
      
      `Optic.id<S>()` is the identity optic over `S`. Chain to focus deeper:
      
      - `.key(k)` - a struct field (always present) -> a `Lens`
      - `.at(k)` - a map/array entry that may be absent -> an `Optional` (fails to focus if missing)
      - `.optionalKey(k)`, `.pick(...)`, `.omit(...)`, `.tag(...)` / `.refine(...)`, `.check(...)`, `.compose(other)`
      
      ## Reading vs. Fallible Focus
      
      `.get(s)` exists on lenses/isos (always-present focus). For optics that may fail to focus (`.at`, prisms), use `.getResult(s)`, which returns a `Result` (v4 renamed `Either` -> `Result`):
      
      ```typescript
      import { Optic, Result } from "effect"
      
      const firstTag = Optic.id<{ tags: ReadonlyArray<string> }>().key("tags").at(0)
      
      const r = firstTag.getResult({ tags: ["a", "b"] }) // Result<string, string>
      Result.isSuccess(r) // true
      ```
      
      `.replace` and `.modify` never throw: if the optic can't focus the target, they return the original `S` unchanged.
      
    • resource-management.md 7.5 KB
      # Resource Management
      
      Effect guarantees deterministic resource cleanup through `Scope`. Resources are released in LIFO order, even on failure or interruption.
      
      ## acquireRelease (Scoped Resource)
      
      Define a resource with its cleanup:
      
      ```typescript
      import { Effect } from "effect"
      
      const managedConnection = Effect.acquireRelease(
        // Acquire
        Effect.tryPromise(() => pool.connect()),
        // Release (always runs - success, failure, or interruption)
        (conn) => Effect.promise(() => conn.release())
      )
      
      // MUST wrap in Effect.scoped to trigger cleanup
      const program = Effect.scoped(
        Effect.gen(function*() {
          const conn = yield* managedConnection
          return yield* conn.query("SELECT * FROM users")
        })
      )
      ```
      
      ## acquireUseRelease (Inline Pattern)
      
      When you want acquire, use, and release in one expression:
      
      ```typescript
      const result = yield* Effect.acquireUseRelease(
        // Acquire
        Effect.tryPromise(() => pool.connect()),
        // Use
        (conn) => Effect.tryPromise(() => conn.query("SELECT 1")),
        // Release
        (conn) => Effect.promise(() => conn.release())
      )
      // No Effect.scoped needed - cleanup is handled inline
      ```
      
      ## Scope and LIFO Ordering
      
      Multiple resources are released in reverse acquisition order:
      
      ```typescript
      const program = Effect.scoped(
        Effect.gen(function*() {
          const db = yield* acquireDb()       // acquired first
          const cache = yield* acquireCache() // acquired second
          const file = yield* acquireFile()   // acquired third
          // ... use all three
        })
        // Cleanup order: file -> cache -> db (LIFO)
      )
      ```
      
      ## Scoped Layers
      
      Layers can manage resource lifecycles:
      
      ```typescript
      // v3
      const DatabaseLayer = Layer.scoped(
        Database,
        Effect.gen(function*() {
          const pool = yield* Effect.acquireRelease(
            Effect.tryPromise(() => createPool()),
            (pool) => Effect.promise(() => pool.end())
          )
          return { query: (sql) => Effect.tryPromise(() => pool.query(sql)) }
        })
      )
      
      // v4 — Layer.scoped is removed. Layer.effect strips Scope from R automatically.
      const DatabaseLayer = Layer.effect(
        Database,
        Effect.gen(function*() {
          const pool = yield* Effect.acquireRelease(
            Effect.tryPromise(() => createPool()),
            (pool) => Effect.promise(() => pool.end())
          )
          return { query: (sql) => Effect.tryPromise(() => pool.query(sql)) }
        })
      )
      // Pool is created when the layer is built
      // Pool is released when the program exits
      ```
      
      ## Background Fibers in Scopes
      
      ```typescript
      const program = Effect.scoped(
        Effect.gen(function*() {
          // This fiber is automatically interrupted when scope closes
          yield* Effect.forkScoped(
            Effect.repeat(healthCheck, Schedule.spaced("30 seconds"))
          )
          // Main work
          yield* serveRequests()
        })
      )
      ```
      
      ## ensuring - Always-Run Finalizer
      
      For cleanup that doesn't need the acquired resource:
      
      ```typescript
      const withCleanup = myEffect.pipe(
        Effect.ensuring(Effect.log("Done, regardless of outcome"))
      )
      ```
      
      ## addFinalizer - Manual Scope Registration
      
      ```typescript
      const program = Effect.scoped(
        Effect.gen(function*() {
          yield* Effect.addFinalizer((exit) =>
            Effect.log(`Exiting with: ${exit._tag}`)
          )
          // ... your logic
        })
      )
      ```
      
      ## Pooling Resources with `Pool`
      
      When a resource is expensive to create and you need many of them concurrently (DB connections, clients), pool them instead of acquiring per-use. `Pool.make` builds a fixed-size pool; `Pool.makeWithTTL` an elastic one that shrinks idle items. Both the pool and `Pool.get` require a `Scope` — the pool's lifetime is the scope it is built in.
      
      ```typescript
      import { Effect, Pool } from "effect"
      
      const acquireConn = Effect.acquireRelease(
        openConnection(),
        (conn) => Effect.promise(() => conn.close())
      )
      
      const program = Effect.scoped(
        Effect.gen(function*() {
          // Fixed-size pool of 10 connections, all created within this scope
          const pool = yield* Pool.make({ acquire: acquireConn, size: 10 })
      
          // Borrow inside its own scope so the item returns to the pool promptly
          const rows = yield* Effect.scoped(
            Pool.get(pool).pipe(
              Effect.flatMap((conn) => conn.query("SELECT 1"))
            )
          )
          return rows
        })
      )
      ```
      
      `Pool.get` hands back a **scoped** resource: wrap the borrow in `Effect.scoped` (or a child scope) so it is released back to the pool deterministically when that scope closes. Use `Pool.makeWithTTL({ acquire, min, max, timeToLive })` for elastic sizing, and `Pool.invalidate(pool, item)` to discard a known-bad item.
      
      ## Refreshable Values with `Resource`
      
      `Resource` caches the latest result of an acquisition and lets you refresh it — either on a `Schedule` (`Resource.auto`) or on demand (`Resource.manual` + `Resource.refresh`). Use it for values that go stale and must be re-fetched without tearing down dependents: rotating credentials, polled remote config, cached tokens. Distinct from `Pool` (many concurrent instances) and `Cache` (keyed lookups) — a `Resource` is one value that periodically renews.
      
      ```typescript
      import { Effect, Resource, Schedule } from "effect"
      
      const program = Effect.scoped(
        Effect.gen(function*() {
          // Re-acquire the token every 50 minutes in the background
          const token = yield* Resource.auto(
            fetchAuthToken,                       // Effect<Token, AuthError>
            Schedule.spaced("50 minutes")
          )
      
          // Read the current cached value (fails with the stored error if the last acquire failed)
          const current = yield* Resource.get(token)
      
          // Force an immediate refresh when needed
          yield* Resource.refresh(token)
        })
      )
      ```
      
      `Resource.auto(acquire, policy)` forks the refresh loop into the surrounding scope; `Resource.manual(acquire)` skips the schedule so you control refresh timing entirely via `Resource.refresh`. Both require a `Scope`.
      
      ## Reference-Counted Resources (`RcRef` / `RcMap`)
      
      When several concurrent consumers should *share* a single expensive resource that is acquired on first use and released once the last user is done, use an `RcRef` (single resource) or `RcMap` (one per key). Each `get` increments a reference count scoped to the current `Scope`; when the count hits zero the resource is released, optionally after an `idleTimeToLive` grace period so a quick re-acquire reuses it.
      
      ```typescript
      import { Effect, RcRef, RcMap } from "effect"
      
      // Single shared resource: one connection behind many borrowers
      const shared = Effect.gen(function*() {
        const ref = yield* RcRef.make({
          acquire: Effect.acquireRelease(openConnection, closeConnection),
          idleTimeToLive: "5 seconds"
        })
      
        // Each borrow is scoped; the connection is opened once and reused
        yield* Effect.scoped(Effect.gen(function*() {
          const conn = yield* RcRef.get(ref)
          yield* useConnection(conn)
        }))
      })
      
      // Keyed variant: one shared resource per key (e.g. per host)
      const perKey = Effect.gen(function*() {
        const clients = yield* RcMap.make({
          lookup: (host: string) => Effect.acquireRelease(connect(host), disconnect),
          idleTimeToLive: "30 seconds"
        })
      
        yield* Effect.scoped(Effect.gen(function*() {
          const client = yield* RcMap.get(clients, "api.example.com")
          yield* client.ping()
        }))
      })
      ```
      
      `RcMap.make` also accepts a `capacity`; exceeding it fails with `Cause.ExceededCapacityError`. Prefer `RcRef`/`RcMap` over `Pool` when consumers should transparently *share* one live instance rather than each borrow a distinct one from a pool.
      
      ## v4: Scope Changes
      
      In v4, `Scope` remains conceptually the same. Key change: `Effect.forkScoped` behavior is unchanged, but `Effect.fork` is renamed to `Effect.forkChild` (which is NOT scope-tied - use `Effect.forkScoped` for scope-tied fibers).
      
    • retry-scheduling.md 6.8 KB
      # Retry and Scheduling
      
      Effect's `Schedule` module provides composable, typed retry and repetition policies.
      
      ## Basic Retry
      
      ```typescript
      import { Effect, Schedule } from "effect"
      
      // Retry up to 3 times
      const retried = Effect.retry(unstableOp, Schedule.recurs(3))
      
      // Retry with exponential backoff
      const retried = Effect.retry(unstableOp, Schedule.exponential("100 millis"))
      
      // Retry with exponential backoff + jitter + max retries (v3)
      const policy = Schedule.exponential("200 millis", 2).pipe(
        Schedule.compose(Schedule.recurs(5)),
        Schedule.jittered
      )
      const retried = Effect.retry(unstableOp, policy)
      
      // v4 — Schedule.compose is removed. Use Schedule.take(n) to bound by attempts:
      const policyV4 = Schedule.exponential("200 millis", 2).pipe(
        Schedule.take(5),
        Schedule.jittered
      )
      const retriedV4 = Effect.retry(unstableOp, policyV4)
      
      // Version-agnostic alternative: pass `times` in the retry options object
      const retriedAny = Effect.retry(unstableOp, {
        schedule: Schedule.exponential("200 millis", 2).pipe(Schedule.jittered),
        times: 5
      })
      ```
      
      ## Built-In Schedules
      
      | Schedule                        | Behavior                                      |
      |---------------------------------|-----------------------------------------------|
      | `Schedule.recurs(n)`           | Retry/repeat up to n times                     |
      | `Schedule.spaced("1 second")`  | Fixed spacing between iterations               |
      | `Schedule.exponential("100 millis")` | Exponential backoff (doubles each time)  |
      | `Schedule.exponential("100 millis", 1.5)` | Custom growth factor             |
      | `Schedule.fibonacci("100 millis")` | Fibonacci backoff                          |
      | `Schedule.fixed("5 seconds")`  | Fixed interval (accounts for elapsed time)     |
      | `Schedule.forever`             | Repeat indefinitely                            |
      | `Schedule.once` (v3 only)      | Run once more — v4: use `Schedule.recurs(0)`   |
      | `Schedule.jittered`            | Add randomness (combine with other schedules)  |
      | `Schedule.take(n)`             | Bound any schedule to n iterations (v4 idiom)  |
      | `Schedule.cron("0 * * * *")`  | Cron-based scheduling                          |
      
      ## Composing Schedules
      
      ```typescript
      // Sequential: first policy, then second (recurs 3 times, then exponential)
      const sequential = Schedule.recurs(3).pipe(
        Schedule.andThen(Schedule.exponential("1 second"))
      )
      
      // Intersection: both constraints must be satisfied
      // (exponential backoff, but max 5 retries)
      // v3: Schedule.compose
      const bounded = Schedule.exponential("100 millis").pipe(
        Schedule.compose(Schedule.recurs(5))
      )
      
      // v4: Schedule.compose is removed. Use Schedule.take(n) for the same intent.
      const boundedV4 = Schedule.exponential("100 millis").pipe(
        Schedule.take(5)
      )
      
      // Union: either constraint can trigger
      const either = Schedule.spaced("1 second").pipe(
        Schedule.either(Schedule.recurs(10))
      )
      ```
      
      ## Conditional Retry (only on specific errors)
      
      ```typescript
      // Retry only on retryable errors
      // Pass `times` in the options object (works in both v3 and v4); the schedule
      // itself just controls delay shape.
      const retried = Effect.retry(fetchFromApi, {
        schedule: Schedule.exponential("200 millis").pipe(Schedule.jittered),
        times: 4,
        while: (error) => error._tag === "NetworkError" || error._tag === "RateLimitError"
      })
      
      // Or using until (inverse condition)
      const retriedUntil = Effect.retry(fetchFromApi, {
        schedule: Schedule.exponential("200 millis").pipe(Schedule.jittered),
        times: 4,
        until: (error) => error._tag === "AuthError" // stop retrying on auth errors
      })
      ```
      
      ## Repetition (success-based)
      
      ```typescript
      // Repeat an effect 5 times
      const repeated = Effect.repeat(pollStatus, Schedule.recurs(5))
      
      // Repeat every second forever
      const polling = Effect.repeat(checkHealth, Schedule.spaced("1 second"))
      
      // Repeat until a condition is met
      const waitForReady = Effect.repeat(checkStatus, {
        until: (status) => status === "ready"
      })
      ```
      
      ## Timeout
      
      ```typescript
      // Timeout after 5 seconds (returns Option - None on timeout)
      const withTimeout = Effect.timeout(slowOp, "5 seconds")
      
      // Timeout with fallback
      const withFallback = Effect.timeoutTo(slowOp, {
        duration: "5 seconds",
        onTimeout: () => Effect.succeed(defaultValue)
      })
      
      // Timeout that fails
      const withError = Effect.timeoutFail(slowOp, {
        duration: "5 seconds",
        onTimeout: () => new TimeoutError({ message: "operation timed out" })
      })
      ```
      
      ## Combining Retry + Timeout
      
      ```typescript
      const resilient = fetchFromUpstream(params).pipe(
        Effect.timeout("10 seconds"),
        // Version-agnostic retry shape — `times` caps the attempts and works in v3 + v4.
        Effect.retry({
          schedule: Schedule.exponential("200 millis").pipe(Schedule.jittered),
          times: 3
        }),
        Effect.withSpan("upstream.fetch")
      )
      ```
      
      ## HttpClient.retryTransient (v3 @effect/platform)
      
      For HTTP calls, `@effect/platform` provides a built-in retry for transient failures (connection errors, 429, 503):
      
      ```typescript
      import { HttpClient } from "@effect/platform"
      
      // v3 — Schedule.compose still works
      const resilientClient = HttpClient.retryTransient({
        schedule: Schedule.exponential("200 millis").pipe(
          Schedule.compose(Schedule.recurs(3))
        )
      })
      
      // v4 (effect/unstable/http) — use Schedule.take(n) instead of compose
      const resilientClientV4 = HttpClient.retryTransient({
        schedule: Schedule.exponential("200 millis").pipe(Schedule.take(3))
      })
      ```
      
      ## Ordered Fallback (ExecutionPlan)
      
      When retrying the *same* effect isn't enough and you need to fall back to a different resource (backup provider, replica DB, alternate region), use `ExecutionPlan` instead of a bare `Schedule`. Each step provides its own `Layer` with optional `attempts` + `schedule`, and `Effect.withExecutionPlan(effect, plan)` walks the steps until one succeeds. See `references/effect-ai.md` for a full example.
      
      ## RateLimiter
      
      ```typescript
      // v3 — top-level module with make/withCost
      import { RateLimiter } from "effect"
      
      const limiter = yield* RateLimiter.make({ limit: 10, interval: "1 second" })
      const limited = RateLimiter.withCost(limiter, 1)(fetchFromApi(params))
      ```
      
      ```typescript
      // v4 — moved to effect/unstable/persistence and uses a Service-based API.
      // There is no withCost; per-call cost is the `tokens` option on consume.
      import { Effect, Layer } from "effect"
      import { RateLimiter } from "effect/unstable/persistence"
      
      const program = Effect.gen(function*() {
        const withLimiter = yield* RateLimiter.makeWithRateLimiter
        return yield* fetchFromApi(params).pipe(
          withLimiter({
            key: "fetchFromApi",
            limit: 10,
            window: "1 second",
            algorithm: "fixed-window",
            onExceeded: "delay",
            tokens: 1 // per-call cost
          })
        )
      })
      
      // Provide the in-memory store + RateLimiter service
      const Live = RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))
      const main = program.pipe(Effect.provide(Live))
      ```
      
    • rpc.md 2.8 KB
      # RPC
      
      `effect/unstable/rpc` provides typed, Schema-validated client/server RPC. Define requests once as an `RpcGroup`; implement them as a handler layer on the server; call them as typed methods on the client. Transport (HTTP, WebSocket, worker) and serialization (JSON, NDJSON, MsgPack) are pluggable layers. (Unstable module, `@since 4.0.0`.)
      
      ```typescript
      import { Rpc, RpcGroup, RpcServer, RpcClient, RpcSerialization } from "effect/unstable/rpc"
      ```
      
      ## Define a Group
      
      ```typescript
      import { Schema } from "effect"
      import { Rpc, RpcGroup } from "effect/unstable/rpc"
      
      class User extends Schema.Class<User>("User")({
        id: Schema.String,
        name: Schema.String
      }) {}
      
      const UserRpcs = RpcGroup.make(
        Rpc.make("GetUser", { payload: { id: Schema.String }, success: User }),
        Rpc.make("ListUsers", { success: Schema.Array(User), stream: true })
      )
      ```
      
      `Rpc.make(tag, { payload?, success?, error?, stream?, primaryKey? })` — `payload` accepts struct fields or a Schema; `stream: true` makes the result a stream of `success`.
      
      ## Implement Handlers (server)
      
      ```typescript
      import { Effect, Layer, Stream } from "effect"
      import { RpcServer } from "effect/unstable/rpc"
      
      const UsersLive = UserRpcs.toLayer(
        UserRpcs.of({
          GetUser: ({ id }) => Effect.succeed(new User({ id, name: "Ada" })),
          ListUsers: () => Stream.fromIterable([new User({ id: "1", name: "Ada" })])
        })
      )
      
      // Server = handlers + protocol + serialization
      const ServerLive = RpcServer.layer(UserRpcs).pipe(
        Layer.provide(UsersLive),
        Layer.provide(RpcSerialization.layerJson)
        // + a protocol layer, e.g. RpcServer.layerProtocolHttp({ path: "/rpc" })
      )
      ```
      
      `group.toLayer(handlers)` builds the handler layer; `group.of({...})` is an identity helper that gives the handler object its types. Handler keys are the rpc tags.
      
      ## Call from the Client
      
      ```typescript
      import { Effect } from "effect"
      import { RpcClient } from "effect/unstable/rpc"
      
      const program = Effect.gen(function*() {
        const client = yield* RpcClient.make(UserRpcs)
        const user = yield* client.GetUser({ id: "1" }) // typed call by tag
        return user
      })
      
      // Wire the client transport: e.g.
      // RpcClient.layerProtocolHttp({ url }) + RpcSerialization.layerJson + HttpClient.HttpClient
      ```
      
      `RpcClient.make(group)` returns a client whose methods are keyed by rpc tag. Streaming rpcs return a `Stream`.
      
      ## Transports and Testing
      
      - Serialization: `RpcSerialization.layerJson` / `layerNdjson` / `layerMsgPack` / `layerJsonRpc()`.
      - Server protocols: `RpcServer.layerProtocolHttp`, `layerProtocolWebsocket`, `layerProtocolSocketServer`, `layerProtocolWorkerRunner`.
      - Client protocols: `RpcClient.layerProtocolHttp`, `layerProtocolWorker({ size })` (a worker-thread pool — see `references/concurrency.md`).
      - Testing without a transport: `RpcTest.makeClient(group)` runs the group in-memory.
      
    • schema.md 7.2 KB
      # Schema
      
      Effect Schema provides bidirectional validation: decode (external input -> typed data) and encode (typed data -> wire format). Always import from `"effect"`, not `"@effect/schema"`.
      
      ```typescript
      import { Schema } from "effect"
      ```
      
      ## Defining Schemas
      
      ```typescript
      const User = Schema.Struct({
        id: Schema.String,
        name: Schema.String,
        age: Schema.Number,
        email: Schema.optionalWith(Schema.String, { exact: true }) // v3
        // v4: Schema.optionalKey(Schema.String)
      })
      
      // Infer the TypeScript type
      type User = typeof User.Type
      // { id: string; name: string; age: number; email?: string }
      ```
      
      ## Common Schema Types
      
      | Schema                       | Decoded Type                    |
      |------------------------------|---------------------------------|
      | `Schema.String`              | `string`                        |
      | `Schema.Number`              | `number`                        |
      | `Schema.Boolean`             | `boolean`                       |
      | `Schema.Literal("a", "b")`  | `"a" \| "b"` (v3 variadic)     |
      | `Schema.Literals(["a","b"])`| `"a" \| "b"` (v4 array)        |
      | `Schema.Array(Schema.String)`| `string[]`                     |
      | `Schema.NullOr(Schema.String)` | `string \| null`             |
      | `Schema.Union(A, B)` (v3)   | `A \| B`                       |
      | `Schema.Union([A, B])` (v4) | `A \| B`                       |
      | `Schema.Record(Schema.String, Schema.Number)` | `Record<string, number>` (v4) |
      | `Schema.NumberFromString`    | string on wire, number decoded  |
      | `Schema.DateFromString`      | string on wire, Date decoded    |
      
      ## Decoding (Parse)
      
      ```typescript
      // Sync (throws on failure)
      const user = Schema.decodeUnknownSync(User)(rawData)
      
      // Effect-based (typed error)
      // v3:
      const user = yield* Schema.decodeUnknown(User)(rawData)
      // v4:
      const user = yield* Schema.decodeUnknownEffect(User)(rawData)
      
      // Returns Exit instead of throwing
      // v3: Schema.decodeUnknownEither(User)(rawData)
      // v4: Schema.decodeUnknownExit(User)(rawData)
      ```
      
      ## Encoding
      
      ```typescript
      // Sync
      const json = Schema.encodeSync(User)(user)
      
      // Effect-based
      // v3: yield* Schema.encode(User)(user)
      // v4: yield* Schema.encodeEffect(User)(user)
      ```
      
      ## Construction (v4)
      
      Every v4 schema exposes three constructor methods. v3's `Schema.makeUnsafe` no longer exists.
      
      ```typescript
      // Throws SchemaError on invalid input
      const user = User.make({ id: "1", name: "Alice", age: 30 })
      
      // Returns Option<Type>
      const maybeUser = User.makeOption({ id: "1", name: "Alice", age: 30 })
      
      // Returns Effect<Type, SchemaError>
      const userEffect = User.makeEffect({ id: "1", name: "Alice", age: 30 })
      ```
      
      ## Error Shape (v4)
      
      Decode/encode/construct failures are `SchemaError` instances carrying a `SchemaIssue.Issue` on the `.issue` field. Narrow thrown errors with `Schema.isSchemaError` and format issues via the `SchemaIssue` module. v3's `ParseResult` module is gone.
      
      Since beta.84, `SchemaError` extends `Data.TaggedError` — it is a native `Error` with `_tag: "SchemaError"`, so within Effect code you can also branch on it with `Effect.catchTag("SchemaError", ...)` instead of a `try`/`catch`. The `SchemaParser` Promise adapters reject an `Error` whose `cause` is the underlying `SchemaIssue.Issue`.
      
      ```typescript
      import { Schema, SchemaIssue } from "effect"
      
      try {
        Schema.decodeUnknownSync(User)(badInput)
      } catch (e) {
        if (Schema.isSchemaError(e) && SchemaIssue.isIssue(e.issue)) {
          const issues = SchemaIssue.makeFormatterStandardSchemaV1()(e.issue).issues
          console.error(issues)
        }
      }
      ```
      
      ## Filters and Validation
      
      ### v3
      
      ```typescript
      const PositiveAge = Schema.Number.pipe(
        Schema.positive(),
        Schema.int()
      )
      
      const ShortString = Schema.String.pipe(
        Schema.minLength(1),
        Schema.maxLength(100)
      )
      ```
      
      ### v4
      
      ```typescript
      const PositiveAge = Schema.Number.check(
        Schema.isGreaterThan(0),
        Schema.isInt()
      )
      
      const ShortString = Schema.String.check(
        Schema.isMinLength(1),
        Schema.isMaxLength(100)
      )
      ```
      
      ## Tagged Error Classes
      
      ### v3
      
      ```typescript
      import { Data } from "effect"
      
      class NotFoundError extends Data.TaggedError("NotFoundError")<{
        readonly id: string
      }> {}
      ```
      
      ### v4
      
      ```typescript
      import { Schema } from "effect"
      
      class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("NotFoundError", {
        id: Schema.String
      }) {}
      ```
      
      ## JSON Schema Generation
      
      Effect Schema generates **JSON Schema Draft-07** (not 2020-12):
      
      ```typescript
      import { JSONSchema, Schema } from "effect"
      
      const jsonSchema = JSONSchema.make(User)
      // { "$schema": "http://json-schema.org/draft-07/schema#", ... }
      ```
      
      ## Struct Operations
      
      ### v3
      
      ```typescript
      const Picked = User.pipe(Schema.pick("id", "name"))
      const Omitted = User.pipe(Schema.omit("age"))
      const Extended = User.pipe(Schema.extend(Schema.Struct({ role: Schema.String })))
      const Partial = User.pipe(Schema.partial)
      ```
      
      ### v4
      
      ```typescript
      import { Struct } from "effect"
      
      const Picked = User.mapFields(Struct.pick(["id", "name"]))
      const Omitted = User.mapFields(Struct.omit(["age"]))
      const Extended = User.mapFields(Struct.assign({ role: Schema.String }))
      const Partial = User.mapFields(Struct.map(Schema.optional))
      ```
      
      ## Transforms
      
      ### v3
      
      ```typescript
      const BoolFromString = Schema.transform(
        Schema.Literal("on", "off"),
        Schema.Boolean,
        {
          decode: (s) => s === "on",
          encode: (b) => b ? "on" : "off"
        }
      )
      ```
      
      ### v4
      
      ```typescript
      import { SchemaTransformation } from "effect"
      
      const BoolFromString = Schema.Literals(["on", "off"]).pipe(
        Schema.decodeTo(
          Schema.Boolean,
          SchemaTransformation.transform({
            decode: (s) => s === "on",
            encode: (b) => b ? "on" : "off"
          })
        )
      )
      ```
      
      ## Branded Types (nominal typing)
      
      Brands give primitives compile-time identity so a `UserId` can't be passed where an `OrderId` is expected, even though both are `string`. Two routes:
      
      **Inside a schema** — `Schema.brand` tags the decoded type:
      
      ```typescript
      const UserId = Schema.Number.pipe(Schema.brand("UserId"))
      type UserId = typeof UserId.Type // number & Brand<"UserId">
      ```
      
      **Standalone** — the `Brand` module builds constructors independent of Schema:
      
      ```typescript
      import { Brand, Schema } from "effect"
      
      type UserId = number & Brand.Brand<"UserId">
      
      // nominal: no runtime validation, just a type-level tag
      const UserId = Brand.nominal<UserId>()
      const id = UserId(123) // UserId
      
      // check: validate from schema checks (throws on failure; .option / .result / .is don't)
      type PositiveInt = number & Brand.Brand<"PositiveInt">
      const PositiveInt = Brand.check<PositiveInt>(Schema.isInt(), Schema.isGreaterThan(0))
      const ok = PositiveInt(5)          // throws if invalid
      const maybe = PositiveInt.option(-1) // Option<PositiveInt> (None here)
      
      // make: validate from a custom predicate (return true/undefined to pass, a string to reject)
      type Even = number & Brand.Brand<"Even">
      const Even = Brand.make<Even>((n) => n % 2 === 0 || `${n} is not even`)
      ```
      
      `Brand.all(A, B)` combines constructors. (Note: v4 uses `Brand.check`/`Brand.make`, not v3's `Brand.refined`/`Brand.error`.)
      
      Use `Schema.brand` when the value already flows through decoding; reach for the standalone `Brand` module for domain primitives constructed in plain code.
      
      ## TypeScript Configuration
      
      Effect Schema requires `strict: true` in tsconfig. Optionally enable `exactOptionalPropertyTypes: true` for precise optional field handling.
      
    • sql.md 4.9 KB
      # SQL
      
      Effect's SQL toolkit gives typed, composable database access with tagged-template queries, Schema-validated decoding, models, and migrations. Core modules live under `effect/unstable/sql/*`; concrete drivers ship as `@effect/sql-*` packages.
      
      > Key fact: a tagged-template query **is an Effect** — `Statement<A> extends Effect<ReadonlyArray<A>, SqlError>`. You `yield*` it directly; there is no `.execute()`.
      
      ## Running Queries
      
      ```typescript
      import { Effect } from "effect"
      import { SqlClient } from "effect/unstable/sql/SqlClient"
      
      interface User {
        readonly id: number
        readonly name: string
      }
      
      const program = Effect.gen(function*() {
        const sql = yield* SqlClient.SqlClient
      
        const id = 1
        const users = yield* sql<User>`SELECT * FROM users WHERE id = ${id}` // ReadonlyArray<User>
      
        yield* sql`INSERT INTO users ${sql.insert({ name: "Alice" })}`
      
        return users
      })
      ```
      
      Interpolated values (`${id}`) are bound parameters, not string-concatenated — safe from injection.
      
      ### Statement helpers (on `sql`)
      
      `sql.insert(record | records)`, `sql.update(record)`, `sql.updateValues(rows, alias)`, `sql.in(values)` / `sql.in(col, values)`, `sql.and(clauses)`, `sql.or(clauses)`, `sql.csv(values)`, `sql.unsafe(raw, params?)`, `sql.literal(raw)`, `sql.onDialect({ pg, sqlite, ... })`.
      
      ### Transactions
      
      ```typescript
      yield* sql.withTransaction(
        Effect.gen(function*() {
          yield* sql`INSERT INTO accounts ${sql.insert({ id: 1, balance: 100 })}`
          yield* sql`UPDATE accounts SET balance = balance - 50 WHERE id = ${1}`
        })
      ) // nested withTransaction uses savepoints
      ```
      
      ## Schema-Validated Queries (`SqlSchema`)
      
      Derive queries that decode rows through an Effect `Schema`. Constructors: `SqlSchema.findAll`, `SqlSchema.findNonEmpty`, `SqlSchema.findOne`, `SqlSchema.findOneOption`, `SqlSchema.void` (there is **no `single`**).
      
      ```typescript
      import { Effect, Schema } from "effect"
      import { SqlClient } from "effect/unstable/sql/SqlClient"
      import * as SqlSchema from "effect/unstable/sql/SqlSchema"
      
      const getUserById = Effect.gen(function*() {
        const sql = yield* SqlClient.SqlClient
        return SqlSchema.findOne({
          Request: Schema.Number,
          Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
          execute: (id) => sql`SELECT id, name FROM users WHERE id = ${id}`
        })
      })
      // findOne fails with NoSuchElementError if no row; findOneOption returns Option
      ```
      
      ## Models and Repositories
      
      `Model.Class` (from `effect/unstable/schema`) defines a table-backed schema with insert/update/json variants. Use `Model.GeneratedByDb` for DB-generated columns (e.g. auto-increment ids) — note this replaced `Model.Generated` (renamed in beta.68). `SqlModel.makeRepository` derives CRUD operations.
      
      ```typescript
      import { Effect, Schema } from "effect"
      import { Model } from "effect/unstable/schema"
      import * as SqlModel from "effect/unstable/sql/SqlModel"
      
      const UserId = Schema.Number.pipe(Schema.brand("UserId"))
      
      class User extends Model.Class<User>("User")({
        id: Model.GeneratedByDb(UserId),
        name: Schema.String,
        createdAt: Model.DateTimeInsertFromDate,
        updatedAt: Model.DateTimeUpdateFromDate
      }) {}
      
      const makeUserRepo = SqlModel.makeRepository(User, {
        tableName: "users",
        spanPrefix: "UserRepo",
        idColumn: "id"
      })
      // yields { insert, insertVoid, update, updateVoid, findById, delete }
      ```
      
      `SqlModel` exports only `makeRepository` and `makeResolvers` (both return Effects requiring `SqlClient`).
      
      ## Migrations (`Migrator`)
      
      `Migrator.make({ ... })` runs pending migrations in a transaction. Loaders: `Migrator.fromFileSystem(dir)`, `fromGlob`, `fromBabelGlob`, `fromRecord`. Driver packages expose a ready layer (e.g. `PgMigrator.layer`).
      
      ## Providing a Driver
      
      Concrete `@effect/sql-*` packages: `@effect/sql-pg`, `-pglite`, `-mysql2`, `-mssql`, `-clickhouse`, `-libsql`, `-d1`, and SQLite variants (`-sqlite-node`, `-sqlite-bun`, `-sqlite-wasm`, ...). Each layer provides both its specific client and the generic `SqlClient`.
      
      ```typescript
      import { Effect, Redacted } from "effect"
      import { PgClient } from "@effect/sql-pg"
      
      const PgLive = PgClient.layer({
        host: "localhost",
        port: 5432,
        database: "app",
        username: "postgres",
        password: Redacted.make("secret"), // password/url are Redacted
        maxConnections: 10
      })
      
      const runnable = program.pipe(Effect.provide(PgLive))
      ```
      
      Use `PgClient.layerConfig(...)` to source the connection from `Config` instead of literals.
      
      ## Errors (`SqlError`)
      
      Every statement fails with `SqlError` (`_tag: "SqlError"`) carrying a `reason`. **Match on `error.reason._tag`, not the top-level `_tag`** (which is always `"SqlError"`):
      
      ```typescript
      program.pipe(
        Effect.catchTag("SqlError", (e) =>
          e.reason._tag === "UniqueViolation"
            ? Effect.succeed("duplicate")
            : Effect.fail(e)
        )
      )
      ```
      
      Reasons include `ConnectionError`, `UniqueViolation` (carries `constraint`), `ConstraintError`, `DeadlockError`, `SerializationError`, `*TimeoutError`, `SqlSyntaxError`, `Authentication`/`AuthorizationError`. Each exposes `isRetryable` for retry policies.
      
    • stm.md 2.4 KB
      # Software Transactional Memory (STM)
      
      Effect's transactional layer lets you compose atomic updates across multiple mutable cells with no locks and no race conditions. Transactions are **optimistic with retry** (MVCC-style: versions are checked at commit; on conflict the whole block re-runs), not lock-based.
      
      The driver is `Effect.tx` — there is **no `Effect.atomic` and no `Effect.transaction`**. Transactional cells are the `Tx*` modules (`TxRef`, `TxQueue`, `TxHashMap`, `TxChunk`, `TxSemaphore`, ...).
      
      ```typescript
      import { Effect, TxRef } from "effect"
      ```
      
      ## Atomic Updates Across Cells
      
      ```typescript
      const transfer = Effect.gen(function*() {
        const from = yield* TxRef.make(100)
        const to = yield* TxRef.make(0)
      
        // Both writes commit together, or neither does
        yield* Effect.tx(
          Effect.gen(function*() {
            yield* TxRef.update(from, (n) => n - 50)
            yield* TxRef.update(to, (n) => n + 50)
          })
        )
      
        return [yield* TxRef.get(from), yield* TxRef.get(to)] // [50, 50]
      })
      ```
      
      - `Effect.tx(effect)` is the transaction **boundary** — the outermost call commits/rolls back atomically. Nested `Effect.tx` calls compose into the same journal (they do not start a separate transaction).
      - `TxRef` operations: `TxRef.make`, `TxRef.get`, `TxRef.set`, `TxRef.update`, `TxRef.modify`. Inside a `tx` block they read/write the transaction journal; outside one they run in a singleton transaction.
      
      ## Waiting for a Condition (`Effect.txRetry`)
      
      To block until some transactional state changes, call `Effect.txRetry`. It marks the transaction for retry and suspends until one of the `TxRef`s it read is modified, then re-runs the block:
      
      ```typescript
      import { Effect, TxRef } from "effect"
      
      // Wait until the queue ref is non-empty, then take one item
      const takeWhenReady = (queue: TxRef.TxRef<ReadonlyArray<string>>) =>
        Effect.tx(
          Effect.gen(function*() {
            const items = yield* TxRef.get(queue)
            if (items.length === 0) {
              return yield* Effect.txRetry // suspend; re-run when `queue` changes
            }
            yield* TxRef.set(queue, items.slice(1))
            return items[0]
          })
        )
      ```
      
      ## Transactional Collections
      
      Beyond `TxRef`, the same atomicity applies to: `TxQueue`, `TxPubSub`, `TxSubscriptionRef`, `TxHashMap`, `TxHashSet`, `TxChunk`, `TxPriorityQueue`, `TxDeferred`, `TxSemaphore`, `TxReentrantLock`. Compose operations on any mix of these inside a single `Effect.tx` for all-or-nothing semantics.
      
    • streams.md 5 KB
      # Streams
      
      Effect Streams are pull-based, lazily evaluated sequences of values with typed errors and resource safety.
      
      ## Creating Streams
      
      ```typescript
      import { Stream } from "effect"
      
      // From an iterable
      const fromArray = Stream.fromIterable([1, 2, 3])
      
      // From a single effect
      const fromEffect = Stream.fromEffect(fetchUser(id))
      
      // From repeated effect on a schedule
      const polling = Stream.fromEffectSchedule(
        checkStatus,
        Schedule.spaced("1 second")
      )
      
      // Paginated API
      const pages = Stream.paginate(1, (page) => [
        fetchPage(page),
        page < totalPages ? Option.some(page + 1) : Option.none()
      ])
      
      // From an async iterable
      const fromAsync = Stream.fromAsyncIterable(
        asyncIterator,
        (err) => new StreamError({ cause: err })
      )
      
      // Callback-based (SSE, WebSocket, etc.)
      const fromCallback = Stream.async<string, never>((emit) => {
        source.on("data", (chunk) => emit.single(chunk))
        source.on("end", () => emit.end())
        source.on("error", (err) => emit.fail(new StreamError({ cause: err })))
      })
      ```
      
      ## Transforming Streams
      
      ```typescript
      const transformed = myStream.pipe(
        Stream.map((x) => x * 2),
        Stream.filter((x) => x > 10),
        Stream.take(5),
        Stream.tap((x) => Effect.log(`Processing: ${x}`))
      )
      
      // Effectful transform
      const enriched = Stream.mapEffect(myStream, (item) =>
        fetchDetails(item.id),
        { concurrency: 5 }
      )
      
      // FlatMap (one-to-many)
      const expanded = Stream.flatMap(usersStream, (user) =>
        Stream.fromIterable(user.posts)
      )
      ```
      
      ## Consuming Streams
      
      ```typescript
      // Collect all into an array
      const items = yield* Stream.runCollect(myStream)
      
      // Fold/reduce
      const sum = yield* Stream.runFold(myStream, 0, (acc, x) => acc + x)
      
      // Process each item
      yield* Stream.runForEach(myStream, (item) => processItem(item))
      
      // Run to completion, discard results
      yield* Stream.runDrain(myStream)
      
      // Using Sink for reusable consumption patterns
      import { Sink } from "effect"
      const count = yield* Stream.run(myStream, Sink.count)
      ```
      
      ## Queue-to-Stream Bridge
      
      ```typescript
      import { Queue, Stream } from "effect"
      
      const queue = yield* Queue.bounded<string>(100)
      
      // Convert queue to stream (stream pulls from queue)
      const stream = Stream.fromQueue(queue)
      
      // Process stream in background, offer to queue from producers
      yield* Effect.forkScoped(
        Stream.runForEach(stream, (msg) => handleMessage(msg))
      )
      
      // Producers offer to queue
      yield* Queue.offer(queue, "new message")
      ```
      
      ## PubSub (Fan-out)
      
      ```typescript
      import { PubSub, Stream } from "effect"
      
      const pubsub = yield* PubSub.bounded<Event>(100)
      
      // Subscribe returns a Queue (each subscriber gets all messages)
      const subscription = yield* PubSub.subscribe(pubsub)
      const stream = Stream.fromQueue(subscription)
      
      // Publish
      yield* PubSub.publish(pubsub, { type: "user_created", userId: "123" })
      ```
      
      ## Chunked Processing
      
      Streams process data in chunks for efficiency:
      
      ```typescript
      // Group into fixed-size chunks
      const chunked = Stream.grouped(myStream, 100)
      
      // Group by time window
      const windowed = Stream.groupedWithin(myStream, 100, "5 seconds")
      ```
      
      ## Error Handling in Streams
      
      ```typescript
      // v3 — Schedule.compose
      const resilient = myStream.pipe(
        Stream.retry(Schedule.exponential("1 second").pipe(
          Schedule.compose(Schedule.recurs(3))
        )),
        Stream.catchAll((error) => Stream.fromIterable(fallbackData))
      )
      
      // v4 — Schedule.compose is removed. Use Schedule.take(n).
      const resilientV4 = myStream.pipe(
        Stream.retry(Schedule.exponential("1 second").pipe(Schedule.take(3))),
        Stream.catchAll((error) => Stream.fromIterable(fallbackData))
      )
      ```
      
      ## SSE / Streaming HTTP Pattern
      
      For bridging HTTP SSE streams into Effect Streams:
      
      ```typescript
      // Using HttpClient to consume SSE
      const sseStream = Effect.gen(function*() {
        const client = yield* HttpClient.HttpClient
        const response = yield* client.execute(
          HttpClientRequest.get(url)
        )
        return response.stream.pipe(
          Stream.decodeText(),
          Stream.splitLines,
          Stream.filter((line) => line.startsWith("data: ")),
          Stream.map((line) => JSON.parse(line.slice(6)))
        )
      })
      ```
      
      ## Framing Streams (NDJSON / MessagePack)
      
      To encode or decode a stream of structured values over a byte stream, pipe it through a `Channel` from the `effect/unstable/encoding` modules with `Stream.pipeThroughChannel`. `Ndjson` frames newline-delimited JSON; `Msgpack` frames MessagePack; `Sse` handles server-sent events. Each module exposes `decode`/`encode` (raw) and `decodeSchema`/`encodeSchema` (validate against a `Schema`) channels.
      
      ```typescript
      import { Schema, Stream } from "effect"
      import { Ndjson } from "effect/unstable/encoding"
      
      const Event = Schema.Struct({ id: Schema.Number, kind: Schema.String })
      
      // Decode a byte stream of newline-delimited JSON into typed values
      const events = byteStream.pipe(
        Stream.pipeThroughChannel(Ndjson.decodeSchema(Event))
      )
      
      // Encode typed values back into an NDJSON byte stream
      const bytes = typedStream.pipe(
        Stream.pipeThroughChannel(Ndjson.encodeSchema(Event))
      )
      ```
      
      For RPC transport framing (a different use), NDJSON/MessagePack are provided as serialization layers — see `references/rpc.md`.
      
    • testing.md 4.7 KB
      # Testing Effect Code
      
      ## @effect/vitest (Recommended)
      
      ```typescript
      import { it } from "@effect/vitest"
      import { Effect } from "effect"
      
      // Effect-based test
      it.effect("fetches user", () =>
        Effect.gen(function*() {
          const user = yield* fetchUser("123")
          expect(user.name).toBe("Alice")
        })
      )
      
      // With layers
      it.effect("fetches user with db", () =>
        Effect.gen(function*() {
          const user = yield* fetchUser("123")
          expect(user.name).toBe("Alice")
        }).pipe(Effect.provide(TestDatabaseLayer))
      )
      
      // With shared layer across tests
      const { it: testWithDb } = it.layer(TestDatabaseLayer)
      
      testWithDb.effect("test 1", () => /* ... */)
      testWithDb.effect("test 2", () => /* ... */)
      ```
      
      ## Layer-Based Test Doubles
      
      Replace real services with test implementations via layers:
      
      ```typescript
      // Production service
      class Database extends Context.Tag("Database")<Database, {
        readonly query: (sql: string) => Effect.Effect<unknown[]>
      }>() {}
      
      // Test layer - deterministic mock
      const TestDatabase = Layer.succeed(Database, {
        query: (sql) => Effect.succeed([{ id: 1, name: "test-user" }])
      })
      
      // Stateful test double
      const TestDatabase = Layer.effect(Database,
        Effect.gen(function*() {
          const store = yield* Ref.make<Map<string, unknown[]>>(new Map())
          return {
            query: (sql) => Effect.gen(function*() {
              const data = yield* Ref.get(store)
              return data.get(sql) ?? []
            })
          }
        })
      )
      ```
      
      ## TestClock (Deterministic Time)
      
      Control time in tests for schedule/timeout/delay testing:
      
      ```typescript
      import { TestClock, TestContext } from "effect"
      
      it.effect("retries 3 times with backoff", () =>
        Effect.gen(function*() {
          // Fork the effect under test (v4: Effect.forkChild instead of Effect.fork)
          const fiber = yield* Effect.fork(
            Effect.retry(failingOp, {
              schedule: Schedule.exponential("1 second"),
              times: 3
            })
          )
      
          // Advance time to trigger retries
          yield* TestClock.adjust("1 second")  // 1st retry
          yield* TestClock.adjust("2 seconds") // 2nd retry
          yield* TestClock.adjust("4 seconds") // 3rd retry
      
          const result = yield* Fiber.join(fiber)
          // Assert result...
        }).pipe(Effect.provide(TestContext.TestContext))
      )
      ```
      
      ## Testing Patterns
      
      ### Assert on typed errors
      
      ```typescript
      it.effect("fails with NotFound", () =>
        Effect.gen(function*() {
          const exit = yield* Effect.exit(fetchUser("nonexistent"))
          expect(exit).toEqual(Exit.fail(new NotFoundError({ id: "nonexistent" })))
        })
      )
      ```
      
      ### Assert on Exit
      
      ```typescript
      import { Exit } from "effect"
      
      it.effect("returns success exit", () =>
        Effect.gen(function*() {
          const exit = yield* Effect.exit(myEffect)
          expect(Exit.isSuccess(exit)).toBe(true)
        })
      )
      ```
      
      ### Test with timeout
      
      ```typescript
      it.effect("completes within 5 seconds", () =>
        myEffect.pipe(
          Effect.timeout("5 seconds"),
          Effect.map((result) => expect(result).toBeDefined())
        )
      )
      ```
      
      ### Override a single service
      
      ```typescript
      it.effect("uses custom logger", () =>
        program.pipe(
          Effect.provideService(Logger, {
            log: (msg) => Effect.sync(() => messages.push(msg))
          })
        )
      )
      ```
      
      ## Property-Based Testing (FastCheck + Schema Arbitrary)
      
      `Schema.toArbitrary(schema)` derives a `fast-check` `Arbitrary` directly from any schema, so you can generate valid sample data and run property tests without writing generators by hand. Effect re-exports fast-check as `FastCheck` from `effect/testing`.
      
      ```typescript
      import { Schema } from "effect"
      import { FastCheck } from "effect/testing"
      
      const Person = Schema.Struct({
        name: Schema.String,
        age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
      })
      
      const PersonArb = Schema.toArbitrary(Person)
      
      // Generate a sample
      const sample = FastCheck.sample(PersonArb, 1)[0]
      
      // Property: every generated value round-trips through encode -> decode
      FastCheck.assert(
        FastCheck.property(PersonArb, (person) => {
          const decoded = Schema.decodeUnknownSync(Person)(
            Schema.encodeSync(Person)(person)
          )
          expect(decoded).toEqual(person)
        })
      )
      ```
      
      Derived arbitraries respect schema checks (`isGreaterThan`, `isMinLength`, etc.), so generated data is always valid input. Combine with `it.effect` to property-test effectful logic over generated inputs.
      
      ## Common Testing Mistakes
      
      1. **Forgetting TestContext**: `TestClock.adjust` requires `TestContext.TestContext` to be provided
      2. **Not forking before adjusting time**: `TestClock.adjust` only affects already-running fibers. Fork the effect first, then adjust
      3. **Testing implementation instead of behavior**: Use Layer swaps to test service behavior, not internal implementation details
      4. **Missing `Effect.exit` for error assertions**: Use `Effect.exit` to inspect the Exit value instead of catching errors
      
  • CHANGELOG.md 7.2 KB
    # Changelog
    
    All notable changes to this skill will be documented in this file.
    
    The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
    and this skill adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
    
    ## [Unreleased]
    
    ## [0.6.5] - 2026-09-09
    
    ### Changed
    - Description condensed to fit the repo's 250-character limit.
    
    ## [0.6.4] - 2026-08-21
    
    ### Changed
    
    - Declared ClawHub browse categories (`development`) and topics in `metadata`, so the release pipeline publishes them instead of leaving the skill in the `other` category.
    
    ### Removed
    
    - `skill-card.md`. The ClawHub CLI strips a root `skill-card.md` from every publish and the registry generates its own card, so the authored file never reached ClawHub.
    
    ## [0.6.3] - 2026-08-07
    
    ### Changed
    
    - Rewrote the frontmatter description: dropped the "Comprehensive" opener and the 14-subsystem enumeration, folded the distinguishing import specifiers into a natural clause, and removed the trailing trigger dump.
    
    ## [0.6.2] - 2026-07-22
    
    ### Added
    
    - skill-card.md release record following NVIDIA's skill-card format
    - metadata.openclaw audited against ClawHub spec (removed ANTHROPIC_API_KEY envVar declared but never referenced in skill content)
    
    ## [0.6.1] - 2026-07-10
    
    ### Changed
    - CHANGELOG preamble pinned to Keep a Changelog 2.0.0 (format unchanged; KaC 2.0.0 keeps existing changelogs valid).
    
    ## [0.6.0] - 2026-07-01
    ### Added
    - `migration-v4.md`: extended the "v4 beta additions" ledger from beta.78 to beta.92 (SchemaError now a tagged `Data.TaggedError`; `Schema.Void` void-return semantics; `Config.make` removed + `Config.withDefault`/`Config.schema` recovery changes; `Effect.try` thunk form; `RpcGroup.toHandlers` definition-first; `Effect.transposeOption`; `Effect.fromOption` callbacks; `Random.choice`; `Latch.isOpen`; `String.configCase`; `Statement.valuesUnprepared`; `Schema.toCodecArrayFromSingle`; `Graph.successors`/`predecessors`; arbitrary-derivation metadata migration; HTTP API streaming responses).
    - `concurrency.md`: child-process execution via `effect/unstable/process` (`ChildProcess`, `ChildProcessSpawner`; `spawner.string`/`lines`/`spawn`, `ChildProcess.pipeTo`).
    - `dependency-injection.md`: `LayerMap.Service` for per-key dynamic layers, and a `ManagedRuntime` bridge pattern for non-Effect frameworks (Hono/Express) with fiber-local request context via `Context.Reference`.
    - `resource-management.md`: `RcRef`/`RcMap` reference-counted shared resources.
    - `streams.md`: stream framing/encoding via `Stream.pipeThroughChannel` + `Ndjson`/`Msgpack` (`effect/unstable/encoding`).
    - `graph.md` (new reference): the `Graph` module - directed/undirected graphs, traversal (`dfs`/`bfs`/`topo`), analysis (`isAcyclic`, connected/strongly-connected components), and shortest paths (`dijkstra`/`astar`/`bellmanFord`/`floydWarshall`).
    - `schema.md`: note that `SchemaError` is a tagged `Data.TaggedError` (catchable via `Effect.catchTag`).
    - SKILL.md progressive-disclosure index entries for the new concepts.
    ### Changed
    - `llm-corrections.md` + SKILL.md: corrected `Schema.makeOption`/`makeEffect` to instance methods (`schema.makeOption(...)`); softened the unverified "PR #2057" attribution for HttpApi-errors-as-defects to a source-grounded statement.
    ### Fixed
    - `error-modeling.md`: warn that `Effect.catch`/`catchTag` don't catch defects or interrupts (use `catchDefect`/`catchCause`) and that `Effect.catch(() => Effect.void)` silently swallows the typed error.
    - `concurrency.md`: clarify that `Effect.forkDetach` parents to the global scope (survives `runtime.dispose()`); use `Effect.forkScoped` for runtime-tied background fibers.
    
    Verified against: effect@4.0.0-beta.92
    
    ## [0.5.0] - 2026-06-09
    ### Added
    - `concurrency.md`: Request / RequestResolver section (batching, dedup, N+1 elimination via `Effect.request`).
    - `effect-ai.md` + `retry-scheduling.md`: `ExecutionPlan` for ordered multi-provider fallback (`Effect.withExecutionPlan`).
    - `http.md`: Multipart file-upload section (`HttpApiSchema.asMultipart`, `Multipart.SingleFileSchema`/`FilesSchema`, limit references).
    - `testing.md`: property-based testing via `Schema.toArbitrary` + `FastCheck` (from `effect/testing`).
    - `schema.md`: branded / nominal types (`Brand.nominal`/`check`/`make`/`all`; notes v4 has no `refined`/`error`).
    - `resource-management.md`: `Resource` (refreshable scoped value via `Resource.auto`/`manual`/`refresh`).
    - `core-patterns.md` + SKILL.md: `Effect.fnUntraced` guidance (use it for helpers that don't need a span/stack-frame; reserve named `Effect.fn` for traced operations).
    - SKILL.md progressive-disclosure index entries for the new concepts.
    
    ### Changed
    - Repositioned the skill to recommend Effect v4 as the default while keeping full v3 support. The stance is now "prefer v4 for new projects; in an existing codebase match the installed version, default to v4 when unclear."
    - `SKILL.md` description now leads with v4 (the recommended default) instead of presenting v3/v4 as co-equal.
    - Version Detection: lists v4 first, replaces the v3-default guidance, keeps the beta caveat (pin an exact `4.0.0-beta.x`).
    - Primary documentation sources grouped as v4 (primary) / v3 (existing codebases) / both.
    - Flipped `v3 / v4` orderings to `v4 / v3` in the starter function set, key modules, DI list, and import patterns so v4 reads as primary.
    
    ## [0.4.0] - 2026-06-04
    ### Added
    - CHANGELOG; upstream tracking established.
    - `references/configuration.md` - Config / ConfigProvider (typed env config, defaults, redacted secrets, schema-validated config, custom providers).
    - `references/sql.md` - SQL toolkit (SqlClient tagged-template queries, SqlSchema, Model/SqlModel repositories, Migrator, driver layers, SqlError reasons).
    - `references/cli.md` - building command-line apps (Command, Flag, Argument, subcommands).
    - `references/rpc.md` - typed client/server RPC (RpcGroup, RpcServer, RpcClient, transports, serialization).
    - `references/distributed.md` - Cluster (sharded entities), Workflow (durable execution), EventLog (event sourcing).
    - `references/stm.md` - software transactional memory (`Effect.tx`, `TxRef`, `Tx*` collections, `Effect.txRetry`).
    - `references/datetime.md` - DateTime (Utc/Zoned, arithmetic, zones, formatting).
    - `references/optics.md` - Optic (immutable nested reads/updates, method-chain API).
    - `core-patterns.md`: `Match` pattern-matching section.
    - `resource-management.md`: `Pool` resource-pooling section.
    - `concurrency.md`: FiberHandle/FiberMap/FiberSet, SubscriptionRef, and worker-threads (RPC-over-worker) sections.
    - `llm-corrections.md`: `Schema.Defect`/`Schema.Error` are constructor functions in v4; `Random.nextUUIDv4` removed (use `Crypto` service).
    
    ### Fixed
    - `error-modeling.md`: `cause: Schema.Defect` -> `cause: Schema.Defect()` (Schema.Defect is a constructor function since beta.76).
    
    ### Changed
    - Bumped tracked `effect` from `4.0.0-beta.58` to `4.0.0-beta.78`.
    - `migration-v4.md`: refreshed the "v4 beta additions" log to cover beta.59-78 (Schema.Error/Defect constructors, Random.nextUUIDv4 removal, `Effect.Yieldable`/`Types.MergeRecord` removals, `catch*` error-channel fix, HttpApiTest, and more).
    - `SKILL.md`: extended the progressive-disclosure index with the new references; added SQL/CLI/RPC/Config to the description.
    
    Verified against: effect@4.0.0-beta.78
    
  • LICENSE.txt 8.9 KB
    Apache License
    Version 2.0, January 2004
    https://www.apache.org/licenses/
    
    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    
    1. Definitions.
    
    "License" shall mean the terms and conditions for use, reproduction, and
    distribution as defined by Sections 1 through 9 of this document.
    
    "Licensor" shall mean the copyright owner or entity authorized by the
    copyright owner that is granting the License.
    
    "Legal Entity" shall mean the union of the acting entity and all other
    entities that control, are controlled by, or are under common control with
    that entity. For the purposes of this definition, "control" means (i) the
    power, direct or indirect, to cause the direction or management of such
    entity, whether by contract or otherwise, or (ii) ownership of fifty percent
    (50%) or more of the outstanding shares, or (iii) beneficial ownership of
    such entity.
    
    "You" (or "Your") shall mean an individual or Legal Entity exercising
    permissions granted by this License.
    
    "Source" form shall mean the preferred form for making modifications,
    including but not limited to software source code, documentation source, and
    configuration files.
    
    "Object" form shall mean any form resulting from mechanical transformation or
    translation of a Source form, including but not limited to compiled object
    code, generated documentation, and conversions to other media types.
    
    "Work" shall mean the work of authorship, whether in Source or Object form,
    made available under the License, as indicated by a copyright notice that is
    included in or attached to the work (an example is provided in the Appendix
    below).
    
    "Derivative Works" shall mean any work, whether in Source or Object form,
    that is based on (or derived from) the Work and for which the editorial
    revisions, annotations, elaborations, or other modifications represent, as a
    whole, an original work of authorship. For the purposes of this License,
    Derivative Works shall not include works that remain separable from, or
    merely link (or bind by name) to the interfaces of, the Work and Derivative
    Works thereof.
    
    "Contribution" shall mean any work of authorship, including the original
    version of the Work and any modifications or additions to that Work or
    Derivative Works thereof, that is intentionally submitted to Licensor for
    inclusion in the Work by the copyright owner or by an individual or Legal
    Entity authorized to submit on behalf of the copyright owner. For the
    purposes of this definition, "submitted" means any form of electronic, verbal,
    or written communication sent to the Licensor or its representatives,
    including but not limited to communication on electronic mailing lists, source
    code control systems, and issue tracking systems that are managed by, or on
    behalf of, the Licensor for the purpose of discussing and improving the Work,
    but excluding communication that is conspicuously marked or otherwise
    designated in writing by the copyright owner as "Not a Contribution."
    
    "Contributor" shall mean Licensor and any individual or Legal Entity on
    behalf of whom a Contribution has been received by Licensor and subsequently
    incorporated within the Work.
    
    2. Grant of Copyright License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable copyright license to
    reproduce, prepare Derivative Works of, publicly display, publicly perform,
    sublicense, and distribute the Work and such Derivative Works in Source or
    Object form.
    
    3. Grant of Patent License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
    section) patent license to make, have made, use, offer to sell, sell, import,
    and otherwise transfer the Work, where such license applies only to those
    patent claims licensable by such Contributor that are necessarily infringed by
    their Contribution(s) alone or by combination of their Contribution(s) with
    the Work to which such Contribution(s) was submitted. If You institute patent
    litigation against any entity (including a cross-claim or counterclaim in a
    lawsuit) alleging that the Work or a Contribution incorporated within the Work
    constitutes direct or contributory patent infringement, then any patent
    licenses granted to You under this License for that Work shall terminate as of
    the date such litigation is filed.
    
    4. Redistribution. You may reproduce and distribute copies of the Work or
    Derivative Works thereof in any medium, with or without modifications, and in
    Source or Object form, provided that You meet the following conditions:
    
    (a) You must give any other recipients of the Work or Derivative Works a copy
    of this License; and
    
    (b) You must cause any modified files to carry prominent notices stating that
    You changed the files; and
    
    (c) You must retain, in the Source form of any Derivative Works that You
    distribute, all copyright, patent, trademark, and attribution notices from
    the Source form of the Work, excluding those notices that do not pertain to
    any part of the Derivative Works; and
    
    (d) If the Work includes a "NOTICE" text file as part of its distribution,
    then any Derivative Works that You distribute must include a readable copy of
    the attribution notices contained within such NOTICE file, excluding those
    notices that do not pertain to any part of the Derivative Works, in at least
    one of the following places: within a NOTICE text file distributed as part of
    the Derivative Works; within the Source form or documentation, if provided
    along with the Derivative Works; or, within a display generated by the
    Derivative Works, if and wherever such third-party notices normally appear.
    The contents of the NOTICE file are for informational purposes only and do not
    modify the License. You may add Your own attribution notices within Derivative
    Works that You distribute, alongside or as an addendum to the NOTICE text from
    the Work, provided that such additional attribution notices cannot be
    construed as modifying the License.
    
    You may add Your own copyright statement to Your modifications and may provide
    additional or different license terms and conditions for use, reproduction, or
    distribution of Your modifications, or for any such Derivative Works as a
    whole, provided Your use, reproduction, and distribution of the Work otherwise
    complies with the conditions stated in this License.
    
    5. Submission of Contributions. Unless You explicitly state otherwise, any
    Contribution intentionally submitted for inclusion in the Work by You to the
    Licensor shall be under the terms and conditions of this License, without any
    additional terms or conditions. Notwithstanding the above, nothing herein
    shall supersede or modify the terms of any separate license agreement you may
    have executed with Licensor regarding such Contributions.
    
    6. Trademarks. This License does not grant permission to use the trade names,
    trademarks, service marks, or product names of the Licensor, except as
    required for reasonable and customary use in describing the origin of the Work
    and reproducing the content of the NOTICE file.
    
    7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
    writing, Licensor provides the Work (and each Contributor provides its
    Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied, including, without limitation, any warranties
    or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    PARTICULAR PURPOSE. You are solely responsible for determining the
    appropriateness of using or redistributing the Work and assume any risks
    associated with Your exercise of permissions under this License.
    
    8. Limitation of Liability. In no event and under no legal theory, whether in
    tort (including negligence), contract, or otherwise, unless required by
    applicable law (such as deliberate and grossly negligent acts) or agreed to in
    writing, shall any Contributor be liable to You for damages, including any
    direct, indirect, special, incidental, or consequential damages of any
    character arising as a result of this License or out of the use or inability to
    use the Work (including but not limited to damages for loss of goodwill, work
    stoppage, computer failure or malfunction, or any and all other commercial
    damages or losses), even if such Contributor has been advised of the
    possibility of such damages.
    
    9. Accepting Warranty or Additional Liability. While redistributing the Work
    or Derivative Works thereof, You may choose to offer, and charge a fee for,
    acceptance of support, warranty, indemnity, or other liability obligations
    and/or rights consistent with this License. However, in accepting such
    obligations, You may act only on Your own behalf and on Your sole
    responsibility, not on behalf of any other Contributor, and only if You agree
    to indemnify, defend, and hold each Contributor harmless for any liability
    incurred by, or claims asserted against, such Contributor by reason of your
    accepting any such warranty or additional liability.
    
    END OF TERMS AND CONDITIONS
    
  • SKILL.md 13.5 KB
    ---
    name: effect-ts
    description: Effect-TS guide for TypeScript, v4 default with v3 support. Use when writing, debugging, or reviewing Effect code across errors, concurrency, services, streams, and schema, or when code imports from 'effect' or any '@effect/*' package.
    metadata:
      version: "0.6.5"
      categories: "development"
      topics: "effect, typescript, functional-programming, concurrency, error-handling"
      upstream: "effect@4.0.0-beta.92"
      openclaw:
        homepage: https://github.com/tenequm/skills/tree/main/skills/effect-ts
        emoji: "🌀"
        envVars:
          - name: OPENAI_API_KEY
            required: false
            description: OpenAI API key for Effect AI examples using the OpenAI provider.
    ---
    
    # Effect-TS
    
    Effect is a TypeScript library for building production-grade software with typed errors, structured concurrency, dependency injection, and built-in observability.
    
    ## Version Detection
    
    Before writing Effect code, detect which version the user is on:
    
    ```bash
    # Check installed version
    cat package.json | grep '"effect"'
    ```
    
    - **v4.x** (recommended, the direction Effect is heading): `Context.Service`, `Effect.catch`, `Effect.forkChild`, `Schema.TaggedErrorClass`
    - **v3.x** (stable, still common in production): `Context.Tag`, `Effect.catchAll`, `Effect.fork`, `Data.TaggedError`
    
    > Note: v4 beta briefly used a `ServiceMap` module, renamed back to `Context` on 2026-04-07 (PR #1961). If you see `ServiceMap.*` in any doc or older beta code, it is the current `Context.*`. Both v3 and v4 import `Context` from `"effect"`; the exports inside differ (`Context.Service` in v4 vs `Context.Tag` in v3).
    
    **Prefer v4 for new projects** - it's where Effect is going. In an existing codebase, match the installed version: don't rewrite v3 code in v4 syntax unless asked. If the version is genuinely unclear, default to v4 and say so. v4 is still in beta, so pin an exact version (`4.0.0-beta.x`) and expect occasional API churn.
    
    ## Primary Documentation Sources
    
    **v4 (primary):**
    - https://github.com/Effect-TS/effect-smol (v4 source + migration guides)
    - https://github.com/Effect-TS/effect-smol/blob/main/LLMS.md (v4 LLM guide)
    
    **v3 (for existing codebases):**
    - https://effect.website/docs (v3 stable docs)
    - https://effect.website/llms.txt (LLM topic index)
    - https://effect.website/llms-full.txt (full docs for large context)
    
    **Both versions:**
    - https://tim-smart.github.io/effect-io-ai/ (concise API list)
    
    ## AI Guardrails: Critical Corrections
    
    LLM outputs frequently contain incorrect Effect APIs. Verify every API against the reference docs before using it.
    
    **Common hallucinations (both versions):**
    
    | Wrong (AI often generates)                   | Correct                                                       |
    |----------------------------------------------|---------------------------------------------------------------|
    | `Effect.cachedWithTTL(...)`                  | `Cache.make({ capacity, timeToLive, lookup })`                |
    | `Effect.cachedInvalidateWithTTL(...)`        | `cache.invalidate(key)` / `cache.invalidateAll()`             |
    | `Effect.mapError(effect, fn)`                | `Effect.mapError(fn)` in pipe, or use `Effect.catchTag`       |
    | `import { Schema } from "@effect/schema"`    | `import { Schema } from "effect"` (v3.10+ and all v4)         |
    | `import { JSONSchema } from "@effect/schema"`| `import { JSONSchema } from "effect"` (v3.10+)                |
    | JSON Schema Draft 2020-12                    | Effect Schema generates **Draft-07**                          |
    | "thread-local storage"                       | "fiber-local storage" via `FiberRef` (v3) / `Context.Reference` (v4) |
    | fibers are "cancelled"                       | fibers are "interrupted"                                      |
    | all queues have back-pressure                | only **bounded** queues; sliding/dropping do not               |
    | `new MyError("message")`                     | `new MyError({ message: "..." })` (Schema errors take objects) |
    
    **v3-specific hallucinations:**
    
    | Wrong                              | Correct (v3)                                        |
    |------------------------------------|-----------------------------------------------------|
    | `Effect.Service` (function call)   | `class Foo extends Effect.Service<Foo>()("id", {})` |
    | `Effect.match(effect, { ... })`    | `Effect.match(effect, { onSuccess, onFailure })`    |
    | `Effect.provide(layer1, layer2)`   | `Effect.provide(Layer.merge(layer1, layer2))`       |
    
    **v4-specific hallucinations (AI may mix v3/v4):**
    
    | Wrong (v3 API used in v4 code)    | Correct (v4)                                         |
    |-----------------------------------|------------------------------------------------------|
    | `Context.Tag("X")` (v3 shape)    | `Context.Service<X>(id)` or class syntax              |
    | `ServiceMap.Service` / `ServiceMap.Reference` | Renamed back to `Context.Service` / `Context.Reference` on 2026-04-07 |
    | `Effect.catchAll(fn)`            | `Effect.catch(fn)`                                    |
    | `Effect.fork(effect)`            | `Effect.forkChild(effect)`                            |
    | `Effect.forkDaemon(effect)`      | `Effect.forkDetach(effect)`                           |
    | `Data.TaggedError`               | `Schema.TaggedErrorClass`                             |
    | `FiberRef.get(ref)`              | `yield* References.X` (a `Context.Reference`)         |
    | `yield* ref` (Ref as Effect)     | `yield* Ref.get(ref)` (Ref is no longer an Effect)    |
    | `yield* fiber` (Fiber as Effect) | `yield* Fiber.join(fiber)` (Fiber is no longer Effect) |
    | `Logger.Default` / `Logger.Live` | `Logger.layer` (v4 naming convention)                 |
    | `Schema.TaggedError`             | `Schema.TaggedErrorClass`                             |
    | `Schema.makeUnsafe(input)`       | `Schema.make(input)` (throws `SchemaError`); also instance methods `schema.makeOption(...)`, `schema.makeEffect(...)` |
    | `ParseResult` (from `"effect"`)  | `SchemaIssue` module + `SchemaError` class; narrow with `Schema.isSchemaError` |
    | `HttpApiEndpoint.get(n, p).pipe(HttpApiEndpoint.setPath(...), setPayload(...), setSuccess(...))` | `HttpApiEndpoint.get(n, p, { params, query, payload, success, error })` (object-option form) |
    | `Otlp.layer({ url, serviceName })` | `OtlpTracer.layer({ url, resource: { serviceName } })` + `OtlpSerialization.layerJson` + `FetchHttpClient.layer` |
    | `import { HttpApi } from "@effect/platform"` (v4) | `import { HttpApi } from "effect/unstable/httpapi"` |
    | HttpApi endpoint schema errors are typed errors by default | In current v4 betas they default to **defects** unless transformed |
    
    **Read `references/llm-corrections.md` for the exhaustive corrections table.**
    
    ## Progressive Disclosure
    
    Read only the reference files relevant to your task:
    
    - Error modeling or typed failures → `references/error-modeling.md`
    - Services, DI, or Layer wiring → `references/dependency-injection.md`
    - Per-key dynamic layers (per-tenant resources, `LayerMap`) → `references/dependency-injection.md`
    - Bridging Effect into non-Effect frameworks (Hono/Express, `ManagedRuntime`) → `references/dependency-injection.md`
    - Retries, timeouts, or backoff → `references/retry-scheduling.md`
    - Fibers, forking, or parallel work → `references/concurrency.md`
    - Request batching, N+1 elimination, DataLoader pattern → `references/concurrency.md`
    - Multi-provider fallback (`ExecutionPlan`) → `references/effect-ai.md` / `references/retry-scheduling.md`
    - Streams, queues, or SSE → `references/streams.md`
    - Framing streams (NDJSON / MessagePack encode-decode) → `references/streams.md`
    - Running child processes / shelling out → `references/concurrency.md`
    - Resource lifecycle or cleanup → `references/resource-management.md`
    - Refreshable values (rotating credentials, polled config) → `references/resource-management.md`
    - Reference-counted shared resources (`RcRef`/`RcMap`) → `references/resource-management.md`
    - Schema validation or decoding → `references/schema.md`
    - Branded / nominal types (`Brand`) → `references/schema.md`
    - Logging, metrics, or tracing → `references/observability.md`
    - HTTP clients or API calls → `references/http.md`
    - HTTP API servers → `references/http.md` (covers both client and server)
    - File uploads / multipart form-data → `references/http.md`
    - LLM/AI integration → `references/effect-ai.md`
    - Configuration, env vars, secrets → `references/configuration.md`
    - SQL / database access → `references/sql.md`
    - Command-line apps → `references/cli.md`
    - Typed client/server RPC → `references/rpc.md`
    - Sharded entities, durable workflows, event sourcing → `references/distributed.md`
    - Transactional state (STM, `Tx*`) → `references/stm.md`
    - Date/time handling → `references/datetime.md`
    - Immutable nested updates (optics) → `references/optics.md`
    - Graphs, dependency ordering, shortest paths, cycle detection → `references/graph.md`
    - Pattern matching (`Match`) → `references/core-patterns.md`
    - Pooling resources (`Pool`) → `references/resource-management.md`
    - Fiber sets, SubscriptionRef, worker threads → `references/concurrency.md`
    - Testing Effect code → `references/testing.md`
    - Property-based testing / generating data from schemas → `references/testing.md`
    - Migrating from async/await → `references/migration-async.md`
    - Migrating from v3 to v4 → `references/migration-v4.md`
    - Core types, gen, pipe, running → `references/core-patterns.md`
    - Full wrong-vs-correct API table → `references/llm-corrections.md`
    
    ## Core Workflow
    
    1. **Detect version** from `package.json` before writing any code
    2. **Clarify boundaries**: identify where IO happens, keep core logic as `Effect` values
    3. **Choose style**: use `Effect.gen` for sequential logic, pipelines for simple transforms. In v4, prefer `Effect.fn("name")` for named functions
    4. **Model errors explicitly**: type expected errors in the `E` channel; treat bugs as defects
    5. **Model dependencies** with services and layers; keep interfaces free of construction logic
    6. **Manage resources** with `Scope` when opening/closing things (files, connections, etc.)
    7. **Provide layers** and run effects only at program edges (`NodeRuntime.runMain` or `ManagedRuntime`)
    8. **Verify APIs exist** before using them - consult https://tim-smart.github.io/effect-io-ai/ or source docs
    
    ## Starter Function Set
    
    Start with these ~20 functions (the official recommended set):
    
    **Creating effects:** `Effect.succeed`, `Effect.fail`, `Effect.sync`, `Effect.tryPromise`
    
    **Composition:** `Effect.gen` (+ `Effect.fn` in v4), `Effect.andThen`, `Effect.map`, `Effect.tap`, `Effect.all`
    
    **Running:** `Effect.runPromise`, `NodeRuntime.runMain` (preferred for entry points)
    
    **Error handling:** `Effect.catchTag`, `Effect.catch` (v4) / `Effect.catchAll` (v3), `Effect.orDie`
    
    **Resources:** `Effect.acquireRelease`, `Effect.acquireUseRelease`, `Effect.scoped`
    
    **Dependencies:** `Effect.provide`, `Effect.provideService`
    
    **Key modules:** `Effect`, `Schema`, `Layer`, `Option`, `Result` (v4) / `Either` (v3), `Array`, `Match`
    
    **DI (v4):** `Context.Service`, `Context.Reference`, `Layer.effect`, `Effect.fn("name")`
    **DI (v3):** `Context.Tag`, `Context.Reference`
    
    ## Import Patterns
    
    Always use barrel imports from `"effect"`:
    
    ```typescript
    import { Context, Effect, Schema, Layer, Option, Stream } from "effect"
    ```
    
    For companion packages, import from the package name. v3 and v4 differ here:
    
    ```typescript
    // v4 (recommended) - platform transports still separate, but HttpApi / observability
    // moved under effect/unstable/*
    import { NodeRuntime } from "@effect/platform-node"
    import { FetchHttpClient } from "effect/unstable/http"
    import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi"
    import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"
    
    // v3 (stable) companion packages
    import { NodeRuntime } from "@effect/platform-node"
    import { HttpClient } from "@effect/platform"
    import { NodeSdk } from "@effect/opentelemetry"
    ```
    
    Avoid deep module imports (`effect/Effect`) unless your bundler requires it for tree-shaking.
    
    ## Output Standards
    
    - Show imports in every code example
    - Prefer `Effect.gen` (imperative) for multi-step logic; pipelines for transforms
    - In v4, use `Effect.fn("name")` instead of bare `Effect.gen` for named functions; use `Effect.fnUntraced` for internal helpers that don't need a span/stack-frame
    - Never call `Effect.runPromise` / `Effect.runSync` inside library code - only at program edges
    - Use `NodeRuntime.runMain` for CLI/server entry points (handles SIGINT gracefully)
    - Use `ManagedRuntime` when integrating Effect into non-Effect frameworks (Hono, Express, etc.)
    - Always `return yield*` when raising an error in a generator (ensures TS understands control flow)
    - Avoid point-free/tacit usage: write `Effect.map((x) => fn(x))` not `Effect.map(fn)` (generics get erased)
    - Keep dependency graphs explicit (services, layers, tags)
    - State the `Effect<A, E, R>` shape when it helps design decisions
    
    ## Agent Quality Checklist
    
    Before outputting Effect code, verify:
    
    - [ ] Every API exists (check against tim-smart API list or source docs)
    - [ ] Imports are from `"effect"` (not `@effect/schema`, `@effect/io`, etc.)
    - [ ] Version matches the user's codebase (v3 vs v4 syntax)
    - [ ] Expected errors are typed in `E`; unexpected failures are defects
    - [ ] `run*` is called only at program edges, not inside library code
    - [ ] Resources opened with `acquireRelease` are wrapped in `Effect.scoped`
    - [ ] Layers are provided before running (no missing `R` requirements)
    - [ ] Generator bodies use `yield*` (not `yield` without `*`)
    - [ ] Error raises in generators use `return yield*` pattern
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related