tryharder 0.1.2 → 0.2.1

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
@@ -38,7 +38,7 @@ const result = await try$
38
38
  catch: () => new RequestFailedError("request failed"),
39
39
  })
40
40
 
41
- // result is OrderStatus | RequestFailedError | RetryExhaustedError | TimeoutError
41
+ // result is OrderStatus | RequestFailedError | TimeoutError
42
42
  ```
43
43
 
44
44
  <details>
@@ -47,7 +47,6 @@ const result = await try$
47
47
  - [Why not plain try/catch?](#why-not-plain-trycatch)
48
48
  - [Features](#features)
49
49
  - [Installation](#installation)
50
- - [Migration from hardtry](#migration-from-hardtry)
51
50
  - [Execution Model](#execution-model)
52
51
  - [Type Semantics](#type-semantics)
53
52
  - [Quick Start](#quick-start)
@@ -59,7 +58,7 @@ const result = await try$
59
58
  - [all and allSettled](#all-and-allsettled)
60
59
  - [flow and $exit](#flow-and-exit)
61
60
  - [gen](#gen)
62
- - [dispose](#dispose)
61
+ - [disposer](#disposer)
63
62
  - [API Reference](#api-reference)
64
63
  - [Common Recipes](#common-recipes)
65
64
  - [When not to use tryharder](#when-not-to-use-tryharder)
@@ -133,7 +132,7 @@ const result = await try$
133
132
  })
134
133
 
135
134
  // result is
136
- // User | UserUnavailableError | RetryExhaustedError | TimeoutError | CancellationError
135
+ // User | UserUnavailableError | TimeoutError | CancellationError
137
136
  ```
138
137
 
139
138
  That is the core shift:
@@ -142,7 +141,8 @@ That is the core shift:
142
141
  - `tryharder` exposes execution policy in the builder chain and failure shape in the return type.
143
142
  - `run(fn)` returns `T | UnhandledException`.
144
143
  - `run({ try, catch })` returns `T | C`.
145
- - Adding `retry(...)`, `timeout(...)`, and `signal(...)` widens the union with `RetryExhaustedError`, `TimeoutError`, and `CancellationError`.
144
+ - Adding `timeout(...)` and `signal(...)` widens the union with `TimeoutError` and `CancellationError`.
145
+ - Adding `retry(...)` changes how persistent failure is reported: with `catch`, the last attempt's error is mapped by `catch`; without `catch`, it surfaces as `RetryExhaustedError` (last error as `cause`) instead of `UnhandledException`.
146
146
 
147
147
  ## Features
148
148
 
@@ -151,7 +151,7 @@ That is the core shift:
151
151
  - **Sync and async parity** - Use the same mental model for `runSync(...)` and `run(...)`.
152
152
  - **Named task orchestration** - Express concurrent and ordered workflows with object-shaped task graphs instead of positional arrays.
153
153
  - **Observable execution hooks** - Add top-level instrumentation with `wrap(...)` without changing task behavior.
154
- - **Resource cleanup** - Register teardown that survives async boundaries with `dispose()` and task disposers.
154
+ - **Resource cleanup** - Register teardown that survives async boundaries with `disposer()` and task disposers.
155
155
  - **No runtime dependencies** - The published package ships without runtime dependencies.
156
156
 
157
157
  ## Installation
@@ -170,39 +170,23 @@ yarn add tryharder
170
170
  pnpm add tryharder
