clutchit 0.0.8 → 0.0.10

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +17 -10
  2. package/README.md +74 -51
  3. package/dist/circuit/circuit.breaker.d.ts +51 -0
  4. package/dist/circuit/circuit.breaker.js +4 -4
  5. package/dist/circuit/index.d.ts +10 -0
  6. package/dist/concurrency/bulkhead.d.ts +28 -0
  7. package/dist/concurrency/bulkhead.js +3 -3
  8. package/dist/concurrency/index.d.ts +23 -0
  9. package/dist/concurrency/rate-limiter.d.ts +29 -0
  10. package/dist/concurrency/rate-limiter.js +2 -2
  11. package/dist/concurrency/ref.d.ts +11 -0
  12. package/dist/concurrency/ref.js +2 -2
  13. package/dist/concurrency/semaphore.d.ts +15 -0
  14. package/dist/concurrency/semaphore.js +2 -2
  15. package/dist/queue/base.queue.d.ts +18 -0
  16. package/dist/queue/base.queue.js +6 -6
  17. package/dist/queue/bounded.queue.d.ts +11 -0
  18. package/dist/queue/bounded.queue.js +10 -10
  19. package/dist/queue/dropping.queue.d.ts +7 -0
  20. package/dist/queue/faults.d.ts +24 -0
  21. package/dist/queue/index.d.ts +15 -0
  22. package/dist/queue/sliding.queue.d.ts +7 -0
  23. package/dist/retry/index.d.ts +8 -0
  24. package/dist/retry/retry.d.ts +21 -0
  25. package/dist/retry/retry.js +2 -2
  26. package/dist/schedule/index.d.ts +36 -0
  27. package/dist/schedule/operators.d.ts +22 -0
  28. package/dist/schedule/runner.d.ts +31 -0
  29. package/dist/schedule/runner.js +2 -2
  30. package/dist/schedule/schedule.d.ts +35 -0
  31. package/dist/timeout/index.d.ts +7 -0
  32. package/dist/timeout/timeout.d.ts +18 -0
  33. package/dist/timeout/timeout.js +2 -2
  34. package/dist/unthrow/do.d.ts +7 -0
  35. package/dist/unthrow/do.js +11 -7
  36. package/dist/unthrow/fault.d.ts +23 -0
  37. package/dist/unthrow/fault.js +15 -6
  38. package/dist/unthrow/helpers.d.ts +16 -0
  39. package/dist/unthrow/helpers.js +32 -9
  40. package/dist/unthrow/index.d.ts +14 -0
  41. package/dist/unthrow/index.js +9 -39
  42. package/dist/unthrow/try.async.d.ts +28 -0
  43. package/dist/unthrow/try.async.js +114 -0
  44. package/dist/unthrow/try.d.ts +56 -0
  45. package/dist/unthrow/{result.js → try.js} +46 -1
  46. package/package.json +19 -2
  47. 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
