> ## Documentation Index
> Fetch the complete documentation index at: https://developers.reintersect.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build with Effect

> When to use the Effect API instead of the Promise API, what to install, the Effect entry points and a complete Effect app.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

`@reintersect/sdk` has two APIs for the same app model. The Promise API in `@reintersect/sdk/app`
takes `async` handlers and any Standard Schema library. The Effect API in
`@reintersect/sdk/effect/app` takes [Effect](https://effect.website) Schema, runs every handler as an
Effect, tracks the services and errors of each handler in its type, and hands you secrets as
`Redacted` values.

Reintersect can't tell the two apart. Both produce the same manifest, receive the same signed
deliveries and render with the same [components](/components/overview), so every guide in this site
applies to both. These pages cover what's different on the Effect side.

## When to use it

Use the Effect API when your backend already runs on Effect, or when you want the compiler to check
that every service your handlers need is provided and that their failures are handled. Use the
Promise API when your app is plain `async` TypeScript or you'd rather write schemas with Zod, ArkType
or Valibot.

| | Promise API | Effect API |
| - | - | - |
| App pieces | `@reintersect/sdk/app` | `@reintersect/sdk/effect/app` |
| Schemas | Standard Schema with JSON Schema, such as Zod or ArkType | Effect Schema, which may transform block state |
| Handlers | `async` functions | Generators or functions returning an `Effect` |
| Dependencies | Modules you import | Services, provided once with `provide(app, layer)` |
| Secrets | `secret()` values with `.reveal()` | `secret()` values as `Redacted<string>` |
| Client in handlers | Methods resolve to `{ data, error }` | Methods return an `Effect` with tagged errors |
| Serving | `app.fetch` and the Node and framework adapters | The same, plus `ReintersectApp.layer` on `HttpLayerRouter` |
| Testing | `@reintersect/sdk/testing` | `@reintersect/sdk/effect/testing` with `@effect/vitest` |

An app uses one API. Adding a piece from `@reintersect/sdk/app` to an Effect app is a type error.
Components from `@reintersect/sdk/components` work with both.

## Install

Scaffold an Effect app, served with `HttpLayerRouter` on Node and tested with `@effect/vitest`:

```sh theme={null}
npm create reintersect@latest -- --template effect
```

To add the Effect API to an existing project, install `effect` and `@effect/platform` next to the
SDK. They're optional peer dependencies, so Promise apps never need them:

```sh theme={null}
npm install @reintersect/sdk effect @effect/platform
```

The SDK supports Effect 3. To serve on Node, add `@effect/platform-node`. For tests, add
`@effect/vitest`.

## Entry points

| Import | What it exports |
| - | - |
| `@reintersect/sdk/effect/app` | `createReintersectApp`, `defineBlock`, `defineAction`, `defineReference`, `defineCommand`, `defineMessageAction`, `defineEvent`, `defineRoute`, `choice`, `secret`, `provide`, `ReintersectApp.layer`, `manifestOf`, `toNodeHandler` and the delivery error classes. |
| `@reintersect/sdk/effect` | `ReintersectAppClient` for calls outside handlers, `renderPayload`, `ReintersectStream`, the member API client (`Reintersect` and `ReintersectClient`) and every API error class. |
| `@reintersect/sdk/effect/testing` | `makeTestHost`, `ReintersectTestHost`, the tree helpers and the test error classes. |
| `@reintersect/sdk/effect/recipes` | The [recipes](/recipes/overview), such as `checklist` and `poll`, as Effect pieces. |
| `@reintersect/sdk/effect/jsx-runtime` | The JSX runtime under the Effect entry. It's the same runtime as `@reintersect/sdk/jsx-runtime`. |
| `@reintersect/sdk/components` | The component catalog, shared by both APIs. |

## A complete app

This app has one block with one action. The action moves a task through a `Tasks` service, and the
app provides that service once with a layer:

<CodeGroup>
  ```ts src/schemas.ts theme={null}
  import { Schema } from "effect";

  export const TaskStatus = Schema.Literal("todo", "doing", "done");

  export type TaskStatus = typeof TaskStatus.Type;

  export const TaskProps = Schema.Struct({ id: Schema.String, title: Schema.String });

  export const Task = Schema.Struct({ ...TaskProps.fields, status: TaskStatus });

  export type Task = typeof Task.Type;

  export const TaskState = Schema.Struct({ status: TaskStatus });

  export const MoveTask = Schema.Struct({ id: Schema.String, status: TaskStatus });
  ```

  ```ts src/tasks.ts theme={null}
  import { Array, Effect, Ref } from "effect";
  import type { Task, TaskStatus } from "./schemas";

  export class Tasks extends Effect.Service<Tasks>()("Tasks", {
    effect: Effect.gen(function* () {
      const tasks = yield* Ref.make<ReadonlyArray<Task>>([
        { id: "TASK-1", title: "Write the launch announcement", status: "doing" },
        { id: "TASK-2", title: "Review the pricing page", status: "todo" },
      ]);

      return {
        find: (id: string) => Effect.map(Ref.get(tasks), Array.findFirst((task) => task.id === id)),
        updateStatus: (id: string, status: TaskStatus) =>
          Ref.update(tasks, Array.map((task) => (task.id === id ? { ...task, status } : task))),
      };
    }),
  }) {}
  ```

  ```tsx src/taskBlock.tsx theme={null}
  import { Actions, Block, Button, Header, Status } from "@reintersect/sdk/components";
  import { defineAction, defineBlock } from "@reintersect/sdk/effect/app";
  import { MoveTask, TaskProps, TaskState } from "./schemas";
  import { Tasks } from "./tasks";

  const STATUS_STATES = { todo: "neutral", doing: "running", done: "success" } as const;

  export const moveTask = defineAction(
    "move",
    { args: MoveTask, state: TaskState, description: "Move the task to another status" },
    function* ({ args }) {
      const tasks = yield* Tasks;

      yield* tasks.updateStatus(args.id, args.status);

      return { state: { status: args.status }, notice: `Moved to ${args.status}` };
    },
  );

  export const TaskBlock = defineBlock({
    type: "task",
    title: "Task",
    insertable: true,
    props: TaskProps,
    state: TaskState,
    initialState: { status: "todo" },
    actions: { move: moveTask },
  }).render((props, { actions, state }) => (
    <Block summary={`${props.id} ${props.title}: ${state.status}`}>
      <Header trailing={<Status state={STATUS_STATES[state.status]} label={state.status} />}>
        {props.title}
      </Header>
      <Actions>
        <Button label="Start" action={actions.move.with({ id: props.id, status: "doing" })} />
      </Actions>
    </Block>
  ));
  ```

  ```ts src/app.ts theme={null}
  import { createReintersectApp, provide } from "@reintersect/sdk/effect/app";
  import { TaskBlock } from "./taskBlock";
  import { Tasks } from "./tasks";

  export const app = provide(
    createReintersectApp({
      name: "Tasks",
      slug: "tasks",
      hosting: "self",
      endpoint: "https://tasks.example.com/api/reintersect",
    }).addBlock(TaskBlock),
    Tasks.Default,
  );
  ```

  ```ts src/server.ts theme={null}
  import { HttpLayerRouter } from "@effect/platform";
  import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
  import { ReintersectApp } from "@reintersect/sdk/effect/app";
  import { Config, Layer } from "effect";
  import { createServer } from "node:http";
  import { app } from "./app";

  const HttpServerLive = NodeHttpServer.layerConfig(createServer, {
    port: Config.port("PORT").pipe(Config.withDefault(3000)),
  });

  HttpLayerRouter.serve(ReintersectApp.layer(app, { path: "/api/reintersect" })).pipe(
    Layer.provide(HttpServerLive),
    Layer.launch,
    NodeRuntime.runMain,
  );
  ```
</CodeGroup>

The action yields `Tasks`, so `moveTask`, `TaskBlock` and the app all carry `Tasks` as a
requirement. Without `provide(app, Tasks.Default)`, serving the app is a type error that names the
missing service. See [Services and layers](/effect/services-and-layers).

Effect Schema decodes the props, state and arguments on the way in and encodes the state your action
returns before Reintersect stores it. See [Handlers and schemas](/effect/handlers).

If `updateStatus` could fail, its error would appear in the action's type. Reintersect would keep the
block as it was and show the member an error. See [Errors](/effect/errors).

## Next steps

<Columns cols={2}>
  <Card title="Handlers and schemas" href="/effect/handlers">
    The handler forms, what's in the context, Effect Schema, environment values and secrets.
  </Card>

  <Card title="Blocks and references" href="/effect/blocks-and-references">
    Actions and views as Effects, components that return an Effect, and resolving links.
  </Card>

  <Card title="Commands, actions and events" href="/effect/commands-and-events">
    Slash commands, message actions, lifecycle events and HTTP routes.
  </Card>

  <Card title="Deferred answers" href="/effect/deferring">
    Answer a command or message action after the delivery returns, with `defer`.
  </Card>

  <Card title="Services and layers" href="/effect/services-and-layers">
    Provide services once, serve the app on `HttpLayerRouter` and swap layers in tests.
  </Card>

  <Card title="Errors" href="/effect/errors">
    What a failure does in each piece, the errors the SDK raises and how to catch them.
  </Card>

  <Card title="Client and streams" href="/effect/client-and-streams">
    Call the Reintersect API from handlers, jobs and scripts, and post to app streams.
  </Card>

  <Card title="Testing" href="/effect/testing">
    Run your app against a fake host inside `@effect/vitest`, with test clocks and fake services.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.