171
171
  ```
172
172
 
173
- ## Migration from hardtry
174
-
175
- Replace import specifiers only:
176
-
177
- - `hardtry` -> `tryharder`
178
- - `hardtry/errors` -> `tryharder/errors`
179
- - `hardtry/types` -> `tryharder/types`
180
-
181
- You can keep the same namespace alias in your code:
182
-
183
- ```ts
184
- import * as try$ from "tryharder"
185
- ```
186
-
187
- No runtime API names changed.
188
-
189
173
  ## Execution Model
190
174
 
191
175
  `tryharder` has three layers: terminal execution APIs, policy builders, and orchestration APIs.
192
176
 
193
- Terminal execution APIs are `run(...)` and `runSync(...)`. They are the points where work is actually executed and a result union is produced. Function form is the minimal shape and returns `T | UnhandledException`. Object form adds a `catch` mapper and returns `T | C`.
177
+ Terminal execution APIs are `run(...)` and `runSync(...)`. They are the points where work is actually executed and a result union is produced. Function form is the minimal shape and returns `T | UnhandledException` by default, or `T | RetryExhaustedError` when retry is configured. Object form adds a `catch` mapper and returns `T | C`.
194
178
 
195
179
  Policy builders decorate terminal execution. `retry(...)`, `timeout(...)`, and `signal(...)` do not run work by themselves; they configure the next terminal call and widen the resulting union with the policy-level failures they can introduce. `retry(limit)` counts the first attempt. `timeout(ms)` applies one total deadline across attempts, delays, and catch handling. `signal(abortSignal)` forwards external cancellation into execution.
196
180
 
197
181
  Orchestration APIs scale the same model from one operation to a task graph. `all(...)` runs a fail-fast named task map. `allSettled(...)` preserves every settled task outcome. `flow(...)` runs an ordered workflow that must explicitly terminate through `this.$exit(...)`.
198
182
 
199
- `wrap(...)` sits above those execution APIs as observational middleware. It can inspect readonly execution context and surround terminal calls, but it is not available after `retry(...)`, `timeout(...)`, or execution-scoped `signal(...)` chains. `gen(...)` offers a more linear way to compose returned unions. `dispose()` provides cleanup registration for work that spans async boundaries.
183
+ `wrap(...)` sits above those execution APIs as observational middleware. It can inspect readonly execution context and surround terminal calls, but it is not available after `retry(...)`, `timeout(...)`, or execution-scoped `signal(...)` chains. `gen(...)` offers a more linear way to compose returned unions. `disposer()` provides cleanup registration for work that spans async boundaries.
200
184
 
201
185
  | Term | Meaning |
202
186
  | --------------------- | ------------------------------------------------------------------------------ |
203
187
  | `run` | Async terminal execution that returns a value, mapped failure, or policy error |
204
188
  | `runSync` | Sync terminal execution for synchronous work only |
205
- | `retry(limit)` | Retry policy where `limit` includes the first attempt |
189
+ | `retry(limit)` | Retry policy; `limit` is a positive integer counting the first attempt |
206
190
  | `timeout(ms)` | Total execution timeout across attempts, delays, and catch handling |
207
191
  | `signal(abortSignal)` | External cancellation for `run(...)` and root-level orchestration |
208
192
  | `wrap(fn)` | Top-level observational middleware around terminal APIs |
@@ -237,10 +221,20 @@ const result = await try$
237
221
  })
238
222
 
239
223
  // result is
240
- // ValidationError | RetryExhaustedError | TimeoutError
224
+ // ValidationError | TimeoutError
241
225
  ```
242
226
 
243
- That inferred union is the contract. A caller can see whether a function returns a domain error, whether retries may exhaust, and whether a deadline may fire, without reading the implementation body.
227
+ That inferred union is the contract. A caller can see whether a function returns a domain error and whether a deadline may fire, without reading the implementation body.
228
+
229
+ The `catch` contract is strict: `catch` maps errors that originated inside `try` — thrown directly, or carried out of the retry loop as the last attempt's error once the retry policy gives up. Policy outcomes (`TimeoutError`, `CancellationError`) never pass through `catch`; they surface typed in the union so you can handle them at the call site:
230
+
231
+ ```ts
232
+ if (result instanceof TimeoutError) {
233
+ // deadline expired; map or handle it here
234
+ }
235
+ ```
236
+
237
+ Without `catch`, unmapped failures are wrapped: `RetryExhaustedError` when a retry policy gave up (for any reason — limit exhausted or `shouldRetry` declining), `UnhandledException` otherwise. The original error is always available as `cause`.
244
238
 
