# AsyncResult

`AsyncResult<A, E>` is the state container that effect-backed and stream-backed
atoms expose. It records the latest observable state of work that may still be
loading, refreshing, retrying, or recovering from failure. The value is always
one of three variants:

- **`Initial`** — nothing has been computed yet.
- **`Success`** — a value `A` is available, along with its `timestamp`.
- **`Failure`** — a `Cause<E>` is available, and the latest previous `Success`
  may be carried alongside it.

Every variant also carries a `waiting` flag, so you can keep rendering the
current state while newer work is in flight.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

// The three variants, all typed AsyncResult<number, string>
const a = AsyncResult.initial<number, string>()
const b = AsyncResult.success(42)
const c = AsyncResult.fail<string, number>("boom")

a._tag // => "Initial"
b._tag // => "Success"
c._tag // => "Failure"
```

## Mental model

The variant answers "**what do we know right now?**", while `waiting` answers
"**is newer work currently running?**". This split is what makes `AsyncResult`
different from `Exit` or `Result`:

- It has an **`Initial`** state for "nothing has happened yet" (no value, no
  error) — neither `Exit` nor `Result` can express this.
- `waiting` is an **overlay, not a fourth variant**. A `Success` can be
  `waiting` (refreshing in the background), and so can a `Failure`.
- A `Failure` can keep the **previous success** (`previousSuccess`). This lets
  UI code show stale data while exposing the latest failure for error displays
  and retry logic — the "smooth refresh" experience.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

// A success that is being refreshed: value is still here, but waiting is true.
const refreshing = AsyncResult.waiting(AsyncResult.success(42))
refreshing.waiting // => true
AsyncResult.value(refreshing) // => Option.some(42)

// A failure that carries the previous success forward.
const failed = AsyncResult.failWithPrevious("network down", {
  previous: Option.some(AsyncResult.success(42))
})
AsyncResult.value(failed) // => Option.some(42)  (stale, but renderable)
AsyncResult.error(failed) // => Option.some("network down")
```

## Common case: rendering every state