- - `Result<T, E>` — synchronous typed error container with `Ok` and `Err` variants
15
- - `ResultAsync<T, E>` — async equivalent backed by `Promise<Result<T, E>>`, implements `PromiseLike`
16
- - `Result.Do` / `ResultAsync.Do` — generator-based Do notation for ergonomic sequential chaining
17
- - `Result.combine` / `ResultAsync.combine` — combine arrays of results, short-circuit on first error
18
- - `Result.combineWithAllErrors` / `ResultAsync.combineWithAllErrors` collect all errors instead of stopping at first
19
- - `Result.fromThrowable` — wrap a throwing sync function into a `Result`
20
- - `ResultAsync.fromPromise` — lift a `Promise` into a `ResultAsync` with typed error mapping
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.isDomain` / `Fault.isInfrastructure` / `Fault.isTransient` type guard helpers
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 `ResultAsync`-returning function repeatedly on a schedule with abort signal support
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 `ResultAsync`-returning function with a deadline
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 — typed errors, retry, timeout, circuit breaking, scheduling, concurrency, and queues.
3
+ Resilience primitives for TypeScript.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/clutchit)](https://www.npmjs.com/package/clutchit)
6
- [![license](https://img.shields.io/npm/l/clutchit)](../LICENSE)
6
+ [![license](https://img.shields.io/npm/l/clutchit)](./LICENSE)
7
7
  [![docs](https://img.shields.io/badge/docs-clutchit.existin.space-blue)](https://clutchit.existin.space)
8
- [![bundle size](https://img.shields.io/bundlephobia/minzip/clutchit)](https://bundlephobia.com/package/clutchit)
9
8
  [![types](https://img.shields.io/npm/types/clutchit)](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` | `Result<T,E>`, `ResultAsync<T,E>`, `Fault`, Do notation |
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 { ResultAsync, Fault } from 'clutchit/unthrow';
37
- import { RetryPolicy } from 'clutchit/retry';
38
- import { withTimeout } from 'clutchit/timeout';
39
- import { Schedule } from 'clutchit/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): ResultAsync<User, ApiFault> {
48
- return ResultAsync.fromPromise(
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
- ### Result
74
+ ### Try
76
75
 
77
76
  ```ts
78
- import { Result } from 'clutchit/unthrow';
77
+ import { ok, err } from 'clutchit/unthrow';
79
78
 
80
- const ok = Result.ok(42);
81
- const fail = Result.err('oops');
79
+ const success = ok(42);
80
+ const failure = err('oops');
82
81
 
83
- ok.map((n) => n * 2); // Ok(84)
84
- ok.andThen((n) => Result.ok(n)); // Ok(42)
85
- ok.unwrapOr(0); // 42
86
- fail.unwrapOr(0); // 0
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
- ok.match({
87
+ success.match({
89
88
  ok: (value) => `got ${value}`,
90
89
  err: (error) => `failed: ${error}`,
91
90
  });
92
91
 
93
- if (ok.isOk()) ok.value; // narrowed
94
- if (fail.isErr()) fail.error; // narrowed
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
- ### ResultAsync
98
+ ### TryAsync
100
99
 
101
- Wraps `Promise<Result<T, E>>`. Awaitable via `PromiseLike`, same chainable API as `Result`.
100
+ Wraps `Promise<Try<T, E>>`. Awaitable via `PromiseLike`, same chainable API as `Try`.
102
101
 
103
102
  ```ts
104
- import { ResultAsync } from 'clutchit/unthrow';
103
+ import { fromPromise } from 'clutchit/unthrow';
105
104
 
106
- const result = ResultAsync.fromPromise(
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 `ResultAsync` or `Promise`)
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 { Result, ResultAsync } from 'clutchit/unthrow';
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
- Result.fromThrowable(
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
- Result.combine([resultA, resultB]); // Result<[A, B], E>
131
- ResultAsync.combine([asyncA, asyncB]); // ResultAsync<[A, B], E>
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
- Result.combineWithAllErrors([a, b, c]); // Result<[A, B, C], E[]>
135
- ResultAsync.combineWithAllErrors([a, b, c]); // ResultAsync<[A, B, C], E[]>
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 { Result, ResultAsync } from 'clutchit/unthrow';
159
+ import { Do, DoAsync } from 'clutchit/unthrow';
144
160
 
145
161
  // Sync
146
- const result = Result.Do(function* () {
147
- const user = yield* findUser(id); // Result<User, NotFoundFault>
148
- const perms = yield* getPermissions(user); // Result<Perms, PermFault>
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
- // Result<{ user, perms }, NotFoundFault | PermFault>
166
+ // Try<{ user, perms }, NotFoundFault | PermFault>
151
167
  });
152
168
 
153
169
  // Async
154
- const result = ResultAsync.Do(async function* () {
155
- const user = yield* fetchUser(id); // ResultAsync<User, NetworkFault>
156
- const order = yield* createOrder(user); // ResultAsync<Order, OrderFault>
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
- // ResultAsync<Order, NetworkFault | OrderFault>
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
- // ResultAsync<number, SyncError | ScheduleInterruptedFault>
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
- // ResultAsync<Data, FetchFault>
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
- // ResultAsync<Data, FetchFault | TimeoutFault>
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); // ResultAsync<void, never>
326
- await counter.set(42); // ResultAsync<void, never>
327
- const next = await counter.modify( // ResultAsync<number, never>
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
- // ResultAsync<T, ServiceFault | CircuitOpenFault>
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); // Result<void, QueueFullFault> — non-blocking
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(); // Result<Job, QueueEmptyFault>
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
@@ -0,0 +1,51 @@
1
+ import { type TryAsync } from '../unthrow/index.js';
2
+ import { Schedule } from '../schedule/index.js';
3
+ export type CircuitState = 'closed' | 'open' | 'half-open';
4
+ declare const CircuitOpenFault_base: abstract new (fields: {
5
+ remainingTimeout: number;
6
+ }) => Readonly<{
7
+ remainingTimeout: number;
8
+ }> & {
9
+ readonly code: "CIRCUIT_OPEN";
10
+ readonly _category: "infrastructure";
11
+ readonly _transient: false;
12
+ readonly message: string;
13
+ };
14
+ export declare class CircuitOpenFault extends CircuitOpenFault_base {
15
+ readonly message: string;
16
+ }
17
+ export interface CircuitBreakerOptions {
18
+ /** Number of consecutive failures before opening. Default: 5 */
19
+ failureThreshold?: number;
20
+ /** Schedule for reset timing. Default: Schedule.constant(30_000) */
21
+ resetSchedule?: Schedule;
22
+ /** Number of trial requests allowed in half-open state. Default: 1 */
23
+ halfOpenAttempts?: number;
24
+ /** Predicate to determine if an error should count as a failure. Default: all errors count. */
25
+ isFailure?: (error: unknown) => boolean;
26
+ /** Called when circuit state changes (for logging/monitoring). */
27
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
28
+ }
29
+ export declare class CircuitBreaker {
30
+ private _state;
31
+ private _failureCount;
32
+ private _resetAttempt;
33
+ private _halfOpenTrials;
34
+ private _nextResetTime;
35
+ private readonly _failureThreshold;
36
+ private readonly _resetSchedule;
37
+ private readonly _maxHalfOpenAttempts;
38
+ private readonly _isFailure;
39
+ private readonly _onStateChange?;
40
+ constructor(options?: CircuitBreakerOptions);
41
+ protect<T, E>(fn: () => TryAsync<T, E>): TryAsync<T, E | CircuitOpenFault>;
42
+ get state(): CircuitState;
43
+ get failureCount(): number;
44
+ reset(): void;
45
+ private onSuccess;
46
+ private onFailure;
47
+ private scheduleReset;
48
+ private transition;
49
+ }
50
+ export {};
51
+ //# sourceMappingURL=circuit.breaker.d.ts.map
@@ -1,4 +1,4 @@
1
- import { Fault, ResultAsync } from '../unthrow/index.js';
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 ResultAsync.err(new CircuitOpenFault({ remainingTimeout }));
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 ResultAsync.err(new CircuitOpenFault({ remainingTimeout: 0 }));
39
+ return errAsync(new CircuitOpenFault({ remainingTimeout: 0 }));
40
40
  }
41
41
  this._halfOpenTrials++;
42
42
  }
43
- return ResultAsync.fromPromise((async () => {
43
+ return fromPromise((async () => {
44
44
  const result = await fn();
45
45
  if (result.isOk()) {
46
46
  this.onSuccess();
@@ -0,0 +1,10 @@
1
+ import { CircuitBreaker } from './circuit.breaker.js';
2
+ import type { CircuitState, CircuitBreakerOptions } from './circuit.breaker.js';
3
+ export { CircuitOpenFault } from './circuit.breaker.js';
4
+ export declare namespace Circuit {
5
+ const Breaker: typeof CircuitBreaker;
6
+ type Breaker = CircuitBreaker;
7
+ type BreakerOptions = CircuitBreakerOptions;
8
+ type State = CircuitState;
9
+ }
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { type TryAsync } from '../unthrow/index.js';
2
+ declare const BulkheadRejectedFault_base: abstract new () => Readonly<Record<string, unknown>> & {
3
+ readonly code: "BULKHEAD_REJECTED";
4
+ readonly _category: "infrastructure";
5
+ readonly _transient: false;
6
+ readonly message: string;
7
+ };
8
+ export declare class BulkheadRejectedFault extends BulkheadRejectedFault_base {
9
+ readonly message = "Bulkhead capacity exceeded";
10
+ }
11
+ export interface BulkheadOptions {
12
+ concurrency: number;
13
+ queue?: number;
14
+ }
15
+ export declare class Bulkhead {
16
+ private _active;
17
+ private readonly _maxConcurrency;
18
+ private readonly _maxQueue;
19
+ private readonly _queue;
20
+ constructor(options: BulkheadOptions);
21
+ execute<T, E>(fn: () => TryAsync<T, E>): TryAsync<T, E | BulkheadRejectedFault>;
22
+ get activeCount(): number;
23
+ get queueSize(): number;
24
+ private acquire;
25
+ private release;
26
+ }
27
+ export {};
28
+ //# sourceMappingURL=bulkhead.d.ts.map
@@ -1,4 +1,4 @@
1
- import { Fault, ResultAsync } from '../unthrow/index.js';
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 ResultAsync.err(new BulkheadRejectedFault());
17
+ return errAsync(new BulkheadRejectedFault());
18
18
  }
19
- return ResultAsync.fromPromise((async () => {
19
+ return fromPromise((async () => {
20
20
  await this.acquire();
21
21
  try {
22
22
  const result = await fn();
@@ -0,0 +1,23 @@
1
+ import { Semaphore as _Semaphore } from './semaphore.js';
2
+ import type { SemaphoreOptions as _SemaphoreOptions } from './semaphore.js';
3
+ import { RateLimiter as _RateLimiter } from './rate-limiter.js';
4
+ import type { RateLimiterOptions as _RateLimiterOptions } from './rate-limiter.js';
5
+ import { Bulkhead as _Bulkhead } from './bulkhead.js';
6
+ import type { BulkheadOptions as _BulkheadOptions } from './bulkhead.js';
7
+ import { Ref as _Ref } from './ref.js';
8
+ export { RateLimitFault } from './rate-limiter.js';
9
+ export { BulkheadRejectedFault } from './bulkhead.js';
10
+ export declare namespace Concurrency {
11
+ const Semaphore: typeof _Semaphore;
12
+ type Semaphore = _Semaphore;
13
+ type SemaphoreOptions = _SemaphoreOptions;
14
+ const RateLimiter: typeof _RateLimiter;
15
+ type RateLimiter = _RateLimiter;
16
+ type RateLimiterOptions = _RateLimiterOptions;
17
+ const Bulkhead: typeof _Bulkhead;
18
+ type Bulkhead = _Bulkhead;
19
+ type BulkheadOptions = _BulkheadOptions;
20
+ const Ref: typeof _Ref;
21
+ type Ref<T> = _Ref<T>;
22
+ }
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { type TryAsync } from '../unthrow/index.js';
2
+ declare const RateLimitFault_base: abstract new (fields: {
3
+ retryAfter: number;
4
+ }) => Readonly<{
5
+ retryAfter: number;
6
+ }> & {
7
+ readonly code: "RATE_LIMITED";
8
+ readonly _category: "infrastructure";
9
+ readonly _transient: true;
10
+ readonly message: string;
11
+ };
12
+ export declare class RateLimitFault extends RateLimitFault_base {
13
+ readonly message: string;
14
+ }
15
+ export interface RateLimiterOptions {
16
+ limit: number;
17
+ window: number;
18
+ }
19
+ export declare class RateLimiter {
20
+ private readonly _limit;
21
+ private readonly _window;
22
+ private readonly _timestamps;
23
+ constructor(options: RateLimiterOptions);
24
+ execute<T, E>(fn: () => TryAsync<T, E>): TryAsync<T, E | RateLimitFault>;
25
+ get remaining(): number;
26
+ private cleanup;
27
+ }
28
+ export {};
29
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -1,4 +1,4 @@
1
- import { Fault, ResultAsync } from '../unthrow/index.js';
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 ResultAsync.err(new RateLimitFault({ retryAfter }));
18
+ return errAsync(new RateLimitFault({ retryAfter }));
19
19
  }
20
20
  this._timestamps.push(Date.now());
21
21
  return fn();
@@ -0,0 +1,11 @@
1
+ import { type TryAsync } from '../unthrow/index.js';
2
+ export declare class Ref<T> {
3
+ private _value;
4
+ private readonly _mutex;
5
+ constructor(initial: T);
6
+ get(): T;
7
+ modify<R>(fn: (current: T) => [next: T, result: R]): TryAsync<R, never>;
8
+ update(fn: (current: T) => T): TryAsync<void, never>;
9
+ set(value: T): TryAsync<void, never>;
10
+ }
11
+ //# sourceMappingURL=ref.d.ts.map
@@ -1,4 +1,4 @@
1
- import { ResultAsync } from '../unthrow/index.js';
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 ResultAsync.ok(result);
16
+ return okAsync(result);
17
17
  });
18
18
  }
19
19
  update(fn) {
@@ -0,0 +1,15 @@
1
+ import { type TryAsync } from '../unthrow/index.js';
2
+ export interface SemaphoreOptions {
3
+ permits: number;
4
+ }
5
+ export declare class Semaphore {
6
+ private _available;
7
+ private readonly _queue;
8
+ constructor(options: SemaphoreOptions);
9
+ execute<T, E>(fn: () => TryAsync<T, E>): TryAsync<T, E>;
10
+ get available(): number;
11
+ get waiting(): number;
12
+ private acquire;
13
+ private release;
14
+ }
15
+ //# sourceMappingURL=semaphore.d.ts.map
@@ -1,4 +1,4 @@
1
- import { ResultAsync } from '../unthrow/index.js';
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 ResultAsync.fromPromise((async () => {
9
+ return fromPromise((async () => {
10
10
  await this.acquire();
11
11
  try {
12
12
  const result = await fn();
@@ -0,0 +1,18 @@
1
+ import { type Try, type TryAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault } from './faults.js';
3
+ export interface QueueOptions {
4
+ capacity: number;
5
+ }
6
+ export declare class BaseQueue<T> {
7
+ protected readonly _buffer: T[];
8
+ protected readonly _capacity: number;
9
+ protected readonly _takeWaiters: Array<(item: T) => void>;
10
+ constructor(options: QueueOptions);
11
+ protected _tryTake(): Try<T, QueueEmptyFault>;
12
+ take(signal?: AbortSignal): TryAsync<T, never>;
13
+ takeBatch(n: number, signal?: AbortSignal): TryAsync<T[], never>;
14
+ poll(): Try<T, QueueEmptyFault>;
15
+ get size(): number;
16
+ get capacity(): number;
17
+ }
18
+ //# sourceMappingURL=base.queue.d.ts.map
@@ -1,4 +1,4 @@
1
- import { Result, ResultAsync } from '../unthrow/index.js';
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 Result.ok(this._buffer.shift());
12
+ return ok(this._buffer.shift());
13
13
  }
14
- return Result.err(new QueueEmptyFault());
14
+ return err(new QueueEmptyFault());
15
15
  }
16
16
  take(signal) {
17
17
  const taken = this._tryTake();
18
18
  if (taken.isOk())
19
- return ResultAsync.ok(taken.value);
20
- return ResultAsync.fromPromise(new Promise((resolve) => {
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 ResultAsync.fromPromise((async () => {
30
+ return fromPromise((async () => {
31
31
  const taken = this._tryTake();
32
32
  let first;
33
33
  if (taken.isOk()) {
@@ -0,0 +1,11 @@
1
+ import { type Try, type TryAsync } from '../unthrow/index.js';
2
+ import { QueueEmptyFault, QueueFullFault } from './faults.js';
3
+ import { BaseQueue, type QueueOptions } from './base.queue.js';
4
+ export declare class BoundedQueue<T> extends BaseQueue<T> {
5
+ private readonly _putWaiters;
6
+ constructor(options: QueueOptions);
7
+ protected _tryTake(): Try<T, QueueEmptyFault>;
8
+ offer(item: T): Try<void, QueueFullFault>;
9
+ put(item: T, signal?: AbortSignal): TryAsync<void, never>;
10
+ }
11
+ //# sourceMappingURL=bounded.queue.d.ts.map