<!-- Generated by scripts/generate-docs.ts. Do not edit directly. -->

# effective-rsc documentation

Use the React and Effect documentation for their underlying concepts. ERSC conventions:

- Only `src/application.tsx` has framework filename semantics.
- Create application values from one ERSC identity and its derived middleware views.
- Provide application services and export the result at `ERSC.make`.
- Import the package root only from the RSC graph.

## Getting started

Create an application with compatible dependencies and Tailwind support:

```sh
bunx create-ersc-app my-application
cd my-application
bun run dev
```

Open `http://localhost:18193`.

In `src/application.tsx`, create one ERSC identity, define a root Layout and Page, compose Routes,
and export `ERSC.make(...)`.

For a production check, run `bun run check`, `bun run build`, and `bun run start`. Both
`ersc dev` and `ersc start` accept `--hostname` and `--port`; flags take precedence over
`HOST` and `PORT`. See the package README for requirements and manual installation.

Files in `public/` are served from `/` with `Cache-Control: public, max-age=0`.

### A minimal application

Create values from one ERSC identity and close it with ERSC.make.

```tsx
import { Effect } from 'effect';
import { Application } from 'effective-rsc';

const ERSC = Application.ersc();

const RootLayout = ERSC.Layout.make({
  render: ({ children }) =>
    Effect.succeed(
      <html lang='en'>
        <body>{children}</body>
      </html>,
    ),
});

const HomePage = ERSC.Page.make({
  render: () => Effect.succeed(<h1>Hello from effective-rsc</h1>),
});

export default ERSC.make({
  routes: ERSC.Routes.make({ layout: RootLayout }).page('/', HomePage),
});
```

### More examples

- **[Importing styles](./docs/01-getting-started/20_styling.tsx)**: ERSC has no magic stylesheet entry; import styles from their owning module.

## Guides

Familiarity with React Server Components and Effect is assumed.

## Server Functions

`ERSC.ServerFn.make` decodes Schema input and runs an Effect handler with application services.

Callers pass the Schema's encoded type and the handler receives its decoded type. Use an ordinary
`Schema.Struct(...)` for object input. Use `Schema.fromFormData(...)` when a native form supplies the
input; a function returning `void` can then be passed directly to `<form action>`. Let the Schema
infer the handler parameter.

For form feedback with `useActionState`, use `input: [StateSchema, FormSchema]` and
`handler: (previousState, form) => ...`. React supplies both arguments; ERSC validates and decodes
each one. Keep the native Server Function reference intact when passing it to `useActionState`
to retain progressive enhancement. A single Array or Tuple Schema still describes one argument.

A successful invocation refreshes the current route.

- **[Creating the Server Function authoring module](./docs/02-guides/01-server-functions/10_ersc.ts)**
- **[Defining a Server Function](./docs/02-guides/01-server-functions/20_follow-author.ts)**: ERSC decodes FormData before running the Effect handler.
- **[Rendering a direct form action](./docs/02-guides/01-server-functions/30_follow-author-button.tsx)**: A FormData Server Function can be passed directly to form action.
- **[Closing the Server Function application](./docs/02-guides/01-server-functions/40_application.tsx)**
- **[Defining a stateful form action](./docs/02-guides/01-server-functions/50_greet.ts)**: A schema list decodes React's previous state and submitted FormData separately.
- **[Rendering a stateful form](./docs/02-guides/01-server-functions/60_greeting-form.tsx)**: Pass the native reference to useActionState so React also owns progressive form submission.

## Services

Define services with Effect, then follow the ERSC composition convention:

1. Declare the complete service union with `Application.ersc<Services>()`.
2. Let Pages, Layouts, Components, and Server Functions require members of that union.
3. Provide the complete Layer once with `ERSC.make({ layer })`.

This keeps implementations at the application composition boundary while preserving each
renderer's inferred service requirements.

- **[An application-owned service](./docs/02-guides/02-services/10_catalog.ts)**: ERSC consumes the service contract and Layer; their construction is ordinary Effect code.
- **[Providing services at the composition boundary](./docs/02-guides/02-services/20_application.tsx)**: Declare the service union on ERSC and provide its Layer once at ERSC.make.