The usual job is turning an `AsyncResult` into UI. Use
[`matchWithWaiting`](#matchwithwaiting) when "is it loading?" should win over the
underlying variant — it handles `waiting` (and `Initial`) first, then splits a
`Failure` into typed errors vs. unexpected defects:

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

type User = { name: string }

const render = (result: AsyncResult.AsyncResult<User, string>): string =>
  AsyncResult.matchWithWaiting(result, {
    onWaiting: () => "Loading…",
    onError: (error) => `Error: ${error}`,
    onDefect: (defect) => `Unexpected: ${String(defect)}`,
    onSuccess: (success) => `Hello, ${success.value.name}`
  })

render(AsyncResult.initial()) // => "Loading…"
render(AsyncResult.waiting(AsyncResult.success({ name: "Ada" }))) // => "Loading…"
render(AsyncResult.success({ name: "Ada" })) // => "Hello, Ada"
render(AsyncResult.fail("offline")) // => "Error: offline"
```

If instead you want the variant to win regardless of `waiting`, use
[`match`](#match), which exposes `Initial` / `Failure` / `Success` directly.
**Where AsyncResult comes from:** You rarely build an `AsyncResult` by hand in app code — atoms produce it for
you. When an [Atom](https://effect.plants.sh/reactivity/atom/) wraps an `Effect` or `Stream`, reading it
yields an `AsyncResult`, transitioning `Initial → waiting → Success | Failure`
as the work runs. The constructors below are mainly for tests, custom atoms, and
[hydration](https://effect.plants.sh/reactivity/atom-ref/).

---

## Type and guards

### `AsyncResult<A, E>`

The three-state union: `Initial<A, E> | Success<A, E> | Failure<A, E>`. `E`
defaults to `never`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

let result: AsyncResult.AsyncResult<number, string>
result = AsyncResult.initial()
result = AsyncResult.success(1)
result = AsyncResult.fail("nope")
```

### `isAsyncResult`

Returns `true` when an unknown value is an `AsyncResult`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isAsyncResult(AsyncResult.success(1)) // => true
AsyncResult.isAsyncResult({ _tag: "Success" }) // => false
```

### `With<R, A, E>`

Type-level helper that rebuilds an `AsyncResult` with new `A`/`E` types while
preserving the *variant* of another result type. Used by combinators like
[`map`](#map) and [`replacePrevious`](#replaceprevious) to keep the variant
precise.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

// Given a Success, With keeps it a Success with new type params.
type R = AsyncResult.With<AsyncResult.Success<number>, string, never>
// => AsyncResult.Success<string, never>
```

### `AsyncResult` namespace

The `AsyncResult.AsyncResult` namespace holds type-level helpers shared by every
variant:

- `AsyncResult.Proto<A, E>` — the common prototype: pipeability, the
  `[TypeId]` marker, the phantom `A`/`E` members, and the `waiting: boolean`
  flag implemented by all three variants.
- `AsyncResult.Success<R>` — extracts the success value type from an
  `AsyncResult` type.
- `AsyncResult.Failure<R>` — extracts the failure error type.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

type R = AsyncResult.AsyncResult<number, string>
type A = AsyncResult.AsyncResult.Success<R> // => number
type E = AsyncResult.AsyncResult.Failure<R> // => string
```

## State models

### `Initial<A, E>`

The state before any success value or failure cause exists. Has only `_tag` and
the inherited `waiting` flag.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const init = AsyncResult.initial<number, string>()
init._tag // => "Initial"
init.waiting // => false
```

### `Success<A, E>`

A successful result. Carries the current `value`, a `timestamp` (ms, when the
value was produced), and the shared `waiting` flag.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const ok = AsyncResult.success(42)
ok._tag // => "Success"
ok.value // => 42
typeof ok.timestamp // => "number"
```

### `Failure<A, E>`

A failed result. Carries a `cause: Cause<E>` and `previousSuccess:
Option<Success<A, E>>` — the most recent success carried forward (if any).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

const f = AsyncResult.fail<string, number>("boom")
f._tag // => "Failure"
f.previousSuccess // => Option.none()
Option.isOption(f.previousSuccess) // => true
```

### `Defect` / `Interrupt`

Phantom type markers used only by the [builder](#builder) to track, at the type
level, whether the defect and interrupt failure cases still need to be handled
before `exhaustive()` becomes available. They have no runtime values.

## Refinements

### `isWaiting`

Returns whether a result has its `waiting` flag set (any variant can be
waiting).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isWaiting(AsyncResult.initial(true)) // => true
AsyncResult.isWaiting(AsyncResult.success(1)) // => false
AsyncResult.isWaiting(AsyncResult.waiting(AsyncResult.success(1))) // => true
```

### `isInitial`

Type guard narrowing to `Initial<A, E>`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isInitial(AsyncResult.initial()) // => true
AsyncResult.isInitial(AsyncResult.success(1)) // => false
```

### `isNotInitial`

Type guard narrowing to `Success<A, E> | Failure<A, E>` (anything that has
"settled" at least once).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isNotInitial(AsyncResult.success(1)) // => true
AsyncResult.isNotInitial(AsyncResult.initial()) // => false
```

### `isSuccess`

Type guard narrowing to `Success<A, E>`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isSuccess(AsyncResult.success(1)) // => true
AsyncResult.isSuccess(AsyncResult.fail("x")) // => false
```

### `isFailure`

Type guard narrowing to `Failure<A, E>`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.isFailure(AsyncResult.fail("x")) // => true
AsyncResult.isFailure(AsyncResult.success(1)) // => false
```

### `isInterrupted`

Returns `true` only for a `Failure` whose `cause` contains *only* interruptions
(e.g. the work was cancelled). Useful for treating cancellation differently from
real errors.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Cause } from "effect"

AsyncResult.isInterrupted(AsyncResult.failure(Cause.interrupt(1))) // => true
AsyncResult.isInterrupted(AsyncResult.fail("boom")) // => false
```

## Constructors

### `initial`

Creates an `Initial` result, optionally marking it as waiting.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.initial().waiting // => false
AsyncResult.initial(true).waiting // => true
```

### `success`

Creates a `Success` from a value. Options can set `waiting` and override the
`timestamp` (defaults to `Date.now()`).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const a = AsyncResult.success(42)
a.value // => 42

const b = AsyncResult.success(42, { waiting: true, timestamp: 1000 })
b.waiting // => true
b.timestamp // => 1000
```

### `failure`

Creates a `Failure` from a `Cause<E>`. Options can attach a `previousSuccess`
and set `waiting`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Cause } from "effect"

const f = AsyncResult.failure(Cause.fail("boom"))
f._tag // => "Failure"
AsyncResult.error(f) // => Option.some("boom")
```

### `fail`

Convenience for [`failure`](#failure) from a typed error — wraps it in
`Cause.fail`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const f = AsyncResult.fail("not found")
AsyncResult.error(f) // => Option.some("not found")
```

### `fromExit`

Converts an `Exit` into a `Success` (when it succeeds) or a `Failure` carrying
the exit's cause (when it fails).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Exit } from "effect"

AsyncResult.fromExit(Exit.succeed(7))._tag // => "Success"
AsyncResult.fromExit(Exit.fail("x"))._tag // => "Failure"
```

### `fromExitWithPrevious`

Like [`fromExit`](#fromexit), but when the exit is a failure it carries forward
the latest success from a previous result — the key move for smooth refresh.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Exit, Option } from "effect"

const prev = Option.some(AsyncResult.success(42))

// Refresh succeeds: new success.
AsyncResult.fromExitWithPrevious(Exit.succeed(43), prev).value // => 43

// Refresh fails: failure keeps the stale value.
const f = AsyncResult.fromExitWithPrevious(Exit.fail("offline"), prev)
AsyncResult.value(f) // => Option.some(42)
AsyncResult.error(f) // => Option.some("offline")
```

### `failureWithPrevious`

Creates a `Failure` from a `Cause`, pulling the latest success out of a previous
result (whether that previous result was itself a `Success` or a `Failure` that
already carried one).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Cause, Option } from "effect"

