resultar 3.10.0 → 4.0.1
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 +244 -77
- 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 +29 -9
- package/dist/agent/versions.json +9 -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
package/dist/agent/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: resultar
|
|
3
|
-
description: Build, review, migrate, test, and document TypeScript code using Resultar
|
|
3
|
+
description: Build, review, migrate, test, and document TypeScript code using Resultar v4, with Result for synchronous values, ResultTask for new lazy workflows, and ResultAsync for in-flight Results and eager compatibility. Covers typed errors, generators, services, scopes, request adapters, and resultar-check. Use when expected failures should become typed values, thrown or rejected work must be wrapped, async policies need design, HTTP JSON needs TypeBox or Zod validation, or an existing Resultar codebase needs review or migration.
|
|
4
4
|
metadata:
|
|
5
5
|
tags: [typescript, error-handling]
|
|
6
6
|
domains: [foundation]
|
|
@@ -8,9 +8,9 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# Resultar Engineering
|
|
10
10
|
|
|
11
|
-
Use Resultar to keep expected failures explicit in TypeScript
|
|
12
|
-
|
|
13
|
-
the final transport, job, CLI, or integration boundary.
|
|
11
|
+
Use Resultar to keep expected failures explicit in TypeScript, starting with Result values and
|
|
12
|
+
adding task execution where the workflow needs it. Preserve concrete success and error types from
|
|
13
|
+
the domain boundary to the final transport, job, CLI, or integration boundary.
|
|
14
14
|
|
|
15
15
|
## Start From Repository Truth
|
|
16
16
|
|
|
@@ -27,22 +27,71 @@ the final transport, job, CLI, or integration boundary.
|
|
|
27
27
|
4. Adapt examples when the consumer uses an older Resultar version; do not silently emit APIs
|
|
28
28
|
that are absent locally.
|
|
29
29
|
|
|
30
|
+
## Adopt Gradually
|
|
31
|
+
|
|
32
|
+
Choose the smallest model that solves the current use case. Resultar does not require a whole
|
|
33
|
+
application rewrite or a move to services, scopes, clocks, or fibers before typed errors are useful.
|
|
34
|
+
|
|
35
|
+
1. Start with `Result<T, E>` in one synchronous validation, parser, or domain operation. `T` is
|
|
36
|
+
the success value and `E` is the expected error type; Result's second parameter is not a
|
|
37
|
+
service requirement.
|
|
38
|
+
2. Use `ResultAsync<T, E>` (`StrictResultAsync<T, E>`) as the in-flight `Result` returned by
|
|
39
|
+
`task.runResult()` or at existing eager, Promise-shaped boundaries. Migrate one adapter or use
|
|
40
|
+
case at a time while preserving execution.
|
|
41
|
+
3. Prefer `ResultTask<T, E, R>` for new asynchronous workflows. It supplies lazy, repeatable
|
|
42
|
+
work; add typed service requirements, owned resources, or structured concurrency when needed.
|
|
43
|
+
`R` tracks unresolved requirements; ordinary tasks use its default `never`.
|
|
44
|
+
4. Add retry when an operation has a retryable error policy. `Retry.policy` works with both
|
|
45
|
+
ResultAsync factories and ResultTask; retry does not require migrating every async call.
|
|
46
|
+
5. Add a custom `Clock` when task time policies need explicit time control or deterministic tests,
|
|
47
|
+
and owned fibers when child lifetimes and cancellation need supervision. Task runs already
|
|
48
|
+
supply a live clock; sequential tasks do not need explicit fibers.
|
|
49
|
+
|
|
50
|
+
Keep existing Result helpers and eager adapters in use as tasks are adopted. One `resultar`
|
|
51
|
+
installation exports all synchronous, lazy `ResultTask`, and in-flight/eager `ResultAsync` helpers
|
|
52
|
+
and types from `"resultar"`.
|
|
53
|
+
In-flight value and Promise constructors (`ResultAsync.ok`, `ResultAsync.err`, `ResultAsync.unit`,
|
|
54
|
+
`ResultAsync.fromPromise`, `ResultAsync.fromSafePromise`, `ResultAsync.try`, and top-level `okAsync`,
|
|
55
|
+
`errAsync`, `unitAsync`, `fromPromise`, `fromSafePromise`, `tryResultAsync`) remain active for
|
|
56
|
+
boundary adaptation and mocks, while static orchestration helpers on `ResultAsync`, `fromCallback`,
|
|
57
|
+
`fromThrowableAsync`, `fromTask`, and `safeTry` are deprecated compatibility APIs.
|
|
58
|
+
`ResultAsync` is a `PromiseLike<Result<T, E>>` class representing an in-flight `Result`: work starts eagerly (or when `task.runResult()` is called) and repeated awaits
|
|
59
|
+
observe the same execution. `ResultTask` has no `.then` method; use explicit runners for every
|
|
60
|
+
Task, including when `R = never`. Prefer `task.runResult()`, `task.runPromise()`, `task.runExit()`,
|
|
61
|
+
and `task.runFork()` in new code; static runners remain equivalent and supported. Satisfying services
|
|
62
|
+
and scopes leaves a Task. Adapt Async with
|
|
63
|
+
`ResultTask.fromResultAsync`; `task.toResultAsync()` and `ResultAsync.fromTask(task)` are deprecated
|
|
64
|
+
aliases of `task.runResult()`. The inbound Task bridge accepts any `PromiseLike<Result<A, E>>`
|
|
65
|
+
or signal-aware factory, without requiring an Async instance. Read the
|
|
66
|
+
[gradual adoption guide](https://github.com/inaiat/resultar/blob/main/docs/gradual-adoption.md).
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pnpm add resultar
|
|
70
|
+
```
|
|
71
|
+
|
|
30
72
|
## Choose The Execution Model
|
|
31
73
|
|
|
32
|
-
| Contract
|
|
33
|
-
|
|
|
34
|
-
| Execution
|
|
35
|
-
| Generator return
|
|
36
|
-
| Error transform / recovery | `mapErr` / `orElse` | `mapErr` / `orElse` | `
|
|
37
|
-
| `
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
| Contract | `Result` / `Result.gen` | `ResultAsync<T, E>` (in-flight `Result`) | `ResultTask<T, E, R>` |
|
|
75
|
+
| -------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
76
|
+
| Execution | Sync values; async `Result.gen` / `fn` return eager Async | Starts eagerly (via `task.runResult()` or in-flight constructors); repeated awaits observe one execution | Lazy blueprint; each explicit `run*` executes again |
|
|
77
|
+
| Generator return | `return value`; `yield*` short-circuits failures | Async `Result.gen` / `Result.fn`; bridge into Task generators | `return value`; compose tasks, services and Results with `yield*` |
|
|
78
|
+
| Error transform / recovery | `mapErr` / `orElse` / `orElseSucceed`; `unwrapOr` returns a value | `mapErr` / `orElse` / `orElseSucceed`; `unwrapOr` returns a Promise | `mapErr` / `orElse` / `orElseSucceed`; `unwrapOr` returns a Task |
|
|
79
|
+
| Boundary fold | `match` / `matchTags` / `matchTagsPartial` return `Out` | Instance: `Promise<Out>`; static: `ResultAsync<Out, never>` | Return `ResultTask<Out, never, R>`; execute with `runPromise` for `Out` |
|
|
80
|
+
| Observation | `inspect` / `inspectError` / `inspectBoth` ignore callback failures | Same, on its eager execution; returned tasks are not executed | Same, lazily; returned tasks are not executed |
|
|
81
|
+
| Fallible side operations | `andThen` | `andThen`; deprecated `tap` / `tapError` remain best-effort observers | `tap` / `tapError` / `tapCause` / `tapBoth` compose failures and requirements |
|
|
82
|
+
| Cleanup | Disposal callbacks are best-effort | `withResource` starts eagerly and releases best-effort | `acquireRelease` + `scoped` await release and preserve failures |
|
|
83
|
+
|
|
84
|
+
Prefer Task for new asynchronous workflows. Preserve the eager Async facade where callers depend
|
|
85
|
+
on it while migrating explicitly. `task.runResult()` returns `ResultAsync<A, E | ScopeError<R>>`,
|
|
86
|
+
converting a single typed failure to `Err` and rejecting defects, interruption, and composite causes.
|
|
87
|
+
`task.runPromise()` resolves the value or
|
|
88
|
+
rejects any failure; `task.runExit()` preserves every exit without rejection; `task.runFork()`
|
|
89
|
+
starts a root fiber immediately. Both instance and static runners enforce service requirements and
|
|
90
|
+
accept the same `ResultTaskRunOptions<R>` (`services`, `signal`, `clock`, `scheduler`, `yieldEvery`).
|
|
91
|
+
Deferred scope release errors join the runner's error type (`E | ScopeError<R>` internally).
|
|
92
|
+
Every runner call starts another execution; there is no public `task.run()` method.
|
|
93
|
+
See [API details](references/api.md).
|
|
94
|
+
For DI, Hono, Fastify and NestJS, read [services and HTTP scopes](references/services.md). Use
|
|
46
95
|
`ResultTask.service<T>()(name)` or DI `Service.require<T>()(name)` for inferred identifiers. Class
|
|
47
96
|
services support optional `requires` with a factory returning a synchronous value or ResultTask
|
|
48
97
|
(Promises and thenables are rejected); generator-only `make`
|
|
@@ -50,17 +99,21 @@ remains available. Check installed versions before using these overloads.
|
|
|
50
99
|
|
|
51
100
|
## Apply Opinionated Defaults
|
|
52
101
|
|
|
53
|
-
- Return `Result<T, E>` for synchronous fallible work and `
|
|
54
|
-
|
|
55
|
-
- Prefer `StrictResult<T, E extends Error>`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
- Return `Result<T, E>` for synchronous fallible work and prefer `ResultTask<T, E, R>` for new
|
|
103
|
+
asynchronous workflows. Use `ResultAsync<T, E>` as the in-flight `Result` returned by `task.runResult()` or for existing eager contracts.
|
|
104
|
+
- Prefer `StrictResult<T, E extends Error>` when errors must extend `Error`. Use
|
|
105
|
+
`StrictResultAsync<T, E extends Error>` on eager boundaries; keep concrete `E` and `R`
|
|
106
|
+
on task boundaries.
|
|
107
|
+
- When migrating an eager async service or repository, return `ResultAsync` / `StrictResultAsync`
|
|
108
|
+
directly instead of `Promise<T>`, `Promise<Result>` or `Promise<StrictResult>`. Map external
|
|
109
|
+
driver failures to typed errors in the adapter; services compose that result directly. Remove
|
|
110
|
+
the function's `async` wrapper; changing only its return annotation cannot change the runtime
|
|
111
|
+
value. This is a migration target for that contract, not a prerequisite to start using Resultar.
|
|
112
|
+
- Enable `preferResultAsync` for the existing eager application contracts being migrated. Choose
|
|
113
|
+
its mode and file scope deliberately; `preferResultAsync: "error"` with
|
|
114
|
+
`preferResultAsyncMode: "all"` enforces a project-wide eager boundary policy, not a universal
|
|
115
|
+
requirement for Resultar adoption. Document local rule suppressions for native framework/driver
|
|
116
|
+
callbacks that require Promise interoperability.
|
|
64
117
|
- Prefer `createTaggedError` for expected domain and application failures that need stable identity,
|
|
65
118
|
metadata, causes, serialization, or exhaustive handling.
|
|
66
119
|
- Keep error unions specific. Let composition widen them naturally; do not collapse them to `Error`
|
|
@@ -69,6 +122,8 @@ remains available. Check installed versions before using these overloads.
|
|
|
69
122
|
arguments, or deliberate terminal/process boundaries.
|
|
70
123
|
- Wrap third-party throws and promise rejections at the edge, then keep the internal flow Resultar-
|
|
71
124
|
native.
|
|
125
|
+
- `ResultTask.map` is synchronous. Compose asynchronous steps with `andThen` and `tryPromise`;
|
|
126
|
+
`ResultAsync.map` and `mapErr` continue to await Promise-returning callbacks.
|
|
72
127
|
- Avoid `T | Error`, sentinel `null`, broad `try/catch`, rejected expected failures, and unsafe casts
|
|
73
128
|
as substitutes for a Resultar error channel.
|
|
74
129
|
- Keep `_unsafeUnwrap()` and `_unsafeUnwrapErr()` in tests. Use `unwrapOrThrow()` only when throwing
|
|
@@ -76,26 +131,62 @@ remains available. Check installed versions before using these overloads.
|
|
|
76
131
|
|
|
77
132
|
For reusable synchronous transformations, use root `pipe` / `flow`. `identity` preserves
|
|
78
133
|
its callback input; `constant(value)` captures a fallback. These utilities do not await or
|
|
79
|
-
execute tasks. Use `
|
|
80
|
-
|
|
81
|
-
|
|
134
|
+
execute tasks. Use `Result.fn` and `ResultTask.fn` (including optional post-body pipeline
|
|
135
|
+
operators) for parameterized generator workflows, and `catchTag` / partial `catchTags` /
|
|
136
|
+
`catchReason` / `catchReasons` / `unwrapReason` for typed
|
|
137
|
+
recovery. Native disposables use `acquireDisposable` in a scope; async disposal is awaited and
|
|
138
|
+
disposal failures become defects. For unconditional task cleanup without a scoped resource, use
|
|
139
|
+
`ensuring(finalizer)`.
|
|
140
|
+
|
|
141
|
+
`Result` and `ResultAsync` expose direct and curried `map`, `mapErr`, `andThen`, `orElse`,
|
|
142
|
+
`orElseSucceed`, `unwrapOr`, `catchTag`, `catchTags`, `catchReason`, `catchReasons`,
|
|
143
|
+
`unwrapReason`, `inspect`, `inspectError`, `inspectBoth`, `as`, `asVoid`, `when`, `unless`, `filterOrElse`, `match`,
|
|
144
|
+
`matchTags`, and `matchTagsPartial` for `pipe`. Async static matching and `unwrapOr` wrap outputs
|
|
145
|
+
in composable Async values; their instance forms remain native Promise terminals.
|
|
146
|
+
Result methods and statics `asyncMap` / `asyncAndThen` (`Result.asyncMap(result, callback)` /
|
|
147
|
+
`Result.asyncAndThen(result, callback)`, with curried forms for `pipe`) bridge synchronous Results into eager `ResultAsync`.
|
|
148
|
+
|
|
149
|
+
For value and domain-union pattern matching, use root `Match` (check installed exports first).
|
|
150
|
+
`Match.type<Input>().pipe(...)` and fluent methods
|
|
151
|
+
build reusable functions; `Match.value` evaluates cases immediately. `Match.fn(selector)` passes
|
|
152
|
+
the narrowed selected value followed by every original argument to handlers, preserving native
|
|
153
|
+
HTTP contexts. `Match.context<Input, [context: Context]>()` passes `(narrowedInput, context)` without repeating
|
|
154
|
+
the input. `tagsExhaustive` / `exhaustive` require full coverage; `orElse` receives the remaining
|
|
155
|
+
type. `Match.result` returns a native Resultar Result for matched/unmatched input, whereas
|
|
156
|
+
`result.match` / `matchTags` consume domain Results. Boolean predicates and safe partial refinements
|
|
157
|
+
do not prove exhaustive coverage. See [API details](references/api.md#pattern-matching).
|
|
158
|
+
|
|
159
|
+
Use Task's canonical `ok`, `err`, `mapErr`, `andThen`, and `orElse` in new code. Deprecated
|
|
160
|
+
`succeed` / `fail`, `mapError`, `flatMap`, and `catchAll` retain their contracts. Prefer `{ ok, err }`
|
|
161
|
+
object handlers and `Retry.policy(...)`; older handler shapes and Task retry options remain supported.
|
|
162
|
+
Prefer `task.runResult()` (or `ResultTask.runResult(task)`); `ResultAsync.fromTask` and static/instance `toResultAsync` remain callable as deprecated aliases.
|
|
163
|
+
Prefer `tryResult` and `tryResultAsync` from `resultar`; their supported aliases retain
|
|
164
|
+
deprecation annotations. Tagged errors keep `.err()`, `.taskErr()`, and `.errAsync()`; new eager code
|
|
165
|
+
can use `errAsync(new DomainError(props))`, importing `errAsync` from `resultar`.
|
|
82
166
|
|
|
83
167
|
## Choose The Composition Operator
|
|
84
168
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
169
|
+
Use `Result.fromNullishOr` to turn missing values into typed errors and `Result.liftPredicate`
|
|
170
|
+
to construct a Result from a predicate or refinement. Both support direct and curried forms;
|
|
171
|
+
callbacks that throw propagate their exceptions. Inspect task cause trees with `Cause.isCause`, `hasFails`,
|
|
172
|
+
`hasDies`, `hasInterrupts`, `failures`, `defects`, `findError`, and `pretty`; keep `findCause` for Error cause chains.
|
|
173
|
+
|
|
174
|
+
| Need | Prefer |
|
|
175
|
+
| ------------------------------------------- | ----------------------------------------- |
|
|
176
|
+
| Transform an `Ok` without failure | `map` |
|
|
177
|
+
| Transform an `Err` | `mapErr` |
|
|
178
|
+
| Validate an `Ok` with a predicate | `filterOrElse` |
|
|
179
|
+
| Continue with a sync fallible step | `andThen` |
|
|
180
|
+
| Start lazy work from a `Result` | `ResultTask.fromResult` + `andThen` |
|
|
181
|
+
| Preserve a sync-to-eager compatibility step | `Result.asyncAndThen` / `.asyncAndThen` |
|
|
182
|
+
| Recover the whole error channel | `orElse` |
|
|
183
|
+
| Recover selected tagged errors | `catchTag` / `catchTags` |
|
|
184
|
+
| Branch directly | `isOk()` / `isErr()` |
|
|
185
|
+
| Convert a result at a boundary | `match` / `matchTags` |
|
|
186
|
+
| Map selected tagged errors with fallback | `matchTagsPartial` |
|
|
187
|
+
| Reuse a named Result combinator | `pipe` |
|
|
188
|
+
| Write a sync linear pipeline | `Result.gen` with `yield*` |
|
|
189
|
+
| Write a lazy async linear pipeline | `ResultTask.gen` with `yield*` |
|
|
99
190
|
|
|
100
191
|
Use `catchTags` for partial pipeline recovery; unhandled tags stay in the error channel. Do not
|
|
101
192
|
invent `partialCatchTags`. Use `matchTagsPartial` only for boundary matching with a deliberate
|
|
@@ -117,6 +208,11 @@ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =
|
|
|
117
208
|
```
|
|
118
209
|
|
|
119
210
|
- Let message-template variables define required constructor props.
|
|
211
|
+
- Use `createTaggedError(options).withProps<Props>()` for precise metadata types, such as
|
|
212
|
+
`{ id: string }`, and optional or structured fields. Template fields remain required; other
|
|
213
|
+
template fields keep their defaults.
|
|
214
|
+
The typed declaration creates a fresh nominal class with compile-time metadata types.
|
|
215
|
+
Pass `cause` to the constructor as usual; its type follows `Error` or the custom base.
|
|
120
216
|
- Preserve `cause` when converting an external failure.
|
|
121
217
|
- Treat `ErrorClass.is(value)` as nominal; do not rely on spoofed `_tag` objects.
|
|
122
218
|
- Use `TaggedEnum` only for lightweight tagged domain variants that do not need to be real errors.
|
|
@@ -124,46 +220,111 @@ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =
|
|
|
124
220
|
|
|
125
221
|
## Compose Async Work
|
|
126
222
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
- Use `
|
|
133
|
-
- Use `
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
223
|
+
Return a Task from new async adapters and services. Use `ResultTask.tryPromise({ try, catch })`
|
|
224
|
+
to create the Promise on each run, capture factory throws and rejections, and map `unknown` into a
|
|
225
|
+
concrete error. Forward the supplied signal to the client. `map` stays synchronous; use `andThen`
|
|
226
|
+
to compose another Task or a synchronous Result.
|
|
227
|
+
|
|
228
|
+
- Use `retry(Retry.policy(...))` and `timeout(milliseconds, onTimeout)` for typed retry and time policy.
|
|
229
|
+
- Use `ResultTask.race` for first success or `raceFirst` for first outcome. They own their children
|
|
230
|
+
and await loser cleanup.
|
|
231
|
+
- Use `ResultTask.forEach` for bounded first-error batch work and `validateAll` to collect
|
|
232
|
+
independent validation errors.
|
|
233
|
+
- Use `acquireRelease` + `scoped` for awaited, failure-preserving resource cleanup.
|
|
234
|
+
- Fold a Task with `match` / `matchTags`, then execute that fold with `folded.runPromise()`.
|
|
235
|
+
Use `runResult` for a Result boundary or `runExit` to inspect every cause without rejection.
|
|
236
|
+
|
|
237
|
+
Request helpers and existing callback adapters return eager `ResultAsync`.
|
|
238
|
+
Create them inside `ResultTask.fromResultAsync((signal) => adapter(...))` when new Task work must
|
|
239
|
+
be lazy and repeatable. Capturing an already-created Async shares its existing execution.
|
|
240
|
+
|
|
241
|
+
For an existing eager contract, keep `fromPromise` for an existing Promise, `tryResultAsync` for
|
|
242
|
+
a Promise factory, `fromSafePromise` only for guaranteed non-rejection, and `fromCallback` for
|
|
243
|
+
subscriptions with cleanup. Its `ResultAsync.retry` / `retryOrElse`, `timeout`, `race` / `raceAll` /
|
|
244
|
+
`raceFirst`, collections, and best-effort `withResource` remain supported; their semantics differ
|
|
245
|
+
from Task ownership. Do not replace such a boundary with a Task without migrating its callers.
|
|
246
|
+
`runPromise(asyncResult)` imported from `resultar` is the legacy terminal; core Task uses
|
|
247
|
+
`task.runPromise()` (or the equivalent `ResultTask.runPromise(task)`).
|
|
248
|
+
|
|
249
|
+
Use `Retry.policy(options)` for retry configuration shared by ResultAsync and ResultTask. It keeps
|
|
250
|
+
attempt state per execution and supplies the same zero-based callback context in both models;
|
|
251
|
+
eager Async options retain their existing signatures. Task also retains its older retry options:
|
|
252
|
+
one-based retry callbacks and boolean jitter differ from the portable policy contract.
|
|
253
|
+
See [retry patterns](references/patterns.md#apply-retry-and-timeout-policy).
|
|
254
|
+
|
|
255
|
+
Task retry, sleeps, and timeouts use the run clock automatically. Keep the default `Clock.live`
|
|
256
|
+
for ordinary execution; pass `clock: Clock.virtual()` at a task run for deterministic time tests.
|
|
257
|
+
For structured parallel work, start with `ResultTask.all` or `forEach` and explicit concurrency.
|
|
258
|
+
Use `forkChild` with `Fiber.join`, `Fiber.await`, or `Fiber.interrupt` only when the use case needs
|
|
259
|
+
to manage a child handle inside its owning scope.
|
|
260
|
+
|
|
261
|
+
`ResultTask.tryPromise` waits for in-flight work on interruption by default. Set
|
|
262
|
+
`interruption: 'detach'` in its options only when the run should stop waiting; underlying work can
|
|
263
|
+
continue, so an adapter must own resources acquired after detachment. Task scope finalizers still run.
|
|
264
|
+
`Fiber.await(fiber)` observes the full child Exit without requesting interruption; `join` unwraps
|
|
265
|
+
the child result and `interrupt` requests cancellation. Task runs yield every 2048 instructions by
|
|
266
|
+
default; `yieldEvery` / `scheduler` run options configure cooperative scheduling, not preemption of
|
|
267
|
+
synchronous callbacks. See [task API semantics](references/api.md#resulttask-lazy-workflows).
|
|
268
|
+
|
|
269
|
+
Use `task.runFork(options)` when a terminal boundary needs an immediately running
|
|
270
|
+
root fiber. Its native `FiberHandle` exposes `id`, `poll()` and removable `addObserver` diagnostics
|
|
271
|
+
after finalizers; diagnostics do not consume owned failures. `forkChild` returns the same native
|
|
272
|
+
handle within its scope. `Fiber.join(fiber, { interruption: 'detach' })` cancels the wait without
|
|
273
|
+
canceling the native target; default join behavior remains `'wait'`. Closing an owning scope still
|
|
274
|
+
interrupts and awaits its children.
|
|
275
|
+
|
|
276
|
+
Return `ResultTask<A, E, R>` directly from lazy service functions and plain success values inside
|
|
277
|
+
`ResultTask.gen` / `fn`. Compose a final step with `return yield* step`; `return step` leaves that
|
|
278
|
+
task unexecuted as the success value. Execute all tasks with an explicit runner;
|
|
279
|
+
`await task` returns the unexecuted Task because Task has no `.then` method.
|
|
280
|
+
|
|
281
|
+
Across `Result`, `ResultAsync`, and `ResultTask`, `inspect` / `inspectError` / `inspectBoth` and
|
|
282
|
+
`log` provide best-effort observation, swallowing callback failures without executing returned
|
|
283
|
+
tasks. Deprecated `Result` and `ResultAsync` `tap` / `tapError` retain that observation contract.
|
|
284
|
+
Only Task `tap` / `tapError` / `tapCause` / `tapBoth` compose fallible side operations whose
|
|
285
|
+
errors and defects propagate.
|
|
148
286
|
|
|
149
287
|
## Use `Result.gen` For Linear Result Flows
|
|
150
288
|
|
|
151
289
|
```ts
|
|
152
|
-
const
|
|
153
|
-
Result.gen(
|
|
290
|
+
const validateUser = (input: CreateUserInput): StrictResult<ValidUserInput, InvalidUserError> =>
|
|
291
|
+
Result.gen(function* () {
|
|
154
292
|
const email = yield* validateEmail(input.email);
|
|
155
|
-
const
|
|
156
|
-
const user = yield* insertUser({ ...input, email: available });
|
|
293
|
+
const name = yield* validateName(input.name);
|
|
157
294
|
|
|
158
|
-
return
|
|
295
|
+
return { email, name };
|
|
159
296
|
});
|
|
160
297
|
```
|
|
161
298
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
299
|
+
Use `ResultTask.gen(function* () { ... })` for new workflows that also perform async work;
|
|
300
|
+
see the [linear Task recipe](references/patterns.md#write-a-linear-workflow). `Result.gen` and
|
|
301
|
+
`Result.fn` also accept async generators, returning eager `ResultAsync`. The deprecated root
|
|
302
|
+
`safeTry` supports both sync and async bodies whose final return is a `Result`.
|
|
303
|
+
Use `ResultTask.gen` for Task workflows; do not invent a `ResultTask.safeTry` alias.
|
|
304
|
+
|
|
305
|
+
- Return the plain success value (`return value`): `Result.gen` wraps plain successes in `Ok`
|
|
306
|
+
automatically without inspecting properties or prototypes.
|
|
307
|
+
- Short-circuit early errors and final fallible steps via `yield*` (`return yield* err(...)` or
|
|
308
|
+
`return yield* step()`). Returning a `Result`, `ResultAsync`, `Promise` (in sync generators), or
|
|
309
|
+
`ResultTask` directly from `Result.gen` is rejected at compile time.
|
|
310
|
+
- A missing success return becomes `Ok(undefined)`; annotate concrete workflow output types where a
|
|
311
|
+
value is required.
|
|
312
|
+
- Deprecated `safeTry` retains synchronous and asynchronous bodies whose final value is a Result.
|
|
313
|
+
It is separate from modern `Result.gen`, whose plain success payloads remain opaque.
|
|
314
|
+
- `Result.safeUnwrap()` remains callable; prefer `yield* result` for direct composition.
|
|
315
|
+
- `Result.fn` wraps a synchronous or asynchronous generator function (plus up to eight post-body pipeline
|
|
316
|
+
operators receiving `(result, ...args)`) with the same `yield*` and plain-value return contract.
|
|
317
|
+
- `ResultTask.gen` and `ResultTask.fn` share the same plain-value return convention with lazy execution.
|
|
318
|
+
Generator annotations (`ResultGenerator`, `ResultAsyncGenerator`, `ResultTaskGenerator`,
|
|
319
|
+
`ResultTaskYieldOf`, `ResultGenOptions`, `AsyncResultGenOptions`, `SafeTryOptions`, and
|
|
320
|
+
`SafeTryAsyncOptions`) are exported from `resultar`.
|
|
321
|
+
- Synchronous `Result.gen` yields Results. Async `Result.gen` yields Results and eager `ResultAsync`
|
|
322
|
+
values. Task generators yield Tasks, Results, and services; bridge Async with `fromResultAsync`.
|
|
323
|
+
- Wrap raw promises before yielding them; prefer `ResultTask.tryPromise` in new Task generators.
|
|
324
|
+
- Do not use raw `await` or broad `try/catch` inside a Resultar `Result.gen` / `safeTry` workflow.
|
|
325
|
+
- `Result.gen` / `safeTry` return the first yielded `Err` without closing the generator; do not
|
|
326
|
+
rely on `finally` for cleanup after early failure. `ResultTask.gen` closes its generator and
|
|
327
|
+
executes yielded cleanup tasks before completion.
|
|
167
328
|
|
|
168
329
|
## Keep Boundaries Concrete
|
|
169
330
|
|
|
@@ -172,6 +333,8 @@ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserE
|
|
|
172
333
|
- Keep transport concerns out of domain code. Convert tagged errors to HTTP responses, CLI exit
|
|
173
334
|
codes, job outcomes, or logs at the outer boundary.
|
|
174
335
|
- Use exhaustive `matchTags` when every tagged error needs a distinct mapping.
|
|
336
|
+
- Prefer inline `matchTags` in ordinary route handlers. Capture the native context or reply in
|
|
337
|
+
the handler closure and let error types be inferred. Extract a mapper when its policy is reused.
|
|
175
338
|
- Use local `catchTag` / `catchTags` only when recovery belongs to domain policy.
|
|
176
339
|
|
|
177
340
|
## Handle HTTP JSON Requests
|
|
@@ -185,6 +348,10 @@ mapping logic:
|
|
|
185
348
|
`z.output<typeof schema>`.
|
|
186
349
|
- Map request, HTTP, invalid-JSON, and validation failures to concrete tagged errors when they cross
|
|
187
350
|
an application boundary.
|
|
351
|
+
- These helpers retain eager `ResultAsync` returns. In a new Task, call the helper inside
|
|
352
|
+
`ResultTask.fromResultAsync((signal) => loadRequest(signal))`, where `loadRequest` creates the
|
|
353
|
+
request helper and forwards that signal to fetch.
|
|
354
|
+
Constructing the helper before the factory would start the request immediately.
|
|
188
355
|
|
|
189
356
|
Read `references/integrations.md` before implementing request adapters or diagnostics.
|
|
190
357
|
|
|
@@ -2,7 +2,7 @@ import { createTaggedError, ok, Result, ResultTask } from 'resultar';
|
|
|
2
2
|
export class InvalidEmailError extends createTaggedError({
|
|
3
3
|
name: 'InvalidEmailError',
|
|
4
4
|
message: 'Invalid email $email',
|
|
5
|
-
}) {
|
|
5
|
+
}).withProps() {
|
|
6
6
|
}
|
|
7
7
|
export const validateEmail = (email) => {
|
|
8
8
|
const normalized = email.trim().toLowerCase();
|
|
@@ -10,12 +10,14 @@ export const validateEmail = (email) => {
|
|
|
10
10
|
};
|
|
11
11
|
export const account = (email) => Result.gen(function* () {
|
|
12
12
|
const normalized = yield* validateEmail(email);
|
|
13
|
-
return
|
|
13
|
+
return { email: normalized };
|
|
14
14
|
});
|
|
15
|
-
export const Greeting = ResultTask.service('greeting');
|
|
15
|
+
export const Greeting = ResultTask.service()('greeting');
|
|
16
16
|
// Creating this task runs no work. The boundary supplies its typed dependency.
|
|
17
17
|
export const welcome = (email) => ResultTask.gen(function* () {
|
|
18
18
|
const normalized = yield* validateEmail(email);
|
|
19
19
|
const greeting = yield* Greeting;
|
|
20
20
|
return `${greeting}, ${normalized}`;
|
|
21
21
|
});
|
|
22
|
+
// The instance runner enforces the same service requirement as ResultTask.runResult.
|
|
23
|
+
export const runWelcome = (email) => welcome(email).runResult({ services: { greeting: 'Welcome' } });
|
|
@@ -4,7 +4,7 @@ import type { StrictResult } from 'resultar'
|
|
|
4
4
|
export class InvalidEmailError extends createTaggedError({
|
|
5
5
|
name: 'InvalidEmailError',
|
|
6
6
|
message: 'Invalid email $email',
|
|
7
|
-
}) {}
|
|
7
|
+
}).withProps<{ email: string }>() {}
|
|
8
8
|
|
|
9
9
|
export const validateEmail = (email: string): StrictResult<string, InvalidEmailError> => {
|
|
10
10
|
const normalized = email.trim().toLowerCase()
|
|
@@ -14,14 +14,19 @@ export const validateEmail = (email: string): StrictResult<string, InvalidEmailE
|
|
|
14
14
|
export const account = (email: string): StrictResult<{ email: string }, InvalidEmailError> =>
|
|
15
15
|
Result.gen(function* () {
|
|
16
16
|
const normalized = yield* validateEmail(email)
|
|
17
|
-
return
|
|
17
|
+
return { email: normalized }
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
export const Greeting = ResultTask.service<string
|
|
20
|
+
export const Greeting = ResultTask.service<string>()('greeting')
|
|
21
21
|
|
|
22
22
|
// Creating this task runs no work. The boundary supplies its typed dependency.
|
|
23
|
-
export const welcome = (email: string) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
23
|
+
export const welcome = (email: string) =>
|
|
24
|
+
ResultTask.gen(function* () {
|
|
25
|
+
const normalized = yield* validateEmail(email)
|
|
26
|
+
const greeting = yield* Greeting
|
|
27
|
+
return `${greeting}, ${normalized}`
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// The instance runner enforces the same service requirement as ResultTask.runResult.
|
|
31
|
+
export const runWelcome = (email: string) =>
|
|
32
|
+
welcome(email).runResult({ services: { greeting: 'Welcome' } })
|