## Routing, parameters, and loading

- Routes are immutable and belong to one ERSC identity.
- `page(path, page)` attaches a Page; `mount(prefix, routes)` nests a route scope.
- Mounted scopes retain their Layout and Loading ancestry.
- On GET/HEAD, the request handler decodes Page parameters once before rendering, with services
  from existing route middleware available. Rejected parameters return an empty `404`, including
  navigation Flight.
- Server Function POST refreshes keep parameter rejection in React's render-error path, preserving
  the completed action result.
- Effect HTTP owns route matching; ERSC rejects duplicate shapes and invalid composition while
  building the graph.

- **[Creating one routing authoring module](./docs/02-guides/03-routing/10_ersc.ts)**
- **[Layout and Loading concerns](./docs/02-guides/03-routing/10_layouts.tsx)**: Layout is Effectful; Loading is synchronous and service-free.
- **[Static and parameterized Pages](./docs/02-guides/03-routing/20_pages.tsx)**: A Page Schema decodes captured path strings for render.
- **[Composing and mounting Routes](./docs/02-guides/03-routing/30_routes.tsx)**: Mounting retains the child graph's Layout and Loading ancestry.
- **[Closing the route graph](./docs/02-guides/03-routing/40_application.ts)**

## Middleware

Create middleware from the base ERSC view, then derive a view with
`ERSC.withMiddleware(middleware)`. The derived view has the same ERSC identity and retains the
middleware scope.

Routes and Server Functions created from the derived view activate that scope. Pages, Layouts, and
Components created from it may require the services declared by the middleware and consume them only
while rendered inside an active scope.

Use `ERSC.Middleware.make<{ provides: CurrentUser }>(handler)` when a middleware provides a
request-scoped service. The handler must provide that service to the downstream Effect. Chain
`withMiddleware` in request order; responses unwind in reverse.

Scoped middleware does not wrap userland HTTP, assets, or unmatched requests. Put server-wide policy
in native global Effect HTTP middleware supplied through the application Layer.

- **[Defining an authenticated view](./docs/02-guides/04-middleware/10_auth.ts)**: Middleware can short-circuit a request and provide typed services downstream.
- **[Consuming middleware data in a Page](./docs/02-guides/04-middleware/20_account-page.tsx)**
- **[Consuming middleware data in a Server Function](./docs/02-guides/04-middleware/30_update-profile.ts)**
- **[Activating middleware with Routes](./docs/02-guides/04-middleware/40_application.tsx)**

## Userland HTTP

Register native Effect `HttpRouter`, `HttpApi`, or RPC layers in the Layer passed to
`ERSC.make({ layer })`. They share the framework's HTTP server, application services, and shutdown
scope.

Register routes that require application services with `HttpRouter.use`, then retain those services
with `Layer.provideMerge`.

Native global middleware belongs in the same application Layer. It observes Page requests, Server
Function requests, userland HTTP, assets, and unmatched requests. ERSC-scoped middleware has narrower
reach.

- **[Composing ERSC and userland HTTP](./docs/02-guides/05-http/10_application-layer.tsx)**: ERSC concerns and native HTTP routes share one application Layer.

## Advanced

These guides describe ERSC's runtime guarantees. See the
[current limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations)
before adopting them.

## Request runtime and lifetimes

The server builds the Layer passed to `ERSC.make` once and releases it at shutdown. Its services have
application lifetime.

Each HTTP request has an independent Effect scope. Server Function handlers run in the HTTP request
fiber. Page, Layout, and Component render Effects run in a request-owned render scope.

Closing the response interrupts unfinished request work and runs its finalizers. Acquire
request-local resources inside the request Effect so their lifetime follows the request
automatically.

Give work that must outlive a request an explicit application-owned scope.

## Client navigation

