clutchit 0.0.8 → 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 -51
- 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)
|
|
@@ -20,7 +19,7 @@ npm install clutchit
|
|
|
20
19
|
|
|
21
20
|
| Import | Description |
|
|
22
21
|
|---|---|
|
|
23
|
-
| `clutchit/unthrow` | `
|
|
22
|
+
| `clutchit/unthrow` | `Try<T,E>`, `TryAsync<T,E>`, `Fault`, Do notation |
|
|
24
23
|
| `clutchit/schedule` | Composable timing schedules — exponential, linear, fibonacci, cron, … |
|
|
25
24
|
| `clutchit/retry` | Retry policy with backoff schedules and abort signal support |
|
|
26
25
|
| `clutchit/timeout` | Timeout wrapper with `AbortSignal` propagation |
|
|
@@ -33,10 +32,10 @@ npm install clutchit
|
|
|
33
32
|
## Quick start
|
|
34
33
|
|
|
35
34
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
import { RetryPolicy }
|
|
38
|
-
import { withTimeout }
|
|
39
|
-
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';
|
|
40
39
|
|
|
41
40
|
class ApiFault extends Fault.Tagged('API_ERROR', 'infrastructure', true)<{
|
|
42
41
|
status: number;
|
|
@@ -44,8 +43,8 @@ class ApiFault extends Fault.Tagged('API_ERROR', 'infrastructure', true)<{
|
|
|
44
43
|
readonly message = `API returned ${this.status}`;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
function fetchUser(id: string, signal: AbortSignal):
|
|
48
|
-
return
|
|
46
|
+
function fetchUser(id: string, signal: AbortSignal): TryAsync<User, ApiFault> {
|
|
47
|
+
return fromPromise(
|
|
49
48
|
fetch(`/api/users/${id}`, { signal }).then((r) => r.json()),
|
|
50
49
|
() => new ApiFault({ status: 500 }),
|
|
51
50
|
);
|
|
@@ -72,38 +71,38 @@ await result.match({
|
|
|
72
71
|
|
|
73
72
|
Typed error handling without exceptions.
|
|
74
73
|
|
|
75
|
-
###
|
|
74
|
+
### Try
|
|
76
75
|
|
|
77
76
|
```ts
|
|
78
|
-
import {
|
|
77
|
+
import { ok, err } from 'clutchit/unthrow';
|
|
79
78
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
79
|
+
const success = ok(42);
|
|
80
|
+
const failure = err('oops');
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
success.match({
|
|
89
88
|
ok: (value) => `got ${value}`,
|
|
90
89
|
err: (error) => `failed: ${error}`,
|
|
91
90
|
});
|
|
92
91
|
|
|
93
|
-
if (
|
|
94
|
-
if (
|
|
92
|
+
if (success.isOk()) success.value; // narrowed
|
|
93
|
+
if (failure.isErr()) failure.error; // narrowed
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
Methods: `isOk`, `isErr`, `map`, `mapErr`, `andThen`, `orElse`, `unwrapOr`, `match`
|
|
96
|
+
Methods: `isOk`, `isErr`, `map`, `mapErr`, `andThen`, `orElse`, `tap`, `tapErr`, `andThrough`, `orThrough`, `catchFault`, `unwrapOr`, `match`
|
|
98
97
|
|
|
99
|
-
###
|
|
98
|
+
### TryAsync
|
|
100
99
|
|
|
101
|
-
Wraps `Promise<
|
|
100
|
+
Wraps `Promise<Try<T, E>>`. Awaitable via `PromiseLike`, same chainable API as `Try`.
|
|
102
101
|
|
|
103
102
|
```ts
|
|
104
|
-
import {
|
|
103
|
+
import { fromPromise } from 'clutchit/unthrow';
|
|
105
104
|
|
|
106
|
-
const result =
|
|
105
|
+
const result = fromPromise(
|
|
107
106
|
fetch('/api/data').then((r) => r.json()),
|
|
108
107
|
(err) => new NetworkFault({ cause: String(err) }),
|
|
109
108
|
);
|
|
@@ -113,26 +112,43 @@ const final = await result
|
|
|
113
112
|
.andThen((items) => validate(items));
|
|
114
113
|
```
|
|
115
114
|
|
|
116
|
-
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`)
|
|
117
116
|
|
|
118
117
|
### Helpers
|
|
119
118
|
|
|
120
119
|
```ts
|
|
121
|
-
import {
|
|
120
|
+
import { ok, err, okAsync, fromPromise, fromThrowable, fromSafePromise, fromAsyncThrowable, combine, combineWithAllErrors, unwrap, flatten, race } from 'clutchit/unthrow';
|
|
122
121
|
|
|
123
122
|
// Wrap a throwable sync function
|
|
124
|
-
|
|
123
|
+
fromThrowable(
|
|
125
124
|
() => JSON.parse(raw),
|
|
126
125
|
(err) => new ParseFault({ message: String(err) }),
|
|
127
126
|
);
|
|
128
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
|
+
|
|
129
143
|
// Combine — short-circuit on first error
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
combine([ok(1), ok(2)]); // Ok([1, 2])
|
|
145
|
+
combine([okAsync(1), okAsync(2)]); // TryAsync<[1, 2], E>
|
|
132
146
|
|
|
133
147
|
// Combine — collect all errors
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
combineWithAllErrors([ok(1), err('a'), err('b')]); // Err(['a', 'b'])
|
|
149
|
+
|
|
150
|
+
// Race — first to resolve wins
|
|
151
|
+
race([okAsync(1), okAsync(2)]);
|
|
136
152
|
```
|
|
137
153
|
|
|
138
154
|
### Do notation
|
|
@@ -140,22 +156,28 @@ ResultAsync.combineWithAllErrors([a, b, c]); // ResultAsync<[A, B, C], E[]>
|
|
|
140
156
|
Generator-based sequential chaining. `yield*` unwraps `Ok`; any `Err` short-circuits the entire block.
|
|
141
157
|
|
|
142
158
|
```ts
|
|
143
|
-
import {
|
|
159
|
+
import { Do, DoAsync } from 'clutchit/unthrow';
|
|
144
160
|
|
|
145
161
|
// Sync
|
|
146
|
-
const result =
|
|
147
|
-
const user = yield* findUser(id); //
|
|
148
|
-
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>
|
|
149
165
|
return { user, perms };
|
|
150
|
-
//
|
|
166
|
+
// Try<{ user, perms }, NotFoundFault | PermFault>
|
|
151
167
|
});
|
|
152
168
|
|
|
153
169
|
// Async
|
|
154
|
-
const result =
|
|
155
|
-
const user = yield* fetchUser(id); //
|
|
156
|
-
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>
|
|
157
173
|
return order;
|
|
158
|
-
//
|
|
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;
|
|
159
181
|
});
|
|
160
182
|
```
|
|
161
183
|
|
|
@@ -222,7 +244,7 @@ const result = Schedule.repeat(
|
|
|
222
244
|
onExecution: (attempt, delay) => console.log(`#${attempt}, next in ${delay}ms`),
|
|
223
245
|
},
|
|
224
246
|
);
|
|
225
|
-
//
|
|
247
|
+
// TryAsync<number, SyncError | ScheduleInterruptedFault>
|
|
226
248
|
```
|
|
227
249
|
|
|
228
250
|
---
|
|
@@ -241,7 +263,7 @@ const retry = new RetryPolicy({
|
|
|
241
263
|
});
|
|
242
264
|
|
|
243
265
|
const result = retry.execute((signal) => fetchData(signal));
|
|
244
|
-
//
|
|
266
|
+
// TryAsync<Data, FetchFault>
|
|
245
267
|
```
|
|
246
268
|
|
|
247
269
|
Options can be set on the constructor (defaults for all calls) or overridden per `execute` call.
|
|
@@ -265,7 +287,7 @@ const result = withTimeout(
|
|
|
265
287
|
(signal) => fetchData(signal),
|
|
266
288
|
5_000,
|
|
267
289
|
);
|
|
268
|
-
//
|
|
290
|
+
// TryAsync<Data, FetchFault | TimeoutFault>
|
|
269
291
|
|
|
270
292
|
// Reusable wrapper
|
|
271
293
|
const withFiveSeconds = createTimeout(5_000);
|
|
@@ -322,9 +344,9 @@ Atomic mutable reference with internal mutex:
|
|
|
322
344
|
```ts
|
|
323
345
|
const counter = new Concurrency.Ref(0);
|
|
324
346
|
|
|
325
|
-
await counter.update((n) => n + 1); //
|
|
326
|
-
await counter.set(42); //
|
|
327
|
-
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>
|
|
328
350
|
(n) => [n + 1, n + 1],
|
|
329
351
|
);
|
|
330
352
|
counter.get(); // current value (sync)
|
|
@@ -347,7 +369,7 @@ const breaker = new Circuit.Breaker({
|
|
|
347
369
|
});
|
|
348
370
|
|
|
349
371
|
const result = breaker.protect(() => callService());
|
|
350
|
-
//
|
|
372
|
+
// TryAsync<T, ServiceFault | CircuitOpenFault>
|
|
351
373
|
|
|
352
374
|
breaker.state; // 'closed' | 'open' | 'half-open'
|
|
353
375
|
breaker.failureCount; // consecutive failures
|
|
@@ -367,13 +389,13 @@ import { Queue, QueueFullFault, QueueEmptyFault } from 'clutchit/queue';
|
|
|
367
389
|
|
|
368
390
|
const q = new Queue.Bounded<Job>({ capacity: 100 });
|
|
369
391
|
|
|
370
|
-
q.offer(job); //
|
|
392
|
+
q.offer(job); // Try<void, QueueFullFault> — non-blocking
|
|
371
393
|
await q.put(job); // blocks until space is available
|
|
372
394
|
await q.put(job, signal); // cancellable
|
|
373
395
|
|
|
374
396
|
const item = await q.take(); // blocks until item available
|
|
375
397
|
const batch = await q.takeBatch(10); // at least 1, up to 10
|
|
376
|
-
const maybe = q.poll(); //
|
|
398
|
+
const maybe = q.poll(); // Try<Job, QueueEmptyFault>
|
|
377
399
|
```
|
|
378
400
|
|
|
379
401
|
### DroppingQueue — drops newest when full
|
|
@@ -422,6 +444,7 @@ class UnauthorizedFault extends Fault.Tagged('UNAUTHORIZED', 'domain')() {
|
|
|
422
444
|
### Type guards
|
|
423
445
|
|
|
424
446
|
```ts
|
|
447
|
+
Fault.is(value); // duck-type check for Fault shape
|
|
425
448
|
Fault.isDomain(fault); // _category === 'domain'
|
|
426
449
|
Fault.isInfrastructure(fault); // _category === 'infrastructure'
|
|
427
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
|