# effect-atom-boundary

> Typed error boundaries for Effect Atom: catch an atom's failures by tag, typed by the atom

```ts
import { AtomErrorBoundary, createErrorBoundary } from "effect-atom-boundary";
```

## Claim

The claiming step of typed error boundaries. A boundary claims errors by
`_tag`; its hook takes a read's result, throws the claimed failures to the
boundary that claims them, and returns the result typed without them.

### `TypeId`

<p class="api-meta"><span class="api-kind">type</span><span>type IDs</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L19">Source</a></p>

```ts
type TypeId = "~effect-atom-boundary/ErrorBoundary";
```

```ts
const TypeId: TypeId;
```

Literal type used as the boundary components' type identifier.
Type identifier for the boundary components, which carry the tags they
claim.

### `TagsOf`

<p class="api-meta"><span class="api-kind">type</span><span>utility types</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L38">Source</a></p>

```ts
type TagsOf<E> = E extends {
  readonly _tag: infer Tag extends string;
}
  ? Tag
  : never;
```

The `_tag`s of an error union.

### `Unclaimed`

<p class="api-meta"><span class="api-kind">type</span><span>utility types</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L49">Source</a></p>

```ts
type Unclaimed<E, Tags extends string> = Exclude<
  E,
  {
    readonly _tag: Tags;
  }
>;
```

The errors of `E` whose `_tag` isn't in `Tags`.

### `Variance`

<p class="api-meta"><span class="api-kind">interface</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L61">Source</a></p>

```ts
interface Variance<Tags extends string> {
  readonly [TypeId]: {
    readonly _Tags: Types.Covariant<Tags>;
  };
}
```

Variance marker carrying the tags a boundary claims.

### `UnclaimedResult`

<p class="api-meta"><span class="api-kind">type</span><span>utility types</span><span>since 0.3.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L76">Source</a></p>

```ts
type UnclaimedResult<R, Tags extends string> =
  R extends AsyncResult.Success<infer A, infer E>
    ? AsyncResult.Success<A, Unclaimed<E, Tags>>
    : R extends AsyncResult.Failure<infer A, infer E>
      ? [Unclaimed<E, Tags>] extends [never]
        ? never
        : AsyncResult.Failure<A, Unclaimed<E, Tags>>
      : R extends AsyncResult.Initial<infer A, infer E>
        ? AsyncResult.Initial<A, Unclaimed<E, Tags>>
        : never;
```

An `AsyncResult`, or a union of them, typed without the errors whose
`_tag` is in `Tags`: a failure with no error left is gone from the union,
so a read whose failures are all claimed is typed as a success.

### `UseErrorBoundary`

<p class="api-meta"><span class="api-kind">interface</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/claim.ts#L121">Source</a></p>

```ts
interface UseErrorBoundary<Tags extends string> {
  <R extends AsyncResult.AsyncResult<unknown, unknown>>(
    result: R,
  ): UnclaimedResult<R, Tags>;
}
```

A boundary's hook: takes the result a component read under the boundary,
throws the failures this boundary and the boundaries above it claim, and
returns the result typed without them.

**When to use**

Wrap each read under the boundary in it, such as
`useIssueBoundary(useAtomResult(issue, handle))`, to throw the claimed
failures to their boundaries and narrow the result to the rest.

**Details**

A failure whose error has a claimed `_tag` is thrown as the error itself,
so the nearest boundary that claims it catches it. A failure with no typed
error, a defect, is thrown too, to the error boundaries above. Any other
result is returned as it is. It takes any `AsyncResult`, from a read that
returns typed failures and suspends while a failed atom refreshes:
`raf-effect-atom`'s `useAtomResult`, or `useAtomSuspense` with
`includeFailure: true` and `suspendOnWaiting: true`.

**Gotchas**

It's a hook, and it throws outside its boundary. It throws a claimed
failure whether or not the atom is refreshing, so a read that returns the
old failure while a `reset` refreshes it, such as `useAtomValue`, throws
it to the boundary again, and the handler stays after the refresh. A read
that throws every failure itself, such as `useAtomSuspense` without
`includeFailure`, throws before the hook sees the result.

## Error boundary

Error boundaries typed by an atom's error. A boundary catches failures by
`_tag`, with a handler per tag typed by the error with that tag, so UI code
never imports an error class. `AtomErrorBoundary` guards one atom;
`createErrorBoundary` makes a shared boundary whose hook narrows the atoms
read under it.

### `CatchTagProps`

<p class="api-meta"><span class="api-kind">interface</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L47">Source</a></p>

