---
url: /unthrown/api/saga.md
---
**@unthrown/saga**

***

# @unthrown/saga

## Saga

### SagaAsyncBuilder

```ts
type SagaAsyncBuilder<T, E> = object;
```

Defined in: [saga.ts:43](https://github.com/btravstack/unthrown/blob/c3fdcf7870bbb87a3ed74f4cc521fde8030cac54/packages/saga/src/saga.ts#L43)

The builder [SagaAsync](#sagaasync) returns: steps in, one `AsyncResult` out.

#### Type Parameters

| Type Parameter | Description |
| ------ | ------ |
| `T` | what the last step produced, and what `run()` answers. |
| `E` | the union of every step's modeled error type. |

#### Properties

| Property | Modifier | Type | Description | Defined in |
| ------ | ------ | ------ | ------ | ------ |
|  `run` | `readonly` | () => `AsyncResult`<`T`, `E`> | Run the steps in order, unwinding LIFO on the first failure. | [saga.ts:63](https://github.com/btravstack/unthrown/blob/c3fdcf7870bbb87a3ed74f4cc521fde8030cac54/packages/saga/src/saga.ts#L63) |
|  `step` | `readonly` | <`T2`, `E2`>(`run`, `undo?`) => [`SagaAsyncBuilder`](#sagaasyncbuilder)<`T2`, `E` | `E2`> | Add a step, with the undo that takes it back. **Remarks** `run` is a **thunk**: an `AsyncResult` starts on construction, so a step built eagerly would run before the saga reached it. It takes no argument; `undo` receives the value its own step produced. Either may answer a plain `Result` in place of an `AsyncResult`, so a synchronous compensation needs no `toAsync()`. An `undo` answers `unknown` in the Ok channel and `never` in the Err one: compensation may not invent a new way for the saga to fail, because the caller is already handling the failure that triggered it. | [saga.ts:58](https://github.com/btravstack/unthrown/blob/c3fdcf7870bbb87a3ed74f4cc521fde8030cac54/packages/saga/src/saga.ts#L58) |

***

### SagaAsync()

```ts
function SagaAsync(): SagaAsyncBuilder<undefined, never>;
```

Defined in: [saga.ts:183](https://github.com/btravstack/unthrown/blob/c3fdcf7870bbb87a3ed74f4cc521fde8030cac54/packages/saga/src/saga.ts#L183)

Start a saga: a sequence of steps, each with an optional compensating undo,
unwound **last-in, first-out** the moment a step fails.

#### Returns

[`SagaAsyncBuilder`](#sagaasyncbuilder)<`undefined`, `never`>

#### Remarks

`DoAsync` is the sibling for a sequence that only goes forward. Reach for a
saga when a later step's failure must take back what the earlier ones did —
a placement to cancel, a reservation to release — and the alternative is the
hand-written walk-back, where two things go wrong quietly: the undos run in
the wrong order, and an `AsyncResult` built outside the failure branch runs
whether or not it was needed (an `AsyncResult` starts on construction, which
is why every argument here is a thunk).

The saga answers what the **last** step produced. A failure — a modeled `Err`
or a `Defect` — unwinds every undo recorded so far and then comes back
unchanged, so a caller triages exactly what it would have without the saga.
The one exception is a **defect inside an undo**: it wins over the failure
that triggered it, because a compensation that broke is the more urgent
report. Every remaining undo still runs first.

It is pure control flow — no timers, no clock, no randomness — so it replays
deterministically inside a workflow sandbox.

#### Examples

```ts
import { SagaAsync } from "@unthrown/saga";

const fulfilled = await SagaAsync()
  .step(() => place(order), () => cancelPlacement(order))
  .step(() => reserveStock(order), () => releaseStock(order))
  .step(() => arrangeShipping(order))
  .run();
// shipping failed → stock released, then placement cancelled, then Err(ShippingUnavailable)
```

```ts
import { ErrAsync, OkAsync } from "unthrown";
import { SagaAsync } from "@unthrown/saga";

// The undo receives the step's own value, so it can take back exactly what
// that step created:
await SagaAsync()
  .step(() => OkAsync({ id: "o-1" }), (order) => releaseStock(order.id))
  .step(() => ErrAsync("shipping is down"))
  .run(); // => Err("shipping is down"), with the stock released first
```
