Effect basics

A service is a named interface for work. A context holds supplied service implementations.

An Effect describes work. Build the description, compose it with other Effects, supply its services, then let a runtime execute it.

Read the three type parameters

Effect.Effect<A, E, R> describes three things:

This method’s full return type is Effect.Effect<FileStats, FileStatsError, never>. The source omits the final parameter because it defaults to never.

packages/core/src/file-inspector.ts · 65e9465f
export class FileInspector extends Context.Service<
  FileInspector,
  {
    readonly inspect: (
      path: string
    ) => Effect.Effect<FileStats, FileStatsError>;
  }
>()("@rat-stack/core/FileInspector", {
  make: Effect.gen(function* makeFileInspector() {
    const fileSystem = yield* FileSystem.FileSystem;

Constructing the inspector needs FileSystem. Its returned method has captured that dependency and does not require it again.

R = never means no services remain to supply. It does not mean the computation cannot fail. E = never means no declared expected failures; it does not rule out defects or interruption.

A description is not a completed operation

Calling inspector.inspect(path) produces an Effect description. It does not read the file immediately. The method composes the read with an error translation before a runtime executes either operation.

packages/core/src/file-inspector.ts · 65e9465f
    const inspect = Effect.fn("FileInspector.inspect")(function* inspect(
      path: string
    ) {
      const content = yield* fileSystem
        .readFile(path)
        .pipe(
          Effect.mapError(
            (error) => new FileStatsError({ path, reason: error.message })
          )
        );

      return summarizeBytes(path, content);
    });

Ordinary JavaScript still evaluates while you build a description. Starting a Promise first, then wrapping it, does not undo that eager work. Keep external work inside the Effect constructor’s callback.

Write a sequence with gen; transform it with pipe

Effect.gen composes Effects in generator syntax. yield* obtains a success value or propagates the failure. The generator describes work for the Effect runtime.

Use pipe for short transforms, such as mapping a value, translating a failure, or providing a service. Use a generator when the next step needs values from earlier steps.

Generators and combinators both compose descriptions without running a nested runtime.

Expected failures and defects are different

An expected failure39 belongs in E, where callers can handle it by its declared type or tag. A defect is an unexpected execution failure. Logging a failure does not make the operation successful. Handle expected failures without hiding defects39 shows the file error and its CLI handler.

Requirements name services to supply

A requirement is a service the computation needs. A Layer describes how to build a service and supply its dependencies.

Layers build implementations and satisfy their construction dependencies. Getting FileInspector from context requires that service; calling its captured inspect method needs no extra service. Read service construction26, Layer composition18, and context and requirements9.

Run at the application boundary

The CLI builds and transforms a program with pipe, then provides its services and their dependencies. NodeRuntime.runMain executes the final Effect:

apps/cli/src/cli.ts · b4d6b8f4
  Effect.provide(
    Layer.mergeAll(
      Layer.provideMerge(FileInspector.layer, NodeServices.layer),
      remoteJoinInterestLayer,
      localPromptLibraryLayer.pipe(Layer.provide(NodeServices.layer)),
      localCafeDirectoryLayer.pipe(Layer.provide(NodeServices.layer)),
      localLearnLayer,
      Flags.defaults(eventFlags),
      Approval.denyAll
    )
  )
);

NodeRuntime.runMain(program);

The Worker returns a fetch Effect to its Alchemy Worker adapter:

apps/site/src/worker.ts · b4d6b8f4
    return {
      fetch: observeRequestLog(
        observeRequestIncidents(
          events === undefined
            ? readerApp
            : Effect.gen(function* captureWithLiveIdentity() {
                return yield* withEventCapture({
                  captureCity: true,
                  identityMode: currentIdentity,
                  runInBackground: inBackground,
                })(readerApp).pipe(Effect.provideContext(events));
              })
        )
      ),
    };

The Worker adapter owns request execution. The domain does not scatter runPromise calls through handlers.

In tests, it.effect owns execution of the returned Effect. The test still supplies the services it needs. See tests through supplied services22. Run Effect at the integration boundary8 covers foreign code, runtime reuse, and disposal.

The common wrong first version

Scattering await Effect.runPromise(...) through library methods creates separate execution boundaries where composition should remain intact. It also turns the expected failure into a rejected Promise rather than preserving the Effect error channel for the caller. Return the composed Effect and let the application boundary run it.

Effect.promise assumes Promise rejection is an unexpected defect. For an external API with expected failures, use Effect.tryPromise with a translation into a named domain error10. Wrapping an already-started Promise also loses control over when its work begins.

  1. Effect basics: build a description before running it.
  2. Schemas define the boundary25: define and decode accepted data.
  3. Expected failures and defects39: name failures without hiding crashes.
  4. Service construction26: capture implementation dependencies.
  5. Layer composition18: supply the graph at an entrypoint.
  6. Configuration7: parse supplied settings.
  7. Testing22: prove behavior through supplied services.
  8. Scopes34: acquire resources inside their owner and release them on exit.
  9. Concurrency23: bound work and stop owned children.
  10. HTTP responses9: check status and decode accepted data.
  11. Tracing7: name meaningful operations and inspect export evidence.
  12. Runtime boundaries8: execute through the framework’s Effect integration and connect disposal.

Sources

  1. Arnaldi, Michael. n.d. TypeScript & Effects with Michael Arnaldi. Happy Path 81.. Michael Arnaldi. Historical explanation at 49:15 of success, errors, and requirements; current API evidence comes from the pinned source. Accessed 2026-10-06.
  2. Arnaldi, Michael. n.d. Why JavaScript Is Broken: How Effect and TypeScript Fixes It.. Michael Arnaldi. Historical explanation at 12:32 of lazy computations that can be transformed before execution. Accessed 2026-10-06.
  3. Effect contributors. 2026. Effect. Effect 4.0.0.. Effect-TS. Effect<A, E, R>, lazy execution, gen, mapError, fn, and typed failure handling. Accessed 2026-10-06.
  4. Effect contributors. 2026. Context. Effect 4.0.0.. Effect-TS. Service keys express requirements; supplied context satisfies them. Accessed 2026-10-06.
  5. Effect contributors. 2026. Effect Vitest integration.. Effect-TS. it.effect owns execution of an Effect returned by a test. Accessed 2026-10-06.
  6. rat-stack contributors. 2026. rat-stack source snapshot.. rat-stack. FileInspector's method type, CLI composition and execution, Worker fetch Effect, and Effect-aware tests. Accessed 2026-10-06.

Last changed 2026-10-24 in c525d23. Source on GitHub. Change log.

Linked from

  1. Accumulate independent errors before writing output. Check every independent job before any job writes an asset.

    Effect 4’s Effect.validate evaluates every collection element.

  2. Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.

    Effect basics — An Effect describes work, expected failures, and required services before a runtime executes it.

  3. CAFE all stars. Public profiles of people and channels who build with Cloudflare, Alchemy, Foldkit and Effect, served by five read-only capabilities.

    CAFE all stars lists people and channels whose public work builds with Cloudflare, Alchemy, Foldkit and Effect.

  4. Change log. What changed in the files served here, newest first.

    Effect basics — edited: fix(site): replace published Markdown tables with lists and fence reader output

  5. Cleanup can be an idempotent postcondition. Already-absent state can be success when absence is the operation’s intended result.

    Put the domain meaning in the service contract; Effect supplies execution.

  6. Concurrent work needs an owner and a bound. Limit active work, retain result order, and stop child work when its owning lifetime ends.

    Return to the Effect reading order.¶

  7. Context and requirements. Effect requirements make a computation's needed services visible in its type.

    See Effect basics and infrastructure as effects.¶

  8. durability. Keep a rat-stack app standing when a provider, pool, or dependency slows down or a worker dies. Use at any I/O boundary, connection pool, provider call, shared waiter, fan-out, flag, gate, or work that can outlive a request, or when asked "what happens when this is slow", "add a timeout", "make this durable", or "what survives a crash".

    Effect runs each fiber as a concurrent computation in one JavaScript runtime, not as an isolated process.

  9. Durability. Seven durable Effect patterns, each with its seam, its fence, and the incident that taught it.

    rat-stack uses Effect’s own primitives for each one, in real seams.

  10. Durability bibliography. Books, checked short quotations, source limits, and Effect mapping provenance.

    Book principles and our Effect mappings are separate.

  11. Effect is not BEAM. Fibers share a runtime; scope ownership does not supply isolated heaps or OTP restart strategies.

    Test a worker defect and a hard runtime exit separately.

  12. Effect keeps the surfaces on one contract. Schema owns values, projections build interfaces, and Layers supply implementations.

    Start with Effect basics, then construct a service and compose its Layers.¶

  13. Failures keep their meaning. Expected refusals, infrastructure faults, and defects need different policies.

    Classification and defect policy remain domain decisions.

  14. Feature flags. Typed feature flags with owners, removal dates, and stable subject rollouts.

    Declared Effect actors record lifecycle transitions.

  15. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  16. Handle expected failures without hiding defects. Declare expected failures, preserve their context, and recover by tag without turning crashes into successful outcomes.

    Such a field does not itself turn an expected error into a runtime defect.¶

  17. HTTP success needs a status check and a decoder. Supply the client, check the response status, decode accepted data, and name failures at the adapter boundary.

    Return to the Effect reading order.¶

  18. learn-rat-stack. Trace a capability through Effect, XState, five projections, the CLI, and Alchemy. Try hosted search and read.

    Read Effect basics before this trace if the computation model is new.

  19. onboard-a-list. Analyze or migrate an email list. Measure sanitation rules, preserve stops, choose a cohort, and verify an authorized import or sending cutover.

    Decode policy data with Effect Schema.

  20. Pure cores reduce timeline coupling. Move deterministic rules out of time-dependent actions and shared mutable state.

    Wrapping a callback in Effect does not make it pure.¶

  21. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.

    Effect basics — An Effect describes work, expected failures, and required services before a runtime executes it.

  22. Recovery needs model tests. Test sequences of failures and recoveries against explicit invariants.

    Check the model by injecting a defect.¶

  23. Replay needs stable inputs. Historical processing must not silently depend on today’s clock or external decisions.

    Activity can record an effect result under an engine.

  24. Retry needs idempotent effects. Repeating an operation must preserve its intended observable result.

    Processing guarantees inside one store do not cover every external side effect.¶

  25. Run Effect at the integration boundary. Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends.

    The basics page shows CLI, Worker and test execution boundaries.

  26. Scopes release resources when work ends. Acquire resources lazily, keep their use inside the owner, and release them after success, failure, or interruption.

    Return to the Effect reading order.¶

  27. Source change history (log.md). The generated change log as Markdown.

    Effect basics — edited: fix(site): replace published Markdown tables with lists and fence reader output

  28. Structure an Effect codebase by domain. Start with folders for application work, services and Layers in the same file, and startup code that supplies implementations.

    Start with Effect basics.¶

  29. Trace operations where they mean something. Name useful operations, add safe context, and distinguish creating a span from delivering it to a backend.

    Return to the Effect reading order.¶

Unlinked mentions (72)