const prev = Option.some(AsyncResult.success(42))
const f = AsyncResult.failureWithPrevious(Cause.fail("boom"), { previous: prev })
AsyncResult.value(f) // => Option.some(42)
```

### `failWithPrevious`

Like [`failureWithPrevious`](#failurewithprevious), but takes a typed error
instead of a `Cause`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

const prev = Option.some(AsyncResult.success(42))
const f = AsyncResult.failWithPrevious("boom", { previous: prev })
AsyncResult.value(f) // => Option.some(42)
AsyncResult.error(f) // => Option.some("boom")
```

### `waiting`

Marks any result as `waiting` (returns it unchanged if already waiting). Pass
`{ touch: true }` to also refresh a `Success`'s timestamp.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const ok = AsyncResult.success(42)
const refreshing = AsyncResult.waiting(ok)
refreshing.waiting // => true
refreshing.value // => 42 (preserved)
```

### `waitingFrom`

Builds a waiting result from an optional previous result: `waiting(previous)`
when one exists, or `initial(true)` when none does. This is exactly the
transition an atom makes when it starts loading.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

AsyncResult.waitingFrom(Option.none())._tag // => "Initial"
AsyncResult.waitingFrom(Option.none()).waiting // => true

const w = AsyncResult.waitingFrom(Option.some(AsyncResult.success(42)))
w._tag // => "Success"
w.waiting // => true
```

## Combinators

### `touch`