245
239
  `Panic` is intentionally separate from that model. It signals programmer errors such as invalid builder usage or invalid task graphs, not expected business-domain failures.
246
240
 
@@ -407,6 +401,8 @@ const result = await try$
407
401
 
408
402
  `timeout(ms)` measures total execution time, not just a single attempt.
409
403
 
404
+ `runSync(...)` stays available after the numeric retry shorthand (`retry(3)`), but not after the object form (`retry({ ... })`) or `timeout(...)` — those policies may need to wait or interrupt, which synchronous execution cannot do. This restriction is intentionally conservative: it is enforced at the type level even for object policies that would be sync-safe at runtime (constant backoff, no delay, no jitter). If you need retries with `runSync(...)`, use the numeric shorthand.
405
+
410
406
  Apply `signal(...)` on the root builder when you want cancellation to cover `all(...)`, `allSettled(...)`, or `flow(...)`.
411
407
 
412
408
  ### wrap
@@ -495,21 +491,21 @@ const value = await try$.gen(function* (use) {
495
491
  })
496
492
  ```
497
493
 
498
- ### dispose
494
+ ### disposer
499
495
 
500
- Use `dispose()` when cleanup should stay colocated with the workflow that allocates the resource, even across async boundaries. The returned `AsyncDisposer` gives you three core operations:
496
+ Use `disposer()` when cleanup should stay colocated with the workflow that allocates the resource, even across async boundaries. The returned `AsyncDisposer` gives you three operations:
501
497
 
502
- - `add(fn)` registers a cleanup callback.
498
+ - `defer(fn)` registers a cleanup callback.
503
499
  - `use(resource)` tracks a disposable resource.
504
- - `cleanup()` runs the registered teardown in reverse order.
500
+ - `dispose()` runs the registered teardown in reverse order (also triggered by leaving an `await using` scope).
505
501
 
506
502
  ```ts
507
- await using disposer = try$.dispose()
503
+ await using disposer = try$.disposer()
508
504
 
