clutchit 0.0.7 → 0.0.9
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/CHANGELOG.md +17 -10
- package/README.md +74 -53
- package/dist/circuit/circuit.breaker.js +4 -4
- package/dist/concurrency/bulkhead.js +3 -3
- package/dist/concurrency/rate-limiter.js +2 -2
- package/dist/concurrency/ref.js +2 -2
- package/dist/concurrency/semaphore.js +2 -2
- package/dist/queue/base.queue.js +6 -6
- package/dist/queue/bounded.queue.js +10 -10
- package/dist/retry/retry.js +2 -2
- package/dist/schedule/runner.js +2 -2
- package/dist/timeout/timeout.js +2 -2
- package/dist/unthrow/do.js +11 -7
- package/dist/unthrow/fault.js +15 -6
- package/dist/unthrow/helpers.js +32 -9
- package/dist/unthrow/index.js +9 -39
- package/dist/unthrow/try.async.js +114 -0
- package/dist/unthrow/{result.js → try.js} +46 -1
- package/package.json +19 -2
- package/dist/unthrow/result.async.js +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -11,22 +11,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
#### `clutchit/unthrow`
|
|
13
13
|
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
-
|
|
19
|
-
- `
|
|
20
|
-
- `
|
|
14
|
+
- `Try<T, E>` — synchronous typed error container with `Ok` and `Err` variants
|
|
15
|
+
- `TryAsync<T, E>` — async equivalent backed by `Promise<Try<T, E>>`, implements `PromiseLike`
|
|
16
|
+
- `ok()`, `err()`, `okAsync()`, `errAsync()` — standalone constructors
|
|
17
|
+
- `Do` / `DoAsync` — generator-based Do notation with optional context parameter
|
|
18
|
+
- Instance methods: `map`, `mapErr`, `andThen`, `orElse`, `tap`, `tapErr`, `andThrough`, `orThrough`, `catchFault`, `unwrapOr`, `match`
|
|
19
|
+
- `fromPromise` — lift a `Promise` into a `TryAsync` with typed error mapping
|
|
20
|
+
- `fromSafePromise` — lift a never-rejecting promise into `TryAsync<T, never>`
|
|
21
|
+
- `fromThrowable` — wrap a throwing sync function into a `Try`
|
|
22
|
+
- `fromAsyncThrowable` — wrap an async throwing function into a `TryAsync`
|
|
23
|
+
- `combine` / `combineWithAllErrors` — combine arrays of `Try` or `TryAsync`
|
|
24
|
+
- `unwrap` — assert a value is non-null/undefined, or return the fault
|
|
25
|
+
- `flatten` — flatten nested `Try<Try<T, E2>, E1>` or `TryAsync<Try<T, E2>, E1>`
|
|
26
|
+
- `race` — race multiple `TryAsync` values, like `Promise.race`
|
|
21
27
|
- `Fault.Tagged` — factory for structured, tagged error classes with `code`, `_category`, and `_transient`
|
|
22
|
-
- `Fault.
|
|
28
|
+
- `Fault.is` — duck-type guard for the `Fault` interface (accepts `unknown`)
|
|
29
|
+
- `Fault.isDomain` / `Fault.isInfrastructure` / `Fault.isTransient` — type guard helpers (accept `unknown`)
|
|
23
30
|
|
|
24
31
|
#### `clutchit/schedule`
|
|
25
32
|
|
|
26
33
|
- `Schedule` — composable timing primitive with `.next(input, attempt)` and `.pipe(operator)` API
|
|
27
34
|
- Factories: `spaced`, `exponential`, `linear`, `constant`, `fixed`, `windowed`, `fibonacci`, `cron`, `forever`, `once`
|
|
28
35
|
- Operators: `recurs`, `cappedDelay`, `jittered`, `upTo`, `andThen`, `union`, `intersect`, `whileInput`, `whileOutput`, `map`
|
|
29
|
-
- `Schedule.repeat` — run a `
|
|
36
|
+
- `Schedule.repeat` — run a `TryAsync`-returning function repeatedly on a schedule with abort signal support
|
|
30
37
|
- `ScheduleInterruptedFault` — returned when `repeat` is cancelled via `AbortSignal`
|
|
31
38
|
|
|
32
39
|
#### `clutchit/retry`
|
|
@@ -36,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
36
43
|
|
|
37
44
|
#### `clutchit/timeout`
|
|
38
45
|
|
|
39
|
-
- `withTimeout(fn, duration)` — wrap a `
|
|
46
|
+
- `withTimeout(fn, duration)` — wrap a `TryAsync`-returning function with a deadline
|
|
40
47
|
- `createTimeout(duration)` — create a reusable timeout wrapper
|
|
41
48
|
- `TimeoutFault` — transient infrastructure fault emitted on deadline exceeded; aborts the underlying operation
|
|
42
49
|
|
package/README.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# clutchit
|
|
2
2
|
|
|
3
|
-
Resilience primitives for TypeScript
|
|
3
|
+
Resilience primitives for TypeScript.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/clutchit)
|
|
6
|
-
[](
|
|
6
|
+
[](./LICENSE)
|
|
7
7
|
[](https://clutchit.existin.space)
|
|
8
|
-
[](https://bundlephobia.com/package/clutchit)
|
|
9
8
|
[](https://www.npmjs.com/package/clutchit)
|
|
10
9
|
|
|
11
10
|
**[Documentation](https://clutchit.existin.space)** · [npm](https://www.npmjs.com/package/clutchit) · [Changelog](./CHANGELOG.md)
|
|
@@ -14,15 +13,13 @@ Resilience primitives for TypeScript — typed errors, retry, timeout, circuit b
|
|
|
14
13
|
|
|
15
14
|
```bash
|
|
16
15
|
npm install clutchit
|
|
17
|
-
# pnpm add clutchit
|
|
18
|
-
# bun add clutchit
|
|
19
16
|
```
|
|
20
17
|
|
|
21
18
|
## Modules
|
|
22
19
|
|
|
23
20
|
| Import | Description |
|
|
24
21
|
|---|---|
|
|
25
|
-
| `clutchit/unthrow` | `
|
|
22
|
+
| `clutchit/unthrow` | `Try<T,E>`, `TryAsync<T,E>`, `Fault`, Do notation |
|
|
26
23
|
| `clutchit/schedule` | Composable timing schedules — exponential, linear, fibonacci, cron, … |
|
|
27
24
|
| `clutchit/retry` | Retry policy with backoff schedules and abort signal support |
|
|
28
25
|
| `clutchit/timeout` | Timeout wrapper with `AbortSignal` propagation |
|
|
@@ -35,10 +32,10 @@ npm install clutchit
|
|
|
35
32
|
## Quick start
|
|
36
33
|
|
|
37
34
|
```ts
|
|
38
|
-
import {
|
|
39
|
-
import { RetryPolicy }
|
|
40
|
-
import { withTimeout }
|
|
41
|
-
import { Schedule }
|
|
35
|
+
import { ok, err, okAsync, errAsync, fromPromise, Fault } from 'clutchit/unthrow';
|
|
36
|
+
import { RetryPolicy } from 'clutchit/retry';
|
|
37
|
+
import { withTimeout } from 'clutchit/timeout';
|
|
38
|
+
import { Schedule } from 'clutchit/schedule';
|
|
42
39
|
|
|
43
40
|
class ApiFault extends Fault.Tagged('API_ERROR', 'infrastructure', true)<{
|
|
44
41
|
status: number;
|
|
@@ -46,8 +43,8 @@ class ApiFault extends Fault.Tagged('API_ERROR', 'infrastructure', true)<{
|
|
|
46
43
|
readonly message = `API returned ${this.status}`;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
function fetchUser(id: string, signal: AbortSignal):
|
|
50
|
-
return
|
|
46
|
+
function fetchUser(id: string, signal: AbortSignal): TryAsync<User, ApiFault> {
|
|
47
|
+
return fromPromise(
|
|
51
48
|
fetch(`/api/users/${id}`, { signal }).then((r) => r.json()),
|
|
52
49
|
() => new ApiFault({ status: 500 }),
|
|
53
50
|
);
|
|
@@ -74,38 +71,38 @@ await result.match({
|
|
|
74
71
|
|
|
75
72
|
Typed error handling without exceptions.
|
|
76
73
|
|
|
77
|
-
###
|
|
74
|
+
### Try
|
|
78
75
|
|
|
79
76
|
```ts
|
|
80
|
-
import {
|
|
77
|
+
import { ok, err } from 'clutchit/unthrow';
|
|
81
78
|
|
|
82
|
-
const
|
|
83
|
-
const
|
|
79
|
+
const success = ok(42);
|
|
80
|
+
const failure = err('oops');
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
success.map((n) => n * 2); // Ok(84)
|
|
83
|
+
success.andThen((n) => ok(n)); // Ok(42)
|
|
84
|
+
success.unwrapOr(0); // 42
|
|
85
|
+
failure.unwrapOr(0); // 0
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
success.match({
|
|
91
88
|
ok: (value) => `got ${value}`,
|
|
92
89
|
err: (error) => `failed: ${error}`,
|
|
93
90
|
});
|
|
94
91
|
|
|
95
|
-
if (
|
|
96
|
-
if (
|
|
92
|
+
if (success.isOk()) success.value; // narrowed
|
|
93
|
+
if (failure.isErr()) failure.error; // narrowed
|
|
97
94
|
```
|
|
98
95
|
|
|
99
|
-
Methods: `isOk`, `isErr`, `map`, `mapErr`, `andThen`, `orElse`, `unwrapOr`, `match`
|
|
96
|
+
Methods: `isOk`, `isErr`, `map`, `mapErr`, `andThen`, `orElse`, `tap`, `tapErr`, `andThrough`, `orThrough`, `catchFault`, `unwrapOr`, `match`
|
|
100
97
|
|
|
101
|
-
###
|
|
98
|
+
### TryAsync
|
|
102
99
|
|
|
103
|
-
Wraps `Promise<
|
|
100
|
+
Wraps `Promise<Try<T, E>>`. Awaitable via `PromiseLike`, same chainable API as `Try`.
|
|
104
101
|
|
|
105
102
|
```ts
|
|
106
|
-
import {
|
|
103
|
+
import { fromPromise } from 'clutchit/unthrow';
|
|
107
104
|
|
|
108
|
-
const result =
|
|
105
|
+
const result = fromPromise(
|
|
109
106
|
fetch('/api/data').then((r) => r.json()),
|
|
110
107
|
(err) => new NetworkFault({ cause: String(err) }),
|
|
111
108
|
);
|
|
@@ -115,26 +112,43 @@ const final = await result
|
|
|
115
112
|
.andThen((items) => validate(items));
|
|
116
113
|
```
|
|
117
114
|
|
|
118
|
-
Methods: `map`, `mapErr`, `andThen`, `orElse`, `unwrapOr`, `match` (all return `
|
|
115
|
+
Methods: `map`, `mapErr`, `andThen`, `orElse`, `tap`, `tapErr`, `andThrough`, `orThrough`, `catchFault`, `unwrapOr`, `match` (all return `TryAsync` or `Promise`)
|
|
119
116
|
|
|
120
117
|
### Helpers
|
|
121
118
|
|
|
122
119
|
```ts
|
|
123
|
-
import {
|
|
120
|
+
import { ok, err, okAsync, fromPromise, fromThrowable, fromSafePromise, fromAsyncThrowable, combine, combineWithAllErrors, unwrap, flatten, race } from 'clutchit/unthrow';
|
|
124
121
|
|
|
125
122
|
// Wrap a throwable sync function
|
|
126
|
-
|
|
123
|
+
fromThrowable(
|
|
127
124
|
() => JSON.parse(raw),
|
|
128
125
|
(err) => new ParseFault({ message: String(err) }),
|
|
129
126
|
);
|
|
130
127
|
|
|
128
|
+
// Wrap an async throwable function
|
|
129
|
+
fromAsyncThrowable(
|
|
130
|
+
() => fetchData(),
|
|
131
|
+
(err) => new NetworkFault({ cause: String(err) }),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Lift a safe (never-rejecting) promise
|
|
135
|
+
fromSafePromise(Promise.resolve(42));
|
|
136
|
+
|
|
137
|
+
// Assert non-null values
|
|
138
|
+
unwrap(ok<string | null, E>(value), new NullFault());
|
|
139
|
+
|
|
140
|
+
// Flatten nested results
|
|
141
|
+
flatten(ok(ok(42))); // Ok(42)
|
|
142
|
+
|
|
131
143
|
// Combine — short-circuit on first error
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
combine([ok(1), ok(2)]); // Ok([1, 2])
|
|
145
|
+
combine([okAsync(1), okAsync(2)]); // TryAsync<[1, 2], E>
|
|
134
146
|
|
|
135
147
|
// Combine — collect all errors
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
combineWithAllErrors([ok(1), err('a'), err('b')]); // Err(['a', 'b'])
|
|
149
|
+
|
|
150
|
+
// Race — first to resolve wins
|
|
151
|
+
race([okAsync(1), okAsync(2)]);
|
|
138
152
|
```
|
|
139
153
|
|
|
140
154
|
### Do notation
|
|
@@ -142,22 +156,28 @@ ResultAsync.combineWithAllErrors([a, b, c]); // ResultAsync<[A, B, C], E[]>
|
|
|
142
156
|
Generator-based sequential chaining. `yield*` unwraps `Ok`; any `Err` short-circuits the entire block.
|
|
143
157
|
|
|
144
158
|
```ts
|
|
145
|
-
import {
|
|
159
|
+
import { Do, DoAsync } from 'clutchit/unthrow';
|
|
146
160
|
|
|
147
161
|
// Sync
|
|
148
|
-
const result =
|
|
149
|
-
const user = yield* findUser(id); //
|
|
150
|
-
const perms = yield* getPermissions(user); //
|
|
162
|
+
const result = Do(function* () {
|
|
163
|
+
const user = yield* findUser(id); // Try<User, NotFoundFault>
|
|
164
|
+
const perms = yield* getPermissions(user); // Try<Perms, PermFault>
|
|
151
165
|
return { user, perms };
|
|
152
|
-
//
|
|
166
|
+
// Try<{ user, perms }, NotFoundFault | PermFault>
|
|
153
167
|
});
|
|
154
168
|
|
|
155
169
|
// Async
|
|
156
|
-
const result =
|
|
157
|
-
const user = yield* fetchUser(id); //
|
|
158
|
-
const order = yield* createOrder(user); //
|
|
170
|
+
const result = DoAsync(async function* () {
|
|
171
|
+
const user = yield* fetchUser(id); // TryAsync<User, NetworkFault>
|
|
172
|
+
const order = yield* createOrder(user); // TryAsync<Order, OrderFault>
|
|
159
173
|
return order;
|
|
160
|
-
//
|
|
174
|
+
// TryAsync<Order, NetworkFault | OrderFault>
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// With context
|
|
178
|
+
const result = Do({ db }, function* (ctx) {
|
|
179
|
+
const user = yield* ctx.db.findUser(id);
|
|
180
|
+
return user;
|
|
161
181
|
});
|
|
162
182
|
```
|
|
163
183
|
|
|
@@ -224,7 +244,7 @@ const result = Schedule.repeat(
|
|
|
224
244
|
onExecution: (attempt, delay) => console.log(`#${attempt}, next in ${delay}ms`),
|
|
225
245
|
},
|
|
226
246
|
);
|
|
227
|
-
//
|
|
247
|
+
// TryAsync<number, SyncError | ScheduleInterruptedFault>
|
|
228
248
|
```
|
|
229
249
|
|
|
230
250
|
---
|
|
@@ -243,7 +263,7 @@ const retry = new RetryPolicy({
|
|
|
243
263
|
});
|
|
244
264
|
|
|
245
265
|
const result = retry.execute((signal) => fetchData(signal));
|
|
246
|
-
//
|
|
266
|
+
// TryAsync<Data, FetchFault>
|
|
247
267
|
```
|
|
248
268
|
|
|
249
269
|
Options can be set on the constructor (defaults for all calls) or overridden per `execute` call.
|
|
@@ -267,7 +287,7 @@ const result = withTimeout(
|
|
|
267
287
|
(signal) => fetchData(signal),
|
|
268
288
|
5_000,
|
|
269
289
|
);
|
|
270
|
-
//
|
|
290
|
+
// TryAsync<Data, FetchFault | TimeoutFault>
|
|
271
291
|
|
|
272
292
|
// Reusable wrapper
|
|
273
293
|
const withFiveSeconds = createTimeout(5_000);
|
|
@@ -324,9 +344,9 @@ Atomic mutable reference with internal mutex:
|
|
|
324
344
|
```ts
|
|
325
345
|
const counter = new Concurrency.Ref(0);
|
|
326
346
|
|
|
327
|
-
await counter.update((n) => n + 1); //
|
|
328
|
-
await counter.set(42); //
|
|
329
|
-
const next = await counter.modify( //
|
|
347
|
+
await counter.update((n) => n + 1); // TryAsync<void, never>
|
|
348
|
+
await counter.set(42); // TryAsync<void, never>
|
|
349
|
+
const next = await counter.modify( // TryAsync<number, never>
|
|
330
350
|
(n) => [n + 1, n + 1],
|
|
331
351
|
);
|
|
332
352
|
counter.get(); // current value (sync)
|
|
@@ -349,7 +369,7 @@ const breaker = new Circuit.Breaker({
|
|
|
349
369
|
});
|
|
350
370
|
|
|
351
371
|
const result = breaker.protect(() => callService());
|
|
352
|
-
//
|
|
372
|
+
// TryAsync<T, ServiceFault | CircuitOpenFault>
|
|
353
373
|
|
|
354
374
|
breaker.state; // 'closed' | 'open' | 'half-open'
|
|
355
375
|
breaker.failureCount; // consecutive failures
|
|
@@ -369,13 +389,13 @@ import { Queue, QueueFullFault, QueueEmptyFault } from 'clutchit/queue';
|
|
|
369
389
|
|
|
370
390
|
const q = new Queue.Bounded<Job>({ capacity: 100 });
|
|
371
391
|
|
|
372
|
-
q.offer(job); //
|
|
392
|
+
q.offer(job); // Try<void, QueueFullFault> — non-blocking
|
|
373
393
|
await q.put(job); // blocks until space is available
|
|
374
394
|
await q.put(job, signal); // cancellable
|
|
375
395
|
|
|
376
396
|
const item = await q.take(); // blocks until item available
|
|
377
397
|
const batch = await q.takeBatch(10); // at least 1, up to 10
|
|
378
|
-
const maybe = q.poll(); //
|
|
398
|
+
const maybe = q.poll(); // Try<Job, QueueEmptyFault>
|
|
379
399
|
```
|
|
380
400
|
|
|
381
401
|
### DroppingQueue — drops newest when full
|
|
@@ -424,6 +444,7 @@ class UnauthorizedFault extends Fault.Tagged('UNAUTHORIZED', 'domain')() {
|
|
|
424
444
|
### Type guards
|
|
425
445
|
|
|
426
446
|
```ts
|
|
447
|
+
Fault.is(value); // duck-type check for Fault shape
|
|
427
448
|
Fault.isDomain(fault); // _category === 'domain'
|
|
428
449
|
Fault.isInfrastructure(fault); // _category === 'infrastructure'
|
|
429
450
|
Fault.isTransient(fault); // infrastructure + _transient
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fault,
|
|
1
|
+
import { Fault, errAsync, fromPromise, } from '../unthrow/index.js';
|
|
2
2
|
import { Schedule } from '../schedule/index.js';
|
|
3
3
|
export class CircuitOpenFault extends Fault.Tagged('CIRCUIT_OPEN', 'infrastructure')() {
|
|
4
4
|
message = `Circuit breaker is open. Retry after ${this.remainingTimeout}ms`;
|
|
@@ -31,16 +31,16 @@ export class CircuitBreaker {
|
|
|
31
31
|
const remainingTimeout = this._nextResetTime
|
|
32
32
|
? Math.max(0, this._nextResetTime - Date.now())
|
|
33
33
|
: 0;
|
|
34
|
-
return
|
|
34
|
+
return errAsync(new CircuitOpenFault({ remainingTimeout }));
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
if (this._state === 'half-open') {
|
|
38
38
|
if (this._halfOpenTrials >= this._maxHalfOpenAttempts) {
|
|
39
|
-
return
|
|
39
|
+
return errAsync(new CircuitOpenFault({ remainingTimeout: 0 }));
|
|
40
40
|
}
|
|
41
41
|
this._halfOpenTrials++;
|
|
42
42
|
}
|
|
43
|
-
return
|
|
43
|
+
return fromPromise((async () => {
|
|
44
44
|
const result = await fn();
|
|
45
45
|
if (result.isOk()) {
|
|
46
46
|
this.onSuccess();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fault,
|
|
1
|
+
import { Fault, errAsync, fromPromise, } from '../unthrow/index.js';
|
|
2
2
|
export class BulkheadRejectedFault extends Fault.Tagged('BULKHEAD_REJECTED', 'infrastructure')() {
|
|
3
3
|
message = 'Bulkhead capacity exceeded';
|
|
4
4
|
}
|
|
@@ -14,9 +14,9 @@ export class Bulkhead {
|
|
|
14
14
|
execute(fn) {
|
|
15
15
|
if (this._active >= this._maxConcurrency &&
|
|
16
16
|
this._queue.length >= this._maxQueue) {
|
|
17
|
-
return
|
|
17
|
+
return errAsync(new BulkheadRejectedFault());
|
|
18
18
|
}
|
|
19
|
-
return
|
|
19
|
+
return fromPromise((async () => {
|
|
20
20
|
await this.acquire();
|
|
21
21
|
try {
|
|
22
22
|
const result = await fn();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fault,
|
|
1
|
+
import { Fault, errAsync } from '../unthrow/index.js';
|
|
2
2
|
export class RateLimitFault extends Fault.Tagged('RATE_LIMITED', 'infrastructure', true)() {
|
|
3
3
|
message = `Rate limit exceeded. Retry after ${this.retryAfter}ms`;
|
|
4
4
|
}
|
|
@@ -15,7 +15,7 @@ export class RateLimiter {
|
|
|
15
15
|
if (this._timestamps.length >= this._limit) {
|
|
16
16
|
const oldest = this._timestamps[0];
|
|
17
17
|
const retryAfter = Math.max(0, oldest + this._window - Date.now());
|
|
18
|
-
return
|
|
18
|
+
return errAsync(new RateLimitFault({ retryAfter }));
|
|
19
19
|
}
|
|
20
20
|
this._timestamps.push(Date.now());
|
|
21
21
|
return fn();
|
package/dist/concurrency/ref.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { okAsync } from '../unthrow/index.js';
|
|
2
2
|
import { Semaphore } from './semaphore.js';
|
|
3
3
|
export class Ref {
|
|
4
4
|
_value;
|
|
@@ -13,7 +13,7 @@ export class Ref {
|
|
|
13
13
|
return this._mutex.execute(() => {
|
|
14
14
|
const [next, result] = fn(this._value);
|
|
15
15
|
this._value = next;
|
|
16
|
-
return
|
|
16
|
+
return okAsync(result);
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
update(fn) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fromPromise } from '../unthrow/index.js';
|
|
2
2
|
export class Semaphore {
|
|
3
3
|
_available;
|
|
4
4
|
_queue = [];
|
|
@@ -6,7 +6,7 @@ export class Semaphore {
|
|
|
6
6
|
this._available = options.permits;
|
|
7
7
|
}
|
|
8
8
|
execute(fn) {
|
|
9
|
-
return
|
|
9
|
+
return fromPromise((async () => {
|
|
10
10
|
await this.acquire();
|
|
11
11
|
try {
|
|
12
12
|
const result = await fn();
|
package/dist/queue/base.queue.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ok, err, okAsync, fromPromise, } from '../unthrow/index.js';
|
|
2
2
|
import { QueueEmptyFault } from './faults.js';
|
|
3
3
|
export class BaseQueue {
|
|
4
4
|
_buffer = [];
|
|
@@ -9,15 +9,15 @@ export class BaseQueue {
|
|
|
9
9
|
}
|
|
10
10
|
_tryTake() {
|
|
11
11
|
if (this._buffer.length > 0) {
|
|
12
|
-
return
|
|
12
|
+
return ok(this._buffer.shift());
|
|
13
13
|
}
|
|
14
|
-
return
|
|
14
|
+
return err(new QueueEmptyFault());
|
|
15
15
|
}
|
|
16
16
|
take(signal) {
|
|
17
17
|
const taken = this._tryTake();
|
|
18
18
|
if (taken.isOk())
|
|
19
|
-
return
|
|
20
|
-
return
|
|
19
|
+
return okAsync(taken.value);
|
|
20
|
+
return fromPromise(new Promise((resolve) => {
|
|
21
21
|
this._takeWaiters.push(resolve);
|
|
22
22
|
signal?.addEventListener('abort', () => {
|
|
23
23
|
const idx = this._takeWaiters.indexOf(resolve);
|
|
@@ -27,7 +27,7 @@ export class BaseQueue {
|
|
|
27
27
|
}), () => undefined);
|
|
28
28
|
}
|
|
29
29
|
takeBatch(n, signal) {
|
|
30
|
-
return
|
|
30
|
+
return fromPromise((async () => {
|
|
31
31
|
const taken = this._tryTake();
|
|
32
32
|
let first;
|
|
33
33
|
if (taken.isOk()) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ok, err, okAsync, fromPromise, } from '../unthrow/index.js';
|
|
2
2
|
import { QueueEmptyFault, QueueFullFault } from './faults.js';
|
|
3
3
|
import { BaseQueue } from './base.queue.js';
|
|
4
4
|
export class BoundedQueue extends BaseQueue {
|
|
@@ -15,42 +15,42 @@ export class BoundedQueue extends BaseQueue {
|
|
|
15
15
|
this._buffer.push(pw.item);
|
|
16
16
|
pw.resolve();
|
|
17
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return ok(item);
|
|
19
19
|
}
|
|
20
20
|
// Buffer empty — try put waiters directly
|
|
21
21
|
const pw = this._putWaiters.shift();
|
|
22
22
|
if (pw) {
|
|
23
23
|
pw.resolve();
|
|
24
|
-
return
|
|
24
|
+
return ok(pw.item);
|
|
25
25
|
}
|
|
26
|
-
return
|
|
26
|
+
return err(new QueueEmptyFault());
|
|
27
27
|
}
|
|
28
28
|
offer(item) {
|
|
29
29
|
// Give directly to a waiting consumer
|
|
30
30
|
const waiter = this._takeWaiters.shift();
|
|
31
31
|
if (waiter) {
|
|
32
32
|
waiter(item);
|
|
33
|
-
return
|
|
33
|
+
return ok(undefined);
|
|
34
34
|
}
|
|
35
35
|
if (this._buffer.length >= this._capacity) {
|
|
36
|
-
return
|
|
36
|
+
return err(new QueueFullFault({ capacity: this._capacity }));
|
|
37
37
|
}
|
|
38
38
|
this._buffer.push(item);
|
|
39
|
-
return
|
|
39
|
+
return ok(undefined);
|
|
40
40
|
}
|
|
41
41
|
put(item, signal) {
|
|
42
42
|
// Give directly to a waiting consumer
|
|
43
43
|
const waiter = this._takeWaiters.shift();
|
|
44
44
|
if (waiter) {
|
|
45
45
|
waiter(item);
|
|
46
|
-
return
|
|
46
|
+
return okAsync(undefined);
|
|
47
47
|
}
|
|
48
48
|
if (this._buffer.length < this._capacity) {
|
|
49
49
|
this._buffer.push(item);
|
|
50
|
-
return
|
|
50
|
+
return okAsync(undefined);
|
|
51
51
|
}
|
|
52
52
|
// Wait for space
|
|
53
|
-
return
|
|
53
|
+
return fromPromise(new Promise((resolve) => {
|
|
54
54
|
const entry = { item, resolve };
|
|
55
55
|
this._putWaiters.push(entry);
|
|
56
56
|
signal?.addEventListener('abort', () => {
|
package/dist/retry/retry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fromPromise } from '../unthrow/index.js';
|
|
2
2
|
import { Schedule } from '../schedule/index.js';
|
|
3
3
|
const NEVER_ABORT = new AbortController().signal;
|
|
4
4
|
const DEFAULT_SCHEDULE = Schedule.exponential(200).pipe(Schedule.recurs(3));
|
|
@@ -32,7 +32,7 @@ export class RetryPolicy {
|
|
|
32
32
|
const onRetry = options?.onRetry ??
|
|
33
33
|
this.defaults?.onRetry;
|
|
34
34
|
const signal = options?.signal ?? this.defaults?.signal;
|
|
35
|
-
return
|
|
35
|
+
return fromPromise((async () => {
|
|
36
36
|
let lastError;
|
|
37
37
|
for (let attempt = 0;; attempt++) {
|
|
38
38
|
const result = await fn(signal ?? NEVER_ABORT);
|
package/dist/schedule/runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fault,
|
|
1
|
+
import { Fault, fromPromise } from '../unthrow/index.js';
|
|
2
2
|
export class ScheduleInterruptedFault extends Fault.Tagged('SCHEDULE_INTERRUPTED', 'infrastructure')() {
|
|
3
3
|
message = 'Scheduled execution was interrupted';
|
|
4
4
|
}
|
|
@@ -30,7 +30,7 @@ function sleep(ms, signal) {
|
|
|
30
30
|
export function repeat(schedule, fn, options) {
|
|
31
31
|
const signal = options?.signal;
|
|
32
32
|
const onExecution = options?.onExecution;
|
|
33
|
-
return
|
|
33
|
+
return fromPromise((async () => {
|
|
34
34
|
for (let attempt = 0;; attempt++) {
|
|
35
35
|
// eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
|
|
36
36
|
if (signal?.aborted)
|
package/dist/timeout/timeout.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Fault,
|
|
1
|
+
import { Fault, fromPromise } from '../unthrow/index.js';
|
|
2
2
|
export class TimeoutFault extends Fault.Tagged('TIMEOUT', 'infrastructure', true)() {
|
|
3
3
|
message = `Operation timed out after ${this.duration}ms`;
|
|
4
4
|
}
|
|
5
5
|
export function withTimeout(fn, duration) {
|
|
6
6
|
const controller = new AbortController();
|
|
7
|
-
return
|
|
7
|
+
return fromPromise(
|
|
8
8
|
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors -- intentional: rejections are typed E | TimeoutFault, caught by fromPromise */
|
|
9
9
|
new Promise((resolve, reject) => {
|
|
10
10
|
const timer = setTimeout(() => {
|
package/dist/unthrow/do.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ok } from './
|
|
2
|
-
import {
|
|
3
|
-
export function Do(generator) {
|
|
4
|
-
const gen =
|
|
1
|
+
import { ok } from './try.js';
|
|
2
|
+
import { TryAsync } from './try.async.js';
|
|
3
|
+
export function Do(ctxOrGenerator, generator) {
|
|
4
|
+
const gen = typeof ctxOrGenerator === 'function'
|
|
5
|
+
? ctxOrGenerator()
|
|
6
|
+
: generator(ctxOrGenerator);
|
|
5
7
|
const next = gen.next();
|
|
6
8
|
while (!next.done) {
|
|
7
9
|
// Each yielded value is an Err — short-circuit
|
|
@@ -9,9 +11,11 @@ export function Do(generator) {
|
|
|
9
11
|
}
|
|
10
12
|
return ok(next.value);
|
|
11
13
|
}
|
|
12
|
-
export function DoAsync(generator) {
|
|
13
|
-
return new
|
|
14
|
-
const gen =
|
|
14
|
+
export function DoAsync(ctxOrGenerator, generator) {
|
|
15
|
+
return new TryAsync((async () => {
|
|
16
|
+
const gen = typeof ctxOrGenerator === 'function'
|
|
17
|
+
? ctxOrGenerator()
|
|
18
|
+
: generator(ctxOrGenerator);
|
|
15
19
|
const next = await gen.next();
|
|
16
20
|
while (!next.done) {
|
|
17
21
|
// Each yielded value is an Err — short-circuit
|
package/dist/unthrow/fault.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
export function
|
|
2
|
-
return
|
|
1
|
+
export function isFault(value) {
|
|
2
|
+
return (typeof value === 'object' &&
|
|
3
|
+
value !== null &&
|
|
4
|
+
typeof value.code === 'string' &&
|
|
5
|
+
typeof value.message === 'string' &&
|
|
6
|
+
(value._category === 'domain' ||
|
|
7
|
+
value._category === 'infrastructure') &&
|
|
8
|
+
typeof value._transient === 'boolean');
|
|
3
9
|
}
|
|
4
|
-
export function
|
|
5
|
-
return
|
|
10
|
+
export function isDomainFault(value) {
|
|
11
|
+
return isFault(value) && value._category === 'domain';
|
|
6
12
|
}
|
|
7
|
-
export function
|
|
8
|
-
return
|
|
13
|
+
export function isInfrastructureFault(value) {
|
|
14
|
+
return isFault(value) && value._category === 'infrastructure';
|
|
15
|
+
}
|
|
16
|
+
export function isTransientFault(value) {
|
|
17
|
+
return (isFault(value) && value._transient && value._category === 'infrastructure');
|
|
9
18
|
}
|
|
10
19
|
export function Fault(code, category, transient) {
|
|
11
20
|
const _transient = (transient ?? false);
|
package/dist/unthrow/helpers.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ok, err } from './
|
|
2
|
-
import {
|
|
1
|
+
import { ok, err } from './try.js';
|
|
2
|
+
import { TryAsync } from './try.async.js';
|
|
3
3
|
export function combine(results) {
|
|
4
4
|
if (results.length === 0) {
|
|
5
5
|
return ok([]);
|
|
6
6
|
}
|
|
7
|
-
if (results[0] instanceof
|
|
8
|
-
const
|
|
9
|
-
return new
|
|
7
|
+
if (results[0] instanceof TryAsync) {
|
|
8
|
+
const asyncTrys = results;
|
|
9
|
+
return new TryAsync(Promise.all(asyncTrys.map((r) => r.then((v) => v))).then((settled) => combineSync(settled)));
|
|
10
10
|
}
|
|
11
11
|
return combineSync(results);
|
|
12
12
|
}
|
|
@@ -23,9 +23,9 @@ export function combineWithAllErrors(results) {
|
|
|
23
23
|
if (results.length === 0) {
|
|
24
24
|
return ok([]);
|
|
25
25
|
}
|
|
26
|
-
if (results[0] instanceof
|
|
27
|
-
const
|
|
28
|
-
return new
|
|
26
|
+
if (results[0] instanceof TryAsync) {
|
|
27
|
+
const asyncTrys = results;
|
|
28
|
+
return new TryAsync(Promise.all(asyncTrys.map((r) => r.then((v) => v))).then((settled) => combineWithAllErrorsSync(settled)));
|
|
29
29
|
}
|
|
30
30
|
return combineWithAllErrorsSync(results);
|
|
31
31
|
}
|
|
@@ -43,7 +43,10 @@ function combineWithAllErrorsSync(results) {
|
|
|
43
43
|
return errors.length > 0 ? err(errors) : ok(values);
|
|
44
44
|
}
|
|
45
45
|
export function fromPromise(promise, errorMapper) {
|
|
46
|
-
return new
|
|
46
|
+
return new TryAsync(promise.then((value) => ok(value), (error) => err(errorMapper(error))));
|
|
47
|
+
}
|
|
48
|
+
export function fromSafePromise(promise) {
|
|
49
|
+
return new TryAsync(promise.then((value) => ok(value)));
|
|
47
50
|
}
|
|
48
51
|
export function fromThrowable(fn, errorMapper) {
|
|
49
52
|
try {
|
|
@@ -53,4 +56,24 @@ export function fromThrowable(fn, errorMapper) {
|
|
|
53
56
|
return err(errorMapper(error));
|
|
54
57
|
}
|
|
55
58
|
}
|
|
59
|
+
export function fromAsyncThrowable(fn, errorMapper) {
|
|
60
|
+
try {
|
|
61
|
+
return fromPromise(fn(), errorMapper);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return new TryAsync(Promise.resolve(err(errorMapper(error))));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function unwrap(result, fault) {
|
|
68
|
+
return result.andThen((value) => (value != null ? ok(value) : err(fault)));
|
|
69
|
+
}
|
|
70
|
+
export function flatten(result) {
|
|
71
|
+
return result.andThen((inner) => inner);
|
|
72
|
+
}
|
|
73
|
+
export function race(results) {
|
|
74
|
+
if (results.length === 0) {
|
|
75
|
+
return new TryAsync(Promise.reject(new Error('race requires at least one TryAsync')));
|
|
76
|
+
}
|
|
77
|
+
return new TryAsync(Promise.race(results.map((r) => r.then((v) => v))));
|
|
78
|
+
}
|
|
56
79
|
//# sourceMappingURL=helpers.js.map
|
package/dist/unthrow/index.js
CHANGED
|
@@ -1,48 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import { fromPromise as _fromPromise, fromThrowable as _fromThrowable, combine as _combine, combineWithAllErrors as _combineWithAllErrors, } from './helpers.js';
|
|
4
|
-
import { Do as _Do, DoAsync } from './do.js';
|
|
5
|
-
import { Fault as _Fault, isDomainFault as _isDomainFault, isInfrastructureFault as _isInfrastructureFault, isTransientFault as _isTransientFault, } from './fault.js';
|
|
6
|
-
// Re-export instance types (needed in user signatures)
|
|
7
|
-
export { Ok, Err } from './result.js';
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
|
-
export var Result;
|
|
10
|
-
(function (Result) {
|
|
11
|
-
Result.ok = _ok;
|
|
12
|
-
Result.err = _err;
|
|
13
|
-
Result.fromThrowable = _fromThrowable;
|
|
14
|
-
function combine(results) {
|
|
15
|
-
return _combine(results);
|
|
16
|
-
}
|
|
17
|
-
Result.combine = combine;
|
|
18
|
-
function combineWithAllErrors(results) {
|
|
19
|
-
return _combineWithAllErrors(results);
|
|
20
|
-
}
|
|
21
|
-
Result.combineWithAllErrors = combineWithAllErrors;
|
|
22
|
-
Result.Do = _Do;
|
|
23
|
-
})(Result || (Result = {}));
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
25
|
-
export var ResultAsync;
|
|
26
|
-
(function (ResultAsync) {
|
|
27
|
-
ResultAsync.ok = okAsync;
|
|
28
|
-
ResultAsync.err = errAsync;
|
|
29
|
-
ResultAsync.fromPromise = _fromPromise;
|
|
30
|
-
function combine(results) {
|
|
31
|
-
return _combine(results);
|
|
32
|
-
}
|
|
33
|
-
ResultAsync.combine = combine;
|
|
34
|
-
function combineWithAllErrors(results) {
|
|
35
|
-
return _combineWithAllErrors(results);
|
|
36
|
-
}
|
|
37
|
-
ResultAsync.combineWithAllErrors = combineWithAllErrors;
|
|
38
|
-
ResultAsync.Do = DoAsync;
|
|
39
|
-
})(ResultAsync || (ResultAsync = {}));
|
|
1
|
+
// ── Fault ────────────────────────────────────────────────────────
|
|
2
|
+
import { Fault as _Fault, isFault as _isFault, isDomainFault as _isDomainFault, isInfrastructureFault as _isInfrastructureFault, isTransientFault as _isTransientFault, } from './fault.js';
|
|
40
3
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
41
4
|
export var Fault;
|
|
42
5
|
(function (Fault) {
|
|
43
6
|
Fault.Tagged = _Fault;
|
|
7
|
+
Fault.is = _isFault;
|
|
44
8
|
Fault.isDomain = _isDomainFault;
|
|
45
9
|
Fault.isInfrastructure = _isInfrastructureFault;
|
|
46
10
|
Fault.isTransient = _isTransientFault;
|
|
47
11
|
})(Fault || (Fault = {}));
|
|
12
|
+
// ── Try ──────────────────────────────────────────────────────────
|
|
13
|
+
export { Ok, Err, ok, err } from './try.js';
|
|
14
|
+
export { TryAsync, okAsync, errAsync } from './try.async.js';
|
|
15
|
+
export { fromPromise, fromSafePromise, fromThrowable, fromAsyncThrowable, combine, combineWithAllErrors, unwrap, flatten, race, } from './helpers.js';
|
|
16
|
+
// ── Do ──────────────────────────────────────────────────────────
|
|
17
|
+
export { Do, DoAsync } from './do.js';
|
|
48
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Ok, Err, ok, err } from './try.js';
|
|
2
|
+
export class TryAsync {
|
|
3
|
+
_promise;
|
|
4
|
+
then;
|
|
5
|
+
constructor(promise) {
|
|
6
|
+
this._promise = promise;
|
|
7
|
+
this.then = promise.then.bind(promise);
|
|
8
|
+
}
|
|
9
|
+
map(fn) {
|
|
10
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
11
|
+
if (result.isErr())
|
|
12
|
+
return new Err(result.error);
|
|
13
|
+
return new Ok(await fn(result.value));
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
mapErr(fn) {
|
|
17
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
18
|
+
if (result.isOk())
|
|
19
|
+
return new Ok(result.value);
|
|
20
|
+
return new Err(await fn(result.error));
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
andThen(fn) {
|
|
24
|
+
return new TryAsync(this._promise.then((result) => {
|
|
25
|
+
if (result.isErr())
|
|
26
|
+
return new Err(result.error);
|
|
27
|
+
const next = fn(result.value);
|
|
28
|
+
return next instanceof TryAsync ? next._promise : next;
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
orElse(fn) {
|
|
32
|
+
return new TryAsync(this._promise.then((result) => {
|
|
33
|
+
if (result.isOk())
|
|
34
|
+
return new Ok(result.value);
|
|
35
|
+
const next = fn(result.error);
|
|
36
|
+
return next instanceof TryAsync ? next._promise : next;
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
tap(fn) {
|
|
40
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
41
|
+
if (result.isOk())
|
|
42
|
+
await fn(result.value);
|
|
43
|
+
return result;
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
tapErr(fn) {
|
|
47
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
48
|
+
if (result.isErr())
|
|
49
|
+
await fn(result.error);
|
|
50
|
+
return result;
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
andThrough(fn) {
|
|
54
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
55
|
+
if (result.isErr())
|
|
56
|
+
return new Err(result.error);
|
|
57
|
+
const through = fn(result.value);
|
|
58
|
+
const throughResult = through instanceof TryAsync ? await through : through;
|
|
59
|
+
if (throughResult.isErr())
|
|
60
|
+
return new Err(throughResult.error);
|
|
61
|
+
return new Ok(result.value);
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
orThrough(fn) {
|
|
65
|
+
return new TryAsync(this._promise.then(async (result) => {
|
|
66
|
+
if (result.isOk())
|
|
67
|
+
return new Ok(result.value);
|
|
68
|
+
const through = fn(result.error);
|
|
69
|
+
const throughResult = through instanceof TryAsync ? await through : through;
|
|
70
|
+
if (throughResult.isErr())
|
|
71
|
+
return new Err(throughResult.error);
|
|
72
|
+
return new Err(result.error);
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
catchFault(code, fn) {
|
|
76
|
+
return new TryAsync(this._promise.then((result) => {
|
|
77
|
+
if (result.isOk())
|
|
78
|
+
return new Ok(result.value);
|
|
79
|
+
const error = result.error;
|
|
80
|
+
if (typeof error === 'object' &&
|
|
81
|
+
error !== null &&
|
|
82
|
+
'code' in error &&
|
|
83
|
+
error.code === code) {
|
|
84
|
+
const next = fn(error);
|
|
85
|
+
return next instanceof TryAsync ? next._promise : next;
|
|
86
|
+
}
|
|
87
|
+
return new Err(error);
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
async unwrapOr(defaultValue) {
|
|
91
|
+
const result = await this;
|
|
92
|
+
return result.unwrapOr(defaultValue);
|
|
93
|
+
}
|
|
94
|
+
async match(cases) {
|
|
95
|
+
const result = await this;
|
|
96
|
+
return result.match(cases);
|
|
97
|
+
}
|
|
98
|
+
async *[Symbol.asyncIterator]() {
|
|
99
|
+
const result = await this._promise;
|
|
100
|
+
if (result.isErr()) {
|
|
101
|
+
// @ts-expect-error -- structurally equivalent, safe for DoAsync notation
|
|
102
|
+
yield result;
|
|
103
|
+
throw new Error('Unreachable: DoAsync generator continued after Err yield');
|
|
104
|
+
}
|
|
105
|
+
return result.value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function okAsync(value) {
|
|
109
|
+
return new TryAsync(Promise.resolve(ok(value)));
|
|
110
|
+
}
|
|
111
|
+
export function errAsync(error) {
|
|
112
|
+
return new TryAsync(Promise.resolve(err(error)));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=try.async.js.map
|
|
@@ -24,6 +24,25 @@ export class Ok {
|
|
|
24
24
|
orElse(_fn) {
|
|
25
25
|
return new Ok(this.value);
|
|
26
26
|
}
|
|
27
|
+
tap(fn) {
|
|
28
|
+
fn(this.value);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
tapErr(_fn) {
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
andThrough(fn) {
|
|
35
|
+
const result = fn(this.value);
|
|
36
|
+
if (result.isErr())
|
|
37
|
+
return new Err(result.error);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
orThrough(_fn) {
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
catchFault(_code, _fn) {
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
27
46
|
unwrapOr(_defaultValue) {
|
|
28
47
|
return this.value;
|
|
29
48
|
}
|
|
@@ -59,6 +78,32 @@ export class Err {
|
|
|
59
78
|
orElse(fn) {
|
|
60
79
|
return fn(this.error);
|
|
61
80
|
}
|
|
81
|
+
tap(_fn) {
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
tapErr(fn) {
|
|
85
|
+
fn(this.error);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
andThrough(_fn) {
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
orThrough(fn) {
|
|
92
|
+
const result = fn(this.error);
|
|
93
|
+
if (result.isErr())
|
|
94
|
+
return new Err(result.error);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
catchFault(code, fn) {
|
|
98
|
+
const error = this.error;
|
|
99
|
+
if (typeof error === 'object' &&
|
|
100
|
+
error !== null &&
|
|
101
|
+
'code' in error &&
|
|
102
|
+
error.code === code) {
|
|
103
|
+
return fn(error);
|
|
104
|
+
}
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
62
107
|
unwrapOr(defaultValue) {
|
|
63
108
|
return defaultValue;
|
|
64
109
|
}
|
|
@@ -77,4 +122,4 @@ export function ok(value) {
|
|
|
77
122
|
export function err(error) {
|
|
78
123
|
return new Err(error);
|
|
79
124
|
}
|
|
80
|
-
//# sourceMappingURL=
|
|
125
|
+
//# sourceMappingURL=try.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clutchit",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"description": "Resilience primitives for TypeScript — typed errors, retry, timeout, circuit breaking, scheduling, concurrency, and queues.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"typescript",
|
|
7
|
+
"resilience",
|
|
8
|
+
"error-handling",
|
|
9
|
+
"result",
|
|
10
|
+
"typed-errors",
|
|
11
|
+
"retry",
|
|
12
|
+
"timeout",
|
|
13
|
+
"circuit-breaker",
|
|
14
|
+
"schedule",
|
|
15
|
+
"backoff",
|
|
16
|
+
"concurrency",
|
|
17
|
+
"semaphore",
|
|
18
|
+
"bulkhead",
|
|
19
|
+
"rate-limiter",
|
|
20
|
+
"queue"
|
|
21
|
+
],
|
|
5
22
|
"author": {
|
|
6
23
|
"name": "hexac",
|
|
7
24
|
"url": "https://existin.space",
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { Ok, Err, ok, err } from './result.js';
|
|
2
|
-
export class ResultAsync {
|
|
3
|
-
_promise;
|
|
4
|
-
then;
|
|
5
|
-
constructor(promise) {
|
|
6
|
-
this._promise = promise;
|
|
7
|
-
this.then = promise.then.bind(promise);
|
|
8
|
-
}
|
|
9
|
-
map(fn) {
|
|
10
|
-
return new ResultAsync(this._promise.then(async (result) => {
|
|
11
|
-
if (result.isErr())
|
|
12
|
-
return new Err(result.error);
|
|
13
|
-
return new Ok(await fn(result.value));
|
|
14
|
-
}));
|
|
15
|
-
}
|
|
16
|
-
mapErr(fn) {
|
|
17
|
-
return new ResultAsync(this._promise.then(async (result) => {
|
|
18
|
-
if (result.isOk())
|
|
19
|
-
return new Ok(result.value);
|
|
20
|
-
return new Err(await fn(result.error));
|
|
21
|
-
}));
|
|
22
|
-
}
|
|
23
|
-
andThen(fn) {
|
|
24
|
-
return new ResultAsync(this._promise.then((result) => {
|
|
25
|
-
if (result.isErr())
|
|
26
|
-
return new Err(result.error);
|
|
27
|
-
const next = fn(result.value);
|
|
28
|
-
return next instanceof ResultAsync ? next._promise : next;
|
|
29
|
-
}));
|
|
30
|
-
}
|
|
31
|
-
orElse(fn) {
|
|
32
|
-
return new ResultAsync(this._promise.then((result) => {
|
|
33
|
-
if (result.isOk())
|
|
34
|
-
return new Ok(result.value);
|
|
35
|
-
const next = fn(result.error);
|
|
36
|
-
return next instanceof ResultAsync ? next._promise : next;
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
async unwrapOr(defaultValue) {
|
|
40
|
-
const result = await this;
|
|
41
|
-
return result.unwrapOr(defaultValue);
|
|
42
|
-
}
|
|
43
|
-
async match(cases) {
|
|
44
|
-
const result = await this;
|
|
45
|
-
return result.match(cases);
|
|
46
|
-
}
|
|
47
|
-
async *[Symbol.asyncIterator]() {
|
|
48
|
-
const result = await this._promise;
|
|
49
|
-
if (result.isErr()) {
|
|
50
|
-
// @ts-expect-error -- structurally equivalent, safe for DoAsync notation
|
|
51
|
-
yield result;
|
|
52
|
-
throw new Error('Unreachable: DoAsync generator continued after Err yield');
|
|
53
|
-
}
|
|
54
|
-
return result.value;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
export function okAsync(value) {
|
|
58
|
-
return new ResultAsync(Promise.resolve(ok(value)));
|
|
59
|
-
}
|
|
60
|
-
export function errAsync(error) {
|
|
61
|
-
return new ResultAsync(Promise.resolve(err(error)));
|
|
62
|
-
}
|
|
63
|
-
//# sourceMappingURL=result.async.js.map
|