ERSC handles eligible document navigations through the browser Navigation API and
`NavigationPrecommitController`. There is no History API fallback. A browser missing either one
still hydrates Client Components and supports Server Functions and streamed current-page refreshes,
including HMR. Links use full-page navigation instead of the client router. Without JavaScript,
the server-rendered document retains working links and natively submitted forms.

Development reports a missing navigation API in the console and a dismissible development-panel
warning. The warning does not block interaction and is absent in production.

An intercepted Page navigation has two milestones:

- **Native commit:** ERSC starts the Flight request in a React Transition, retains the common Layout
  prefix, and publishes the destination in another Transition after the asynchronous load. The
  precommit handler settles at the destination's first UI commit. The Navigation API can
  then commit the URL and history entry, apply focus and default scroll, and finish any React View
  Transition without waiting for Flight EOF.
- **Stream completion:** after native commit, the client router owns any remaining Flight stream
  until EOF or until React confirms that another render retired it. A completed tree is cached for
  the exact Navigation API history-entry id that committed for that navigation.

Canceling or superseding before commit interrupts the client transport and server request Effects.
A scheduled destination is discarded before its stream is released, so no rollback is needed. The
current UI and stream remain live while a successor prepares and retire only after React confirms a
different render. After native commit, Browser Stop no longer owns the stream; later Flight failures
use React's Error Boundary handling.

Back/Forward traversal reuses a completed cached payload. Push, replace, and uncached traversal
fetch fresh Flight. Disposing a history entry evicts its payload; a Server Function refresh clears
the traversal cache because a mutation may affect any route.

Flight redirects use the response's final URL; a non-success or non-Flight response becomes a
full-document navigation. Native focus and scroll remain enabled. Because Suspense content may
continue after native commit, history can remember an intermediate fallback's scroll position;
stream-aware restoration is not yet implemented.

### React View Transitions

Applications own React `<ViewTransition>` boundaries and all animation CSS. ERSC does not wrap the
route tree or call `document.startViewTransition()`. It calls React's `addTransitionType()` inside
the same Transition that publishes an initial navigation or refresh render, so application
boundaries can select animation policy without delaying native navigation until Flight EOF.

The types are additive:

| Publication                                            | Added types                                        |
| ------------------------------------------------------ | -------------------------------------------------- |
| Every routed navigation                                | `navigation`, `navigation-${event.navigationType}` |
| Push navigation                                        | `navigation-forward`                               |
| Backward traversal                                     | `navigation-backward`                              |
| Forward traversal                                      | `navigation-forward`                               |
| Navigation with `event.hasUAVisualTransition`          | `navigation-ua-visual-transition`                  |
| Server Function response tree or current-route refresh | `server-function`                                  |
| HMR current-route refresh                              | `hmr-refresh`                                      |

`event.navigationType` is `push`, `replace`, or `traverse`. Replace has no direction type. A
traversal has no direction type when either history index is unavailable or the indices are equal.
Applications may suppress author animation for `navigation-ua-visual-transition` and `hmr-refresh`,
but ERSC does not impose that policy.

These types describe only the first publication. Suspense content that resolves later renders in a
separate, untyped React Transition. Applications should use their own Suspense-specific
`<ViewTransition>` boundaries and styling for those reveals.

## Server Function execution and refresh

Hydrated invocations and progressively enhanced forms execute the same request-scoped Effect
handler. A hydrated response contains the Server Function result and a refreshed route tree; a
progressively enhanced response contains a complete document with the refreshed tree and form state.

For hydrated calls, the result Promise settles independently from the route refresh. ERSC commits
the refreshed tree in a React transition and keeps the request active through React commit and
Flight EOF. Disconnecting interrupts unfinished request work and the response stream.

Hydrated invocations may execute concurrently. Only the latest invocation may apply its response's
route tree while its original history entry remains current and no navigation is active. Other
responses trigger a fresh current-route refresh. A response tree interrupts any older current-route
refresh before rendering, then rechecks applicability after cleanup completes. Effect owns refresh
loading and cancellation; the React Transition publishes the tree without waiting for its own
commit inside an async Action.

After a successful mutation, ERSC clears the Back/Forward traversal cache because any route may have
changed.

## Production startup