```ts
interface CatchTagProps<E> {
  readonly error: E;
  /**
   * Refreshes what failed and renders the children again: an
   * `AtomErrorBoundary`'s atom, or every atom in the registry whose failure a
   * `createErrorBoundary` boundary has a handler for.
   */
  readonly reset: () => void;
}
```

Props for a `catchTags` handler: the caught error, and `reset`.

### `CatchTags`

<p class="api-meta"><span class="api-kind">type</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L66">Source</a></p>

```ts
type CatchTags<E, Tags extends TagsOf<E> = TagsOf<E>> = {
  readonly [Tag in Tags]?: (
    props: CatchTagProps<
      Extract<
        E,
        {
          readonly _tag: Tag;
        }
      >
    >,
  ) => React.ReactNode;
};
```

One optional handler per `_tag` of `E` (or per tag in `Tags`), each typed by
the error with that tag. A handler renders as a component, so it can call
hooks.

### `ErrorsOf`

<p class="api-meta"><span class="api-kind">type</span><span>utility types</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L105">Source</a></p>

```ts
type ErrorsOf<Source> =
  Source extends AtomRpc.AtomRpcClient<
    infer _Self,
    infer _Id,
    infer _Rpcs
  >
    ? Source["query"] extends (
        ...args: ReadonlyArray<never>
      ) => infer Query
      ? Query extends Atom.Atom<
          AsyncResult.AsyncResult<infer _A, infer E>
        >
        ? E
        : never
      : never
    : Source extends AtomHttpApi.AtomHttpApiClient<
          infer _Self,
          infer _Id,
          infer Groups
        >
      ? EndpointErrors<HttpApiGroup.Endpoints<Groups>>
      : Source extends Atom.Atom<
            AsyncResult.AsyncResult<infer _A, infer E>
          >
        ? E
        : Source extends (
              ...args: ReadonlyArray<never>
            ) => Atom.Atom<AsyncResult.AsyncResult<infer _A, infer E>>
          ? E
          : never;
```

The error type of what a boundary's `errors` names: an `AtomRpc` client
(the errors of every procedure), an `AtomHttpApi` client (the errors of
every endpoint and its middleware), an atom, or an atom family.

### `AtomErrorBoundaryProps`

<p class="api-meta"><span class="api-kind">interface</span><span>components</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L261">Source</a></p>

```ts
interface AtomErrorBoundaryProps<A, E> {
  /** The atom whose failures this boundary catches. */
  readonly atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>;
  /** A handler per `_tag` of the atom's error. */
  readonly catchTags: NoInfer<CatchTags<E>>;
  readonly children?: React.ReactNode | undefined;
}
```

Props for an error boundary that catches one atom's failures by `_tag`.

### `AtomErrorBoundary`

<p class="api-meta"><span class="api-kind">const</span><span>components</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L326">Source</a></p>

```ts
const AtomErrorBoundary: <A, E>(
  props: AtomErrorBoundaryProps<A, E>,
) => React.ReactNode;
```

Provides an error boundary for one atom that renders a handler per `_tag`
of the atom's error, typed by the error with that tag.

**When to use**

Use when a subtree reads one atom and some of its failures should render
in place of the subtree, without the reading component handling them.

**Details**

The reads under it throw the atom's failures, as `useAtomSuspense` does
without `includeFailure`. A failure whose tag has a handler renders that
handler; its `reset` refreshes the atom and renders the children again. A
new `atom` clears a caught failure.

React runs no error boundaries on the server, so during server rendering
it reads `atom` before its children, from the registry or its hydrated
state, and renders the handler for a failure that has settled. The
browser keeps that HTML while it hydrates, then renders the children,
which throw the failure to it again.

**Gotchas**

A failure without a handler, or an error that isn't the one the atom fails
with in the boundary's registry, such as one a read of another atom threw,
goes to the error boundaries above. Read with `suspendOnWaiting: true`, so
a read waits for the refresh `reset` starts instead of throwing the old
failure again. `reset` refreshes the atom in the registry
the boundary renders in, not in a `RegistryContext` provided under it. A
handler renders as a component: one written inline mounts again, losing its
state, each time the parent renders.

**Example** (Catching a missing issue)

```tsx
<AtomErrorBoundary
  atom={issue(id)}
  catchTags={{
    IssueNotFound: ({ error, reset }) => <NotFound id={error.id} onRetry={reset} />
  }}
>
  <IssuePage id={id} />
</AtomErrorBoundary>

function IssuePage(props: { readonly id: number }) {
  const result = useAtomSuspense(issue(props.id), { suspendOnWaiting: true })
  return <h1>{result.value.title}</h1>
}
```

**See also**

