resultar 3.7.0 → 3.8.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.
@@ -0,0 +1,264 @@
1
+ # Resultar Requests And Validation Tooling
2
+
3
+ Use this reference for HTTP JSON integrations and Resultar-specific diagnostics. Verify package
4
+ versions and local README files before copying an example.
5
+
6
+ ## Contents
7
+
8
+ - [Choose A Request Package](#choose-a-request-package)
9
+ - [Use The Schema-Agnostic Core](#use-the-schema-agnostic-core)
10
+ - [Use The TypeBox Adapter](#use-the-typebox-adapter)
11
+ - [Use The Zod Adapter](#use-the-zod-adapter)
12
+ - [Map Request Errors](#map-request-errors)
13
+ - [Apply Retry Correctly](#apply-retry-correctly)
14
+ - [Validate In The Preferred Order](#validate-in-the-preferred-order)
15
+ - [Configure The Native Checker](#configure-the-native-checker)
16
+ - [Act On Diagnostics](#act-on-diagnostics)
17
+ - [Discover Repository Commands](#discover-repository-commands)
18
+
19
+ ## Choose A Request Package
20
+
21
+ | Contract style | Package | Success type |
22
+ | -------------------------------- | -------------------------- | ---------------------------- |
23
+ | Custom type guard or decoder | `resultar-request` | Type guard or decoder output |
24
+ | TypeBox schema | `resultar-request-typebox` | `Static<typeof schema>` |
25
+ | Zod schema, including transforms | `resultar-request-zod` | `z.output<typeof schema>` |
26
+
27
+ All three share the same transport implementation. The adapters add schema validation and re-export
28
+ the public `resultar-request` types. Install only the core and schema adapter the service needs.
29
+
30
+ Keep request payloads concrete. Do not widen a known response contract to `JsonRecord`,
31
+ `Record<string, unknown>`, or `unknown` after validation.
32
+
33
+ ## Use The Schema-Agnostic Core
34
+
35
+ Use a type guard when validation does not transform the payload:
36
+
37
+ ```ts
38
+ import { requestJson } from "resultar-request";
39
+
40
+ type User = { readonly email: string; readonly id: string };
41
+
42
+ const isUser = (value: unknown): value is User =>
43
+ typeof value === "object" &&
44
+ value !== null &&
45
+ "email" in value &&
46
+ typeof value.email === "string" &&
47
+ "id" in value &&
48
+ typeof value.id === "string";
49
+
50
+ const user = requestJson({
51
+ request: () => fetch(`https://example.com/users/${userId}`),
52
+ validator: isUser,
53
+ });
54
+ ```
55
+
56
+ Use `decode` when validation also normalizes or transforms the response. The decoder must make
57
+ success and validation failure explicit; check the installed package types for the exact shape.
58
+
59
+ ## Use The TypeBox Adapter
60
+
61
+ ```ts
62
+ import { createTaggedError } from "resultar";
63
+ import {
64
+ requestJson as requestJsonTypeBox,
65
+ type RequestJsonMapError,
66
+ } from "resultar-request-typebox";
67
+ import { Type, type Static } from "typebox";
68
+
69
+ const userSchema = Type.Object(
70
+ {
71
+ email: Type.String(),
72
+ id: Type.String(),
73
+ plan: Type.Union([Type.Literal("free"), Type.Literal("team")]),
74
+ },
75
+ { additionalProperties: false },
76
+ );
77
+
78
+ type User = Static<typeof userSchema>;
79
+
80
+ class UserApiError extends createTaggedError({
81
+ name: "UserApiError",
82
+ message: "User API $reason: $detail",
83
+ }) {}
84
+
85
+ const mapUserError = {
86
+ 404: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "not-found" }),
87
+ http: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "http" }),
88
+ invalidJson: ({ cause }) =>
89
+ new UserApiError({ cause, detail: cause.message, reason: "invalid-json" }),
90
+ request: ({ cause }) => new UserApiError({ cause, detail: cause.message, reason: "request" }),
91
+ validation: ({ errors }) =>
92
+ new UserApiError({ detail: `${errors.length} validation issue(s)`, reason: "validation" }),
93
+ } satisfies RequestJsonMapError;
94
+
95
+ const user = requestJsonTypeBox({
96
+ request: () => fetch(`https://example.com/users/${userId}`),
97
+ schema: userSchema,
98
+ mapError: mapUserError,
99
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
100
+ });
101
+ ```
102
+
103
+ The success channel is `User`, derived from `Static<typeof userSchema>`. Keep the named adapter
104
+ alias when a file uses more than one request helper or when the schema library should be obvious at
105
+ the call site.
106
+
107
+ ## Use The Zod Adapter
108
+
109
+ ```ts
110
+ import { createTaggedError } from "resultar";
111
+ import { requestJson as requestJsonZod, type RequestJsonMapError } from "resultar-request-zod";
112
+ import { z } from "zod";
113
+
114
+ const userSchema = z
115
+ .object({ email: z.string().email(), id: z.string(), seats: z.number().int().nonnegative() })
116
+ .transform((user) => ({
117
+ ...user,
118
+ email: user.email.toLowerCase(),
119
+ seatLabel: `${user.seats} seats`,
120
+ }));
121
+
122
+ type User = z.output<typeof userSchema>;
123
+
124
+ class UserApiError extends createTaggedError({
125
+ name: "UserApiError",
126
+ message: "User API $reason: $detail",
127
+ }) {}
128
+
129
+ const mapUserError = {
130
+ 404: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "not-found" }),
131
+ http: ({ statusCode }) => new UserApiError({ detail: `HTTP ${statusCode}`, reason: "http" }),
132
+ invalidJson: ({ cause }) =>
133
+ new UserApiError({ cause, detail: cause.message, reason: "invalid-json" }),
134
+ request: ({ cause }) => new UserApiError({ cause, detail: cause.message, reason: "request" }),
135
+ validation: ({ errors }) =>
136
+ new UserApiError({ detail: `${errors.length} validation issue(s)`, reason: "validation" }),
137
+ } satisfies RequestJsonMapError;
138
+
139
+ const user = requestJsonZod({
140
+ request: () => fetch(`https://example.com/users/${userId}`),
141
+ schema: userSchema,
142
+ mapError: mapUserError,
143
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
144
+ });
145
+ ```
146
+
147
+ Use `z.output`, not `z.input` or an independently maintained interface, because Zod transforms can
148
+ change the success type.
149
+
150
+ ## Map Request Errors
151
+
152
+ Without `mapError`, request helpers return the package's `RequestError` union. Use that union for
153
+ infrastructure-local code. At an application boundary, use `mapError` to convert these categories
154
+ to concrete tagged errors:
155
+
156
+ - request creation, network, or fetch failure;
157
+ - status-specific HTTP failure, with numeric handlers taking precedence over `http`;
158
+ - invalid JSON;
159
+ - response validation or decode failure.
160
+
161
+ Preserve `cause` for request and JSON failures and preserve status, URL, validation details, and
162
+ other safe context needed for diagnosis. Do not map every category to a message-only `Error`.
163
+
164
+ ## Apply Retry Correctly
165
+
166
+ ```ts
167
+ const user = requestJsonZod({
168
+ request: () => fetch(`https://example.com/users/${userId}`),
169
+ schema: userSchema,
170
+ retry: { times: 2, delayMs: ({ nextAttempt }) => nextAttempt * 100, jittered: 0.2 },
171
+ });
172
+ ```
173
+
174
+ The default policy retries request failures and HTTP `5xx`, but not `4xx`, invalid JSON, or
175
+ validation failures. Retry requires a request factory; an already-created promise cannot be
176
+ restarted. Ensure each attempt receives a fresh request. The current request retry configuration
177
+ does not expose ResultAsync's internal retry signal, so do not claim that `requestJson`
178
+ automatically forwards policy cancellation. Supply an external fetch signal through the request
179
+ closure when the caller owns separate cancellation.
180
+
181
+ ## Validate In The Preferred Order
182
+
183
+ 1. Run the native `resultar-check` CLI as the authoritative local and CI gate. It uses TypeScript-Go
184
+ to run compiler diagnostics and all Resultar rules over the same project.
185
+ 2. Run the repository's formatter, build, tests, static analysis, package smoke tests, and runnable
186
+ examples.
187
+
188
+ The Go analyzer is the single implementation of Resultar diagnostics.
189
+
190
+ ## Configure The Native Checker
191
+
192
+ Install Resultar Check. A separate TypeScript installation is not required:
193
+
194
+ ```sh
195
+ pnpm add -D resultar-check
196
+ pnpm exec resultar-check
197
+ ```
198
+
199
+ Add the checker configuration marker to `tsconfig.json` and tune rules only when project policy
200
+ requires it:
201
+
202
+ ```json
203
+ {
204
+ "$schema": "./node_modules/resultar-check/schema.json",
205
+ "compilerOptions": {
206
+ "plugins": [
207
+ {
208
+ "name": "resultar-check",
209
+ "noDiscard": "error",
210
+ "noUnsafeAwait": "error",
211
+ "preferAndThen": "error",
212
+ "preferMapErr": "error",
213
+ "unsafeResultTypeAssertion": "error"
214
+ }
215
+ ]
216
+ }
217
+ }
218
+ ```
219
+
220
+ The `plugins` entry is consumed by the native CLI and stdio LSP server; it does not install an editor
221
+ extension. Start `resultar-check lsp` from the editor's language-server configuration. Findings use
222
+ rule IDs such as `resultar/no-discard`; pass `--format json` (or `--json`) when machine-readable JSON
223
+ Lines output is needed.
224
+
225
+ Read `packages/check/README.md` for all rules, severities, modes, and command-line flags. Use narrow
226
+ `ignoreFilePatterns` only for deliberate tests, scripts, generated files, or terminal process boundaries.
227
+
228
+ ## Act On Diagnostics
229
+
230
+ When an agent encounters a diagnostic, fix the architecture first:
231
+
232
+ - handle, compose, return, or intentionally discard a returned Resultar value; a discard does not
233
+ run a lazy task and must not be applied just to silence diagnostics;
234
+ - replace nested `Result` from `map` with `andThen`;
235
+ - replace `Promise<Result>` / `Promise<StrictResult>` contracts with `ResultAsync` /
236
+ `StrictResultAsync` and return composed operations directly (`prefer-result-async`);
237
+ - enable `preferResultAsyncMode: "all"` with `preferResultAsync: "error"` to also catch raw
238
+ `Promise<T>` repository/service contracts and inferred returns; map driver errors in adapters
239
+ and document local suppressions for required native framework or conversion callbacks;
240
+ - replace error-only recovery with `mapErr`;
241
+ - replace project-wide `try/catch` with `tryResult` or `tryResultAsync` when `noTryCatch` is enabled;
242
+ - wrap raw promise awaits in Resultar contexts;
243
+ - use `yield*` and avoid `try/catch` inside `Result.gen` / `safeTry`;
244
+ - preserve the inferred error channel instead of asserting it away.
245
+
246
+ ## Discover Repository Commands
247
+
248
+ Inspect `package.json` scripts instead of assuming a package manager or command. In the Resultar
249
+ repository, the relevant root commands currently include:
250
+
251
+ ```sh
252
+ pnpm fmt:check
253
+ pnpm build
254
+ pnpm check
255
+ pnpm test
256
+ pnpm analyze
257
+ pnpm smoke:package
258
+ pnpm test:examples
259
+ pnpm test:agents
260
+ ```
261
+
262
+ Run the narrowest useful command while iterating, then the complete relevant validation set before
263
+ claiming completion. Report pre-existing dependency, lockfile, or environment blockers separately
264
+ from failures introduced by the skill work.
@@ -0,0 +1,246 @@
1
+ # Resultar Implementation Patterns
2
+
3
+ Use these patterns as starting points, then align names and types with the consuming domain.
4
+
5
+ ## Contents
6
+
7
+ - [Model A Domain Failure](#model-a-domain-failure)
8
+ - [Compose Sync And Async Steps](#compose-sync-and-async-steps)
9
+ - [Write A Linear Workflow](#write-a-linear-workflow)
10
+ - [Wrap External Work](#wrap-external-work)
11
+ - [Apply Retry And Timeout Policy](#apply-retry-and-timeout-policy)
12
+ - [Process Batches](#process-batches)
13
+ - [Acquire And Release Resources](#acquire-and-release-resources)
14
+ - [Recover Locally And Match At Boundaries](#recover-locally-and-match-at-boundaries)
15
+ - [Add Observability](#add-observability)
16
+ - [Migrate Incrementally](#migrate-incrementally)
17
+ - [Test Runtime And Type Behavior](#test-runtime-and-type-behavior)
18
+
19
+ ## Model A Domain Failure
20
+
21
+ ```ts
22
+ import { createTaggedError, ok } from "resultar";
23
+ import type { StrictResult } from "resultar";
24
+
25
+ class InvalidEmailError extends createTaggedError({
26
+ name: "InvalidEmailError",
27
+ message: "Invalid email $email",
28
+ }) {}
29
+
30
+ const validateEmail = (email: string): StrictResult<string, InvalidEmailError> =>
31
+ email.includes("@") ? ok(email.trim().toLowerCase()) : InvalidEmailError.err({ email });
32
+ ```
33
+
34
+ Use real tagged errors when the failure crosses a service, HTTP, job, queue, CLI, or integration
35
+ boundary. Keep strings, enums, or `TaggedEnum` variants for intentionally local flows.
36
+
37
+ ## Compose Sync And Async Steps
38
+
39
+ ```ts
40
+ type CreateUserError = InvalidEmailError | UserAlreadyExistsError | SaveUserError;
41
+
42
+ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
43
+ validateEmail(input.email)
44
+ .asyncAndThen(ensureEmailAvailable)
45
+ .andThen((email) => saveUser({ ...input, email }));
46
+ ```
47
+
48
+ - Use `map` only for an infallible value transform.
49
+ - Use `filterOrElse` for predicate validation.
50
+ - Use `andThen` when the next operation can fail.
51
+ - Use `asyncAndThen` only for the sync-to-async bridge.
52
+ - Keep each function's error type narrow and allow the final union to be inferred.
53
+
54
+ ## Write A Linear Workflow
55
+
56
+ ```ts
57
+ const createUser = (input: CreateUserInput): StrictResultAsync<User, CreateUserError> =>
58
+ Result.gen(async function* () {
59
+ const email = yield* validateEmail(input.email);
60
+ yield* ensureEmailAvailable(email);
61
+ const user = yield* saveUser({ ...input, email });
62
+
63
+ return ok(user);
64
+ });
65
+ ```
66
+
67
+ Use `yield*` for every fallible Resultar step. Wrap an uncontrolled promise with
68
+ `tryResultAsync` before yielding it. Do not mix raw `await` or broad `try/catch` into the generator.
69
+
70
+ ## Wrap External Work
71
+
72
+ ```ts
73
+ class ProfileApiError extends createTaggedError({
74
+ name: "ProfileApiError",
75
+ message: "Profile API failed for $userId",
76
+ }) {}
77
+
78
+ type LoadProfileOptions = {
79
+ readonly attempt?: number;
80
+ readonly signal?: AbortSignal;
81
+ };
82
+
83
+ const loadProfile = (
84
+ userId: string,
85
+ options: LoadProfileOptions = {},
86
+ ): StrictResultAsync<Profile, ProfileApiError> =>
87
+ tryResultAsync(
88
+ () => profileClient.load(userId, options),
89
+ (cause) => new ProfileApiError({ cause, userId }),
90
+ );
91
+ ```
92
+
93
+ Prefer a promise factory because creating the promise may throw synchronously. Use
94
+ `fromPromise(existingPromise, mapper)` only when the promise already exists, and use
95
+ `fromSafePromise` only when non-rejection is guaranteed by contract.
96
+
97
+ For callback or subscription APIs, use `fromCallback` and return cleanup from `subscribe`:
98
+
99
+ ```ts
100
+ const message = fromCallback<Message, SubscriptionError>({
101
+ catch: (cause) => new SubscriptionError({ cause, topic }),
102
+ signal,
103
+ subscribe: ({ resolve, reject }) => {
104
+ const subscription = client.subscribe(topic, resolve, reject);
105
+
106
+ return () => subscription.close();
107
+ },
108
+ });
109
+ ```
110
+
111
+ Check the installed `ResultAsyncFromCallbackOptions` type for exact client integration details.
112
+
113
+ ## Apply Retry And Timeout Policy
114
+
115
+ ```ts
116
+ const profile = ResultAsync.timeout(
117
+ (signal) =>
118
+ ResultAsync.retry(
119
+ (attempt, retrySignal) => loadProfile(userId, { attempt, signal: retrySignal }),
120
+ {
121
+ times: 2,
122
+ delayMs: ({ nextAttempt }) => nextAttempt * 100,
123
+ jittered: 0.2,
124
+ while: (error) => error._tag === "ProfileApiError",
125
+ signal,
126
+ },
127
+ ),
128
+ {
129
+ timeoutMs: 1_500,
130
+ onTimeout: () => new ProfileTimeoutError({ timeoutMs: 1_500, userId }),
131
+ },
132
+ );
133
+ ```
134
+
135
+ Forward the supplied signal into the underlying operation. Confirm whether the desired timeout
136
+ covers each attempt or the complete retry budget, then nest the policies accordingly. Use
137
+ `retryOrElse` only when fallback after exhaustion is domain policy.
138
+
139
+ Choose races deliberately:
140
+
141
+ - `race` / `raceAll`: first settled `Ok` or `Err` wins.
142
+ - `raceFirst`: first `Ok` wins; both must fail before an `Err` is returned.
143
+ - `raceWith`: winner-specific logic needs access to the still-running task.
144
+
145
+ ## Process Batches
146
+
147
+ Stop at the first error with bounded concurrency:
148
+
149
+ ```ts
150
+ const saved = ResultAsync.forEach(rows, saveRow, { concurrency: 8 });
151
+ ```
152
+
153
+ Collect all independent errors instead:
154
+
155
+ ```ts
156
+ const validated = ResultAsync.validateAll(rows, validateRow, { concurrency: 8 });
157
+ ```
158
+
159
+ Use `{ discard: true }` with `forEach` when successful values are intentionally unused. Do not
160
+ silently discard the returned `ResultAsync` itself.
161
+
162
+ ## Acquire And Release Resources
163
+
164
+ ```ts
165
+ const imported = ResultAsync.withResource({
166
+ acquire: (signal) => openImportSession({ signal }),
167
+ use: (session, signal) => importRows(session, rows, { signal }),
168
+ release: (session) => session.close(),
169
+ });
170
+ ```
171
+
172
+ Release runs after every successful acquire path, but release failures are best-effort. If cleanup
173
+ failure must affect the returned result, include that operation in `use` or otherwise model it as a
174
+ typed Resultar step.
175
+
176
+ ## Recover Locally And Match At Boundaries
177
+
178
+ Recover only where the fallback is part of application policy:
179
+
180
+ ```ts
181
+ const profile = loadProfile(userId).catchTags({
182
+ ProfileNotFoundError: () => ok(defaultProfile),
183
+ ProfileDisabledError: () => ok(disabledProfile),
184
+ });
185
+ ```
186
+
187
+ Unhandled tags remain in the error channel. At the HTTP boundary, map the complete union:
188
+
189
+ ```ts
190
+ const response = await createUser(input).matchTags((user) => ({ body: user, statusCode: 201 }), {
191
+ InvalidEmailError: (error) => ({ body: error.toJSON(), statusCode: 400 }),
192
+ UserAlreadyExistsError: (error) => ({ body: error.toJSON(), statusCode: 409 }),
193
+ SaveUserError: (error) => ({ body: error.toJSON(), statusCode: 500 }),
194
+ });
195
+ ```
196
+
197
+ Use `matchTagsPartial` only when a shared fallback is intentional. Keep HTTP status codes out of
198
+ domain functions.
199
+
200
+ ## Add Observability
201
+
202
+ ```ts
203
+ const observed = loadProfile(userId)
204
+ .tap((profile) => metrics.increment("profile.loaded", { userId: profile.userId }))
205
+ .tapError((error) => metrics.increment("profile.failed", { tag: error._tag }))
206
+ .log((profile, error) => logger.info({ error, profile }, "profile result"));
207
+ ```
208
+
209
+ These `ResultAsync` callbacks are best-effort and cannot change the result.
210
+ `ResultTask.tap` and `tapError` instead propagate returned task failures and turn callback
211
+ throws or rejected Promises into defects. Use `andThen` or `orElse` when a
212
+ logging, auditing, publishing, or cleanup failure must affect control flow.
213
+
214
+ ## Migrate Incrementally
215
+
216
+ 1. Identify the outermost throwing or rejecting dependency.
217
+ 2. Create concrete tagged errors for expected domain and infrastructure failures.
218
+ 3. Wrap throws and rejections at that edge with `tryResult` or `tryResultAsync`.
219
+ 4. Change callers to compose with `map`, `andThen`, `asyncAndThen`, and `mapErr`.
220
+ 5. Move transport conversion to one boundary matcher.
221
+ 6. Replace broad error types and unsafe casts with inferred unions.
222
+ 7. Enable `resultar-check` rules and remove narrow temporary exceptions.
223
+
224
+ Avoid converting the whole codebase in one unsafe cast. A wrapper at an integration seam lets the
225
+ typed error channel spread in reviewable steps.
226
+
227
+ ## Test Runtime And Type Behavior
228
+
229
+ ```ts
230
+ import { equal, ok as assert } from "node:assert";
231
+ import { describe, expectTypeOf, it } from "vite-plus/test";
232
+
233
+ describe("validateEmail", () => {
234
+ it("returns a typed tagged error", () => {
235
+ const result = validateEmail("invalid");
236
+
237
+ assert(result.isErr());
238
+ equal(result.error._tag, "InvalidEmailError");
239
+ expectTypeOf(result.error).toEqualTypeOf<InvalidEmailError>();
240
+ });
241
+ });
242
+ ```
243
+
244
+ Test every expected error path, not just `Ok`. Add type tests for inferred unions, required tagged
245
+ error props, narrowing, and exhaustive handlers. Use deterministic fake clocks, signals, and
246
+ clients for retry, timeout, cancellation, race, concurrency, and cleanup tests.
@@ -0,0 +1,122 @@
1
+ # Resultar Review And Migration Checklist
2
+
3
+ Use this checklist for implementation reviews, migrations, documentation changes, and final
4
+ validation. Report only issues supported by the installed Resultar version and repository evidence.
5
+
6
+ ## Source And Scope
7
+
8
+ - Confirm the installed `resultar`, request adapter, and `resultar-check` versions.
9
+ - Inspect local exports, types, package README files, and runnable examples before suggesting an API.
10
+ - Separate issues introduced by the change from pre-existing workspace or dependency problems.
11
+ - Preserve unrelated changes in a dirty worktree.
12
+
13
+ ## Failure Model
14
+
15
+ - Expected failures return `Result`, `ResultAsync`, or `ResultTask`; programmer defects may still throw.
16
+ - Application and integration boundaries prefer `StrictResult` / `StrictResultAsync`.
17
+ - Tagged errors have stable names, useful metadata, inferred template props, and preserved causes.
18
+ - Error unions remain concrete instead of widening early to `Error`, `unknown`, or a string.
19
+ - `TaggedEnum` is limited to lightweight non-Error domain variants.
20
+ - Sensitive metadata is redacted when serialization or logging could expose it.
21
+
22
+ ## Composition
23
+
24
+ - `map` performs only infallible success transforms.
25
+ - `mapErr` performs error-only transforms.
26
+ - `filterOrElse` represents predicate validation.
27
+ - `andThen` composes fallible steps; `asyncAndThen` is used for the sync-to-async bridge.
28
+ - `orElse` recovers the whole error channel; `catchTag` / `catchTags` recover selected domain cases.
29
+ - `safeTry` uses `yield*` for Resultar values and contains no raw `await` or broad `try/catch`.
30
+ - Returned `Result`, `ResultAsync`, and `ResultTask` values are handled or explicitly discarded by supported policy.
31
+ - Type assertions do not narrow away possible errors.
32
+
33
+ ## Async Boundaries And Policy
34
+
35
+ - External throws and rejections are wrapped once, close to the uncontrolled dependency.
36
+ - Error mappers accept `unknown` and produce concrete errors with `cause`.
37
+ - Promise factories use `tryResultAsync`; already-created promises use `fromPromise`.
38
+ - `fromSafePromise` is used only when rejection is impossible by contract.
39
+ - Callback integrations provide cleanup and model `AbortError` where applicable.
40
+ - Retry policy defines bounded attempts, delay/jitter, retryable errors, and cancellation.
41
+ - Timeout scope is explicit: per attempt or across the complete retry budget.
42
+ - `race`/`raceAll` are used for first settlement; `raceFirst` for first success.
43
+ - Signals reach the underlying client so cancellation is cooperative rather than cosmetic.
44
+ - Batch concurrency is bounded when workload size or dependency capacity requires it.
45
+ - `validateAll` is used only when collecting all independent failures is desired.
46
+ - `withResource` cleanup is treated as best-effort; important cleanup failures are modeled in the
47
+ result channel.
48
+
49
+ ## Boundaries And Recovery
50
+
51
+ - Domain code does not contain transport status codes, framework responses, or CLI exit behavior.
52
+ - `matchTags` handles the complete tagged error union at the boundary.
53
+ - `matchTagsPartial` and `matchErrorPartial` have intentional fallbacks.
54
+ - An `Error` handler exists when untagged errors can reach tagged matching.
55
+ - Recovery is local only when the fallback is genuine domain/application policy.
56
+ - `_unsafeUnwrap*` remains in tests; `runSync`, `runPromise`, and `unwrapOrThrow` appear only at
57
+ deliberate terminal boundaries.
58
+
59
+ ## HTTP Requests
60
+
61
+ - `resultar-request` is used for a custom guard/decoder; TypeBox and Zod use their dedicated
62
+ adapters.
63
+ - TypeBox success types derive from `Static<typeof schema>`.
64
+ - Zod success types derive from `z.output<typeof schema>` so transforms are preserved.
65
+ - Validated values are not widened back to `JsonRecord`, `Record<string, unknown>`, or `unknown`.
66
+ - Error mapping distinguishes request, HTTP, invalid-JSON, and validation failures.
67
+ - Numeric HTTP handlers take precedence over generic HTTP mapping where needed.
68
+ - Retry uses a request factory and does not retry non-retryable `4xx`, JSON, or validation failures
69
+ without explicit policy.
70
+
71
+ ## Observability And Cleanup
72
+
73
+ - `Result` / `ResultAsync` observation failures are best-effort; `ResultTask.tap` / `tapError`
74
+ can introduce typed failures or defects.
75
+ - Resource cleanup and observation use the semantics of the concrete execution model.
76
+ - Audits, publishes, or cleanup that must succeed use `andThen`, `orElse`, or another Resultar
77
+ boundary.
78
+ - Logs expose stable tags and safe context without leaking redacted values.
79
+
80
+ ## Tests And Documentation
81
+
82
+ - Runtime tests cover `Ok` and every expected `Err` branch.
83
+ - Type tests cover inferred error unions, required tagged-error props, narrowing, and exhaustive
84
+ handlers.
85
+ - Retry, timeout, cancellation, races, concurrency, and cleanup use deterministic fakes.
86
+ - Tests use the repository's actual runner; this repository uses `vite-plus/test`.
87
+ - Documentation examples compile or have a matching runnable example.
88
+ - Core request, TypeBox, and Zod docs link to one another as alternatives/adapters.
89
+ - API names, package links, and version claims match current source.
90
+ - `Result.gen` returns a Result; `ResultTask.gen` returns a plain success value.
91
+ - `runResult` rejection paths and deferred release errors are handled at the owning boundary.
92
+ - An intentional-discard fix is not mistaken for executing or handling a lazy task.
93
+
94
+ ## Validation Order
95
+
96
+ 1. Run the `resultar-check` CLI as the authoritative project check.
97
+ 2. Confirm the `resultar-check lsp` stdio server emits Resultar diagnostics and quick fixes in the
98
+ editor.
99
+ 3. Use JSONL, SARIF, or JUnit output when a CI/review system needs structured diagnostics; keep the
100
+ native CLI as the authoritative check.
101
+ 4. Run formatting, build, tests, analysis, package smoke tests, and example workflows that apply.
102
+ 5. Report exactly which commands passed, failed, or were blocked.
103
+
104
+ ## Review Red Flags
105
+
106
+ - `T | Error`, sentinel `null`, message strings, or thrown expected failures replace a Resultar
107
+ channel.
108
+ - A broad `try/catch` surrounds an entire workflow instead of wrapping one external boundary.
109
+ - Project-wide `try/catch` remains after the strict `noTryCatch` migration rule is enabled.
110
+ - `map` returns another `Result` or `ResultAsync`.
111
+ - `orElse` only replaces one error with another and should be `mapErr`.
112
+ - A raw promise is awaited inside a Resultar workflow without a typed mapper.
113
+ - `safeTry` contains raw `await`, plain `yield`, `try/catch`, or legacy `safeUnwrap()`.
114
+ - A Resultar value is created and ignored.
115
+ - `_unsafeUnwrap()` appears in production code.
116
+ - An assertion makes an error union narrower than the implementation can guarantee.
117
+ - Tagged errors override generated constructors, mismatch class names, or omit useful causes.
118
+ - A schema adapter returns a broad JSON type or duplicates the schema with a drifting interface.
119
+ - A promise rather than a request factory is configured for retry.
120
+ - Hand-written retry, timeout, or race logic duplicates a built-in ResultAsync policy.
121
+ - A lint-only pass is described as full Resultar validation.
122
+ - Documentation recommends an API absent from the installed package.
@@ -0,0 +1,39 @@
1
+ # Typed services and HTTP scopes
2
+
3
+ Read the installed `resultar-di/README.md`, `resultar-hono/README.md` and `resultar-fastify/README.md` for the exact companion
4
+ versions. The primary DI exports are `createModule`, `service`, and `resource`; class tokens and
5
+ named factory overloads belong to `resultar-di/advanced`.
6
+
7
+ - Register a service token with `.singleton(Token)`, `.scoped(Token)`, or `.transient(Token)`.
8
+ Dependency objects are inferred from tokens. Use `service(name, task)` for asynchronous
9
+ initialization; an async function passed as an ordinary factory is a defect in the contract.
10
+ - Keep a server's serving-and-waiting task inside its owning `use` / ResultTask scope. Returning a
11
+ live resource from a completed scope returns a resource whose finalizer has already run.
12
+ - Use `createHonoApp` for Hono routes. Ordinary `await` and `result.match` inside a route are valid;
13
+ generator-specific restrictions do not apply to a route body.
14
+ - Use `createHonoServices(module).middleware(keys, { locals })` to add services to an existing Hono
15
+ router. Inline route middleware infers `c.var.services` while preserving `c.env` and native RPC
16
+ responses. Install one services middleware per request; consume/cancel responses before `close()`.
17
+ - Use `createFastifyPlugin({ services, bindings, appBindings?, locals? })` for native Fastify routes.
18
+ `app.services` contains the selected startup singletons; `request.services` is available from
19
+ `preHandler` through response completion. Register authentication providing locals earlier.
20
+ Infer decorators with `InferAppServices` / `InferRequestServices`; augment Fastify in the application
21
+ or use its typed `getDecorator`. Annotate module/locals callback parameters with native Fastify
22
+ types when their inferred return values participate in selection checking.
23
+ - Keep business logic in plain factories with async methods when sufficient. Register them using
24
+ `service(name, dependencies, factory)`. Map results explicitly through `matchTags` and native
25
+ response APIs; the framework adapters install no status table or `reply.result` helper.
26
+ - `scope.useSingletons(keys, callback)` restricts selected services and callback tokens to the root
27
+ lifetime. Keep its callback active while serving; it is not a free-standing resolver. Factory
28
+ initialization stays lazy until selection; resource release remains explicit.
29
+ - `scope.fetch`, `services.http`, and `createHonoApp` keep the request scope open until the Response
30
+ body ends, fails, or is canceled. Tests must consume or cancel response bodies before shutdown.
31
+ - Stop and drain the server, finish/cancel persistent streams, then await application close.
32
+ Handle typed release errors; defects and composite causes may reject `close()`.
33
+ - Supply authenticated request locals through the appropriate per-request integration. A singleton
34
+ cannot capture scoped services or request-local data. Extracting authentication remains the
35
+ framework adapter's responsibility.
36
+
37
+ For a complete runnable pattern, see the workspace `examples/hono/` and `examples/resultar/`
38
+ application lifecycle examples. The distributed guide includes the checked core recipe at
39
+ `examples/workflow.mjs`; use the published declarations to adapt companion integrations.
@@ -0,0 +1,10 @@
1
+ {
2
+ "resultar-check": "3.2.0",
3
+ "resultar-di": "0.2.0",
4
+ "resultar-fastify": "0.2.0",
5
+ "resultar-hono": "0.2.0",
6
+ "resultar-request": "0.1.2",
7
+ "resultar-request-typebox": "0.1.2",
8
+ "resultar-request-zod": "0.1.2",
9
+ "resultar": "3.8.0"
10
+ }