Run `ersc build`, then `ersc start`. A custom Bun entry can await
`start({ root, hostname, port })` from `effective-rsc/server`. All options are required;
`root` is the application directory. Deploy its `.ersc/`, `public/`, and runtime dependencies.

The Promise resolves when ready; startup failures reject and exit. ERSC owns signal handling
and cleanup, so do not wrap it in `BunRuntime.runMain`.

### Deployment adapters

`ersc build --adapter <package>` runs an installed adapter after compilation; it does not upload.
Without the flag, packaging is skipped and previous output remains.

Adapters export `build: BuildHook` from `./build`, with types from `effective-rsc/build`.
The hook receives absolute `root`, `serverDir`, `clientDir`, and `publicDir` paths and returns
`Effect<void, Error, Scope>`. Inputs are read-only; adapters provide dependencies and ERSC owns
cleanup/cancellation. Failures stop the build.

### Server entry

Save this as `server.ts` in the application root and run it with `bun server.ts` after building.

```ts
import { start } from 'effective-rsc/server';

await start({
  hostname: 'localhost',
  port: 18193,
  root: import.meta.dir,
});
```

## API reference

Under the `react-server` condition, the package root exports `Application`.
`Application.ersc<Services>()` returns `Component`, `Layout`, `Loading`, `Page`, `Middleware`,
`Routes`, `ServerFn`, `withMiddleware`, and `make`. Values from different ERSC identities cannot
be composed.

## Application

`Application.ersc<Services>()` creates one application-scoped ERSC identity and its base authoring
view. `Services` is the complete server-service union; omit it for a service-free application.

`ERSC.make({ routes, layer })` closes the route graph and application runtime. Export its result from
`src/application.tsx`. `layer` is required unless `Services` is `never`; it may provide the declared
services and register native Effect HTTP on the framework router.

## Page

- `ERSC.Page.make({ render })` creates a static route leaf.
- `ERSC.Page.make({ params, render })` creates a parameterized route leaf.

`render` returns an Effect whose requirements fit the ERSC service union. For parameterized Pages,
the Schema's encoded keys must exactly match the path parameters and accept strings. Compose the
Page with `Routes.page`.

Pages produce React output. On GET/HEAD, the request handler decodes parameters once before
rendering, with services from existing route middleware available. Rejected parameters receive
an empty `404`, including navigation Flight requests; unmatched routes also receive native `404`
responses. Other failures keep their existing
error behavior.

Server Function POST refreshes decode parameters inside Page rendering. A rejection follows React's
render-error path without replacing the completed Server Function result with a `404`.

## Layout

`ERSC.Layout.make({ render })` creates an Effectful wrapper with one `children` outlet. It may require
ERSC application services. The root Layout owns the HTML document; nested Layouts own route scopes.

## Loading

`ERSC.Loading.make({ render })` creates a Routes-scope fallback. `render` is synchronous and cannot
require services. A scope accepts at most one Loading value.

## Component

`ERSC.Component.make({ render })` creates a non-route Effectful Server Component. Props are inferred
from `render`; requirements must fit the ERSC service union. Use it only in the RSC graph.

- **[An Effectful Server Component](./docs/04-api-reference/05-component/10_component.tsx)**: Component runs its render Effect in the current ERSC render scope.

## Middleware

`ERSC.Middleware.make(handler)` adapts an Effect HTTP middleware to the current ERSC identity.
`ERSC.withMiddleware(middleware)` returns a derived authoring view of that same identity.

Use `ERSC.Middleware.make<{ provides: CurrentUser }>(handler)` when the handler provides a service to
the downstream Effect. Multiple services use a union. The derived view adds those services to the
requirements available to Page, Layout, Component, ServerFn, Routes, Middleware, and further derived
views.

Routes and ServerFn activate retained middleware. Page, Layout, and Component consume its services
only while React renders them inside an active scope. Rendering one outside its required scope is a
programmer error and throws `TypeError`.

Chain `withMiddleware` in request order. Ancestors run before descendants; response transformations
unwind in reverse. A middleware repeated in one resolved mounted route chain is rejected. Shared
middleware across mounted scopes runs once.

