Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions .changeset/prefer-pre-lifted.md

This file was deleted.

55 changes: 0 additions & 55 deletions .changeset/saga-async.md

This file was deleted.

2 changes: 2 additions & 0 deletions packages/boxed/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @unthrown/boxed

## 5.8.0

## 5.7.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/boxed/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/boxed",
"version": "5.7.0",
"version": "5.8.0",
"description": "Boxed interop for unthrown",
"keywords": [
"boxed",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# unthrown

## 5.8.0

## 5.7.0

## 5.6.0
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "unthrown",
"version": "5.7.0",
"version": "5.8.0",
"description": "Explicit errors as values, with a separate defect (panic) channel",
"keywords": [
"defect",
Expand Down
2 changes: 2 additions & 0 deletions packages/effect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @unthrown/effect

## 5.8.0

## 5.7.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/effect/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/effect",
"version": "5.7.0",
"version": "5.8.0",
"description": "Effect interop for unthrown",
"keywords": [
"effect",
Expand Down
2 changes: 2 additions & 0 deletions packages/neverthrow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @unthrown/neverthrow

## 5.8.0

## 5.7.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/neverthrow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/neverthrow",
"version": "5.7.0",
"version": "5.8.0",
"description": "neverthrow interop for unthrown",
"keywords": [
"errors-as-values",
Expand Down
43 changes: 43 additions & 0 deletions packages/oxlint/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# @unthrown/oxlint

## 5.8.0

### Minor Changes

- 40f7a54: `unthrown/prefer-pre-lifted`: ban `.toAsync()` on a freshly constructed
`Ok(...)` / `Err(...)`.

```ts
Ok(value).toAsync(); // → OkAsync(value)
Err(error).toAsync(); // → ErrAsync(error)
Ok().toAsync(); // → OkAsync()
Ok(undefined).toAsync(); // → OkAsync()
```

**The receiver is the whole test**, which is what makes this rule safe where
`prefer-ensure` was not. `prefer-ensure` had to decide whether an `Ok(x)` inside
a callback carried the same `x` the callback was handed — an identity judgement
across a scope, and the source of its false positives. Here the question is
syntactic: a call to the imported `Ok` or `Err`, immediately followed by
`.toAsync()`. So `.toAsync()` on a `Result` that already exists — a variable, a
call's return, a ternary, `fromNullable(...)` — is the combinator doing its
actual job and is never reported.

Autofixable for the same reason: the pre-lifted name with the arguments
untouched, `Ok()` and `Ok(undefined)` both collapsing to `OkAsync()`, and the
specifier added to the existing `unthrown` import when the name is free.

**Opt-in**, beside `no-throw` and `no-get-or-throw` — a spelling preference, not
a thesis about correctness. It is a rule rather than a convention because that
is the profile only a linter holds: `btravstack/btravstack` documented this
convention, asserted one violation, and a sweep found thirty-three. It
type-checks, tests stay green, and it is invisible in review.

The fix never adds a value specifier to an `import type { … }`, and treats a
type-only `OkAsync` binding as taken rather than as already imported — both
would produce code that does not compile. A shadowed `undefined` parameter is
resolved through scope, so only the global collapses to `OkAsync()`.

This repository now enables it too, and the autofix cleaned 21 sites across
`packages/boxed`, `packages/effect` and four examples.

Closes #260.

## 5.7.0

## 5.6.0
Expand Down
2 changes: 1 addition & 1 deletion packages/oxlint/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/oxlint",
"version": "5.7.0",
"version": "5.8.0",
"description": "oxlint plugin enforcing unthrown's conventions",
"keywords": [
"errors-as-values",
Expand Down
57 changes: 57 additions & 0 deletions packages/saga/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @unthrown/saga

## 5.8.0

### Minor Changes

- e06b3b2: `@unthrown/saga`: a sequence whose steps carry compensating undos, unwound LIFO
the moment one fails.

`DoAsync` sequences steps where a later one needs an earlier one's **value**.
There was nothing for the sequence where a later step's **failure** has to undo
the earlier ones — so every saga was hand-written, with two traps in the
spelling:

- **Ordering.** The undos must run in reverse of the steps that earned them.
Nothing checked it, and getting it backwards is silent.
- **Eagerness.** An `AsyncResult` starts on construction, so an undo built
outside the failure branch runs whether or not it was needed — the hazard
`unthrown/no-async-result-race` exists for.

```ts
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 the Err
```

Every argument is a **thunk**, so nothing is built before the saga reaches it.
Comment thread
btravers marked this conversation as resolved.
`run()` answers the last step's value, and a failure — `Err` or `Defect` — comes
back **unchanged**, so a caller triages exactly what it would have without the
saga. `run` takes no argument; an `undo` receives its own step's value, and
either may answer a plain `Result` in place of an `AsyncResult`. An `undo`
answers `unknown` in the Ok channel and `never` in the Err one: compensation
may not invent a new way to fail, because the caller is already handling the
one that triggered it. The single
exception is a **defect inside an undo** — it wins over the failure that
triggered it, since a compensation that broke is the more urgent report, and
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, which is where its first consumer
runs.

It ships as a satellite package rather than a core export because it is a
**pattern** built on the public surface — it operates no channel `unthrown`
does not already expose — and core is a finishable library. Installing it is
the opt-in; the compiler holds the boundary, since it imports nothing private.

Closes #268.
2 changes: 1 addition & 1 deletion packages/saga/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/saga",
"version": "5.7.0",
"version": "5.8.0",
"description": "A saga for unthrown's AsyncResult: steps with compensating undos, unwound LIFO",
"keywords": [
"compensation",
Expand Down
2 changes: 2 additions & 0 deletions packages/standard-schema/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @unthrown/standard-schema

## 5.8.0

## 5.7.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/standard-schema/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/standard-schema",
"version": "5.7.0",
"version": "5.8.0",
Comment thread
btravers marked this conversation as resolved.
"description": "Standard Schema (Zod, Valibot, ArkType, …) interop for unthrown",
"keywords": [
"arktype",
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @unthrown/vitest

## 5.8.0

## 5.7.0

## 5.6.0
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unthrown/vitest",
"version": "5.7.0",
"version": "5.8.0",
"description": "Vitest matchers for unthrown",
"keywords": [
"errors-as-values",
Expand Down
Loading