resultar 3.6.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.
- package/LICENSE +1 -0
- package/README.md +151 -15
- package/dist/agent/SKILL.md +224 -0
- package/dist/agent/agents/openai.yaml +4 -0
- package/dist/agent/examples/workflow.mjs +21 -0
- package/dist/agent/examples/workflow.mts +27 -0
- package/dist/agent/references/api.md +249 -0
- package/dist/agent/references/integrations.md +264 -0
- package/dist/agent/references/patterns.md +246 -0
- package/dist/agent/references/review-checklist.md +122 -0
- package/dist/agent/references/services.md +39 -0
- package/dist/agent/versions.json +10 -0
- package/dist/index.d.ts +343 -172
- package/dist/index.js +902 -279
- package/dist/llms.txt +11 -0
- package/package.json +7 -6
|
@@ -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.
|