## Reach

| Request                          | Route scope                          | Server Function scope | Native global middleware |
| -------------------------------- | ------------------------------------ | --------------------- | ------------------------ |
| Page GET/HEAD                    | Matched chain                        | No                    | Yes                      |
| Hydrated Server Function POST    | Remaining route chain around refresh | Server Function chain | Yes                      |
| Progressive Server Function POST | Remaining route chain around refresh | Server Function chain | Yes                      |
| Userland HTTP, assets, unmatched | No                                   | No                    | Yes                      |

During a Server Function request, middleware already active for the Server Function is not
executed again for the refreshed route, even if it appears at another position in that route chain.
Remaining route middleware wraps refreshed rendering.

Native global Effect HTTP middleware is separate. Register it through the application Layer for
server-wide policy.

## Routes

`ERSC.Routes.make({ layout?, loading? })` creates an immutable route scope.

- `routes.page(path, page)` adds a Page at an absolute Effect HTTP pattern. Parameter Schema keys
  must exactly match path parameters.
- `routes.mount(prefix, childRoutes)` mounts a non-empty graph of the same ERSC identity below an
  absolute, parameter-free prefix.
- Mounted scopes retain their Layout, Loading, and middleware ancestry.

Both operations return new Routes values. Conflicting matcher shapes and `/_ersc/assets` are
rejected. Root Routes require a Layout and at least one Page.

Routes created from a derived authoring view activate its middleware.

## ServerFn

`ERSC.ServerFn.make({ input, handler })` creates a native React Server Function reference. `input`
decodes the invocation payload and infers the handler parameter; do not annotate it. The handler
returns an Effect whose requirements fit the ERSC service universe. The client reference accepts the
Schema's encoded type and resolves `Promise<Output>`; the handler receives its decoded type.

For multiple positional arguments, supply a readonly schema list as `input`. Each caller argument
uses its Schema's encoded type; each handler argument uses its decoded type, in the same order.
Inline lists infer their tuple shape without `as const`. Use `input: []` for no arguments.
`input: Schema.Array(...)` and `input: Schema.Tuple(...)` still describe one argument, not a
positional argument list.

```ts
const followAuthor = ERSC.ServerFn.make({
  input: Schema.Struct({ authorId: Schema.NonEmptyString }),
  handler: ({ authorId }) => Effect.succeed({ authorId, following: true }),
});
```

Schema transformations may use a different encoded type. To pass a Server Function directly to
`form.action`, decode `FormData` and return `void`:

```tsx
const followAuthorForm = ERSC.ServerFn.make({
  input: Schema.fromFormData(Schema.Struct({ authorId: Schema.NonEmptyString })),
  handler: ({ authorId }) => Effect.logInfo('Followed author', { authorId }),
});

<form action={followAuthorForm}>
  <input name='authorId' />
  <button type='submit'>Follow</button>
</form>;
```

A ServerFn created from a derived view activates its middleware for the POST. The Middleware
reference defines refresh reach and ordering.

For a `useActionState` form, declare both the previous state and submitted FormData:

```ts
const StateSchema = Schema.Struct({ message: Schema.String });
const FormSchema = Schema.fromFormData(Schema.Struct({ name: Schema.NonEmptyString }));

const greet = ERSC.ServerFn.make({
  input: [StateSchema, FormSchema],
  handler: (_previousState, { name }) => Effect.succeed({ message: `Hello, ${name}` }),
});
```

Pass the native reference directly to `useActionState(greet, { message: '' })` and its returned
action to `<form action>`. React supplies previous state and FormData for hydrated and progressive
submissions. Previous state is client input: validate it, but never trust it for authorization or
authoritative application state. Native `.bind` can prefill leading arguments.

Direct server invocation throws. Encode expected failure in a discriminated output union; unexpected
failures reject the Promise. Browser requests require an Origin matching the application host and
may contain at most 10 MiB. See the
[known limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations)
for the typed failure channel and progressive bound arguments.