Refreshes the `timestamp` of a `Success` (preserving value and `waiting`).
Non-success results are returned unchanged.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const old = AsyncResult.success(42, { timestamp: 1000 })
const fresh = AsyncResult.touch(old)
fresh.value // => 42
fresh.timestamp // => Date.now() (refreshed, no longer 1000)
```

### `replacePrevious`

Swaps the `previousSuccess` stored inside a `Failure` for the latest success
found in another result. Non-failures pass through unchanged.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

const f = AsyncResult.fail<string, number>("boom")
const withPrev = AsyncResult.replacePrevious(
  f,
  Option.some(AsyncResult.success(99))
)
AsyncResult.value(withPrev) // => Option.some(99)
```

### `map`

Maps the success value. Also maps the `previousSuccess` carried inside a
`Failure`; `Initial` is left unchanged.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.map(AsyncResult.success(2), (n) => n * 10).value // => 20
AsyncResult.map(AsyncResult.initial<number>(), (n) => n * 10)._tag // => "Initial"

// Pipeable form
AsyncResult.success(2).pipe(AsyncResult.map((n) => n + 1)).value // => 3
```

### `flatMap`

Maps the success value to another `AsyncResult` and flattens. `Initial` passes
through; a `Failure` keeps its cause and remaps any stored previous success. The
mapping function also receives the originating `Success`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const r = AsyncResult.flatMap(AsyncResult.success(5), (n) =>
  n > 0 ? AsyncResult.success(n * 2) : AsyncResult.fail("non-positive")
)
r.value // => 10

AsyncResult.flatMap(AsyncResult.initial<number, string>(), () =>
  AsyncResult.success(1)
)._tag // => "Initial"
```

### `match`

Pattern matches on the variant directly: `onInitial`, `onFailure`, `onSuccess`.
Use this when the variant should win over `waiting`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const describe = (r: AsyncResult.AsyncResult<number, string>) =>
  AsyncResult.match(r, {
    onInitial: () => "initial",
    onFailure: (f) => `failure (waiting=${f.waiting})`,
    onSuccess: (s) => `success: ${s.value}`
  })

describe(AsyncResult.success(7)) // => "success: 7"
describe(AsyncResult.initial()) // => "initial"
```

### `matchWithError`

Like [`match`](#match), but splits a `Failure` into a typed `onError` (the first
typed `E` in the cause) vs. `onDefect` (a squashed non-error cause, e.g. an
unexpected throw or interrupt).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Cause } from "effect"

const handle = (r: AsyncResult.AsyncResult<number, string>) =>
  AsyncResult.matchWithError(r, {
    onInitial: () => "initial",
    onError: (error) => `error: ${error}`,
    onDefect: (defect) => `defect: ${String(defect)}`,
    onSuccess: (s) => `ok: ${s.value}`
  })

handle(AsyncResult.fail("boom")) // => "error: boom"
handle(AsyncResult.failure(Cause.die("kaboom"))) // => "defect: kaboom"
```

### `matchWithWaiting`

The UI-oriented matcher: `onWaiting` runs for any `waiting` result *and* for
`Initial`, before the variant branches. Otherwise behaves like
[`matchWithError`](#matchwitherror) (typed `onError` vs. `onDefect`).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const view = (r: AsyncResult.AsyncResult<number, string>) =>
  AsyncResult.matchWithWaiting(r, {
    onWaiting: () => "spinner",
    onError: (e) => `error: ${e}`,
    onDefect: (d) => `crash: ${String(d)}`,
    onSuccess: (s) => `value: ${s.value}`
  })

view(AsyncResult.initial()) // => "spinner"
view(AsyncResult.waiting(AsyncResult.success(1))) // => "spinner"
view(AsyncResult.success(1)) // => "value: 1"
view(AsyncResult.fail("boom")) // => "error: boom"
```

### `all`

Combines an iterable or record of `AsyncResult`s (and plain values) into a
single `AsyncResult`. Returns the first non-success result as-is, or a `Success`
of the collected values — marked `waiting` if any combined success was waiting.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

// Record: collected into a success of the same shape.
const r = AsyncResult.all({
  user: AsyncResult.success("Ada"),
  age: AsyncResult.success(36),
  plan: "free" // plain value passes through
})
r._tag // => "Success"
r.value // => { user: "Ada", age: 36, plan: "free" }

// Short-circuits on the first non-success.
AsyncResult.all([
  AsyncResult.success(1),
  AsyncResult.fail("boom"),
  AsyncResult.success(3)
])._tag // => "Failure"
```

### `toExit`

Converts a result to an `Exit`: succeed for `Success`, fail the cause for
`Failure`, and fail with `NoSuchElementError` for `Initial`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Exit } from "effect"