509
505
  {
510
506
  const connection = await db.connect()
511
507
 
512
- disposer.add(async () => {
508
+ disposer.defer(async () => {
513
509
  await connection.close()
514
510
  })
515
511
 
@@ -523,32 +519,44 @@ await using disposer = try$.dispose()
523
519
 
524
520
  ### Runtime
525
521
 
526
- | Export | Description |
527
- | -------------- | ----------------------------------------------------------------------- |
528
- | `run` | Async terminal execution API |
529
- | `runSync` | Sync terminal execution API |
530
- | `retry` | Create an execution-scoped retry builder |
531
- | `retryOptions` | Normalize retry policy input |
532
- | `timeout` | Add a total execution timeout |
533
- | `signal` | Add external cancellation to execution or root-level orchestration |
534
- | `wrap` | Add top-level observational middleware |
535
- | `all` | Run a fail-fast parallel named task graph |
536
- | `allSettled` | Run a settled parallel named task graph |
537
- | `flow` | Run an ordered workflow with explicit early exit |
538
- | `gen` | Compose `run(...)` results through generators |
539
- | `dispose` | Create an `AsyncDisposer` helper with `add()`, `use()`, and `cleanup()` |
522
+ | Export | Description |
523
+ | -------------- | ------------------------------------------------------------------------- |
524
+ | `run` | Async terminal execution API |
525
+ | `runSync` | Sync terminal execution API |
526
+ | `retry` | Create an execution-scoped retry builder |
527
+ | `retryOptions` | Normalize retry policy input |
528
+ | `timeout` | Add a total execution timeout |
529
+ | `signal` | Add external cancellation to execution or root-level orchestration |
530
+ | `wrap` | Add top-level observational middleware |
531
+ | `all` | Run a fail-fast parallel named task graph |
532
+ | `allSettled` | Run a settled parallel named task graph |
533
+ | `flow` | Run an ordered workflow with explicit early exit |
534
+ | `gen` | Compose `run(...)` results through generators |
535
+ | `disposer` | Create an `AsyncDisposer` helper with `defer()`, `use()`, and `dispose()` |
540
536
 
541
537
  ### Errors
542
538
 
543
539
  Exports from `tryharder/errors`:
544
540
 
545
- | Export | Description |
546
- | --------------------- | --------------------------------------------------------- |
547
- | `CancellationError` | Returned or thrown when execution is externally cancelled |
548
- | `TimeoutError` | Returned when timed execution expires |
549
- | `RetryExhaustedError` | Returned when retry attempts are exhausted |
550
- | `UnhandledException` | Returned when function-form execution throws |
551
- | `Panic` | Thrown for programmer errors and invalid API usage |
541
+ | Export | Description |
542
+ | --------------------- | --------------------------------------------------------------------------------------------------------- |
543
+ | `CancellationError` | Returned or thrown when execution is externally cancelled |
544
+ | `TimeoutError` | Returned when timed execution expires |
545
+ | `RetryExhaustedError` | Returned when a retry policy gives up and no `catch` is provided; the last attempt's error is the `cause` |
546
+ | `UnhandledException` | Returned when function-form execution throws |
547
+ | `Panic` | Thrown for programmer errors and invalid API usage |
548
+
549
+ Each error class has a matching type guard: `isCancellationError`, `isTimeoutError`, `isRetryExhaustedError`, `isUnhandledException`, and `isPanic`. Prefer the guards over `instanceof` — they also match by `error.name`, so they keep working when two copies of `tryharder` end up in one dependency graph or when errors cross realm boundaries, where `instanceof` silently fails.
550
+
551
+ ```ts
552
+ import { isTimeoutError } from "tryharder/errors"
553
+
554
+ const result = await try$.timeout(1_000).run(fetchUser)
555
+
556
+ if (isTimeoutError(result)) {
557
+ // handle the deadline here
558
+ }
559
+ ```
552
560
 
553
561
  ### Types
554
562
 
@@ -557,7 +565,7 @@ Exports from `tryharder/types`:
557
565
  | Export | Description |
558
566
  | ------------------ | ---------------------------------------------------- |
559
567
  | `AllSettledResult` | Settled result map returned by `allSettled(...)` |
560
- | `AsyncDisposer` | Async cleanup helper returned by `dispose()` |
568
+ | `AsyncDisposer` | Async cleanup helper returned by `disposer()` |
561
569
  | `SettledFulfilled` | Fulfilled branch of a settled task result |
562
570
  | `SettledRejected` | Rejected branch of a settled task result |
563
571
  | `SettledResult` | Union of fulfilled and rejected settled task results |
package/dist/errors.d.ts CHANGED
@@ -23,4 +23,9 @@ declare class Panic extends Error {
23
23
  message?: string;
24
24
  });
25
25
  }
26
- export { UnhandledException, TimeoutError, RetryExhaustedError, PanicCode, Panic, CancellationError };
26
+ declare function isCancellationError(error: unknown): error is CancellationError;
27
+ declare function isTimeoutError(error: unknown): error is TimeoutError;
28
+ declare function isRetryExhaustedError(error: unknown): error is RetryExhaustedError;
29
+ declare function isUnhandledException(error: unknown): error is UnhandledException;
30
+ declare function isPanic(error: unknown): error is Panic;
31
+ export { isUnhandledException, isTimeoutError, isRetryExhaustedError, isPanic, isCancellationError, UnhandledException, TimeoutError, RetryExhaustedError, PanicCode, Panic, CancellationError };
package/dist/errors.js CHANGED
@@ -3,9 +3,19 @@ import {
3
3
  Panic,
4
4
  RetryExhaustedError,
5
5
  TimeoutError,
6
- UnhandledException
7
- } from "./shared/chunk-f024nrjy.js";
6
+ UnhandledException,
7
+ isCancellationError,
8
+ isPanic,
9
+ isRetryExhaustedError,
10
+ isTimeoutError,
11
+ isUnhandledException
12
+ } from "./shared/chunk-nw88v1ee.js";
8
13
  export {
14
+ isUnhandledException,
15
+ isTimeoutError,
16
+ isRetryExhaustedError,
17
+ isPanic,
18
+ isCancellationError,
9
19
  UnhandledException,
10
20
  TimeoutError,
11
21
  RetryExhaustedError,
@@ -13,5 +23,5 @@ export {
13
23
  CancellationError
14
24
  };
15
25
 
16
- //# debugId=7909B944ED0605C464756E2164756E21
17
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICBdLAogICJtYXBwaW5ncyI6ICIiLAogICJkZWJ1Z0lkIjogIjc5MDlCOTQ0RUQwNjA1QzQ2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9
26
+ //# debugId=796F554FE5FDACF264756E2164756E21
27
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICBdLAogICJtYXBwaW5ncyI6ICIiLAogICJkZWJ1Z0lkIjogIjc5NkY1NTRGRTVGREFDRjI2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9
package/dist/index.d.ts CHANGED
@@ -17,11 +17,20 @@ declare class UnhandledException extends Error {
17
17
  constructor(message?: string, options?: ErrorOptions);
18
18
  }
19
19
  interface AsyncDisposer extends AsyncDisposable {
20
- add(fn: () => void | PromiseLike<void>): void;
21
- cleanup(): Promise<void>;
20
+ /**
21
+ * Registers a cleanup callback. Callbacks run in reverse registration order (LIFO) when the
22
+ * disposer is disposed.
23
+ */
22
24
  defer(fn: () => void | PromiseLike<void>): void;
25
+ /**
26
+ * Tracks a disposable resource and returns it.
27
+ */
23
28
  use<T extends AsyncDisposable | Disposable | null | undefined>(value: T): T;
24
- disposeAsync(): Promise<void>;
29
+ /**
30
+ * Runs all registered teardown in reverse registration order. Equivalent to leaving an `await
31
+ * using` scope.
32
+ */
33
+ dispose(): Promise<void>;
25
34
  }
26
35
  type TaskRecord = Record<string, any>;
27
36
  type TaskValidation<T extends TaskRecord> = { [K in keyof T] : T[K] extends (...args: any[]) => any ? T[K] : never };
@@ -131,6 +140,27 @@ type RetryPolicy = LinearBackoffRetryPolicy | ExponentialBackoffRetryPolicy | Co
131
140
  * - `RetryPolicy`: detailed retry settings
132
141
  */
133
142
  type RetryOptions = number | RetryPolicy;
143
+ declare const INVALID_RETRY_LIMIT: unique symbol;
144
+ type InvalidRetryLimit = {
145
+ readonly [INVALID_RETRY_LIMIT]: "retry() requires a positive integer limit";
146
+ };
147
+ /**
148
+ * Compile-time validation for literal retry limits. Rejects `0`, negative, and fractional literals;
149
+ * non-literal `number` values pass and are validated at runtime by {@link retryOptions} instead.
150
+ * (`Infinity`/`NaN` have no literal types, so they are runtime-only.)
151
+ */
152
+ type ValidateRetryLimit<N extends number> = number extends N ? number : `${N}` extends `-${string}` | `${string}.${string}` ? InvalidRetryLimit : N extends 0 ? InvalidRetryLimit : number;
153
+ /**
154
+ * Compile-time validation for full {@link RetryOptions} values, including unions of number and
155
+ * policy forms. Distributes over union members so that mixed inputs like `flag ? 3 : { backoff:
156
+ * "constant", limit: 5 }` are accepted while any member carrying an invalid literal limit still
157
+ * fails to compile.
158
+ */
159
+ type ValidateRetryOptions<P extends RetryOptions> = P extends number ? ValidateRetryLimit<P> : P extends {
160
+ limit: infer N extends number;
161
+ } ? {
162
+ limit: ValidateRetryLimit<N>;
163
+ } : never;
134
164
  declare function retryOptions(policy: RetryOptions): RetryPolicy;
135
165
  declare const FLOW_EXIT_BRAND: unique symbol;
136
166
  type FlowExit<T> = {
@@ -153,6 +183,12 @@ interface RunAsyncOptions<
153
183
  Ctx extends BaseTryCtx = BaseTryCtx
154
184
  > {
155
185
  try: RunTryFn<T, Ctx>;
186
+ /**
187
+ * Maps errors that originated inside `try` — thrown directly, or carried out of the retry loop as
188
+ * the last attempt's error once the retry policy gives up. Policy outcomes (timeout,
189
+ * cancellation) and defects (`Panic`) never pass through `catch`: they are reported directly,
190
+ * even when they fire while the handler is running.
191
+ */
156
192
  catch: RunCatchFn<E>;
157
193
  }
158
194
  type AsyncRunInput<
@@ -171,6 +207,12 @@ interface SyncRunOptions<
171
207
  Ctx extends BaseTryCtx = BaseTryCtx
172
208
  > {
173
209
  try: SyncRunTryFn<T, Ctx>;
210
+ /**
211
+ * Maps errors that originated inside `try` — thrown directly, or carried out of the retry loop as
212
+ * the last attempt's error once the retry policy gives up. Policy outcomes (timeout,
213
+ * cancellation) and defects (`Panic`) never pass through `catch`: they are reported directly,
214
+ * even when they fire while the handler is running.
215
+ */
174
216
  catch: SyncRunCatchFn<E>;
175
217
  }
176
218
  type SyncRunInput<
@@ -204,7 +246,13 @@ interface BuilderConfig {
204
246
  */
205
247
  wraps?: WrapFn[];
206
248
  }
207
- type ConfigRunErrors = RetryExhaustedError | TimeoutError | CancellationError;
249
+ type ConfigRunErrors = TimeoutError | CancellationError;
250
+ /**
251
+ * The failure type for try functions without a catch handler. With retry configured, any give-up
252
+ * (limit exhausted or `shouldRetry` declining) is reported as {@link RetryExhaustedError} carrying
253
+ * the last attempt's error as `cause`; otherwise failures are wrapped in {@link UnhandledException}.
254
+ */
255
+ type UnmappedError<HasRetry extends boolean> = HasRetry extends true ? RetryExhaustedError : UnhandledException;
208
256
  type OrchestrationMethods = "all" | "allSettled" | "flow";
209
257
  type SignalBuilderHiddenMethods<SupportsOrchestration extends boolean> = "runSync" | "wrap" | (SupportsOrchestration extends true ? never : OrchestrationMethods);
210
258
  type ExecutionBuilderSurface<
@@ -229,17 +277,37 @@ declare class RunBuilder<
229
277
  constructor(config?: BuilderConfig);
230
278
  protected buildTimeoutConfig(ms: number): BuilderConfig;
231
279
  protected buildSignalConfig(signal: AbortSignal): BuilderConfig;
232
- retry(policy: number): ExecutionBuilderSurface<E | RetryExhaustedError, true>;
233
- retry(policy: RetryOptions): AsyncExecutionBuilderSurface<E | RetryExhaustedError, true>;
280
+ retry<N extends number>(policy: N & ValidateRetryLimit<N>): ExecutionBuilderSurface<E, true>;
281
+ retry<N extends number>(policy: RetryPolicy & {
282
+ limit: N & ValidateRetryLimit<N>;
283
+ }): AsyncExecutionBuilderSurface<E, true>;
284
+ retry<const P extends RetryOptions>(policy: P & ValidateRetryOptions<P>): AsyncExecutionBuilderSurface<E, true>;
234
285
  timeout(ms: number): AsyncExecutionBuilderSurface<E | TimeoutError, HasRetry>;
235
286
  signal(signal: AbortSignal): SignalBuilderSurface<E | CancellationError, HasRetry, SupportsOrchestration>;
236
287
  wrap(fn: WrapFn): RunBuilder<E, HasRetry, SupportsOrchestration>;
237
- run<T>(tryFn: RunTryFn<T, TryCtxFor<HasRetry>>): Promise<T | UnhandledException | E>;
288
+ /**
289
+ * Executes an async unit of work.
290
+ *
291
+ * `catch` maps errors that originated inside `try` — thrown directly, or carried out of the retry
292
+ * loop as the last attempt's error once the retry policy gives up. Policy outcomes ({@link
293
+ * TimeoutError}, {@link CancellationError}) and defects (`Panic`) never pass through `catch`; they
294
+ * surface typed in the return union (or are thrown, for `Panic`).
295
+ *
296
+ * Without `catch`, unmapped failures are wrapped: {@link RetryExhaustedError} when a retry policy
297
+ * gave up, {@link UnhandledException} otherwise. The original error is available as `cause`.
298
+ */
299
+ run<T>(tryFn: RunTryFn<T, TryCtxFor<HasRetry>>): Promise<T | UnmappedError<HasRetry> | E>;
238
300
  run<
239
301
  T,
240
302
  C
241
303
  >(options: AsyncRunInput<T, C, TryCtxFor<HasRetry>>): Promise<T | C | E>;
242
- runSync<T>(tryFn: SyncRunTryFn<T, TryCtxFor<HasRetry>>): T | UnhandledException | E;
304
+ /**
305
+ * Executes a sync unit of work.
306
+ *
307
+ * Follows the same `catch` contract as {@link RunBuilder.run}: `catch` maps try-originated errors
308
+ * (including retry give-up); policy outcomes and defects never pass through it.
309
+ */
310
+ runSync<T>(tryFn: SyncRunTryFn<T, TryCtxFor<HasRetry>>): T | UnmappedError<HasRetry> | E;
243
311
  runSync<
244
312
  T,
245
313
  C
@@ -251,7 +319,11 @@ declare class RunBuilder<
251
319
  allSettled<T extends TaskRecord>(tasks: T & TaskValidation<NoInfer<T>> & ThisType<InferredTaskContext<T>>): Promise<AllSettledResult<T>>;
252
320
  flow<T extends TaskRecord>(tasks: T & ThisType<InferredFlowTaskContext<T>>): Promise<FlowResult<T>>;
253
321
  }
254
- declare function dispose(): AsyncDisposer;
322
+ /**
323
+ * Creates an {@link AsyncDisposer}: register cleanup with `defer(fn)` or `use(resource)`, then run
324
+ * teardown with `await d.dispose()` or by declaring the disposer with `await using`.
325
+ */
326
+ declare function disposer(): AsyncDisposer;
255
327
  type GenUnwrap<T> = Exclude<Awaited<T>, Error>;
256
328
  type GenUse = <T>(value: T) => Generator<T, GenUnwrap<T>, GenUnwrap<T>>;
257
329
  type GenErrors<TYield> = Extract<Awaited<TYield>, Error>;
@@ -276,4 +348,4 @@ declare const runSync2: RunBuilder["runSync"];
276
348
  declare const signal: RunBuilder["signal"];
277
349
  declare const timeout: RunBuilder["timeout"];
278
350
  declare const wrap: RunBuilder["wrap"];
279
- export { wrap, timeout, signal, runSync2 as runSync, run, retryOptions, retry, driveGen as gen, flow, dispose, allSettled2 as allSettled, all };
351
+ export { wrap, timeout, signal, runSync2 as runSync, run, retryOptions, retry, driveGen as gen, flow, disposer, allSettled2 as allSettled, all };