resultar 3.7.0 → 3.9.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 CHANGED
@@ -494,13 +494,12 @@ The callback runs only when the task executes. Use `tryPromise({ try, catch })`
494
494
  specific error type; both forms receive the runtime signal and preserve interruption semantics.
495
495
 
496
496
  Workflows can request typed services with `yield*` and receive them at the boundary. Pass the service
497
- type and its literal identifier so the named environment remains checked:
497
+ type first, then let the second call infer its literal identifier:
498
498
 
499
499
  ```ts
500
500
  const Database = ResultTask.service<
501
- { findUser: (id: string) => Promise<string> },
502
- 'Database'
503
- >('Database')
501
+ { findUser: (id: string) => Promise<string> }
502
+ >()('Database')
504
503
 
505
504
  const taskWithDatabase = ResultTask.gen(function* () {
506
505
  const database = yield* Database
@@ -529,6 +528,16 @@ await ResultTask.runResult(readyWithOne)
529
528
  await ResultTask.runResult(readyWithAll)
530
529
  ```
531
530
 
531
+ The direct form `ResultTask.service<DatabaseContract, 'Database'>('Database')` remains supported.
532
+ The curried form can also be yielded inline: `yield* ResultTask.service<DatabaseContract>()('Database')`.
533
+ Requirements belong to `R` in `ResultTask<T, E, R>`; `Result` has no service lookup API.
534
+
535
+ Every tag creation has its own symbol identity. Inline tags resolve through named environments
536
+ (`provideServices` or the DI module). `provideService(token, value)` supplies both an exact token binding and a named fallback.
537
+ Reuse the same token to select its exact binding when identifiers collide; a separately created
538
+ same-name tag can resolve the named fallback. Exact token bindings take precedence. For class services and explicit dependency maps, see
539
+ [`Service.require` and `requires` in resultar-di](../di/README.md#class-services-and-explicit-requirements).
540
+
532
541
  Infrastructure such as a dependency-injection adapter can resolve service tags lazily with
533
542
  `provideServiceResolver(task, { Logger: () => loggerTask })`. Each required identifier has a
534
543
  typed lazy provider. Provider errors and external requirements remain in the resulting task type;
@@ -962,6 +971,16 @@ The
962
971
  covers overloads, aliases, callback semantics, collection helpers, conditional helpers, tagged
963
972
  reasons, disposable results, and compatibility APIs.
964
973
 
974
+ ## Coding agents
975
+
976
+ Each npm build includes `dist/llms.txt`, `dist/agent/SKILL.md`, focused references, a compiled
977
+ core workflow example, and `dist/agent/versions.json`. Start from these installed files so the
978
+ guide matches the package version. Companion package READMEs take precedence when their installed
979
+ versions differ from the recorded companion versions. The online `main` guide tracks development.
980
+
981
+ The guide distinguishes `Result.gen` from `ResultTask.gen`, immediate from lazy execution,
982
+ observation from effectful `tap`, and `runResult` from the non-rejecting `runExit` boundary.
983
+
965
984
  ## More Documentation
966
985
 
967
986
  - [Full Resultar guide](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md)
@@ -0,0 +1,227 @@
1
+ ---
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.
4
+ metadata:
5
+ tags: [typescript, error-handling]
6
+ domains: [foundation]
7
+ ---
8
+
9
+ # Resultar Engineering
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.
14
+
15
+ ## Start From Repository Truth
16
+
17
+ 1. Inspect the consuming project's `package.json`, lockfile, Resultar version, module format, and
18
+ TypeScript version before proposing code.
19
+ 2. Prefer the installed package types and local source over remembered APIs. The core package
20
+ ships this guide at `node_modules/resultar/dist/agent/SKILL.md`; check its `versions.json` before
21
+ using companion-package recipes. Online `main` documentation may describe a newer release.
22
+ 3. In the Resultar repository, use these sources in order:
23
+ - `packages/resultar/src/index.ts` for public exports.
24
+ - `packages/resultar/README.md` and `DOCUMENTATION.md` for current semantics and examples.
25
+ - `packages/check/README.md` for diagnostics.
26
+ - `packages/request*/README.md` and `examples/request/` for request integrations.
27
+ 4. Adapt examples when the consumer uses an older Resultar version; do not silently emit APIs
28
+ that are absent locally.
29
+
30
+ ## Choose The Execution Model
31
+
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).
45
+ For DI, Hono and Fastify, read [services and HTTP scopes](references/services.md). Use
46
+ `ResultTask.service<T>()(name)` or DI `Service.require<T>()(name)` for inferred identifiers. Class
47
+ services support optional `requires` with a factory returning ResultTask; generator-only `make`
48
+ remains available. Check installed versions before using these overloads.
49
+
50
+ ## Apply Opinionated Defaults
51
+
52
+ - Return `Result<T, E>` for synchronous fallible work and `ResultAsync<T, E>` for asynchronous
53
+ fallible work.
54
+ - Prefer `StrictResult<T, E extends Error>` and `StrictResultAsync<T, E extends Error>` at
55
+ application, service, HTTP, job, queue, CLI, and integration boundaries.
56
+ - Return `ResultAsync` / `StrictResultAsync` directly from async services and repositories instead
57
+ of `Promise<T>`, `Promise<Result>` or `Promise<StrictResult>`. Map external driver failures to
58
+ typed errors in the repository adapter; services compose that result directly. Remove the
59
+ function's `async` wrapper; changing only its return annotation cannot change the runtime value.
60
+ - Use `preferResultAsync: "error"` with `preferResultAsyncMode: "all"` to enforce these application
61
+ contracts. Document local rule suppressions for native framework/driver callbacks that require
62
+ Promise interoperability.
63
+ - Prefer `createTaggedError` for expected domain and application failures that need stable identity,
64
+ metadata, causes, serialization, or exhaustive handling.
65
+ - Keep error unions specific. Let composition widen them naturally; do not collapse them to `Error`
66
+ before the boundary.
67
+ - Return `Err` for expected failures. Reserve `throw` for programmer defects, invalid library
68
+ arguments, or deliberate terminal/process boundaries.
69
+ - Wrap third-party throws and promise rejections at the edge, then keep the internal flow Resultar-
70
+ native.
71
+ - Avoid `T | Error`, sentinel `null`, broad `try/catch`, rejected expected failures, and unsafe casts
72
+ as substitutes for a Resultar error channel.
73
+ - Keep `_unsafeUnwrap()` and `_unsafeUnwrapErr()` in tests. Use `unwrapOrThrow()` only when throwing
74
+ is explicitly the intended boundary behavior.
75
+
76
+ ## Choose The Composition Operator
77
+
78
+ | Need | Prefer |
79
+ | ----------------------------------------- | ------------------------ |
80
+ | Transform an `Ok` without failure | `map` |
81
+ | Transform an `Err` | `mapErr` |
82
+ | Validate an `Ok` with a predicate | `filterOrElse` |
83
+ | Continue with a sync fallible step | `andThen` |
84
+ | Continue from `Result` into `ResultAsync` | `asyncAndThen` |
85
+ | Recover the whole error channel | `orElse` |
86
+ | Recover selected tagged errors | `catchTag` / `catchTags` |
87
+ | Branch directly | `isOk()` / `isErr()` |
88
+ | Convert a result at a boundary | `match` / `matchTags` |
89
+ | Map selected tagged errors with fallback | `matchTagsPartial` |
90
+ | Reuse a named Result combinator | `pipe` |
91
+ | Write a readable linear pipeline | `safeTry` with `yield*` |
92
+
93
+ Use `catchTags` for partial pipeline recovery; unhandled tags stay in the error channel. Do not
94
+ invent `partialCatchTags`. Use `matchTagsPartial` only for boundary matching with a deliberate
95
+ fallback.
96
+
97
+ ## Model Tagged Errors
98
+
99
+ ```ts
100
+ import { createTaggedError, ok } from "resultar";
101
+ import type { StrictResult } from "resultar";
102
+
103
+ class InvalidEmailError extends createTaggedError({
104
+ name: "InvalidEmailError",
105
+ message: "Invalid email $email",
106
+ }) {}
107
+
108
+ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =>
109
+ email.includes("@") ? ok(email) : InvalidEmailError.err({ email });
110
+ ```
111
+
112
+ - Let message-template variables define required constructor props.
113
+ - Preserve `cause` when converting an external failure.
114
+ - Treat `ErrorClass.is(value)` as nominal; do not rely on spoofed `_tag` objects.
115
+ - Use `TaggedEnum` only for lightweight tagged domain variants that do not need to be real errors.
116
+ - Include an `Error` handler when a matched union can contain untagged errors.
117
+
118
+ ## Compose Async Work
119
+
120
+ Wrap uncontrolled async work with the helper that matches how it is created:
121
+
122
+ - Use `fromPromise(promise, toError)` for an already-created promise.
123
+ - Use `tryResultAsync({ try, catch })` or `tryResultAsync(factory, toError)` when creating the
124
+ promise can throw synchronously.
125
+ - Use `fromSafePromise` only when rejection is impossible by contract.
126
+ - Use `fromCallback` for callback or subscription APIs with explicit cleanup.
127
+ - Use `runPromise` only at a terminal boundary that intentionally resolves or throws.
128
+
129
+ Prefer lazy Resultar policies over ad hoc timers or `Promise.race`:
130
+
131
+ - Use `ResultAsync.retry` / `retryOrElse` for typed retry and fallback policy.
132
+ - Use `timeout`, `race`, `raceAll`, or `raceFirst` for signal-aware concurrency.
133
+ - Use mapped `forEach` for first-error batch work and mapped `validateAll` for collecting every
134
+ independent validation error.
135
+ - Use `withResource` when successful acquisition must always trigger best-effort release.
136
+ - Pass the provided abort signal into the underlying client; cancellation is cooperative.
137
+
138
+ On `Result` and `ResultAsync`, use `tap`, `tapError`, and `log` for best-effort observation.
139
+ `ResultTask.tap` and `tapError` participate in failure handling; they do not swallow failures. Use `andThen`, `orElse`, or a
140
+ Resultar wrapper when callback failure must change control flow.
141
+
142
+ ## Use `Result.gen` For Linear Result Flows
143
+
144
+ ```ts
145
+ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
146
+ Result.gen(async function* () {
147
+ const email = yield* validateEmail(input.email);
148
+ const available = yield* ensureEmailAvailable(email);
149
+ const user = yield* insertUser({ ...input, email: available });
150
+
151
+ return ok(user);
152
+ });
153
+ ```
154
+
155
+ - `safeTry` is the top-level alias of `Result.gen`; both have the same rules.
156
+ - Use `yield*` for both `Result` and `ResultAsync` values.
157
+ - Wrap raw promises before yielding them.
158
+ - Do not use raw `await` or broad `try/catch` inside a Resultar `safeTry` workflow.
159
+ - Prefer `yield* result` over legacy `safeUnwrap()`.
160
+
161
+ ## Keep Boundaries Concrete
162
+
163
+ - Define request, command, and event payloads with concrete types; do not widen them to generic JSON
164
+ records before the use case.
165
+ - Keep transport concerns out of domain code. Convert tagged errors to HTTP responses, CLI exit
166
+ codes, job outcomes, or logs at the outer boundary.
167
+ - Use exhaustive `matchTags` when every tagged error needs a distinct mapping.
168
+ - Use local `catchTag` / `catchTags` only when recovery belongs to domain policy.
169
+
170
+ ## Handle HTTP JSON Requests
171
+
172
+ Use the Resultar request family instead of repeating fetch, parsing, validation, retry, and error
173
+ mapping logic:
174
+
175
+ - Use `resultar-request` with `validator` or `decode` for schema-library-agnostic boundaries.
176
+ - Use `resultar-request-typebox` when contracts use TypeBox and preserve `Static<typeof schema>`.
177
+ - Use `resultar-request-zod` when contracts use Zod and preserve transformed
178
+ `z.output<typeof schema>`.
179
+ - Map request, HTTP, invalid-JSON, and validation failures to concrete tagged errors when they cross
180
+ an application boundary.
181
+
182
+ Read `references/integrations.md` before implementing request adapters or diagnostics.
183
+
184
+ ## Validate In The Preferred Order
185
+
186
+ 1. Run the `resultar-check` CLI as the authoritative project and CI gate. It runs TypeScript and the
187
+ full Resultar rule set against the project.
188
+ 2. Configure `resultar-check lsp` as the editor's stdio language server for inline diagnostics and
189
+ quick fixes. It reads the same project configuration as the CLI.
190
+ 3. Use JSONL, SARIF, or JUnit output when another CI or review system needs structured diagnostics;
191
+ do not present those consumers as replacements for the native checker.
192
+ 4. Run the repository's native formatting, build, tests, analysis, package smoke, and example
193
+ workflows when available.
194
+
195
+ JSONL fixes distinguish `correction` from `intentional-discard`; LSP actions carry the same
196
+ classification in `data.kind`. Do not apply a discard just to silence the checker: `void task`
197
+ does not execute a lazy task or handle its errors. Corrections still need compilation and tests.
198
+
199
+ Treat stable `resultar/*` rule IDs as architecture feedback. Fix the typed boundary or composition
200
+ problem instead of suppressing diagnostics broadly. Use narrow ignore patterns only for deliberate
201
+ test, script, generated, or process-boundary exceptions.
202
+
203
+ ## Test And Document Behavior
204
+
205
+ - Cover `Ok` and every expected `Err` path with runtime tests.
206
+ - Cover inferred error unions, tagged-error props, narrowing, and exhaustive handlers with type
207
+ tests. Use `expectTypeOf` and `@ts-expect-error` where supported.
208
+ - Use the consuming project's test runner. Use `vite-plus/test` inside this repository.
209
+ - Test retry, timeout, cancellation, concurrency, and cleanup policies with deterministic fakes.
210
+ - Derive documentation examples from checked source or runnable examples and keep package links
211
+ reciprocal when packages are alternatives or adapters.
212
+
213
+ ## Review Before Finishing
214
+
215
+ - Run the project's Resultar CLI check after edits.
216
+ - Confirm no Result, ResultAsync, or ResultTask value is silently discarded.
217
+ - Confirm mapped errors preserve `cause` and useful metadata.
218
+ - Confirm boundary handlers cover the actual error union.
219
+ - Confirm request validation derives the downstream type instead of widening it.
220
+ - Confirm validation claims match commands that actually passed.
221
+
222
+ ## Load References Selectively
223
+
224
+ - Read `references/api.md` for current exports, collection helpers, and async policy semantics.
225
+ - Read `references/patterns.md` for copyable implementation and migration patterns.
226
+ - Read `references/integrations.md` for request adapters and the CLI/editor/lint workflow.
227
+ - Read `references/review-checklist.md` for reviews, migrations, and final validation.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Resultar Engineering"
3
+ short_description: "Build and review typed Resultar workflows"
4
+ default_prompt: "Use $resultar to build or review a typed Resultar workflow."
@@ -0,0 +1,21 @@
1
+ import { createTaggedError, ok, Result, ResultTask } from 'resultar';
2
+ export class InvalidEmailError extends createTaggedError({
3
+ name: 'InvalidEmailError',
4
+ message: 'Invalid email $email',
5
+ }) {
6
+ }
7
+ export const validateEmail = (email) => {
8
+ const normalized = email.trim().toLowerCase();
9
+ return normalized.includes('@') ? ok(normalized) : InvalidEmailError.err({ email });
10
+ };
11
+ export const account = (email) => Result.gen(function* () {
12
+ const normalized = yield* validateEmail(email);
13
+ return ok({ email: normalized });
14
+ });
15
+ export const Greeting = ResultTask.service('greeting');
16
+ // Creating this task runs no work. The boundary supplies its typed dependency.
17
+ export const welcome = (email) => ResultTask.gen(function* () {
18
+ const normalized = yield* validateEmail(email);
19
+ const greeting = yield* Greeting;
20
+ return `${greeting}, ${normalized}`;
21
+ });
@@ -0,0 +1,27 @@
1
+ import { createTaggedError, ok, Result, ResultTask } from 'resultar'
2
+ import type { StrictResult } from 'resultar'
3
+
4
+ export class InvalidEmailError extends createTaggedError({
5
+ name: 'InvalidEmailError',
6
+ message: 'Invalid email $email',
7
+ }) {}
8
+
9
+ export const validateEmail = (email: string): StrictResult<string, InvalidEmailError> => {
10
+ const normalized = email.trim().toLowerCase()
11
+ return normalized.includes('@') ? ok(normalized) : InvalidEmailError.err({ email })
12
+ }
13
+
14
+ export const account = (email: string): StrictResult<{ email: string }, InvalidEmailError> =>
15
+ Result.gen(function* () {
16
+ const normalized = yield* validateEmail(email)
17
+ return ok({ email: normalized })
18
+ })
19
+
20
+ export const Greeting = ResultTask.service<string, 'greeting'>('greeting')
21
+
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
+ })
@@ -0,0 +1,249 @@
1
+ # Resultar v3.7+ API Guide
2
+
3
+ Use this reference after checking the version installed by the consuming project. The local package
4
+ types and source are authoritative when they differ from this guide.
5
+
6
+ ## Contents
7
+
8
+ - [Package Baseline](#package-baseline)
9
+ - [Core Types And Constructors](#core-types-and-constructors)
10
+ - [Composition And Recovery](#composition-and-recovery)
11
+ - [Tagged Errors](#tagged-errors)
12
+ - [Boundary Matching](#boundary-matching)
13
+ - [Async Boundaries](#async-boundaries)
14
+ - [Async Policies](#async-policies)
15
+ - [Collections And Control Flow](#collections-and-control-flow)
16
+ - [`safeTry`](#safetry)
17
+ - [ResultTask (Lazy Workflows)](#resulttask-lazy-workflows)
18
+ - [Observation And Cleanup](#observation-and-cleanup)
19
+
20
+ ## Package Baseline
21
+
22
+ Resultar v3.7 is an ESM package targeting Node.js 24+. The repository currently uses TypeScript 7.
23
+ Do not infer consumer requirements from memory: inspect `package.json`, the lockfile, and exported
24
+ types first.
25
+
26
+ Prefer named imports and type-only imports:
27
+
28
+ ```ts
29
+ import { ResultAsync, createTaggedError, ok, safeTry, tryResult, tryResultAsync } from "resultar";
30
+ import type { StrictResult, StrictResultAsync } from "resultar";
31
+ ```
32
+
33
+ `tryCatch` and `tryCatchAsync` remain compatibility aliases. Prefer `tryResult` and
34
+ `tryResultAsync` in new v3.7+ code.
35
+
36
+ ## Core Types And Constructors
37
+
38
+ | Need | API |
39
+ | --------------------------------------------------- | ---------------------------------------- |
40
+ | Synchronous fallible value | `Result<T, E>` |
41
+ | Asynchronous fallible value | `ResultAsync<T, E>` |
42
+ | Error-only sync channel | `StrictResult<T, E extends Error>` |
43
+ | Error-only async channel | `StrictResultAsync<T, E extends Error>` |
44
+ | Successful value | `ok(value)` / `okAsync(value)` |
45
+ | Successful `undefined` | `unit()` / `unitAsync()` |
46
+ | Expected failure | `err(error)` / `errAsync(error)` |
47
+ | Catch synchronous throws now | `tryResult(fn, toError)` |
48
+ | Wrap a synchronous throwing function | `fromThrowable(fn, toError)` |
49
+ | Catch promise rejection or a throwing async factory | `tryResultAsync(input, toError)` |
50
+ | Wrap an async throwing function | `fromThrowableAsync(fn, toError)` |
51
+ | Deliberately leave Resultar at a terminal boundary | `runSync(result)` / `runPromise(result)` |
52
+
53
+ Use the named options form when it improves readability:
54
+
55
+ ```ts
56
+ const parsed = tryResult({
57
+ try: () => JSON.parse(input) as unknown,
58
+ catch: (cause) => new ParsePayloadError({ cause }),
59
+ });
60
+ ```
61
+
62
+ `runSync` returns the `Ok` value or throws the `Err`; `runPromise` resolves the `Ok` value or
63
+ rejects with the `Err`. Keep both at framework, bootstrap, CLI, or test boundaries.
64
+
65
+ ## Composition And Recovery
66
+
67
+ - `map` transforms an `Ok` value with an infallible function.
68
+ - `mapErr` transforms an `Err` value.
69
+ - `filterOrElse` validates or narrows an `Ok` value and introduces a typed error on failure.
70
+ - `andThen` chains fallible work. On `ResultAsync`, it accepts sync or async Resultar work.
71
+ - `asyncAndThen` bridges a synchronous `Result` into `ResultAsync`.
72
+ - `orElse` recovers the complete error channel.
73
+ - `catchTag` recovers one tagged error.
74
+ - `catchTags` recovers selected tagged errors; unhandled variants stay in the error union.
75
+ - `pipe` applies named combinators without changing Result semantics.
76
+
77
+ Let each step expose its narrow error type and let composition infer the union:
78
+
79
+ ```ts
80
+ type CreateUserError = InvalidEmailError | UserExistsError | SaveUserError;
81
+
82
+ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
83
+ validateEmail(input.email)
84
+ .asyncAndThen(ensureEmailAvailable)
85
+ .andThen((email) => saveUser({ ...input, email }));
86
+ ```
87
+
88
+ Do not manually cast the final error union. A surprising inferred union is usually evidence that a
89
+ step has a broad or inaccurate signature.
90
+
91
+ ## Tagged Errors
92
+
93
+ `createTaggedError` creates nominal `Error` subclasses with stable `_tag`, message-template props,
94
+ `cause`, `fingerprint`, `toJSON()`, and `.err()` support.
95
+
96
+ ```ts
97
+ class UserNotFoundError extends createTaggedError({
98
+ name: "UserNotFoundError",
99
+ message: "User $userId was not found in $source",
100
+ }) {}
101
+
102
+ const missing = UserNotFoundError.err({ userId, source: "database" });
103
+ ```
104
+
105
+ - Let `$variables` in the message template define required constructor props.
106
+ - Preserve the original failure with `cause` when adapting external code.
107
+ - Keep the configured `name` equal to the class name.
108
+ - Do not override the generated constructor.
109
+ - Treat `ErrorClass.is(value)` as a nominal `instanceof`-based guard; a matching `_tag` is not
110
+ sufficient.
111
+ - Avoid reserved template fields such as `_tag`, `name`, `message`, `messageTemplate`,
112
+ `fingerprint`, `stack`, and `cause`.
113
+ - Use `TaggedEnum`/`taggedEnum` for lightweight tagged data that should not be an `Error`.
114
+ - Use `redact`, `isRedacted`, and `revealRedacted` when sensitive error metadata must not serialize
115
+ or log by default.
116
+ - Use `findCause` or an instance's `findCause` to inspect a typed cause chain.
117
+
118
+ ## Boundary Matching
119
+
120
+ Use branch methods inside an algorithm and matching when converting the completed result:
121
+
122
+ - `isOk()` / `isErr()` narrow for direct control flow.
123
+ - `match(onOk, onErr)` handles a simple boundary.
124
+ - `matchTags(onOk, handlers)` exhaustively maps tagged errors.
125
+ - `matchTagsPartial(onOk, handlers, fallback)` handles selected tags with a deliberate fallback.
126
+ - `matchError(error, handlers)` handles an error value directly.
127
+ - `matchErrorPartial(error, handlers, fallback)` partially handles an error value.
128
+
129
+ Include an `Error` handler whenever an error union can contain an untagged `Error`. Do not invent
130
+ `partialCatchTags`; `catchTags` already supports partial recovery.
131
+
132
+ ## Async Boundaries
133
+
134
+ Choose the wrapper based on ownership of the promise:
135
+
136
+ - `fromPromise(existingPromise, toError)` wraps a promise that has already been created.
137
+ - `tryResultAsync(() => createPromise(), toError)` also catches synchronous factory throws and is
138
+ preferred when the agent controls creation.
139
+ - `fromSafePromise(promise)` is only for promises guaranteed not to reject by contract.
140
+ - `fromCallback(options)` adapts callback or subscription APIs and returns
141
+ `ResultAsync<T, E | AbortError>`. Supply cleanup and forward cancellation where supported.
142
+ - `AbortError` and `isAbortError` identify Resultar cancellation.
143
+
144
+ Always map `unknown` causes into a specific error near the external boundary:
145
+
146
+ ```ts
147
+ const loadUser = (id: string): StrictResultAsync<User, LoadUserError> =>
148
+ tryResultAsync(
149
+ () => client.loadUser(id),
150
+ (cause) => new LoadUserError({ cause, id }),
151
+ );
152
+ ```
153
+
154
+ ## Async Policies
155
+
156
+ Use the built-in lazy policies instead of hand-written `Promise.race`, timers, or retry loops:
157
+
158
+ - `ResultAsync.retry(task, options)` retries typed failures and includes `AbortError` in the error
159
+ channel. Use `times`, `delayMs`, `jittered`, `while`, and the provided signal deliberately.
160
+ - `ResultAsync.retryOrElse(task, options)` applies a typed fallback after retry exhaustion. The
161
+ exhausted task error is replaced by the fallback error channel.
162
+ - `ResultAsync.timeout(task, options)` returns the task error or the typed `onTimeout` error.
163
+ - `ResultAsync.race(left, right)` and `raceAll(tasks)` return the first settled result.
164
+ - `ResultAsync.raceFirst(left, right)` returns the first `Ok`; if both fail, it returns the last
165
+ observed `Err`.
166
+ - `ResultAsync.raceWith(left, right, handlers)` exposes the winner and a handle to the still-running
167
+ task for custom cooperative policy.
168
+ - `ResultAsync.withResource({ acquire, use, release })` releases after every acquire-success path.
169
+ Release errors are best-effort and do not enter the result channel; model cleanup in `use` when it
170
+ must affect control flow.
171
+
172
+ Race, timeout, retry, callback, and resource helpers rely on cooperative cancellation. Pass their
173
+ `AbortSignal` into fetch, database, SDK, timer, or subscription work; aborting a wrapper cannot stop
174
+ an underlying operation that ignores the signal.
175
+
176
+ ## Collections And Control Flow
177
+
178
+ Both `Result` and `ResultAsync` provide collection helpers:
179
+
180
+ - `combine` preserves array/tuple/record shape and returns the first error.
181
+ - `combineWithAllErrors` collects every error.
182
+ - `validateAll` collects all validation errors; async mapped use supports bounded `concurrency`.
183
+ - `zip` combines exactly two values.
184
+ - `firstSuccessOf` tries candidates until one succeeds and otherwise returns the final error.
185
+ - `forEach` stops at the first error; use `{ discard: true }` when successful values are irrelevant.
186
+ - `when`, `unless`, `loop`, and `iterate` model Resultar-native control flow where they improve
187
+ clarity.
188
+
189
+ Prefer `ResultAsync.forEach(items, mapper, { concurrency })` for bounded first-error processing and
190
+ `ResultAsync.validateAll(items, mapper, { concurrency })` for independent validation where every
191
+ error matters.
192
+
193
+ ## `safeTry`
194
+
195
+ Use `safeTry` when a linear generator is clearer than a chain:
196
+
197
+ ```ts
198
+ const workflow = safeTry(async function* () {
199
+ const input = yield* validateInput(raw);
200
+ const remote = yield* loadRemote(input);
201
+ const saved = yield* saveRemote(remote);
202
+
203
+ return ok(saved);
204
+ });
205
+ ```
206
+
207
+ Use `yield*` for `Result` and `ResultAsync`. Wrap raw promises before yielding. Avoid raw `await`,
208
+ `try/catch`, and legacy `safeUnwrap()` inside the generator.
209
+
210
+ ## ResultTask (Lazy Workflows)
211
+
212
+ `ResultTask<A, E, R>` is a lazy, reusable description of work: building one starts nothing, and
213
+ each `run*` call executes it again. Never substitute `ResultAsync` (already-started) where
214
+ laziness matters.
215
+
216
+ - Build: `succeed`, `fail`, `fromResult`, `sync`, `try`, `tryPromise` (`try`/`tryPromise` map
217
+ external throws into `E`; `sync` treats them as defects).
218
+ - Compose lazily: `map`, `flatMap`, `catchAll`, `tap`, `gen` with `yield*` (also accepts
219
+ `yield* result`: `Ok` unwraps, `Err` short-circuits into `E`).
220
+ - Provide services: `yield* Tag`, then `provideService` / `provideServices` /
221
+ `provideServiceResolver` (data-first and curried); missing or incompatible providers are
222
+ compile-time errors, never runtime surprises.
223
+ - Resources: `acquireRelease` + `scoped`; LIFO awaited finalizers run on every exit; deferred
224
+ release errors live in `R` until `scoped` folds them into `E`; `catchAll` recovers a release
225
+ failure only after `scoped`.
226
+ - Boundaries: `runExit` preserves `Success`/`Fail`/`Die`/`Interrupt`/`Sequential`; `runResult`
227
+ returns one `Err` and rejects the rest (`AbortError` for interrupts, `ResultTaskCauseError`
228
+ for composites); `runPromise` unwraps or rejects. Cancellation is cooperative: no fibers,
229
+ `race`, `timeout`, or scheduler exist on `ResultTask` yet — use the `ResultAsync` helpers.
230
+
231
+ `Result.gen` is the canonical name for linear Result workflows; `safeTry` remains as its alias.
232
+ For lazy `ResultTask` workflows with typed services and scopes, see `packages/resultar/README.md`
233
+ (Lazy Workflows With ResultTask) and `docs/rfcs/rfc-0001-result-task-core.md`.
234
+
235
+ ## Observation And Cleanup
236
+
237
+ On `Result` and `ResultAsync`, `tap`, `tapError`, and `log` preserve the original result. Callback throws and rejected callback
238
+ promises are intentionally ignored, so use them only for best-effort logging, metrics, tracing, and
239
+ observation. Use `andThen`, `orElse`, or a Resultar boundary when side-effect failure must alter the
240
+ workflow.
241
+
242
+ `toDisposable` and `toAsyncDisposable` integrate with Node.js `using` / `await using`; their
243
+ cleanup callbacks are also best-effort. Prefer `withResource` for acquire/use/release workflows.
244
+
245
+ On `ResultTask`, `tap` and `tapError` are lazy effectful composition. Returned task errors and
246
+ requirements join the workflow type. A thrown callback or rejected Promise becomes `Die`;
247
+ a returned `ResultTask.fail(error)` becomes a typed failure. Use an explicitly recovered task
248
+ when observation must be best-effort. `runExit` preserves these exits; `runResult` can reject.
249
+ Do not substitute `ResultAsync.withResource` for awaited ResultTask resource ownership.