resultar 3.9.1 → 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 +676 -766
- package/dist/agent/SKILL.md +257 -74
- package/dist/agent/examples/workflow.mjs +5 -3
- package/dist/agent/examples/workflow.mts +13 -8
- package/dist/agent/references/api.md +350 -91
- package/dist/agent/references/integrations.md +64 -10
- package/dist/agent/references/patterns.md +232 -74
- package/dist/agent/references/review-checklist.md +60 -19
- package/dist/agent/references/services.md +26 -6
- package/dist/agent/versions.json +8 -8
- package/dist/index.d.ts +3204 -943
- package/dist/index.js +5604 -2215
- package/dist/llms.txt +1 -1
- package/package.json +9 -9
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,21 +27,70 @@ 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
|
-
|
|
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).
|
|
45
94
|
For DI, Hono and Fastify, 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
|
|
@@ -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,27 +122,71 @@ 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
|
|
75
130
|
is explicitly the intended boundary behavior.
|
|
76
131
|
|
|
132
|
+
For reusable synchronous transformations, use root `pipe` / `flow`. `identity` preserves
|
|
133
|
+
its callback input; `constant(value)` captures a fallback. These utilities do not await or
|
|
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`.
|
|
166
|
+
|
|
77
167
|
## Choose The Composition Operator
|
|
78
168
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
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*` |
|
|
93
190
|
|
|
94
191
|
Use `catchTags` for partial pipeline recovery; unhandled tags stay in the error channel. Do not
|
|
95
192
|
invent `partialCatchTags`. Use `matchTagsPartial` only for boundary matching with a deliberate
|
|
@@ -111,6 +208,11 @@ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =
|
|
|
111
208
|
```
|
|
112
209
|
|
|
113
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.
|
|
114
216
|
- Preserve `cause` when converting an external failure.
|
|
115
217
|
- Treat `ErrorClass.is(value)` as nominal; do not rely on spoofed `_tag` objects.
|
|
116
218
|
- Use `TaggedEnum` only for lightweight tagged domain variants that do not need to be real errors.
|
|
@@ -118,46 +220,111 @@ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =
|
|
|
118
220
|
|
|
119
221
|
## Compose Async Work
|
|
120
222
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
- Use `
|
|
127
|
-
- Use `
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
142
286
|
|
|
143
287
|
## Use `Result.gen` For Linear Result Flows
|
|
144
288
|
|
|
145
289
|
```ts
|
|
146
|
-
const
|
|
147
|
-
Result.gen(
|
|
290
|
+
const validateUser = (input: CreateUserInput): StrictResult<ValidUserInput, InvalidUserError> =>
|
|
291
|
+
Result.gen(function* () {
|
|
148
292
|
const email = yield* validateEmail(input.email);
|
|
149
|
-
const
|
|
150
|
-
const user = yield* insertUser({ ...input, email: available });
|
|
293
|
+
const name = yield* validateName(input.name);
|
|
151
294
|
|
|
152
|
-
return
|
|
295
|
+
return { email, name };
|
|
153
296
|
});
|
|
154
297
|
```
|
|
155
298
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
|
161
328
|
|
|
162
329
|
## Keep Boundaries Concrete
|
|
163
330
|
|
|
@@ -166,6 +333,8 @@ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserE
|
|
|
166
333
|
- Keep transport concerns out of domain code. Convert tagged errors to HTTP responses, CLI exit
|
|
167
334
|
codes, job outcomes, or logs at the outer boundary.
|
|
168
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.
|
|
169
338
|
- Use local `catchTag` / `catchTags` only when recovery belongs to domain policy.
|
|
170
339
|
|
|
171
340
|
## Handle HTTP JSON Requests
|
|
@@ -179,6 +348,10 @@ mapping logic:
|
|
|
179
348
|
`z.output<typeof schema>`.
|
|
180
349
|
- Map request, HTTP, invalid-JSON, and validation failures to concrete tagged errors when they cross
|
|
181
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.
|
|
182
355
|
|
|
183
356
|
Read `references/integrations.md` before implementing request adapters or diagnostics.
|
|
184
357
|
|
|
@@ -187,7 +360,8 @@ Read `references/integrations.md` before implementing request adapters or diagno
|
|
|
187
360
|
1. Run the `resultar-check` CLI as the authoritative project and CI gate. It runs TypeScript and the
|
|
188
361
|
full Resultar rule set against the project.
|
|
189
362
|
2. Configure `resultar-check lsp` as the editor's stdio language server for inline diagnostics and
|
|
190
|
-
quick fixes. It
|
|
363
|
+
quick fixes. It analyzes unsaved buffers and dependencies with versioned edits; hover shows
|
|
364
|
+
`T`, `E`, and `R`. It reads the same project configuration as the CLI.
|
|
191
365
|
3. Use JSONL, SARIF, or JUnit output when another CI or review system needs structured diagnostics;
|
|
192
366
|
do not present those consumers as replacements for the native checker.
|
|
193
367
|
4. Run the repository's native formatting, build, tests, analysis, package smoke, and example
|
|
@@ -197,6 +371,15 @@ JSONL fixes distinguish `correction` from `intentional-discard`; LSP actions car
|
|
|
197
371
|
classification in `data.kind`. Do not apply a discard just to silence the checker: `void task`
|
|
198
372
|
does not execute a lazy task or handle its errors. Corrections still need compilation and tests.
|
|
199
373
|
|
|
374
|
+
Use `resultar-check overview --json` to inventory exported workflows/errors/services and
|
|
375
|
+
`resultar-check quickfixes --json` to inspect proposed edits without writing files. Preserve
|
|
376
|
+
concrete task requirements: assertions must not hide services or pending scope errors.
|
|
377
|
+
Remove unused suppression entries; disabled rules are excluded from usage checks.
|
|
378
|
+
|
|
379
|
+
The checker schema combines standard `tsconfig` options and Resultar rules for editor
|
|
380
|
+
completion/validation. Schema resolution and compiler compatibility are separate checks;
|
|
381
|
+
read [checker configuration](references/integrations.md#configure-the-native-checker) for details.
|
|
382
|
+
|
|
200
383
|
Treat stable `resultar/*` rule IDs as architecture feedback. Fix the typed boundary or composition
|
|
201
384
|
problem instead of suppressing diagnostics broadly. Use narrow ignore patterns only for deliberate
|
|
202
385
|
test, script, generated, or process-boundary exceptions.
|
|
@@ -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' } })
|