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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: resultar
3
- description: Build, review, migrate, test, and document TypeScript code using Resultar v3.7+, including Result, ResultAsync, ResultTask, StrictResult, createTaggedError, Result.gen/safeTry, request adapters, and resultar-check. Use when expected failures should become typed values, thrown or rejected work must be wrapped, Resultar pipelines or async policies need design, HTTP JSON needs TypeBox or Zod validation, Resultar diagnostics need configuration, or an existing Resultar codebase needs review or migration.
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 without introducing an application
12
- runtime. Produce code that preserves concrete success and error types from the domain boundary to
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 | `Result` / `Result.gen` | `ResultAsync` | `ResultTask` |
33
- | --- | --- | --- | --- |
34
- | Execution | Sync values; `Result.gen` also accepts async generators | Starts immediately | Building/composing starts nothing; each `run*` executes again |
35
- | Generator return | `return ok(value)`; `yield*` short-circuits failures | Compose through `Result.gen` with `yield*` | `return value`; compose tasks, services and Results with `yield*` |
36
- | Error transform / recovery | `mapErr` / `orElse` | `mapErr` / `orElse` | `mapError` / `catchAll` |
37
- | `tap` / `tapError` failure | Ignored observation failure | Ignored observation failure | Returned task failures join `E`; throws/rejected Promises become `Die` |
38
- | Cleanup | Disposal callbacks are best-effort | `withResource` release is best-effort | `acquireRelease` + `scoped` await release and preserve failures |
39
-
40
- Use `ResultAsync` for existing Promise-shaped use cases. Choose `ResultTask` for reusable lazy
41
- work, typed service requirements, or owned resource lifetimes. Preserve existing eager/lazy
42
- contracts; converting an already-started Promise or ResultAsync cannot undo its execution.
43
- `ResultTask.runResult` can reject on defects, interruption, and composite causes. Use `runExit`
44
- when the boundary must inspect every exit without rejection. See [API details](references/api.md).
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 `ResultAsync<T, E>` for asynchronous
54
- fallible work.
55
- - Prefer `StrictResult<T, E extends Error>` and `StrictResultAsync<T, E extends Error>` at
56
- application, service, HTTP, job, queue, CLI, and integration boundaries.
57
- - Return `ResultAsync` / `StrictResultAsync` directly from async services and repositories instead
58
- of `Promise<T>`, `Promise<Result>` or `Promise<StrictResult>`. Map external driver failures to
59
- typed errors in the repository adapter; services compose that result directly. Remove the
60
- function's `async` wrapper; changing only its return annotation cannot change the runtime value.
61
- - Use `preferResultAsync: "error"` with `preferResultAsyncMode: "all"` to enforce these application
62
- contracts. Document local rule suppressions for native framework/driver callbacks that require
63
- Promise interoperability.
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
- | Need | Prefer |
80
- | ----------------------------------------- | ------------------------ |
81
- | Transform an `Ok` without failure | `map` |
82
- | Transform an `Err` | `mapErr` |
83
- | Validate an `Ok` with a predicate | `filterOrElse` |
84
- | Continue with a sync fallible step | `andThen` |
85
- | Continue from `Result` into `ResultAsync` | `asyncAndThen` |
86
- | Recover the whole error channel | `orElse` |
87
- | Recover selected tagged errors | `catchTag` / `catchTags` |
88
- | Branch directly | `isOk()` / `isErr()` |
89
- | Convert a result at a boundary | `match` / `matchTags` |
90
- | Map selected tagged errors with fallback | `matchTagsPartial` |
91
- | Reuse a named Result combinator | `pipe` |
92
- | Write a readable linear pipeline | `safeTry` with `yield*` |
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
- Wrap uncontrolled async work with the helper that matches how it is created:
122
-
123
- - Use `fromPromise(promise, toError)` for an already-created promise.
124
- - Use `tryResultAsync({ try, catch })` or `tryResultAsync(factory, toError)` when creating the
125
- promise can throw synchronously.
126
- - Use `fromSafePromise` only when rejection is impossible by contract.
127
- - Use `fromCallback` for callback or subscription APIs with explicit cleanup.
128
- - Use `runPromise` only at a terminal boundary that intentionally resolves or throws.
129
-
130
- Prefer lazy Resultar policies over ad hoc timers or `Promise.race`:
131
-
132
- - Use `ResultAsync.retry` / `retryOrElse` for typed retry and fallback policy.
133
- - Use `timeout`, `race`, `raceAll`, or `raceFirst` for signal-aware concurrency.
134
- - Use mapped `forEach` for first-error batch work and mapped `validateAll` for collecting every
135
- independent validation error.
136
- - Use `withResource` when successful acquisition must always trigger best-effort release.
137
- - Pass the provided abort signal into the underlying client; cancellation is cooperative.
138
-
139
- On `Result` and `ResultAsync`, use `tap`, `tapError`, and `log` for best-effort observation.
140
- `ResultTask.tap` and `tapError` participate in failure handling; they do not swallow failures. Use `andThen`, `orElse`, or a
141
- Resultar wrapper when callback failure must change control flow.
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 createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
147
- Result.gen(async function* () {
290
+ const validateUser = (input: CreateUserInput): StrictResult<ValidUserInput, InvalidUserError> =>
291
+ Result.gen(function* () {
148
292
  const email = yield* validateEmail(input.email);
149
- const available = yield* ensureEmailAvailable(email);
150
- const user = yield* insertUser({ ...input, email: available });
293
+ const name = yield* validateName(input.name);
151
294
 
152
- return ok(user);
295
+ return { email, name };
153
296
  });
154
297
  ```
155
298
 
156
- - `safeTry` is the top-level alias of `Result.gen`; both have the same rules.
157
- - Use `yield*` for both `Result` and `ResultAsync` values.
158
- - Wrap raw promises before yielding them.
159
- - Do not use raw `await` or broad `try/catch` inside a Resultar `safeTry` workflow.
160
- - Prefer `yield* result` over legacy `safeUnwrap()`.
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 reads the same project configuration as the CLI.
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 ok({ email: normalized });
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 ok({ email: normalized })
17
+ return { email: normalized }
18
18
  })
19
19
 
20
- export const Greeting = ResultTask.service<string, 'greeting'>('greeting')
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) => ResultTask.gen(function* () {
24
- const normalized = yield* validateEmail(email)
25
- const greeting = yield* Greeting
26
- return `${greeting}, ${normalized}`
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' } })