tryharder 0.1.0 → 0.1.2

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
@@ -59,10 +59,9 @@ const result = await try$
59
59
  - [all and allSettled](#all-and-allsettled)
60
60
  - [flow and $exit](#flow-and-exit)
61
61
  - [gen](#gen)
62
- - [dispose](#dispose)
62
+ - [dispose](#dispose)
63
63
  - [API Reference](#api-reference)
64
64
  - [Common Recipes](#common-recipes)
65
- - [Limitations](#limitations)
66
65
  - [When not to use tryharder](#when-not-to-use-tryharder)
67
66
  - [Contributing](#contributing)
68
67
  - [Acknowledgments](#acknowledgments)
@@ -498,37 +497,46 @@ const value = await try$.gen(function* (use) {
498
497
 
499
498
  ### dispose
500
499
 
501
- Use `dispose()` when cleanup should stay colocated with the workflow that allocates the resource, even across async boundaries.
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:
501
+
502
+ - `add(fn)` registers a cleanup callback.
503
+ - `use(resource)` tracks a disposable resource.
504
+ - `cleanup()` runs the registered teardown in reverse order.
502
505
 
503
506
  ```ts
504
507
  await using disposer = try$.dispose()
505
- const connection = await db.connect()
506
508
 
507
- disposer.defer(async () => {
508
- await connection.close()
509
- })
509
+ {
510
+ const connection = await db.connect()
511
+
512
+ disposer.add(async () => {
513
+ await connection.close()
514
+ })
510
515
 
511
- const user = await connection.users.findById("user_123")
516
+ const user = await connection.users.findById("user_123")
517
+ }
512
518
  ```
513
519
 
520
+ `tryharder` handles the cleanup bookkeeping internally, so native `DisposableStack` or `AsyncDisposableStack` globals are not required.
521
+
514
522
  ## API Reference
515
523
 
516
524
  ### Runtime
517
525
 
518
- | Export | Description |
519
- | -------------- | ------------------------------------------------------------------ |
520
- | `run` | Async terminal execution API |
521
- | `runSync` | Sync terminal execution API |
522
- | `retry` | Create an execution-scoped retry builder |
523
- | `retryOptions` | Normalize retry policy input |
524
- | `timeout` | Add a total execution timeout |
525
- | `signal` | Add external cancellation to execution or root-level orchestration |
526
- | `wrap` | Add top-level observational middleware |
527
- | `all` | Run a fail-fast parallel named task graph |
528
- | `allSettled` | Run a settled parallel named task graph |
529
- | `flow` | Run an ordered workflow with explicit early exit |
530
- | `gen` | Compose `run(...)` results through generators |
531
- | `dispose` | Create an `AsyncDisposableStack` helper |
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()` |
532
540
 
533
541
  ### Errors
534
542
 
@@ -549,6 +557,7 @@ Exports from `tryharder/types`:
549
557
  | Export | Description |
550
558
  | ------------------ | ---------------------------------------------------- |
551
559
  | `AllSettledResult` | Settled result map returned by `allSettled(...)` |
560
+ | `AsyncDisposer` | Async cleanup helper returned by `dispose()` |
552
561
  | `SettledFulfilled` | Fulfilled branch of a settled task result |
553
562
  | `SettledRejected` | Rejected branch of a settled task result |
554
563
  | `SettledResult` | Union of fulfilled and rejected settled task results |
@@ -557,7 +566,7 @@ Exports from `tryharder/types`:
557
566
  ```ts
558
567
  import * as try$ from "tryharder"
559
568
  import { Panic, TimeoutError, UnhandledException } from "tryharder/errors"
560
- import type { FlowExit, SettledResult } from "tryharder/types"
569
+ import type { AsyncDisposer, FlowExit, SettledResult } from "tryharder/types"
561
570
  ```
562
571
 
563
572
  ## Common Recipes
@@ -672,20 +681,11 @@ const value = await try$.run({
672
681
  })
673
682
  ```
674
683
 
675
- ## Limitations
676
-
677
- - `tryharder` currently assumes `DisposableStack` and `AsyncDisposableStack` are available at runtime because the executor layer uses them internally, not just when you call `dispose()` yourself.
678
- - This can break consumers on runtimes that do not yet provide those globals, notably Firefox and Safari.
679
- - The tracked fix is [#36 Bundle DisposableStack polyfill for runtimes without native support](https://github.com/adelrodriguez/tryharder/issues/36).
680
- - Until that issue is resolved, use `tryharder` on runtimes with native disposable-stack support or provide a compatible polyfill before loading the package.
684
+ ## When not to use
681
685
 
682
- ## When not to use tryharder
686
+ When you can use [`Effect`](https://github.com/Effect-TS/effect) in your codebase.
683
687
 
684
- - **Small scripts or one-off tasks** - Plain `try/catch` is often simpler when you do not need retries, cancellation, or orchestration.
685
- - **You already use an effect system or result abstraction** - If your codebase already has a consistent execution model, adding `tryharder` may be redundant.
686
- - **Your workflows are mostly straightforward Promise chains** - `all(...)` and `flow(...)` help when coordination matters; otherwise native composition may be clearer.
687
- - **Your team prefers explicit `Result` values everywhere** - `tryharder` centers execution wrappers, not a dedicated result data type.
688
- - **You do not want policy-driven execution behavior** - If retry and timeout semantics are unnecessary overhead, the abstraction may not pay for itself.
688
+ Seriously, Effect is a much more powerful and complete solution.
689
689
 
690
690
  ## Contributing
691
691
 
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  type PanicCode = "ALL_CATCH_HANDLER_REJECT" | "ALL_CATCH_HANDLER_THROW" | "FLOW_NO_EXIT" | "ORCHESTRATION_UNSUPPORTED_POLICY" | "RETRY_INVALID_LIMIT" | "RUN_CATCH_HANDLER_REJECT" | "RUN_CATCH_HANDLER_THROW" | "RUN_SYNC_ASYNC_RETRY_POLICY" | "RUN_SYNC_CATCH_HANDLER_THROW" | "RUN_SYNC_CATCH_PROMISE" | "RUN_SYNC_TRY_PROMISE" | "RUN_SYNC_WRAPPED_RESULT_PROMISE" | "TASK_INVALID_HANDLER" | "TASK_SELF_REFERENCE" | "TIMEOUT_INVALID_MS" | "TASK_UNKNOWN_REFERENCE" | "UNREACHABLE_RETRY_POLICY_BACKOFF";
2
- /** @internal */
3
- declare class ControlError extends Error {}
2
+ /**
3
+ * @internal
4
+ */
5
+ declare class ControlError extends Error {
6
+ constructor(message?: string, options?: ErrorOptions);
7
+ }
4
8
  declare class CancellationError extends ControlError {
5
9
  constructor(message?: string, options?: ErrorOptions);
6
10
  }
package/dist/errors.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  RetryExhaustedError,
5
5
  TimeoutError,
6
6
  UnhandledException
7
- } from "./shared/chunk-vqm4xgxv.js";
7
+ } from "./shared/chunk-f024nrjy.js";
8
8
  export {
9
9
  UnhandledException,
10
10
  TimeoutError,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- /** @internal */
2
- declare class ControlError extends Error {}
1
+ /**
2
+ * @internal
3
+ */
4
+ declare class ControlError extends Error {
5
+ constructor(message?: string, options?: ErrorOptions);
6
+ }
3
7
  declare class CancellationError extends ControlError {
4
8
  constructor(message?: string, options?: ErrorOptions);
5
9
  }
@@ -12,13 +16,20 @@ declare class RetryExhaustedError extends Error {
12
16
  declare class UnhandledException extends Error {
13
17
  constructor(message?: string, options?: ErrorOptions);
14
18
  }
19
+ interface AsyncDisposer extends AsyncDisposable {
20
+ add(fn: () => void | PromiseLike<void>): void;
21
+ cleanup(): Promise<void>;
22
+ defer(fn: () => void | PromiseLike<void>): void;
23
+ use<T extends AsyncDisposable | Disposable | null | undefined>(value: T): T;
24
+ disposeAsync(): Promise<void>;
25
+ }
15
26
  type TaskRecord = Record<string, any>;
16
27
  type TaskValidation<T extends TaskRecord> = { [K in keyof T] : T[K] extends (...args: any[]) => any ? T[K] : never };
17
28
  type TaskResult<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
18
29
  type InferredTaskContext<T extends TaskRecord> = {
19
30
  $result: { readonly [K in keyof T] : ReturnType<T[K]> extends Promise<infer R> ? Promise<R> : Promise<ReturnType<T[K]>> };
20
31
  $signal: AbortSignal;
21
- $disposer: AsyncDisposableStack;
32
+ $disposer: AsyncDisposer;
22
33
  };
23
34
  type AllValue<T extends TaskRecord> = { [K in keyof T] : TaskResult<T[K]> };
24
35
  interface AllCatchContext<T extends TaskRecord> {
@@ -168,8 +179,8 @@ type SyncRunInput<
168
179
  Ctx extends BaseTryCtx = BaseTryCtx
169
180
  > = SyncRunTryFn<T, Ctx> | SyncRunOptions<T, E, Ctx>;
170
181
  /**
171
- * Wraps are observational hooks: they can inspect execution context and
172
- * surround execution, but they must not mutate context or replace it.
182
+ * Wraps are observational hooks: they can inspect execution context and surround execution, but
183
+ * they must not mutate context or replace it.
173
184
  */
174
185
  type WrapCtx = Readonly<Omit<TryCtx, "retry">> & {
175
186
  readonly retry: Readonly<TryCtx["retry"]>;
@@ -216,7 +227,6 @@ declare class RunBuilder<
216
227
  > {
217
228
  protected readonly config: BuilderConfig;
218
229
  constructor(config?: BuilderConfig);
219
- protected buildRetryConfig(policy: RetryOptions): BuilderConfig;
220
230
  protected buildTimeoutConfig(ms: number): BuilderConfig;
221
231
  protected buildSignalConfig(signal: AbortSignal): BuilderConfig;
222
232
  retry(policy: number): ExecutionBuilderSurface<E | RetryExhaustedError, true>;
@@ -241,7 +251,7 @@ declare class RunBuilder<
241
251
  allSettled<T extends TaskRecord>(tasks: T & TaskValidation<NoInfer<T>> & ThisType<InferredTaskContext<T>>): Promise<AllSettledResult<T>>;
242
252
  flow<T extends TaskRecord>(tasks: T & ThisType<InferredFlowTaskContext<T>>): Promise<FlowResult<T>>;
243
253
  }
244
- declare function dispose(): AsyncDisposableStack;
254
+ declare function dispose(): AsyncDisposer;
245
255
  type GenUnwrap<T> = Exclude<Awaited<T>, Error>;
246
256
  type GenUse = <T>(value: T) => Generator<T, GenUnwrap<T>, GenUnwrap<T>>;
247
257
  type GenErrors<TYield> = Extract<Awaited<TYield>, Error>;