- [`createErrorBoundary`](https://raf-effect-atom.katt.dev/docs/api-effect-atom-boundary.md#createErrorBoundary) for a shared boundary that narrows the reads under it

### `ErrorBoundaryComponent`

<p class="api-meta"><span class="api-kind">interface</span><span>components</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L360">Source</a></p>

```ts
interface ErrorBoundaryComponent<
  Tags extends string,
> extends Variance<Tags> {
  (props: {
    readonly children?: React.ReactNode | undefined;
  }): React.ReactNode;
  readonly displayName: string;
}
```

A boundary component: renders the `catchTags` handler for the errors it
claims and throws the rest to the boundaries above it.

### `ErrorBoundary`

<p class="api-meta"><span class="api-kind">type</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L375">Source</a></p>

```ts
type ErrorBoundary<Tags extends string> = readonly [
  Boundary: ErrorBoundaryComponent<Tags>,
  useBoundary: UseErrorBoundary<Tags>,
];
```

A boundary component and its hook, which narrows the results read under
it, named freely by destructuring.

### `ErrorBoundaryOptions`

<p class="api-meta"><span class="api-kind">interface</span><span>models</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L387">Source</a></p>

```ts
interface ErrorBoundaryOptions<
  Source,
  Tags extends TagsOf<ErrorsOf<Source>>,
  ParentTags extends string,
> {
  /** A handler per `_tag` this boundary claims. */
  readonly catchTags: CatchTags<ErrorsOf<Source>, Tags>;
  /**
   * What the handlers' errors come from, for the types: an `AtomRpc` or
   * `AtomHttpApi` client, an atom, or an atom family.
   */
  readonly errors: Source;
  /** Names it in messages and React DevTools: `"Auth"` is `<AuthBoundary>`. */
  readonly name: string;
  /** The boundary it sits under, which must be mounted above it. */
  readonly parent?: ErrorBoundaryComponent<ParentTags> | undefined;
}
```

Options for `createErrorBoundary`.

### `createErrorBoundary`

<p class="api-meta"><span class="api-kind">const</span><span>constructors</span><span>since 0.1.0</span><a href="https://github.com/KATT/raf-effect-atom/blob/main/packages/effect-atom-boundary/src/error-boundary.tsx#L494">Source</a></p>

```ts
const createErrorBoundary: <
  Source,
  Tags extends TagsOf<ErrorsOf<Source>>,
  ParentTags extends string = never,
>(
  options: ErrorBoundaryOptions<Source, Tags, ParentTags>,
) => ErrorBoundary<Tags | ParentTags>;
```

Creates a shared error boundary that claims some errors by `_tag`, returned
as `[Boundary, useBoundary]`.

**When to use**

Use for failures that belong to the app rather than to the component that
read them, such as being signed out, so the components under the boundary
only handle what's left.

**Details**

A component under it wraps its read in the hook:
`useBoundary(useAtomResult(issue, handle))` throws the failures this
boundary or one above it claims to the nearest boundary that claims them,
and returns the result typed without them. `parent` is the boundary it
sits under; the claimed tags add up along the chain. `catchTags`' keys are tags
of `errors`' error type, so a misspelled tag is a type error and each
handler's `error` is typed.

A handler's `reset` refreshes every atom in the registry the boundary
renders in whose settled failure has a tag this boundary handles, not only
the one whose read threw:
one outage fails every atom on the page, and an atom that fails after the
handler replaced the children throws to no boundary.

React runs no error boundaries on the server, so during server rendering
it looks through the registry's atoms before rendering its children, and
renders its handler for a settled failure it catches. It only sees atoms
that already ran in that registry, such as the loaders' on TanStack
Start, or under `raf-effect-atom`'s `HydrationBoundary` on React Router.
A failure it doesn't see is thrown on the server and caught in the
browser.

**Gotchas**

A boundary matches `_tag` only: a failure with a claimed tag from any atom
under it is caught. During server rendering, that's any atom in the
registry, read under it or not. The boundary throws without its `parent`
mounted above it, and the hook throws outside its boundary. The hook needs
a read that returns its failures, such as `raf-effect-atom`'s
`useAtomResult`: `useAtomSuspense` without `includeFailure` throws every
failure before the hook sees it.

**Example** (A sign-in boundary)

```tsx
const [AuthBoundary, useAuthBoundary] = createErrorBoundary({
  name: "Auth",
  errors: Issues,
  catchTags: { Unauthorized: ({ reset }) => <SignIn onSignedIn={reset} /> }
})

function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
  const result = useAuthBoundary(useAtomResult(issue, props.issue))
  // AsyncResult<Issue, IssueNotFound | RpcClientError>
}
```

**See also**

- [`AtomErrorBoundary`](https://raf-effect-atom.katt.dev/docs/api-effect-atom-boundary.md#AtomErrorBoundary) for a boundary that guards one atom
