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 +35 -35
- package/dist/errors.d.ts +6 -2
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +17 -7
- package/dist/index.js +200 -45
- package/dist/shared/{chunk-vqm4xgxv.js → chunk-f024nrjy.js} +6 -3
- package/dist/types.d.ts +8 -1
- package/package.json +9 -10
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
|
-
|
|
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
|
-
|
|
508
|
-
await
|
|
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 `
|
|
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
|
-
##
|
|
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
|
-
|
|
686
|
+
When you can use [`Effect`](https://github.com/Effect-TS/effect) in your codebase.
|
|
683
687
|
|
|
684
|
-
|
|
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
|
-
/**
|
|
3
|
-
|
|
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
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
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:
|
|
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
|
-
*
|
|
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():
|
|
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>;
|