resultar 3.10.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +667 -815
- package/dist/agent/SKILL.md +243 -76
- package/dist/agent/examples/workflow.mjs +5 -3
- package/dist/agent/examples/workflow.mts +13 -8
- package/dist/agent/references/api.md +339 -94
- package/dist/agent/references/integrations.md +49 -9
- package/dist/agent/references/patterns.md +232 -74
- package/dist/agent/references/review-checklist.md +60 -19
- package/dist/agent/references/services.md +25 -5
- package/dist/agent/versions.json +8 -8
- package/dist/index.d.ts +3183 -976
- package/dist/index.js +5583 -2258
- package/dist/llms.txt +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Resultar
|
|
1
|
+
# Resultar v4 Development API Guide
|
|
2
2
|
|
|
3
3
|
Use this reference after checking the version installed by the consuming project. The local package
|
|
4
4
|
types and source are authoritative when they differ from this guide.
|
|
@@ -9,46 +9,70 @@ types and source are authoritative when they differ from this guide.
|
|
|
9
9
|
- [Core Types And Constructors](#core-types-and-constructors)
|
|
10
10
|
- [Composition And Recovery](#composition-and-recovery)
|
|
11
11
|
- [Tagged Errors](#tagged-errors)
|
|
12
|
+
- [Pattern Matching](#pattern-matching)
|
|
12
13
|
- [Boundary Matching](#boundary-matching)
|
|
13
14
|
- [Async Boundaries](#async-boundaries)
|
|
14
|
-
- [
|
|
15
|
+
- [Task Policies](#task-policies)
|
|
16
|
+
- [Eager Async Compatibility Policies](#eager-async-compatibility-policies)
|
|
15
17
|
- [Collections And Control Flow](#collections-and-control-flow)
|
|
16
|
-
- [`safeTry`](#safetry)
|
|
18
|
+
- [`Result.gen` / `safeTry`](#resultgen--safetry)
|
|
17
19
|
- [ResultTask (Lazy Workflows)](#resulttask-lazy-workflows)
|
|
18
20
|
- [Observation And Cleanup](#observation-and-cleanup)
|
|
19
21
|
|
|
20
22
|
## Package Baseline
|
|
21
23
|
|
|
22
|
-
Resultar
|
|
24
|
+
Resultar is an ESM package targeting Node.js 24+. The repository currently uses TypeScript 7.
|
|
23
25
|
Do not infer consumer requirements from memory: inspect `package.json`, the lockfile, and exported
|
|
24
26
|
types first.
|
|
25
27
|
|
|
26
28
|
Prefer named imports and type-only imports:
|
|
27
29
|
|
|
28
30
|
```ts
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
Result,
|
|
33
|
+
ResultAsync,
|
|
34
|
+
ResultTask,
|
|
35
|
+
createTaggedError,
|
|
36
|
+
ok,
|
|
37
|
+
Retry,
|
|
38
|
+
tryResult,
|
|
39
|
+
tryResultAsync,
|
|
40
|
+
} from "resultar";
|
|
30
41
|
import type { StrictResult, StrictResultAsync } from "resultar";
|
|
31
42
|
```
|
|
32
43
|
|
|
33
|
-
`
|
|
34
|
-
`
|
|
44
|
+
`try`, the default export, and `tryCatch` remain deprecated aliases of `tryResult`.
|
|
45
|
+
`ResultAsync` and `StrictResultAsync` are the official in-flight `Result` types (`PromiseLike<Result<T, E>>`), returned by `task.runResult()` / `ResultTask.runResult(task)`.
|
|
46
|
+
Active in-flight constructors (`okAsync`, `errAsync`, `unitAsync`, `fromPromise`, `fromSafePromise`, `tryResultAsync`) and deprecated eager adapters/orchestration helpers (`fromThrowableAsync`, `fromCallback`, `tryAsync`, `tryCatchAsync`, `safeTry`, `runPromise`, and static orchestration helpers on `ResultAsync`) are exported directly from `"resultar"`.
|
|
47
|
+
`tryAsync` / `tryCatchAsync` retain `tryResultAsync` behavior.
|
|
48
|
+
|
|
49
|
+
`Result.gen` / `Result.fn` support sync and async generators; their async form returns eager `ResultAsync`.
|
|
50
|
+
Deprecated `safeTry` also supports both forms, retaining its final-Result contract. No plugin installation is needed.
|
|
35
51
|
|
|
36
52
|
## Core Types And Constructors
|
|
37
53
|
|
|
38
|
-
| Need
|
|
39
|
-
|
|
|
40
|
-
| Synchronous fallible value
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
| Error-only
|
|
44
|
-
|
|
|
45
|
-
| Successful
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
54
|
+
| Need | API |
|
|
55
|
+
| -------------------------------------------------- | ---------------------------------------------------- |
|
|
56
|
+
| Synchronous fallible value | `Result<T, E>` |
|
|
57
|
+
| New asynchronous workflow | `ResultTask<T, E, R>` |
|
|
58
|
+
| In-flight / eager async value | `ResultAsync<T, E>` |
|
|
59
|
+
| Error-only sync channel | `StrictResult<T, E extends Error>` |
|
|
60
|
+
| Error-only in-flight / eager async channel | `StrictResultAsync<T, E extends Error>` |
|
|
61
|
+
| Successful synchronous / lazy value | `ok(value)` / `ResultTask.ok(value)` |
|
|
62
|
+
| Successful synchronous / lazy `undefined` | `unit()` / `ResultTask.ok(undefined)` |
|
|
63
|
+
| Expected synchronous / lazy failure | `err(error)` / `ResultTask.err(error)` |
|
|
64
|
+
| In-flight / eager constructors | `okAsync(value)` / `unitAsync()` / `errAsync(error)` |
|
|
65
|
+
| Reject a missing value | `Result.fromNullishOr(value, onNullish)` |
|
|
66
|
+
| Construct from a predicate or refinement | `Result.liftPredicate(value, predicate, onFalse)` |
|
|
67
|
+
| Catch synchronous throws now | `tryResult(fn, toError)` |
|
|
68
|
+
| Wrap a synchronous throwing function | `fromThrowable(fn, toError)` |
|
|
69
|
+
| Defer a Promise factory and catch rejection | `ResultTask.tryPromise({ try, catch })` |
|
|
70
|
+
| Wrap an in-flight Promise or catch eagerly | `fromPromise(p, toError)` / `tryResultAsync(input, toError)` |
|
|
71
|
+
| Preserve a deprecated eager async function wrapper | `fromThrowableAsync(fn, toError)` |
|
|
72
|
+
| Leave a sync / Task channel at a terminal boundary | `runSync(result)` / `task.runPromise()` |
|
|
73
|
+
| Unwrap an in-flight `ResultAsync` at a boundary | `runPromise(asyncResult)` (deprecated) |
|
|
74
|
+
|
|
75
|
+
Active in-flight constructors (`okAsync`, `unitAsync`, `errAsync`, `fromPromise`, `fromSafePromise`, `tryResultAsync`) and deprecated eager compatibility helpers (`fromThrowableAsync`, `fromCallback`, `tryAsync`, `tryCatchAsync`, `safeTry`, `runPromise`) are exported from `resultar`.
|
|
52
76
|
|
|
53
77
|
Use the named options form when it improves readability:
|
|
54
78
|
|
|
@@ -59,39 +83,61 @@ const parsed = tryResult({
|
|
|
59
83
|
});
|
|
60
84
|
```
|
|
61
85
|
|
|
62
|
-
`runSync` returns the `Ok` value or throws the `Err
|
|
63
|
-
|
|
86
|
+
`runSync` returns the `Ok` value or throws the `Err`. `task.runPromise()` executes a Task and
|
|
87
|
+
resolves its success value or rejects on failure. `runPromise` from `resultar` is the eager
|
|
88
|
+
compatibility terminal. Keep runners at framework, bootstrap, CLI, or test boundaries; Tasks need an explicit
|
|
89
|
+
runner even when they have no requirements.
|
|
90
|
+
|
|
91
|
+
`Result.fromNullishOr` preserves `NonNullable<T>` and accepts other falsy values.
|
|
92
|
+
`Result.liftPredicate` preserves the input type for boolean predicates and narrows it for
|
|
93
|
+
refinements. Both also accept curried callbacks for `pipe`; curried forms retain concrete
|
|
94
|
+
input types, including literals. Error factories receive the original input and run only
|
|
95
|
+
on rejection. Callback exceptions propagate; use `tryResult` to capture external throws.
|
|
64
96
|
|
|
65
97
|
## Composition And Recovery
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
`Result`, `ResultAsync`, and `ResultTask` expose direct and curried `map`, `mapErr`, `andThen`, `orElse`,
|
|
100
|
+
`orElseSucceed`, `unwrapOr`, `catchTag`, `catchTags`, `catchReason`, `catchReasons`,
|
|
101
|
+
`unwrapReason`, `inspect`, `inspectError`, `inspectBoth`, `match`, `matchTags`,
|
|
102
|
+
`matchTagsPartial`, `as`, `asVoid`, and `filterOrElse`. Use `pipe(result, Result.map(f))` or
|
|
103
|
+
`Result.map(result, f)` for the same fluent behavior. `ResultAsync` is the official in-flight `Result` class (`PromiseLike<Result<T, E>>`), returned by `task.runResult()` and exported from `resultar`. Result's `asyncMap` / `asyncAndThen`
|
|
104
|
+
methods and statics remain supported.
|
|
105
|
+
|
|
106
|
+
- `map` transforms an `Ok` value synchronously on Result and Task; Async also awaits Promise-returning callbacks.
|
|
107
|
+
- `mapErr` transforms an `Err` value synchronously on Result and Task; Async also awaits Promise-returning callbacks.
|
|
69
108
|
- `filterOrElse` validates or narrows an `Ok` value and introduces a typed error on failure.
|
|
70
|
-
- `andThen` chains fallible work.
|
|
71
|
-
- `asyncAndThen`
|
|
109
|
+
- `andThen` chains fallible work. Async accepts a synchronous Result or another Async; Task accepts a synchronous Result or another Task. Use core `ResultTask.fromResultAsync` or `task.runResult()` (with deprecated aliases `task.toResultAsync()` and `ResultAsync.fromTask(task)`) to bridge models.
|
|
110
|
+
- `Result.asyncAndThen(result, callback)` / `.asyncAndThen` and `Result.asyncMap(result, callback)` / `.asyncMap`
|
|
111
|
+
bridge a synchronous Result into eager `ResultAsync`; both static helpers retain curried forms for `pipe`.
|
|
72
112
|
- `orElse` recovers the complete error channel.
|
|
113
|
+
- `orElseSucceed` recovers the complete error channel with a plain fallback value.
|
|
73
114
|
- `catchTag` recovers one tagged error.
|
|
74
115
|
- `catchTags` recovers selected tagged errors; unhandled variants stay in the error union.
|
|
75
|
-
- `
|
|
116
|
+
- `catchReason` / `catchReasons` / `unwrapReason` recover or promote nested tagged `reason` fields.
|
|
117
|
+
- `pipe` applies named combinators without changing Result or ResultTask semantics.
|
|
76
118
|
|
|
77
119
|
Let each step expose its narrow error type and let composition infer the union:
|
|
78
120
|
|
|
79
121
|
```ts
|
|
80
122
|
type CreateUserError = InvalidEmailError | UserExistsError | SaveUserError;
|
|
81
123
|
|
|
82
|
-
const createUser = (input: CreateUserInput):
|
|
83
|
-
validateEmail(input.email)
|
|
84
|
-
.
|
|
124
|
+
const createUser = (input: CreateUserInput): ResultTask<User, CreateUserError> =>
|
|
125
|
+
ResultTask.fromResult(validateEmail(input.email))
|
|
126
|
+
.andThen(ensureEmailAvailable)
|
|
85
127
|
.andThen((email) => saveUser({ ...input, email }));
|
|
86
128
|
```
|
|
87
129
|
|
|
130
|
+
Here the asynchronous helpers return Tasks. A deliberate transition into an eager compatibility
|
|
131
|
+
API uses `Result.asyncAndThen(validateEmail(email), ensureEmailAvailableAsync)` (or `.asyncAndThen(ensureEmailAvailableAsync)`).
|
|
132
|
+
|
|
88
133
|
Do not manually cast the final error union. A surprising inferred union is usually evidence that a
|
|
89
134
|
step has a broad or inaccurate signature.
|
|
90
135
|
|
|
91
136
|
## Tagged Errors
|
|
92
137
|
|
|
93
138
|
`createTaggedError` creates nominal `Error` subclasses with stable `_tag`, message-template props,
|
|
94
|
-
`cause`, `fingerprint`, `toJSON()`, and `.err()` support.
|
|
139
|
+
`cause`, `fingerprint`, `toJSON()`, and `.err()` / `.taskErr()` / `.errAsync()` support. New eager
|
|
140
|
+
recipes can use `errAsync(new DomainError(props))`, importing `errAsync` from `resultar`.
|
|
95
141
|
|
|
96
142
|
```ts
|
|
97
143
|
class UserNotFoundError extends createTaggedError({
|
|
@@ -114,28 +160,74 @@ const missing = UserNotFoundError.err({ userId, source: "database" });
|
|
|
114
160
|
- Use `redact`, `isRedacted`, and `revealRedacted` when sensitive error metadata must not serialize
|
|
115
161
|
or log by default.
|
|
116
162
|
- Use `findCause` or an instance's `findCause` to inspect a typed cause chain.
|
|
163
|
+
- `createTaggedError(options).withProps<Props>()` refines declared template fields and adds typed
|
|
164
|
+
optional or structured metadata. Template fields remain required. Existing factory calls keep
|
|
165
|
+
their inferred `string | number | Redacted<unknown>`
|
|
166
|
+
fields. Each typed declaration creates a fresh nominal class with the same configuration,
|
|
167
|
+
preserving metadata from getters and non-enumerable properties. Constructor input types are
|
|
168
|
+
checked by TypeScript, without runtime validation.
|
|
169
|
+
`cause` is passed as usual to the constructor and retains its `Error` or custom base type;
|
|
170
|
+
omit it from `Props`.
|
|
171
|
+
|
|
172
|
+
## Pattern Matching
|
|
173
|
+
|
|
174
|
+
Check installed root exports before using the workspace `Match` addition. It supports typed pattern
|
|
175
|
+
matching with pipe composition and fluent methods:
|
|
176
|
+
|
|
177
|
+
Use inline `result.matchTags` for ordinary HTTP routes, capturing the native context or reply.
|
|
178
|
+
Use a contextual matcher when several routes share the same response mapping.
|
|
179
|
+
|
|
180
|
+
- `Match.type<Input>()` builds a reusable matcher; `Match.value(input)` evaluates cases eagerly.
|
|
181
|
+
- `Match.fn(selector)` preserves selector arguments. Handlers receive the narrowed value followed
|
|
182
|
+
by all original arguments; this supports reusable native Hono/Fastify HTTP mappings.
|
|
183
|
+
- `Match.context<Input, [context: Context]>()` passes the matched input and typed context once,
|
|
184
|
+
preserving optional/rest arguments in the compiled function. Fluent and pipe composition work.
|
|
185
|
+
- `when`, `whenOr`, `whenAnd` and `not` handle literals, predicates and nested plain-object/array
|
|
186
|
+
prefix patterns. Literal comparison uses `===`.
|
|
187
|
+
- `tag`, partial `tags`, `tagsExhaustive`, `valueTags` and `typeTags` handle tagged unions;
|
|
188
|
+
`discriminator`, `discriminators` and `discriminatorsExhaustive` support other property names.
|
|
189
|
+
- `exhaustive` proves full coverage; `orElse` handles the remaining input. `orElseAbsurd` throws on
|
|
190
|
+
unmatched input without proving completeness.
|
|
191
|
+
- `Match.result` returns native `Result<Matched, Remaining>`; unmatched input is retained in Err.
|
|
192
|
+
It does not consume an existing domain Result. `option` returns a plain `MatchOption` tagged value.
|
|
193
|
+
- `withReturnType<Output>()` constrains handler returns. Boolean predicates, `nonEmptyString`,
|
|
194
|
+
safe `instanceOf`, optional handlers and widened literal variables retain unproven cases.
|
|
195
|
+
- Exceptions and Promises retain ordinary JavaScript behavior; matching does not run tasks or
|
|
196
|
+
await handler Promises. Existing Result/ResultAsync boundary matching retains its semantics.
|
|
197
|
+
|
|
198
|
+
See `packages/resultar/README.md#pattern-matching-with-match` for guards and all operations.
|
|
117
199
|
|
|
118
200
|
## Boundary Matching
|
|
119
201
|
|
|
120
202
|
Use branch methods inside an algorithm and matching when converting the completed result:
|
|
121
203
|
|
|
122
|
-
- `isOk()` / `isErr()` narrow for direct control flow.
|
|
204
|
+
- `isOk()` / `isErr()` narrow a synchronous `Result` for direct control flow.
|
|
123
205
|
- `match(onOk, onErr)` handles a simple boundary.
|
|
124
206
|
- `matchTags(onOk, handlers)` exhaustively maps tagged errors.
|
|
125
207
|
- `matchTagsPartial(onOk, handlers, fallback)` handles selected tags with a deliberate fallback.
|
|
126
208
|
- `matchError(error, handlers)` handles an error value directly.
|
|
127
209
|
- `matchErrorPartial(error, handlers, fallback)` partially handles an error value.
|
|
128
210
|
|
|
211
|
+
On synchronous `Result`, `match`, `matchTags`, and `matchTagsPartial` return `Out`.
|
|
212
|
+
On `ResultAsync` instances, they are native `Promise<Out>` terminals that await handler returns.
|
|
213
|
+
The static/direct/curried Async forms return composable `ResultAsync<Awaited<Out>, never>` values;
|
|
214
|
+
static `unwrapOr` similarly returns Async while the instance method returns a Promise.
|
|
215
|
+
On Task, they are lazy folds returning `ResultTask<Out, never, R>`; execute the folded
|
|
216
|
+
Task with `folded.runPromise()` for `Out`. `unwrapOr` follows the same value / Promise /
|
|
217
|
+
Task distinction. Task has no `.then` method, so `await task` does not execute it.
|
|
218
|
+
|
|
129
219
|
Include an `Error` handler whenever an error union can contain an untagged `Error`. Do not invent
|
|
130
220
|
`partialCatchTags`; `catchTags` already supports partial recovery.
|
|
131
221
|
|
|
132
222
|
## Async Boundaries
|
|
133
223
|
|
|
134
|
-
|
|
224
|
+
Prefer `ResultTask.tryPromise({ try, catch })` for new asynchronous workflows. Its
|
|
225
|
+
factory starts on each explicit task run. For in-flight or eager `ResultAsync` contracts, the
|
|
226
|
+
following constructors and wrappers (exported from `resultar`) share one execution across awaits:
|
|
135
227
|
|
|
136
228
|
- `fromPromise(existingPromise, toError)` wraps a promise that has already been created.
|
|
137
229
|
- `tryResultAsync(() => createPromise(), toError)` also catches synchronous factory throws and is
|
|
138
|
-
preferred when the
|
|
230
|
+
preferred when the caller controls creation of an in-flight `ResultAsync`.
|
|
139
231
|
- `fromSafePromise(promise)` is only for promises guaranteed not to reject by contract.
|
|
140
232
|
- `fromCallback(options)` adapts callback or subscription APIs and returns
|
|
141
233
|
`ResultAsync<T, E | AbortError>`. Supply cleanup and forward cancellation where supported.
|
|
@@ -144,25 +236,64 @@ Choose the wrapper based on ownership of the promise:
|
|
|
144
236
|
Always map `unknown` causes into a specific error near the external boundary:
|
|
145
237
|
|
|
146
238
|
```ts
|
|
147
|
-
const loadUser = (id: string):
|
|
148
|
-
|
|
149
|
-
() => client.loadUser(id),
|
|
150
|
-
(cause) => new LoadUserError({ cause, id }),
|
|
151
|
-
);
|
|
239
|
+
const loadUser = (id: string): ResultTask<User, LoadUserError> =>
|
|
240
|
+
ResultTask.tryPromise({
|
|
241
|
+
try: (signal) => client.loadUser(id, { signal }),
|
|
242
|
+
catch: (cause) => new LoadUserError({ cause, id }),
|
|
243
|
+
});
|
|
152
244
|
```
|
|
153
245
|
|
|
154
|
-
|
|
246
|
+
`ResultTask.fromResultAsync((signal) => existingAdapter(id, signal))` defers creation of an eager
|
|
247
|
+
Async until each Task run. The bridge accepts structural `PromiseLike<Result<A, E>>` values and
|
|
248
|
+
signal-aware factories, including native Promises; callers need not construct a ResultAsync instance.
|
|
249
|
+
Passing an existing Async captures its already-started execution.
|
|
250
|
+
Request-package helpers remain eager Async APIs; create them inside the factory when composing
|
|
251
|
+
new Task work. Prefer `task.runResult()` (or `ResultTask.runResult(task)`) to start one
|
|
252
|
+
requirement-free Task as an in-flight `ResultAsync`. `ResultAsync.fromTask(task)` and static/instance
|
|
253
|
+
`ResultTask.toResultAsync` remain supported as deprecated compatibility aliases with the same execution boundary.
|
|
254
|
+
|
|
255
|
+
## Task Policies
|
|
256
|
+
|
|
257
|
+
Prefer these lazy policies for new workflows:
|
|
155
258
|
|
|
156
|
-
|
|
259
|
+
- `task.retry(Retry.policy(options))` closes each attempt's scope before retrying; use `orElse`
|
|
260
|
+
after retry when exhausted-failure recovery is application policy.
|
|
261
|
+
- `task.timeout(milliseconds, onTimeout)` interrupts its child and awaits finalizers.
|
|
262
|
+
- `ResultTask.race(left, right)` selects the first success and preserves all-failure causes in
|
|
263
|
+
`Parallel`; `raceFirst` selects the first completed outcome. Both await loser cleanup.
|
|
264
|
+
- `all`, `forEach`, and `validateAll` accept a bounded `concurrency` option.
|
|
265
|
+
- `acquireRelease` / `acquireDisposable` + `scoped` await release and preserve cleanup failures.
|
|
266
|
+
|
|
267
|
+
The run supplies a live clock and scheduling by default. Use explicit run options for controlled
|
|
268
|
+
time or cooperative instruction yields. Pass the signal from `tryPromise` to the underlying client;
|
|
269
|
+
Task cancellation cannot preempt a synchronous callback. See the [Task details](#resulttask-lazy-workflows)
|
|
270
|
+
for interruption and cause semantics.
|
|
271
|
+
|
|
272
|
+
## Eager Async Compatibility Policies
|
|
273
|
+
|
|
274
|
+
While `ResultAsync` and `StrictResultAsync` are the official in-flight `Result` types, `ResultAsync` also retains the following deprecated static orchestration APIs for existing eager contracts.
|
|
275
|
+
Use its built-in signal-aware policies when maintaining those contracts:
|
|
157
276
|
|
|
158
277
|
- `ResultAsync.retry(task, options)` retries typed failures and includes `AbortError` in the error
|
|
159
278
|
channel. Use `times`, `delayMs`, `jittered`, `while`, and the provided signal deliberately.
|
|
160
279
|
- `ResultAsync.retryOrElse(task, options)` applies a typed fallback after retry exhaustion. The
|
|
161
280
|
exhausted task error is replaced by the fallback error channel.
|
|
281
|
+
- `Retry.policy(options)` supplies a reusable, frozen configuration snapshot to both
|
|
282
|
+
`ResultAsync.retry` / `retryOrElse` and `ResultTask.retry`. It retains no execution state.
|
|
283
|
+
Spread the policy to add an Async-only `signal` or `orElse`. Canonical callbacks receive
|
|
284
|
+
`RetryContext`: zero-based `attempt`, `nextAttempt`, and retries available before scheduling
|
|
285
|
+
(`retriesRemaining`). `while` runs only while budget remains; awaited `onRetry` is best-effort.
|
|
286
|
+
In ResultTask, abort/timeout stops waiting for either hook after attempt scope closure; external
|
|
287
|
+
work may continue, but late outcomes cannot schedule the observer, delay, or next attempt.
|
|
288
|
+
`delayMs` accepts a number or context function, `backoff` multiplies the base delay, and numeric
|
|
289
|
+
`jittered` varies it in both directions. Task also retains `ResultTaskRetryOptions`: its delay
|
|
290
|
+
callback receives `(retry, error)`, predicates receive `(error, retry)`, retry is one-based,
|
|
291
|
+
and boolean `jittered: true` samples between zero and the computed delay. Existing ResultAsync
|
|
292
|
+
options keep their separate callbacks and predicate calls at exhaustion.
|
|
162
293
|
- `ResultAsync.timeout(task, options)` returns the task error or the typed `onTimeout` error.
|
|
163
|
-
- `ResultAsync.race(left, right)` and `raceAll(tasks)` return the first
|
|
164
|
-
|
|
165
|
-
|
|
294
|
+
- `ResultAsync.race(left, right)` and `raceAll(tasks)` return the first `Ok`, ignoring earlier `Err`
|
|
295
|
+
results. If every task fails, they return the last completed `Err`.
|
|
296
|
+
- `ResultAsync.raceFirst(left, right)` returns the first completed `Result`, whether `Ok` or `Err`.
|
|
166
297
|
- `ResultAsync.raceWith(left, right, handlers)` exposes the winner and a handle to the still-running
|
|
167
298
|
task for custom cooperative policy.
|
|
168
299
|
- `ResultAsync.withResource({ acquire, use, release })` releases after every acquire-success path.
|
|
@@ -171,93 +302,207 @@ Use the built-in lazy policies instead of hand-written `Promise.race`, timers, o
|
|
|
171
302
|
|
|
172
303
|
Race, timeout, retry, callback, and resource helpers rely on cooperative cancellation. Pass their
|
|
173
304
|
`AbortSignal` into fetch, database, SDK, timer, or subscription work; aborting a wrapper cannot stop
|
|
174
|
-
an underlying operation that ignores the signal.
|
|
305
|
+
an underlying operation that ignores the signal. ResultAsync races and timeouts abort pending
|
|
306
|
+
losers without waiting for their settlement or cleanup. ResultTask races and timeouts own their
|
|
307
|
+
children and await cleanup before completing.
|
|
175
308
|
|
|
176
309
|
## Collections And Control Flow
|
|
177
310
|
|
|
178
|
-
|
|
311
|
+
`Result`, `ResultAsync`, and `ResultTask` provide collection and control-flow helpers:
|
|
179
312
|
|
|
180
|
-
- `combine`
|
|
181
|
-
- `combineWithAllErrors` collects every error.
|
|
313
|
+
- `all` (and `combine` on `Result` / `ResultAsync`) preserve array/tuple/record shape and return the first error.
|
|
314
|
+
- `combineWithAllErrors` collects every error (`Result` / `ResultAsync`).
|
|
182
315
|
- `validateAll` collects all validation errors; async mapped use supports bounded `concurrency`.
|
|
183
316
|
- `zip` combines exactly two values.
|
|
184
317
|
- `firstSuccessOf` tries candidates until one succeeds and otherwise returns the final error.
|
|
185
|
-
- `forEach` stops at the first error
|
|
186
|
-
|
|
187
|
-
|
|
318
|
+
- `forEach` stops at the first error. Result and Async accept `{ discard: true }`; use
|
|
319
|
+
`.asVoid()` on the returned Task when its successful array is irrelevant.
|
|
320
|
+
- `if`, `when`, `unless`, `loop`, and `iterate` model Resultar-native control flow where they improve
|
|
321
|
+
clarity. Result and Async also retain `whenResult` / `unlessResult`.
|
|
322
|
+
`when` and `unless` support direct, curried (`pipe`), and fluent calls. The conditions accept
|
|
323
|
+
boolean and Result conditions; Async additionally accepts Async conditions and Task accepts
|
|
324
|
+
Task conditions. Adapt between models explicitly.
|
|
188
325
|
|
|
189
|
-
Prefer `
|
|
190
|
-
`
|
|
191
|
-
|
|
326
|
+
Prefer `ResultTask.forEach(items, mapper, { concurrency })` for new bounded workflows and
|
|
327
|
+
`ResultTask.validateAll` for collecting independent validation errors. Existing eager Async
|
|
328
|
+
collection helpers retain their execution and concurrency contracts.
|
|
192
329
|
|
|
193
|
-
## `safeTry`
|
|
330
|
+
## `Result.gen` / `safeTry`
|
|
194
331
|
|
|
195
|
-
Use `
|
|
332
|
+
Use modern `Result.gen` for a synchronous linear Result flow:
|
|
196
333
|
|
|
197
334
|
```ts
|
|
198
|
-
const workflow =
|
|
335
|
+
const workflow = Result.gen(function* () {
|
|
199
336
|
const input = yield* validateInput(raw);
|
|
200
|
-
const
|
|
201
|
-
const saved = yield* saveRemote(remote);
|
|
337
|
+
const normalized = yield* normalizeInput(input);
|
|
202
338
|
|
|
203
|
-
return
|
|
339
|
+
return normalized;
|
|
204
340
|
});
|
|
205
341
|
```
|
|
206
342
|
|
|
207
|
-
|
|
208
|
-
`
|
|
343
|
+
`Result.gen` / `Result.fn` also accept async generators, returning eager `ResultAsync`. Deprecated
|
|
344
|
+
`safeTry` accepts both forms with final-Result returns. For new workflows with async work, prefer
|
|
345
|
+
`ResultTask.gen(function* () { ... })`, yielding Tasks, Results, and services.
|
|
346
|
+
Async `Result.gen` / `Result.fn` can yield Results and `ResultAsync` values. Wrap raw Promises before yielding; a Task generator
|
|
347
|
+
uses `tryPromise` or `fromResultAsync(factory)`.
|
|
348
|
+
|
|
349
|
+
Avoid raw `await` and broad `try/catch` inside these generators. Plain `return value` becomes
|
|
350
|
+
`Ok(value)` in modern `Result.gen`; fallible steps and early
|
|
351
|
+
errors short-circuit via `yield*` (`return yield* err(...)` or `return yield* step()`). Returning a
|
|
352
|
+
`Result`, `ResultAsync`, `Promise` (in sync generators), or `ResultTask` directly from `Result.gen`
|
|
353
|
+
is rejected at compile time. `safeTry` retains the deprecated synchronous and
|
|
354
|
+
asynchronous final-Result contract, separate from modern `Result.gen`.
|
|
355
|
+
`Result.safeUnwrap()` also remains callable with a
|
|
356
|
+
deprecation annotation; prefer `yield* result`.
|
|
357
|
+
An early `Err` returns without closing the suspended generator; `finally` does not run on that path.
|
|
358
|
+
`ResultTask.gen` closes its generator and interprets yielded cleanup before completing.
|
|
359
|
+
A missing return becomes `Ok(undefined)`, so annotate concrete workflow output types where required.
|
|
360
|
+
Native async generators await their return expressions; async `Result.gen` wraps the
|
|
361
|
+
completed iterator value in `Ok` without inspecting or assimilating it again.
|
|
362
|
+
|
|
363
|
+
`ResultGenerator<T, E = never>` annotates sync `Result.gen` / `Result.fn` bodies and reusable
|
|
364
|
+
sub-generators. `ResultTaskGenerator<A, E = never, R = never>` and `ResultTaskYieldOf<E = never, R = never>`
|
|
365
|
+
annotate Task generators. `ResultGenOptions` and `SafeTryOptions` describe the synchronous
|
|
366
|
+
object forms, and `ResultAsyncGenerator`, `AsyncResultGenOptions`, and `SafeTryAsyncOptions`
|
|
367
|
+
describe eager async bodies (all exported from `resultar`).
|
|
368
|
+
`Result.fn(body, ...operators)` wraps a synchronous or asynchronous parameterized body and applies
|
|
369
|
+
up to eight post-body operators receiving `(result, ...args)`.
|
|
370
|
+
`safeTry` retains final-Result constraints and remains distinct from modern `gen`
|
|
371
|
+
functions, which wrap plain success payloads without inspecting them.
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
// Existing safeTry contract: the final return is already a Result.
|
|
375
|
+
const legacy = safeTry(function* () {
|
|
376
|
+
const input = yield* validateInput(raw);
|
|
377
|
+
return ok(input);
|
|
378
|
+
});
|
|
379
|
+
```
|
|
209
380
|
|
|
210
381
|
## Function Utilities
|
|
211
382
|
|
|
212
383
|
Root `pipe` supports zero through eight transforms; `flow` supports one through nine
|
|
213
384
|
functions and preserves the first function's arguments/receiver. Both compose synchronously.
|
|
214
385
|
`identity` returns the original value; `constant(value)` returns a callback capturing that value.
|
|
215
|
-
Use `result.match({ ok: identity,
|
|
386
|
+
Use `result.match({ ok: identity, err: constant(fallback) })` for fixed boundary fallbacks.
|
|
216
387
|
|
|
217
388
|
## ResultTask (Lazy Workflows)
|
|
218
389
|
|
|
219
|
-
`ResultTask<A, E, R>` is
|
|
220
|
-
|
|
221
|
-
|
|
390
|
+
`ResultTask<A, E, R>` is the preferred API for new asynchronous workflows. It is a
|
|
391
|
+
lazy, reusable description: building one starts nothing, and each explicit `run*`
|
|
392
|
+
executes it again. It has no `.then` method, including when `R = never`.
|
|
393
|
+
`ResultAsync` is the in-flight `Result` returned by `task.runResult()` (and exported from `resultar`). For repeatable
|
|
394
|
+
Promise work, pass a factory to `ResultTask.tryPromise` or `fromResultAsync`; an
|
|
395
|
+
already-created Promise cannot be deferred or replayed.
|
|
396
|
+
|
|
397
|
+
Prefer instance runners in new recipes; static forms remain equivalent and supported:
|
|
398
|
+
|
|
399
|
+
| Instance form | Equivalent static form | Boundary contract |
|
|
400
|
+
| --------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
401
|
+
| `task.runResult(options?)` | `ResultTask.runResult(task, options?)` | `ResultAsync<A, E \| ScopeError<R>>`; returns a single typed failure as `Err`, rejects defects, interruption, and composite causes |
|
|
402
|
+
| `task.runPromise(options?)` | `ResultTask.runPromise(task, options?)` | `Promise<A>`; resolves the value or rejects any failure |
|
|
403
|
+
| `task.runExit(options?)` | `ResultTask.runExit(task, options?)` | `Promise<Exit<A, E>>`; preserves every outcome without rejection |
|
|
404
|
+
| `task.runFork(options?)` | `ResultTask.runFork(task, options?)` | Starts a root fiber immediately and returns `FiberHandle<A, E>` |
|
|
405
|
+
|
|
406
|
+
Both forms accept the same `ResultTaskRunOptions<R>` and check required services. Options include
|
|
407
|
+
`services`, `signal`, `clock`, `scheduler`, and `yieldEvery`. Pending scope release errors join
|
|
408
|
+
the runner error type (`E | ScopeError<R>` internally). Each call executes again; no runner memoizes
|
|
409
|
+
the result, and there is no public `task.run()` method.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const task = ResultTask.sync(() => "ready");
|
|
413
|
+
const first = await task.runResult();
|
|
414
|
+
const second = await ResultTask.runResult(task); // Same contract, separate execution
|
|
415
|
+
```
|
|
222
416
|
|
|
223
|
-
- Build: `
|
|
417
|
+
- Build: `ok`, `err`, `fromResult`, `sync`, `try`, `tryPromise` (`try`/`tryPromise` map
|
|
224
418
|
external throws into `E`; `sync` treats them as defects).
|
|
225
|
-
- Compose lazily: `map`, `
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
419
|
+
- Compose lazily: `map`, `as`, `asVoid`, `filterOrElse`, `andThen`, `orElse`, `orElseSucceed`, `unwrapOr`, `catchCause`, `sandbox`, `unsandbox`, `catchTag`, partial `catchTags`, `catchReason`, `catchReasons`, `unwrapReason` (`andThen`/`orElse`/`catchCause`/`catchTag`/`catchTags`/`catchReason`/`catchReasons` accept callbacks returning `ResultTask` or synchronous `Result`), `match`, `matchTags`, `matchTagsPartial`, `inspect`, `inspectError`, `inspectCause`, `inspectBoth`, `tap`, `tapError`, `tapCause`, `tapBoth`, `onExit`, `delay`, `timed`, `zip` (accepting `{ concurrent?: boolean }`), `makeSemaphore` / `Semaphore`, `firstSuccessOf`, `if`, `when`, `unless`, `loop`, `iterate`, `gen` with `yield*` (constrained at compile time via `ResultTaskYieldable` to `ResultTask`, `Result`, and `ServiceTag`; `Ok` unwraps, `Err` short-circuits into `E`).
|
|
420
|
+
- `map` is synchronous and preserves returned values. Compose an asynchronous step with
|
|
421
|
+
`andThen` and `ResultTask.tryPromise` instead of expecting `map` to await a Promise.
|
|
422
|
+
- `fn(function* (args) { ... }, ...operators)` creates a lazy parameterized function, capturing arguments/
|
|
423
|
+
receiver, applying up to eight post-body pipeline operators receiving `(task, ...args)`, and creating fresh generators on each execution; infer all three task channels.
|
|
229
424
|
- Tagged task recovery has instance/direct/curried forms. Handler errors and requirements
|
|
230
425
|
join the source; unhandled tags and scope markers remain. Defects/interruptions pass through;
|
|
231
|
-
composites follow `
|
|
426
|
+
composites follow `orElse`'s `ResultTaskCauseError` policy. Use `catchCause` or `sandbox` / `unsandbox` when recovering from defects (`Die`) or composite (`Sequential` / `Parallel`) causes without swallowing external interruption or pending scope release failures.
|
|
232
427
|
- `acquireDisposable` prefers async disposal, awaits it once in LIFO order, and treats
|
|
233
428
|
disposal failures as defects while preserving sequential body/cleanup causes.
|
|
429
|
+
- `ensuring(finalizer)` and `onExit((exit) => finalizer)` run a cleanup task or callback unconditionally on `Success`, `Fail`, `Die`, and `Interrupt` with an uninterrupted signal, combining body and finalizer failures into `Cause.Sequential`.
|
|
234
430
|
- Provide services: `yield* Tag`, then `provideService` / `provideServices` /
|
|
235
431
|
`provideServiceResolver` (data-first and curried); missing or incompatible providers are
|
|
236
|
-
|
|
432
|
+
checked at typed provider and run boundaries.
|
|
237
433
|
- Resources: `acquireRelease` / `acquireDisposable` + `scoped`; LIFO awaited finalizers run on every exit; deferred
|
|
238
|
-
release errors live in `R` until `scoped` folds them into `E`; `
|
|
434
|
+
release errors live in `R` until `scoped` folds them into `E`; `orElse` recovers a release
|
|
239
435
|
failure only after `scoped`.
|
|
240
|
-
- Boundaries: `runExit` preserves `Success`/`Fail`/`Die`/`Interrupt`/`Sequential`; `runResult`
|
|
241
|
-
returns one `Err` and
|
|
242
|
-
for composites); `runPromise` unwraps or rejects. Cancellation is cooperative:
|
|
243
|
-
`
|
|
244
|
-
|
|
245
|
-
`
|
|
436
|
+
- Boundaries: `runExit` preserves `Success`/`Fail`/`Die`/`Interrupt`/`Sequential`/`Parallel`; `runResult`
|
|
437
|
+
returns `ResultAsync<A, E | ScopeError<R>>` (resolving one `Err` and rejecting the rest: `AbortError` for interrupts, `ResultTaskCauseError`
|
|
438
|
+
for composites); `runPromise` unwraps or rejects. Cancellation is cooperative: fibers belong to scopes, and race/timeout await losing child cleanup.
|
|
439
|
+
`all`/`forEach` accept a concurrency limit; `validateAll` collects typed failures in input order; `ResultTask.makeSemaphore(permits)` / `Semaphore.make(permits)` shares a FIFO permit pool across tasks and fibers with `withPermit` and `withPermits`.
|
|
440
|
+
Retry closes each attempt scope and uses the run clock for backoff and jitter.
|
|
441
|
+
- `tryPromise({ try, catch, interruption })` defaults to `'wait'`: an in-flight promise settles
|
|
442
|
+
before closure. `'detach'` stops waiting after abort without stopping the underlying work.
|
|
443
|
+
Late outcomes are observed but do not replace the interrupted Exit; adapters own resources
|
|
444
|
+
acquired later. The callback-only overload keeps `'wait'`, and registered finalizers remain awaited.
|
|
445
|
+
- Fibers keep the existing `join` / `interrupt` shape. Namespace `Fiber.await(fiber)` lazily
|
|
446
|
+
observes the full Exit without requesting interruption; `join` unwraps the result and `interrupt`
|
|
447
|
+
requests cancellation and waits for finalizers.
|
|
448
|
+
- `Fiber.join(fiber, { interruption: 'detach' })` stops waiting on waiter cancellation without
|
|
449
|
+
canceling the target or consuming its failure. Default `'wait'` preserves existing behavior.
|
|
450
|
+
The owning scope still closes its children on exit.
|
|
451
|
+
- `task.runFork(options)` immediately starts a root run and returns `FiberHandle<A, E>`.
|
|
452
|
+
Native handles from `runFork` / `forkChild` have `id`, `poll()` and `addObserver(callback)`;
|
|
453
|
+
namespace `Fiber.poll` / `Fiber.addObserver` delegate the same diagnostics. Poll is `undefined`
|
|
454
|
+
while running; observers and snapshots expose the full Exit after cleanup. Observers return an
|
|
455
|
+
unsubscribe function, notify once, and ignore callback failures. Diagnostics do not consume failures.
|
|
456
|
+
- Run options accept `yieldEvery` (default 2048; positive safe integer or `Infinity` to disable)
|
|
457
|
+
and `scheduler: TaskScheduler`. `Scheduler.live` uses `setImmediate`; a custom scheduler provides
|
|
458
|
+
`yield(signal): PromiseLike<void>`. A dispatcher throw/rejection produces a `Die` and switches that
|
|
459
|
+
fiber to `Scheduler.live`, still attempting release instructions even if the dispatcher failed
|
|
460
|
+
during cleanup; release failures remain alongside it in `Cause.Sequential`.
|
|
461
|
+
Nested execution shares one
|
|
462
|
+
fiber budget; children get independent budgets. This schedules between instructions, not within
|
|
463
|
+
a synchronous callback, and is independent of the run clock.
|
|
464
|
+
`Clock.virtual().advanceBy` drains pending default scheduler yields for that clock before advancing;
|
|
465
|
+
other clocks and custom scheduler queues remain independently controlled.
|
|
466
|
+
An infinite runnable chain that never waits for other work prevents virtual time from advancing.
|
|
467
|
+
|
|
468
|
+
Inside both `Result.gen` and `ResultTask.gen`, return a plain success value and use `yield*` for
|
|
469
|
+
fallible steps or early errors; `safeTry` retains its final Result contract.
|
|
470
|
+
Prefer Task's `ok`, `err`, `mapErr`, `andThen`, and `orElse`, with their
|
|
471
|
+
instance/direct/curried forms where applicable. Deprecated `succeed` / `fail`, `mapError`,
|
|
472
|
+
`flatMap`, and `catchAll` retain their contracts. Task object handlers support `{ ok, err }`,
|
|
473
|
+
`{ ok, error }`, and `{ onSuccess, onFailure }`; positional `match(onOk, onErr)` also remains available.
|
|
474
|
+
`task.runResult()` eagerly starts one requirement-free Task
|
|
475
|
+
execution and returns a memoized `ResultAsync`. `ResultAsync.fromTask(task)` and static/instance `ResultTask.toResultAsync`
|
|
476
|
+
remain supported as deprecated aliases of `runResult()`.
|
|
477
|
+
`ResultTask.fromResultAsync(existing)` accepts any `PromiseLike<Result<A, E>>` and observes that
|
|
478
|
+
execution; its signal-aware factory form creates fresh work on each Task run.
|
|
246
479
|
For lazy `ResultTask` workflows with typed services and scopes, see `packages/resultar/README.md`
|
|
247
480
|
(Lazy Workflows With ResultTask) and `docs/rfcs/rfc-0001-result-task-core.md`.
|
|
248
481
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
482
|
+
Use runtime `Cause.isCause`, `hasFails`, `hasDies`, `hasInterrupts`, `failures`, and `defects` to inspect the existing `Cause<E>`
|
|
483
|
+
tree. `Cause.findError` selects its first typed error left to right, returning
|
|
484
|
+
`Result<E, Cause<never>>`; when no typed failure exists, `Err` contains the original tree.
|
|
485
|
+
`Cause.pretty` formats every leaf in order, retaining Error stacks, circular payload handling,
|
|
486
|
+
and JSON redaction. Traversal is iterative; sequential and parallel structure is retained.
|
|
487
|
+
Use `findCause(error, ErrorClass)` for a JavaScript Error's own cause chain.
|
|
255
488
|
|
|
256
|
-
|
|
257
|
-
cleanup callbacks are also best-effort. Prefer `withResource` for acquire/use/release workflows.
|
|
489
|
+
## Observation And Cleanup
|
|
258
490
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
491
|
+
Across `Result`, `ResultAsync`, and `ResultTask`, `inspect` / `inspectError` / `inspectBoth` and
|
|
492
|
+
`log` preserve the original result. Task object handlers use `{ ok, err }`; Result and Async
|
|
493
|
+
also accept their older object forms. Callback
|
|
494
|
+
throws and rejected callback Promises are ignored, and returned tasks are not executed
|
|
495
|
+
or composed. Deprecated `Result` and `ResultAsync` `tap` / `tapError` retain that
|
|
496
|
+
best-effort observation behavior.
|
|
497
|
+
|
|
498
|
+
Result `toDisposable` and Async `toAsyncDisposable` integrate with Node.js `using` / `await using`; their
|
|
499
|
+
cleanup callbacks are also best-effort. `ResultAsync.withResource` preserves its existing
|
|
500
|
+
best-effort release contract. Task has no `toAsyncDisposable`; use scoped acquisition or `ensuring`.
|
|
501
|
+
|
|
502
|
+
On `ResultTask`, `tap` / `tapError` / `tapCause` / `tapBoth` provide lazy effectful composition. Returned task errors and
|
|
503
|
+
requirements join the workflow type. Callback throws and rejected Promises become `Die`;
|
|
504
|
+
a returned `ResultTask.err(error)` or `err(error)` becomes a typed failure. `inspect` / `inspectError` / `inspectCause` /
|
|
505
|
+
`inspectBoth` (`{ ok, err }`) are the best-effort
|
|
506
|
+
alternative: they ignore observer failures and never execute returned tasks. `runExit` preserves
|
|
507
|
+
every exit; `runResult` can reject. Do not substitute best-effort `withResource` release for
|
|
508
|
+
awaited `acquireRelease` + `scoped` resource ownership.
|