Exit.isSuccess(AsyncResult.toExit(AsyncResult.success(1))) // => true
Exit.isFailure(AsyncResult.toExit(AsyncResult.initial())) // => true (NoSuchElementError)
```

## Accessors

### `value`

Returns the current success value, *or* the previous success stored in a
failure, as an `Option`. Returns `None` for `Initial` and for failures with no
previous success.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Option } from "effect"

AsyncResult.value(AsyncResult.success(42)) // => Option.some(42)
AsyncResult.value(AsyncResult.initial()) // => Option.none()

const f = AsyncResult.failWithPrevious("boom", {
  previous: Option.some(AsyncResult.success(42))
})
AsyncResult.value(f) // => Option.some(42)  (stale value still readable)
```
**Caution:** Because `value` and `getOrElse` will read the *previous* success out of a
failure, they can't tell stale data from a current success. When that
distinction matters, inspect [`cause`](#cause) or [`error`](#error) (or check
the `_tag`) to know whether the result is actually a `Failure`.

### `getOrElse`

Returns the value from [`value`](#value), or evaluates a lazy fallback when no
current or previous success exists.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.getOrElse(AsyncResult.success(42), () => 0) // => 42
AsyncResult.getOrElse(AsyncResult.initial<number>(), () => 0) // => 0

// Pipeable form
AsyncResult.initial<number>().pipe(AsyncResult.getOrElse(() => -1)) // => -1
```

### `getOrThrow`

Like [`getOrElse`](#getorelse) but throws `NoSuchElementError` when no current
or previous success exists.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.getOrThrow(AsyncResult.success(42)) // => 42
AsyncResult.getOrThrow(AsyncResult.initial()) // => throws NoSuchElementError
```

### `cause`

Returns the failure `Cause` as an `Option` — `Some` only for a `Failure`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

AsyncResult.cause(AsyncResult.fail("boom")) // => Option.some(Cause.fail("boom"))
AsyncResult.cause(AsyncResult.success(1)) // => Option.none()
```

### `error`

Returns the first *typed* error `E` from a failure cause. Returns `None` for
successes, initials, defects, and interrupt-only causes.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Cause } from "effect"

AsyncResult.error(AsyncResult.fail("boom")) // => Option.some("boom")
AsyncResult.error(AsyncResult.failure(Cause.die("oops"))) // => Option.none() (defect)
AsyncResult.error(AsyncResult.success(1)) // => Option.none()
```

## Builder

### `builder`

Creates a fluent, type-tracked renderer for an `AsyncResult`. Each handler
(`onWaiting`, `onInitial`, `onSuccess`, `onError`, `onErrorTag`, `onErrorIf`,
`onDefect`, `onInterrupt`, `onFailure`) narrows the set of remaining cases at the
type level. `exhaustive()` only becomes callable once every possible case is
handled; otherwise finish with `orElse`, `orNull`, or `render`.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

const result = AsyncResult.success(42) as AsyncResult.AsyncResult<number, string>

const rendered = AsyncResult.builder(result)
  .onInitialOrWaiting(() => "Loading…")
  .onSuccess((value) => `Value: ${value}`)
  // onFailure handles the whole cause (typed errors, defects, and interrupts),
  // which is what makes the remaining cases empty and exhaustive() callable.
  .onFailure((cause) => `Failed: ${String(cause)}`)
  .exhaustive()
// => "Value: 42"
```

The builder shines when an error channel is a union of tagged errors — handle
each tag (which narrows `E`) and the compiler tracks what's left:

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"

type AppError =
  | { readonly _tag: "NotFound" }
  | { readonly _tag: "Forbidden" }

const result = AsyncResult.fail<AppError, string>({
  _tag: "NotFound"
}) as AsyncResult.AsyncResult<string, AppError>

const text = AsyncResult.builder(result)
  .onWaiting(() => "…")
  .onSuccess((value) => value)
  .onErrorTag("NotFound", () => "Not found")
  .onErrorTag("Forbidden", () => "Forbidden")
  .orElse(() => "Unknown")
// => "Not found"
```

### `Builder` (type)

The type behind [`builder`](#builder). It is a `Pipeable` that exposes
case-handler methods conditionally based on which cases are still
unhandled — `onSuccess` disappears once success is handled, `onErrorTag` narrows
`E`, and `exhaustive()` only appears when the remaining `A`, `E`, `Initial`,
defect, and interrupt slots are all `never`. Terminal methods:

- `orElse(f)` — produce the accumulated output or evaluate the fallback.
- `orNull()` — produce the output or `null`.
- `render()` — produce the output, or `null` for an unhandled `Initial`; throws
  the squashed cause for an unhandled `Failure`.
- `exhaustive()` — produce the output, available only when all cases are
  handled.

## Schemas

`AsyncResult` is serializable, which is what powers SSR/client
[hydration](https://effect.plants.sh/reactivity/atom-ref/): the server renders an atom to an
`AsyncResult`, encodes it, and the client decodes it back into the exact same
state (including `waiting`, `timestamp`, and `previousSuccess`).

### `Schema` (constructor)

Builds a `Schema.Codec` for `AsyncResult` from optional `success` and `error`
schemas. The encoded form is a tagged union of `Initial` / `Success` /
`Failure`, with the cause encoded via `Schema.Cause` (covering defects too).

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Schema } from "effect"

const ResultSchema = AsyncResult.Schema({
  success: Schema.Number,
  error: Schema.String
})

// Encode for transport (e.g. embed in server-rendered HTML)
const encoded = Schema.encodeUnknownSync(ResultSchema)(AsyncResult.success(42))
// => { _tag: "Success", value: 42, waiting: false, timestamp: <number> }

// Decode on the client — round-trips back to a real AsyncResult
const decoded = Schema.decodeUnknownSync(ResultSchema)(encoded)
AsyncResult.isAsyncResult(decoded) // => true
AsyncResult.value(decoded) // => Option.some(42)
```

Both `success` and `error` are optional and default to `Schema.Never` — pass
only what your result actually carries:

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Schema } from "effect"

// Success-only result; the error channel is never.
const OnlySuccess = AsyncResult.Schema({ success: Schema.String })
```

### `Schema` (interface)

The type returned by the [`Schema` constructor](#schema-constructor). It extends
`Schema.declareConstructor<...>` and additionally exposes the `success` and
`error` sub-schemas it was built from, so they can be reused.

```ts
import { AsyncResult } from "effect/unstable/reactivity/AsyncResult"
import { Schema } from "effect"

const ResultSchema = AsyncResult.Schema({
  success: Schema.Number,
  error: Schema.String
})

ResultSchema.success // => Schema.Number
ResultSchema.error // => Schema.String
```
**Note:** `AsyncResult` is an unstable (`effect/unstable/...`) module and its API may
change before stabilization. Import it from
`effect/unstable/reactivity/AsyncResult`.