clutchit 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -0,0 +1,530 @@
1
+ # clutchit
2
+
3
+ Resilience primitives for TypeScript — retry, circuit breaker, timeout, rate limiting, and more with typed errors.
4
+
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)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install clutchit
12
+ ```
13
+
14
+ ## Quick Example
15
+
16
+ ```typescript
17
+ import { ResultAsync, Fault } from 'clutchit/unthrow';
18
+ import { Retry } from 'clutchit/retry';
19
+ import { Timeout } from 'clutchit/timeout';
20
+ import { Schedule } from 'clutchit/schedule';
21
+
22
+ class ApiFault extends Fault.Tagged('API_ERROR', 'infrastructure', true)<{
23
+ status: number;
24
+ }>() {
25
+ readonly message = `API returned ${this.status}`;
26
+ }
27
+
28
+ function fetchUser(id: string, signal: AbortSignal) {
29
+ return ResultAsync.fromPromise(
30
+ fetch(`/api/users/${id}`, { signal }).then((r) => r.json()),
31
+ (err) => new ApiFault({ status: 500 }),
32
+ );
33
+ }
34
+
35
+ const retry = new Retry.Policy({
36
+ schedule: Schedule.exponential(200).pipe(Schedule.recurs(3)),
37
+ });
38
+
39
+ const result = Timeout.wrap(
40
+ (signal) => retry.execute((s) => fetchUser('123', s)),
41
+ 5000,
42
+ );
43
+
44
+ await result.match({
45
+ ok: (user) => console.log(user),
46
+ err: (fault) => console.error(fault.code, fault.message),
47
+ });
48
+ ```
49
+
50
+ ## Modules
51
+
52
+ | Import | Description |
53
+ |--------|-------------|
54
+ | `clutchit/unthrow` | `Result<T, E>`, `ResultAsync<T, E>`, `Fault`, Do notation |
55
+ | `clutchit/schedule` | Composable timing schedules (exponential, linear, fibonacci, cron, ...) |
56
+ | `clutchit/retry` | Retry policies with configurable backoff and cancellation |
57
+ | `clutchit/timeout` | Timeout wrappers with `AbortSignal` propagation |
58
+ | `clutchit/concurrency` | Semaphore, RateLimiter, Bulkhead, Ref |
59
+ | `clutchit/circuit` | Circuit breaker with progressive reset |
60
+ | `clutchit/queue` | Bounded, Dropping, and Sliding queues with backpressure |
61
+
62
+ ---
63
+
64
+ ## `clutchit/unthrow`
65
+
66
+ Typed error handling without exceptions. `Result<T, E>` for sync, `ResultAsync<T, E>` for async.
67
+
68
+ ### Result
69
+
70
+ ```typescript
71
+ import { Result } from 'clutchit/unthrow';
72
+
73
+ const ok = Result.ok(42); // Ok<number, never>
74
+ const fail = Result.err('oops'); // Err<never, string>
75
+
76
+ ok.map((n) => n * 2); // Ok(84)
77
+ ok.andThen((n) => Result.ok(n)); // Ok(42)
78
+ ok.unwrapOr(0); // 42
79
+ fail.unwrapOr(0); // 0
80
+
81
+ ok.match({
82
+ ok: (value) => `got ${value}`,
83
+ err: (error) => `failed: ${error}`,
84
+ });
85
+ ```
86
+
87
+ Methods: `map`, `mapErr`, `andThen`, `orElse`, `unwrapOr`, `match`, `isOk`, `isErr`
88
+
89
+ ### ResultAsync
90
+
91
+ Wraps `Promise<Result<T, E>>` with the same chainable methods. Awaitable via `PromiseLike`.
92
+
93
+ ```typescript
94
+ import { ResultAsync } from 'clutchit/unthrow';
95
+
96
+ const result = ResultAsync.fromPromise(
97
+ fetch('/api/data').then((r) => r.json()),
98
+ (err) => ({ code: 'FETCH_FAILED', message: String(err) }),
99
+ );
100
+
101
+ const final = result
102
+ .map((data) => data.items)
103
+ .andThen((items) => validateItems(items))
104
+ .mapErr((err) => normalize(err));
105
+ ```
106
+
107
+ ### Helpers
108
+
109
+ ```typescript
110
+ import { Result, ResultAsync } from 'clutchit/unthrow';
111
+
112
+ // Wrap a throwable sync function
113
+ Result.fromThrowable(() => JSON.parse(raw), (err) => ParseFault(String(err)));
114
+
115
+ // Combine results — fail on first error
116
+ Result.combine([resultA, resultB, resultC]); // Result<[A, B, C], E>
117
+ ResultAsync.combine([asyncA, asyncB]); // ResultAsync<[A, B], E>
118
+
119
+ // Combine results — accumulate all errors
120
+ Result.combineWithAllErrors([resultA, resultB]); // Result<[A, B], E[]>
121
+ ```
122
+
123
+ ### Do Notation
124
+
125
+ Generator-based monadic syntax. Yields unwrap `Ok` values; any `Err` short-circuits.
126
+
127
+ ```typescript
128
+ import { Result, ResultAsync } from 'clutchit/unthrow';
129
+
130
+ // Sync
131
+ const result = Result.Do(function* () {
132
+ const user = yield* findUser(id);
133
+ const perms = yield* getPermissions(user);
134
+ return { user, perms };
135
+ });
136
+ // Result<{ user: User; perms: Permissions }, NotFoundFault | PermissionFault>
137
+
138
+ // Async
139
+ const result = ResultAsync.Do(async function* () {
140
+ const user = yield* findUser(id);
141
+ const order = yield* createOrder(user, items);
142
+ yield* sendConfirmation(order);
143
+ return order;
144
+ });
145
+ ```
146
+
147
+ ---
148
+
149
+ ## `clutchit/schedule`
150
+
151
+ Composable timing primitive. Determines delay and continuation for each attempt. Used internally by retry and circuit breaker — also useful standalone.
152
+
153
+ ### Factories
154
+
155
+ ```typescript
156
+ import { Schedule } from 'clutchit/schedule';
157
+
158
+ Schedule.spaced(1000); // Fixed 1s delay
159
+ Schedule.exponential(200); // 200, 400, 800, 1600, ...
160
+ Schedule.exponential(200, 3); // 200, 600, 1800, ... (custom multiplier)
161
+ Schedule.linear(100); // 100, 200, 300, 400, ...
162
+ Schedule.fibonacci(100); // 100, 100, 200, 300, 500, ...
163
+ Schedule.constant(5000); // Alias for spaced
164
+ Schedule.fixed(1000); // Fixed 1s intervals (compensates for execution time)
165
+ Schedule.windowed(60_000); // Aligned to wall-clock minute boundaries
166
+ Schedule.cron('*/5 * * * *'); // Every 5 minutes (cron expression)
167
+ Schedule.cron('0 4 * * MON-FRI', { timezone: 'UTC' }); // 4am weekdays UTC
168
+ Schedule.forever; // Zero-delay, never stops
169
+ Schedule.once; // Single attempt then stop
170
+ ```
171
+
172
+ ### Operators
173
+
174
+ Compose via `.pipe()`:
175
+
176
+ ```typescript
177
+ const schedule = Schedule.exponential(200)
178
+ .pipe(Schedule.recurs(5)) // Max 5 attempts
179
+ .pipe(Schedule.cappedDelay(10_000)) // Cap at 10s
180
+ .pipe(Schedule.jittered()) // +/- 25% random jitter
181
+ .pipe(Schedule.upTo(60_000)); // Stop after 60s total
182
+ ```
183
+
184
+ | Operator | Description |
185
+ |----------|-------------|
186
+ | `recurs(n)` | Limit to `n` total attempts |
187
+ | `cappedDelay(ms)` | Cap individual delay |
188
+ | `jittered(factor?)` | Random jitter (default +/- 25%) |
189
+ | `upTo(ms)` | Stop after total elapsed time |
190
+ | `andThen(schedule)` | Run first schedule, then second |
191
+ | `union(schedule)` | Shorter delay wins, either continues |
192
+ | `intersect(schedule)` | Longer delay wins, both must continue |
193
+ | `whileInput(pred)` | Continue while input matches |
194
+ | `whileOutput(pred)` | Continue while output matches |
195
+ | `map(fn)` | Transform schedule output |
196
+
197
+ ### Repeat
198
+
199
+ Execute a function repeatedly on a schedule:
200
+
201
+ ```typescript
202
+ const schedule = Schedule.spaced(5000).pipe(Schedule.recurs(10));
203
+
204
+ await Schedule.repeat(
205
+ schedule,
206
+ (signal) => pollForUpdates(signal),
207
+ { signal: controller.signal },
208
+ );
209
+ ```
210
+
211
+ Returns `ScheduleInterruptedFault` if cancelled via signal.
212
+
213
+ ---
214
+
215
+ ## `clutchit/retry`
216
+
217
+ Retry policies with configurable backoff, error filtering, and cancellation.
218
+
219
+ ```typescript
220
+ import { Retry } from 'clutchit/retry';
221
+ import { Schedule } from 'clutchit/schedule';
222
+
223
+ const policy = new Retry.Policy({
224
+ schedule: Schedule.exponential(200).pipe(Schedule.recurs(3)),
225
+ retryOn: (error) => Fault.isTransient(error),
226
+ onRetry: (error, attempt, delay) => {
227
+ console.log(`Retry #${attempt} in ${delay}ms: ${error.code}`);
228
+ },
229
+ });
230
+
231
+ const result = policy.execute((signal) => fetchData(signal));
232
+ ```
233
+
234
+ ### RetryOptions
235
+
236
+ | Option | Type | Default |
237
+ |--------|------|---------|
238
+ | `schedule` | `Schedule` | `exponential(200) \| recurs(3)` |
239
+ | `retryOn` | `(error: E) => boolean` | Retries transient errors (`_transient === true`) |
240
+ | `onRetry` | `(error, attempt, delay) => void` | — |
241
+ | `signal` | `AbortSignal` | — |
242
+
243
+ Per-call options override constructor defaults.
244
+
245
+ ---
246
+
247
+ ## `clutchit/timeout`
248
+
249
+ Wrap async operations with a timeout. Propagates cancellation via `AbortSignal`.
250
+
251
+ ```typescript
252
+ import { Timeout } from 'clutchit/timeout';
253
+
254
+ // Inline
255
+ const result = Timeout.wrap(
256
+ (signal) => fetchData(signal),
257
+ 5000,
258
+ );
259
+ // ResultAsync<Data, FetchFault | TimeoutFault>
260
+
261
+ // Reusable wrapper
262
+ const withTimeout = Timeout.create(5000);
263
+ const result = withTimeout((signal) => fetchData(signal));
264
+ ```
265
+
266
+ `TimeoutFault` is `transient: true` — it will be retried by default when composed with `Retry.Policy`.
267
+
268
+ ---
269
+
270
+ ## `clutchit/concurrency`
271
+
272
+ ### Semaphore
273
+
274
+ Limit concurrent access to a resource:
275
+
276
+ ```typescript
277
+ import { Concurrency } from 'clutchit/concurrency';
278
+
279
+ const sem = new Concurrency.Semaphore({ permits: 3 });
280
+
281
+ const result = sem.execute(() => fetchData());
282
+
283
+ sem.available; // remaining permits
284
+ sem.waiting; // queued requests
285
+ ```
286
+
287
+ ### RateLimiter
288
+
289
+ Sliding-window rate limiting:
290
+
291
+ ```typescript
292
+ const limiter = new Concurrency.RateLimiter({
293
+ limit: 100,
294
+ window: 60_000, // 100 requests per minute
295
+ });
296
+
297
+ const result = limiter.execute(() => callApi());
298
+ // Err(RateLimitFault) if limit exceeded
299
+
300
+ limiter.remaining; // available capacity
301
+ ```
302
+
303
+ `RateLimitFault` is `transient: true` with a `retryAfter` field.
304
+
305
+ ### Bulkhead
306
+
307
+ Isolate failures by limiting concurrent executions with an optional queue:
308
+
309
+ ```typescript
310
+ const bulkhead = new Concurrency.Bulkhead({
311
+ concurrency: 5,
312
+ queue: 10, // default: 0
313
+ });
314
+
315
+ const result = bulkhead.execute(() => processRequest());
316
+ // Err(BulkheadRejectedFault) if capacity + queue full
317
+
318
+ bulkhead.activeCount; // currently executing
319
+ bulkhead.queueSize; // waiting in queue
320
+ ```
321
+
322
+ ### Ref
323
+
324
+ Thread-safe mutable reference (atomic via internal mutex):
325
+
326
+ ```typescript
327
+ const counter = new Concurrency.Ref(0);
328
+
329
+ await counter.update((n) => n + 1);
330
+ await counter.set(0);
331
+
332
+ const prev = await counter.modify((n) => [n + 1, n]); // [newValue, result]
333
+ counter.get(); // current value (non-atomic read)
334
+ ```
335
+
336
+ ---
337
+
338
+ ## `clutchit/circuit`
339
+
340
+ Circuit breaker with three states: **closed** (normal), **open** (rejecting), **half-open** (testing recovery).
341
+
342
+ ```typescript
343
+ import { Circuit } from 'clutchit/circuit';
344
+ import { Schedule } from 'clutchit/schedule';
345
+
346
+ const breaker = new Circuit.Breaker({
347
+ failureThreshold: 5,
348
+ resetSchedule: Schedule.exponential(30_000).pipe(Schedule.cappedDelay(300_000)),
349
+ halfOpenAttempts: 1,
350
+ isFailure: (error) => error.code !== 'NOT_FOUND',
351
+ onStateChange: (from, to) => console.log(`Circuit: ${from} -> ${to}`),
352
+ });
353
+
354
+ const result = breaker.protect(() => callService());
355
+ // Err(CircuitOpenFault) when circuit is open
356
+
357
+ breaker.state; // 'closed' | 'open' | 'half-open'
358
+ breaker.failureCount; // consecutive failures
359
+ breaker.reset(); // force-close
360
+ ```
361
+
362
+ ### State Machine
363
+
364
+ ```
365
+ CLOSED ──(failures >= threshold)──> OPEN
366
+ OPEN ──(reset delay expires)───> HALF-OPEN
367
+ HALF-OPEN ──(success)────────────> CLOSED
368
+ HALF-OPEN ──(failure)────────────> OPEN (progressive delay increase)
369
+ ```
370
+
371
+ ### CircuitBreakerOptions
372
+
373
+ | Option | Type | Default |
374
+ |--------|------|---------|
375
+ | `failureThreshold` | `number` | `5` |
376
+ | `resetSchedule` | `Schedule` | `constant(30_000)` |
377
+ | `halfOpenAttempts` | `number` | `1` |
378
+ | `isFailure` | `(error) => boolean` | All errors count |
379
+ | `onStateChange` | `(from, to) => void` | — |
380
+
381
+ ---
382
+
383
+ ## `clutchit/queue`
384
+
385
+ In-memory bounded queues with backpressure. All blocking operations support `AbortSignal`.
386
+
387
+ ### BoundedQueue
388
+
389
+ Both producers and consumers can block:
390
+
391
+ ```typescript
392
+ import { Queue } from 'clutchit/queue';
393
+
394
+ const q = new Queue.Bounded<Job>({ capacity: 100 });
395
+
396
+ q.offer(job); // Result<void, QueueFullFault> — non-blocking
397
+ await q.put(job); // blocks until space available
398
+ await q.put(job, signal); // cancellable
399
+
400
+ const item = await q.take(); // blocks until item available
401
+ const batch = await q.takeBatch(10); // at least 1, up to 10
402
+ const maybe = q.poll(); // Result<T, QueueEmptyFault> — non-blocking
403
+ ```
404
+
405
+ ### DroppingQueue
406
+
407
+ Drops the **newest** item when full. Producer never blocks:
408
+
409
+ ```typescript
410
+ const q = new Queue.Dropping<Event>({ capacity: 1000 });
411
+
412
+ q.offer(event); // boolean — true if accepted, false if dropped
413
+ ```
414
+
415
+ ### SlidingQueue
416
+
417
+ Drops the **oldest** item when full. Producer never blocks:
418
+
419
+ ```typescript
420
+ const q = new Queue.Sliding<Event>({ capacity: 1000 });
421
+
422
+ q.offer(event); // void — always accepts, evicts oldest if needed
423
+ ```
424
+
425
+ ---
426
+
427
+ ## Fault Model
428
+
429
+ Faults are classes created via `Fault.Tagged(code, category, transient?)`. Each class serves as both a value (constructor) and a type — no separate `type X = ReturnType<typeof X>` needed. Discriminated via `code` string literal and `instanceof`.
430
+
431
+ ### Defining Faults
432
+
433
+ ```typescript
434
+ import { Fault } from 'clutchit/unthrow';
435
+
436
+ // Domain fault with fields
437
+ class NotFoundFault extends Fault.Tagged('NOT_FOUND', 'domain')<{
438
+ resource: string;
439
+ id: string;
440
+ }>() {
441
+ readonly message = `${this.resource} "${this.id}" not found`;
442
+ }
443
+
444
+ // Infrastructure fault, no fields
445
+ class ServiceDownFault extends Fault.Tagged('SERVICE_DOWN', 'infrastructure')() {
446
+ readonly message = 'Service is unavailable';
447
+ }
448
+
449
+ // Transient fault (eligible for automatic retry)
450
+ class TimeoutFault extends Fault.Tagged('TIMEOUT', 'infrastructure', true)<{
451
+ duration: number;
452
+ }>() {
453
+ readonly message = `Operation timed out after ${this.duration}ms`;
454
+ }
455
+
456
+ // Usage
457
+ const fault = new NotFoundFault({ resource: 'User', id: '123' });
458
+ fault.code; // 'NOT_FOUND'
459
+ fault.resource; // 'User'
460
+ fault instanceof NotFoundFault; // true
461
+ ```
462
+
463
+ ### Categories
464
+
465
+ | Category | `_category` | `_transient` | Meaning |
466
+ |----------|------------|-------------|---------|
467
+ | Domain | `'domain'` | `false` | Business rule violation — not retryable |
468
+ | Infrastructure | `'infrastructure'` | `false` | System failure — not retryable |
469
+ | Transient | `'infrastructure'` | `true` | Temporary failure — safe to retry |
470
+
471
+ ### Type Guards
472
+
473
+ ```typescript
474
+ Fault.isTransient(fault); // infrastructure + transient
475
+ Fault.isDomain(fault); // domain fault
476
+ Fault.isInfrastructure(fault); // infrastructure (transient or not)
477
+ ```
478
+
479
+ ### Built-in Faults
480
+
481
+ | Fault | Code | Transient | Module |
482
+ |-------|------|-----------|--------|
483
+ | `TimeoutFault` | `TIMEOUT` | yes | `clutchit/timeout` |
484
+ | `RateLimitFault` | `RATE_LIMITED` | yes | `clutchit/concurrency` |
485
+ | `CircuitOpenFault` | `CIRCUIT_OPEN` | no | `clutchit/circuit` |
486
+ | `BulkheadRejectedFault` | `BULKHEAD_REJECTED` | no | `clutchit/concurrency` |
487
+ | `ScheduleInterruptedFault` | `SCHEDULE_INTERRUPTED` | no | `clutchit/schedule` |
488
+ | `QueueFullFault` | `QUEUE_FULL` | no | `clutchit/queue` |
489
+ | `QueueEmptyFault` | `QUEUE_EMPTY` | no | `clutchit/queue` |
490
+
491
+ ---
492
+
493
+ ## Composing Primitives
494
+
495
+ Retry, timeout, and circuit breaker compose naturally:
496
+
497
+ ```typescript
498
+ import { Retry } from 'clutchit/retry';
499
+ import { Timeout } from 'clutchit/timeout';
500
+ import { Circuit } from 'clutchit/circuit';
501
+ import { Schedule } from 'clutchit/schedule';
502
+
503
+ const retry = new Retry.Policy({
504
+ schedule: Schedule.exponential(200).pipe(
505
+ Schedule.recurs(3),
506
+ Schedule.cappedDelay(5000),
507
+ Schedule.jittered(),
508
+ ),
509
+ });
510
+
511
+ const breaker = new Circuit.Breaker({
512
+ failureThreshold: 5,
513
+ resetSchedule: Schedule.exponential(30_000),
514
+ });
515
+
516
+ function callService() {
517
+ return breaker.protect(() =>
518
+ Timeout.wrap(
519
+ (signal) => retry.execute(
520
+ (signal) => doRequest(signal),
521
+ ),
522
+ 10_000,
523
+ ),
524
+ );
525
+ }
526
+ ```
527
+
528
+ ## License
529
+
530
+ [MIT](./LICENSE)
@@ -1,16 +1,19 @@
1
1
  import { ResultAsync } from '../unthrow/index.js';
2
2
  import { Schedule } from '../schedule/index.js';
3
3
  export type CircuitState = 'closed' | 'open' | 'half-open';
4
- export declare const CircuitOpenFault: (remainingTimeout: number) => Readonly<{
5
- code: "CIRCUIT_OPEN";
6
- _category: "infrastructure";
7
- _transient: false;
8
- message: string;
9
- } & Omit<{
10
- message: string;
4
+ declare const CircuitOpenFault_base: abstract new (fields: {
11
5
  remainingTimeout: number;
12
- }, "message">>;
13
- export type CircuitOpenFault = ReturnType<typeof CircuitOpenFault>;
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
+ }
14
17
  export interface CircuitBreakerOptions {
15
18
  /** Number of consecutive failures before opening. Default: 5 */
16
19
  failureThreshold?: number;
@@ -44,4 +47,5 @@ export declare class CircuitBreaker {
44
47
  private scheduleReset;
45
48
  private transition;
46
49
  }
50
+ export {};
47
51
  //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -1,13 +1,8 @@
1
1
  import { Fault, ResultAsync } from '../unthrow/index.js';
2
2
  import { Schedule } from '../schedule/index.js';
3
- export const CircuitOpenFault = Fault.define({
4
- code: 'CIRCUIT_OPEN',
5
- category: 'infrastructure',
6
- create: (remainingTimeout) => ({
7
- message: `Circuit breaker is open. Retry after ${remainingTimeout}ms`,
8
- remainingTimeout,
9
- }),
10
- });
3
+ export class CircuitOpenFault extends Fault.Tagged('CIRCUIT_OPEN', 'infrastructure')() {
4
+ message = `Circuit breaker is open. Retry after ${this.remainingTimeout}ms`;
5
+ }
11
6
  export class CircuitBreaker {
12
7
  _state = 'closed';
13
8
  _failureCount = 0;
@@ -33,15 +28,15 @@ export class CircuitBreaker {
33
28
  this._halfOpenTrials = 0;
34
29
  }
35
30
  else {
36
- const remaining = this._nextResetTime
31
+ const remainingTimeout = this._nextResetTime
37
32
  ? Math.max(0, this._nextResetTime - Date.now())
38
33
  : 0;
39
- return ResultAsync.err(CircuitOpenFault(remaining));
34
+ return ResultAsync.err(new CircuitOpenFault({ remainingTimeout }));
40
35
  }
41
36
  }
42
37
  if (this._state === 'half-open') {
43
38
  if (this._halfOpenTrials >= this._maxHalfOpenAttempts) {
44
- return ResultAsync.err(CircuitOpenFault(0));
39
+ return ResultAsync.err(new CircuitOpenFault({ remainingTimeout: 0 }));
45
40
  }
46
41
  this._halfOpenTrials++;
47
42
  }
@@ -1,6 +1,6 @@
1
1
  import { CircuitBreaker as _CircuitBreaker } from './circuit-breaker.js';
2
2
  export type { CircuitState, CircuitBreakerOptions } from './circuit-breaker.js';
3
- export type { CircuitOpenFault } from './circuit-breaker.js';
3
+ export { CircuitOpenFault } from './circuit-breaker.js';
4
4
  export declare namespace Circuit {
5
5
  const Breaker: typeof _CircuitBreaker;
6
6
  }
@@ -1,4 +1,5 @@
1
1
  import { CircuitBreaker as _CircuitBreaker } from './circuit-breaker.js';
2
+ export { CircuitOpenFault } from './circuit-breaker.js';
2
3
  // eslint-disable-next-line @typescript-eslint/no-namespace
3
4
  export var Circuit;
4
5
  (function (Circuit) {
@@ -1,13 +1,13 @@
1
1
  import { ResultAsync } from '../unthrow/index.js';
2
- export declare const BulkheadRejectedFault: () => Readonly<{
3
- code: "BULKHEAD_REJECTED";
4
- _category: "infrastructure";
5
- _transient: false;
6
- message: string;
7
- } & Omit<{
8
- message: string;
9
- }, "message">>;
10
- export type BulkheadRejectedFault = ReturnType<typeof BulkheadRejectedFault>;
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
11
  export interface BulkheadOptions {
12
12
  concurrency: number;
13
13
  queue?: number;
@@ -24,4 +24,5 @@ export declare class Bulkhead {
24
24
  private acquire;
25
25
  private release;
26
26
  }
27
+ export {};
27
28
  //# sourceMappingURL=bulkhead.d.ts.map
@@ -1,11 +1,7 @@
1
1
  import { Fault, ResultAsync } from '../unthrow/index.js';
2
- export const BulkheadRejectedFault = Fault.define({
3
- code: 'BULKHEAD_REJECTED',
4
- category: 'infrastructure',
5
- create: () => ({
6
- message: 'Bulkhead capacity exceeded',
7
- }),
8
- });
2
+ export class BulkheadRejectedFault extends Fault.Tagged('BULKHEAD_REJECTED', 'infrastructure')() {
3
+ message = 'Bulkhead capacity exceeded';
4
+ }
9
5
  export class Bulkhead {
10
6
  _active = 0;
11
7
  _maxConcurrency;
@@ -18,7 +14,7 @@ export class Bulkhead {
18
14
  execute(fn) {
19
15
  if (this._active >= this._maxConcurrency &&
20
16
  this._queue.length >= this._maxQueue) {
21
- return ResultAsync.err(BulkheadRejectedFault());
17
+ return ResultAsync.err(new BulkheadRejectedFault());
22
18
  }
23
19
  return ResultAsync.fromPromise((async () => {
24
20
  await this.acquire();
@@ -4,9 +4,9 @@ import { Bulkhead as _Bulkhead } from './bulkhead.js';
4
4
  import { Ref as _Ref } from './ref.js';
5
5
  export type { SemaphoreOptions } from './semaphore.js';
6
6
  export type { RateLimiterOptions } from './rate-limiter.js';
7
- export type { RateLimitFault } from './rate-limiter.js';
7
+ export { RateLimitFault } from './rate-limiter.js';
8
8
  export type { BulkheadOptions } from './bulkhead.js';
9
- export type { BulkheadRejectedFault } from './bulkhead.js';
9
+ export { BulkheadRejectedFault } from './bulkhead.js';
10
10
  export declare namespace Concurrency {
11
11
  const Semaphore: typeof _Semaphore;
12
12
  const RateLimiter: typeof _RateLimiter;
@@ -2,6 +2,8 @@ import { Semaphore as _Semaphore } from './semaphore.js';
2
2
  import { RateLimiter as _RateLimiter } from './rate-limiter.js';
3
3
  import { Bulkhead as _Bulkhead } from './bulkhead.js';
4
4
  import { Ref as _Ref } from './ref.js';
5
+ export { RateLimitFault } from './rate-limiter.js';
6
+ export { BulkheadRejectedFault } from './bulkhead.js';
5
7
  // eslint-disable-next-line @typescript-eslint/no-namespace
6
8
  export var Concurrency;
7
9
  (function (Concurrency) {
@@ -1,14 +1,17 @@
1
1
  import { ResultAsync } from '../unthrow/index.js';
2
- export declare const RateLimitFault: (retryAfter: number) => Readonly<{
3
- code: "RATE_LIMITED";
4
- _category: "infrastructure";
5
- _transient: true;
6
- message: string;
7
- } & Omit<{
8
- message: string;
2
+ declare const RateLimitFault_base: abstract new (fields: {
9
3
  retryAfter: number;
10
- }, "message">>;
11
- export type RateLimitFault = ReturnType<typeof RateLimitFault>;
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
+ }
12
15
  export interface RateLimiterOptions {
13
16
  limit: number;
14
17
  window: number;
@@ -22,4 +25,5 @@ export declare class RateLimiter {
22
25
  get remaining(): number;
23
26
  private cleanup;
24
27
  }
28
+ export {};
25
29
  //# sourceMappingURL=rate-limiter.d.ts.map
@@ -1,13 +1,7 @@
1
1
  import { Fault, ResultAsync } from '../unthrow/index.js';
2
- export const RateLimitFault = Fault.define({
3
- code: 'RATE_LIMITED',
4
- category: 'infrastructure',
5
- transient: true,
6
- create: (retryAfter) => ({
7
- message: `Rate limit exceeded. Retry after ${retryAfter}ms`,
8
- retryAfter,
9
- }),
10
- });
2
+ export class RateLimitFault extends Fault.Tagged('RATE_LIMITED', 'infrastructure', true)() {
3
+ message = `Rate limit exceeded. Retry after ${this.retryAfter}ms`;
4
+ }
11
5
  export class RateLimiter {
12
6
  _limit;
13
7
  _window;
@@ -21,7 +15,7 @@ export class RateLimiter {
21
15
  if (this._timestamps.length >= this._limit) {
22
16
  const oldest = this._timestamps[0];
23
17
  const retryAfter = Math.max(0, oldest + this._window - Date.now());
24
- return ResultAsync.err(RateLimitFault(retryAfter));
18
+ return ResultAsync.err(new RateLimitFault({ retryAfter }));
25
19
  }
26
20
  this._timestamps.push(Date.now());
27
21
  return fn();
@@ -11,7 +11,7 @@ export class BaseQueue {
11
11
  if (this._buffer.length > 0) {
12
12
  return Result.ok(this._buffer.shift());
13
13
  }
14
- return Result.err(QueueEmptyFault());
14
+ return Result.err(new QueueEmptyFault());
15
15
  }
16
16
  take(signal) {
17
17
  const taken = this._tryTake();
@@ -4,8 +4,8 @@ import { BaseQueue, type QueueOptions } from './base.queue.js';
4
4
  export declare class BoundedQueue<T> extends BaseQueue<T> {
5
5
  private readonly _putWaiters;
6
6
  constructor(options: QueueOptions);
7
- protected _tryTake(): Result<T, ReturnType<typeof QueueEmptyFault>>;
8
- offer(item: T): Result<void, ReturnType<typeof QueueFullFault>>;
7
+ protected _tryTake(): Result<T, QueueEmptyFault>;
8
+ offer(item: T): Result<void, QueueFullFault>;
9
9
  put(item: T, signal?: AbortSignal): ResultAsync<void, never>;
10
10
  }
11
11
  //# sourceMappingURL=bounded.queue.d.ts.map
@@ -23,7 +23,7 @@ export class BoundedQueue extends BaseQueue {
23
23
  pw.resolve();
24
24
  return Result.ok(pw.item);
25
25
  }
26
- return Result.err(QueueEmptyFault());
26
+ return Result.err(new QueueEmptyFault());
27
27
  }
28
28
  offer(item) {
29
29
  // Give directly to a waiting consumer
@@ -33,7 +33,7 @@ export class BoundedQueue extends BaseQueue {
33
33
  return Result.ok(undefined);
34
34
  }
35
35
  if (this._buffer.length >= this._capacity) {
36
- return Result.err(QueueFullFault(this._capacity));
36
+ return Result.err(new QueueFullFault({ capacity: this._capacity }));
37
37
  }
38
38
  this._buffer.push(item);
39
39
  return Result.ok(undefined);
@@ -1,20 +1,24 @@
1
- export declare const QueueFullFault: (capacity: number) => Readonly<{
2
- code: "QUEUE_FULL";
3
- _category: "infrastructure";
4
- _transient: false;
5
- message: string;
6
- } & Omit<{
7
- message: string;
1
+ declare const QueueFullFault_base: abstract new (fields: {
8
2
  capacity: number;
9
- }, "message">>;
10
- export type QueueFullFault = ReturnType<typeof QueueFullFault>;
11
- export declare const QueueEmptyFault: () => Readonly<{
12
- code: "QUEUE_EMPTY";
13
- _category: "infrastructure";
14
- _transient: false;
15
- message: string;
16
- } & Omit<{
17
- message: string;
18
- }, "message">>;
19
- export type QueueEmptyFault = ReturnType<typeof QueueEmptyFault>;
3
+ }) => Readonly<{
4
+ capacity: number;
5
+ }> & {
6
+ readonly code: "QUEUE_FULL";
7
+ readonly _category: "infrastructure";
8
+ readonly _transient: false;
9
+ readonly message: string;
10
+ };
11
+ export declare class QueueFullFault extends QueueFullFault_base {
12
+ readonly message: string;
13
+ }
14
+ declare const QueueEmptyFault_base: abstract new () => Readonly<Record<string, unknown>> & {
15
+ readonly code: "QUEUE_EMPTY";
16
+ readonly _category: "infrastructure";
17
+ readonly _transient: false;
18
+ readonly message: string;
19
+ };
20
+ export declare class QueueEmptyFault extends QueueEmptyFault_base {
21
+ readonly message = "Queue is empty";
22
+ }
23
+ export {};
20
24
  //# sourceMappingURL=faults.d.ts.map
@@ -1,17 +1,8 @@
1
1
  import { Fault } from '../unthrow/index.js';
2
- export const QueueFullFault = Fault.define({
3
- code: 'QUEUE_FULL',
4
- category: 'infrastructure',
5
- create: (capacity) => ({
6
- message: `Queue is full (capacity: ${capacity})`,
7
- capacity,
8
- }),
9
- });
10
- export const QueueEmptyFault = Fault.define({
11
- code: 'QUEUE_EMPTY',
12
- category: 'infrastructure',
13
- create: () => ({
14
- message: 'Queue is empty',
15
- }),
16
- });
2
+ export class QueueFullFault extends Fault.Tagged('QUEUE_FULL', 'infrastructure')() {
3
+ message = `Queue is full (capacity: ${this.capacity})`;
4
+ }
5
+ export class QueueEmptyFault extends Fault.Tagged('QUEUE_EMPTY', 'infrastructure')() {
6
+ message = 'Queue is empty';
7
+ }
17
8
  //# sourceMappingURL=faults.js.map
@@ -2,7 +2,7 @@ import { BoundedQueue as _BoundedQueue } from './bounded.queue.js';
2
2
  import { DroppingQueue as _DroppingQueue } from './dropping.queue.js';
3
3
  import { SlidingQueue as _SlidingQueue } from './sliding.queue.js';
4
4
  export type { QueueOptions } from './base.queue.js';
5
- export type { QueueFullFault, QueueEmptyFault } from './faults.js';
5
+ export { QueueFullFault, QueueEmptyFault } from './faults.js';
6
6
  export declare namespace Queue {
7
7
  const Bounded: typeof _BoundedQueue;
8
8
  const Dropping: typeof _DroppingQueue;
@@ -1,6 +1,7 @@
1
1
  import { BoundedQueue as _BoundedQueue } from './bounded.queue.js';
2
2
  import { DroppingQueue as _DroppingQueue } from './dropping.queue.js';
3
3
  import { SlidingQueue as _SlidingQueue } from './sliding.queue.js';
4
+ export { QueueFullFault, QueueEmptyFault } from './faults.js';
4
5
  // eslint-disable-next-line @typescript-eslint/no-namespace
5
6
  export var Queue;
6
7
  (function (Queue) {
@@ -15,6 +15,9 @@ export declare namespace Schedule {
15
15
  const forever: ScheduleClass<unknown, number>;
16
16
  const once: ScheduleClass<unknown, number>;
17
17
  const fibonacci: (one: number) => ScheduleClass;
18
+ const cron: (expression: string, options?: {
19
+ timezone?: string;
20
+ }) => ScheduleClass;
18
21
  const recurs: typeof _recurs;
19
22
  const cappedDelay: typeof _cappedDelay;
20
23
  const jittered: typeof _jittered;
@@ -14,6 +14,7 @@ export var Schedule;
14
14
  Schedule.forever = ScheduleClass.forever;
15
15
  Schedule.once = ScheduleClass.once;
16
16
  Schedule.fibonacci = ScheduleClass.fibonacci;
17
+ Schedule.cron = ScheduleClass.cron;
17
18
  Schedule.recurs = _recurs;
18
19
  Schedule.cappedDelay = _cappedDelay;
19
20
  Schedule.jittered = _jittered;
@@ -1,14 +1,14 @@
1
1
  import { ResultAsync } from '../unthrow/index.js';
2
2
  import type { Schedule } from './schedule.js';
3
- export declare const ScheduleInterruptedFault: () => Readonly<{
4
- code: "SCHEDULE_INTERRUPTED";
5
- _category: "infrastructure";
6
- _transient: false;
7
- message: string;
8
- } & Omit<{
9
- message: string;
10
- }, "message">>;
11
- export type ScheduleInterruptedFault = ReturnType<typeof ScheduleInterruptedFault>;
3
+ declare const ScheduleInterruptedFault_base: abstract new () => Readonly<Record<string, unknown>> & {
4
+ readonly code: "SCHEDULE_INTERRUPTED";
5
+ readonly _category: "infrastructure";
6
+ readonly _transient: false;
7
+ readonly message: string;
8
+ };
9
+ export declare class ScheduleInterruptedFault extends ScheduleInterruptedFault_base {
10
+ readonly message = "Scheduled execution was interrupted";
11
+ }
12
12
  export interface RepeatOptions {
13
13
  /** External cancellation signal. Aborts the loop when signalled. */
14
14
  signal?: AbortSignal;
@@ -27,4 +27,5 @@ export interface RepeatOptions {
27
27
  * Returns the last schedule output on completion.
28
28
  */
29
29
  export declare function repeat<Out, E>(schedule: Schedule<unknown, Out>, fn: (signal: AbortSignal) => ResultAsync<void, E>, options?: RepeatOptions): ResultAsync<Out, E | ScheduleInterruptedFault>;
30
+ export {};
30
31
  //# sourceMappingURL=runner.d.ts.map
@@ -1,11 +1,7 @@
1
1
  import { Fault, ResultAsync } from '../unthrow/index.js';
2
- export const ScheduleInterruptedFault = Fault.define({
3
- code: 'SCHEDULE_INTERRUPTED',
4
- category: 'infrastructure',
5
- create: () => ({
6
- message: 'Scheduled execution was interrupted',
7
- }),
8
- });
2
+ export class ScheduleInterruptedFault extends Fault.Tagged('SCHEDULE_INTERRUPTED', 'infrastructure')() {
3
+ message = 'Scheduled execution was interrupted';
4
+ }
9
5
  const NEVER_ABORT = new AbortController().signal;
10
6
  function sleep(ms, signal) {
11
7
  return new Promise((resolve) => {
@@ -38,7 +34,7 @@ export function repeat(schedule, fn, options) {
38
34
  for (let attempt = 0;; attempt++) {
39
35
  // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
40
36
  if (signal?.aborted)
41
- throw ScheduleInterruptedFault();
37
+ throw new ScheduleInterruptedFault();
42
38
  const result = await fn(signal ?? NEVER_ABORT);
43
39
  // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
44
40
  if (result.isErr())
@@ -50,7 +46,7 @@ export function repeat(schedule, fn, options) {
50
46
  await sleep(step.delay, signal);
51
47
  // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
52
48
  if (signal?.aborted)
53
- throw ScheduleInterruptedFault();
49
+ throw new ScheduleInterruptedFault();
54
50
  }
55
51
  })(), (e) => e);
56
52
  }
@@ -27,5 +27,9 @@ export declare class Schedule<In = unknown, Out = number> {
27
27
  static once: Schedule;
28
28
  /** Fibonacci delay sequence: one, one, 2*one, 3*one, 5*one, 8*one, ... */
29
29
  static fibonacci: (one: number) => Schedule;
30
+ /** Cron expression schedule (aligned to cron windows) */
31
+ static cron: (expression: string, options?: {
32
+ timezone?: string;
33
+ }) => Schedule;
30
34
  }
31
35
  //# sourceMappingURL=schedule.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { Cron } from 'croner';
1
2
  export class Schedule {
2
3
  _next;
3
4
  constructor(_next) {
@@ -90,5 +91,16 @@ export class Schedule {
90
91
  return { delay, continue: true, output: attempt };
91
92
  });
92
93
  };
94
+ /** Cron expression schedule (aligned to cron windows) */
95
+ static cron = (expression, options) => {
96
+ const pattern = new Cron(expression, options?.timezone ? { timezone: options.timezone } : undefined);
97
+ return new Schedule((_input, attempt) => {
98
+ const next = pattern.nextRun();
99
+ if (!next)
100
+ return { delay: 0, continue: false, output: attempt };
101
+ const delay = Math.max(0, next.getTime() - Date.now());
102
+ return { delay, continue: true, output: attempt };
103
+ });
104
+ };
93
105
  }
94
106
  //# sourceMappingURL=schedule.js.map
@@ -1,16 +1,8 @@
1
- import { withTimeout as _withTimeout, createTimeout as _createTimeout } from './timeout.js';
2
- export type { TimeoutFault } from './timeout.js';
1
+ import { TimeoutFault as _TimeoutFault, withTimeout as _withTimeout, createTimeout as _createTimeout } from './timeout.js';
2
+ export { TimeoutFault } from './timeout.js';
3
3
  export declare namespace Timeout {
4
4
  const wrap: typeof _withTimeout;
5
5
  const create: typeof _createTimeout;
6
- const Fault: (duration: number) => Readonly<{
7
- code: "TIMEOUT";
8
- _category: "infrastructure";
9
- _transient: true;
10
- message: string;
11
- } & Omit<{
12
- message: string;
13
- duration: number;
14
- }, "message">>;
6
+ const Fault: typeof _TimeoutFault;
15
7
  }
16
8
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { TimeoutFault as _TimeoutFault, withTimeout as _withTimeout, createTimeout as _createTimeout, } from './timeout.js';
2
+ export { TimeoutFault } from './timeout.js';
2
3
  // eslint-disable-next-line @typescript-eslint/no-namespace
3
4
  export var Timeout;
4
5
  (function (Timeout) {
@@ -1,14 +1,18 @@
1
1
  import { ResultAsync } from '../unthrow/index.js';
2
- export declare const TimeoutFault: (duration: number) => Readonly<{
3
- code: "TIMEOUT";
4
- _category: "infrastructure";
5
- _transient: true;
6
- message: string;
7
- } & Omit<{
8
- message: string;
2
+ declare const TimeoutFault_base: abstract new (fields: {
9
3
  duration: number;
10
- }, "message">>;
11
- export type TimeoutFault = ReturnType<typeof TimeoutFault>;
4
+ }) => Readonly<{
5
+ duration: number;
6
+ }> & {
7
+ readonly code: "TIMEOUT";
8
+ readonly _category: "infrastructure";
9
+ readonly _transient: true;
10
+ readonly message: string;
11
+ };
12
+ export declare class TimeoutFault extends TimeoutFault_base {
13
+ readonly message: string;
14
+ }
12
15
  export declare function withTimeout<T, E>(fn: (signal: AbortSignal) => ResultAsync<T, E>, duration: number): ResultAsync<T, E | TimeoutFault>;
13
16
  export declare function createTimeout(duration: number): <T, E>(fn: (signal: AbortSignal) => ResultAsync<T, E>) => ResultAsync<T, E | TimeoutFault>;
17
+ export {};
14
18
  //# sourceMappingURL=timeout.d.ts.map
@@ -1,13 +1,7 @@
1
1
  import { Fault, ResultAsync } from '../unthrow/index.js';
2
- export const TimeoutFault = Fault.define({
3
- code: 'TIMEOUT',
4
- category: 'infrastructure',
5
- transient: true,
6
- create: (duration) => ({
7
- message: `Operation timed out after ${duration}ms`,
8
- duration,
9
- }),
10
- });
2
+ export class TimeoutFault extends Fault.Tagged('TIMEOUT', 'infrastructure', true)() {
3
+ message = `Operation timed out after ${this.duration}ms`;
4
+ }
11
5
  export function withTimeout(fn, duration) {
12
6
  const controller = new AbortController();
13
7
  return ResultAsync.fromPromise(
@@ -15,7 +9,7 @@ export function withTimeout(fn, duration) {
15
9
  new Promise((resolve, reject) => {
16
10
  const timer = setTimeout(() => {
17
11
  controller.abort();
18
- reject(TimeoutFault(duration));
12
+ reject(new TimeoutFault({ duration }));
19
13
  }, duration);
20
14
  void fn(controller.signal).then((result) => {
21
15
  clearTimeout(timer);
@@ -4,34 +4,19 @@ export interface Fault {
4
4
  readonly _category: 'domain' | 'infrastructure';
5
5
  readonly _transient: boolean;
6
6
  }
7
- export type DomainFault = Fault & {
7
+ export declare function isDomainFault(error: Fault): error is Fault & {
8
8
  readonly _category: 'domain';
9
- readonly _transient: false;
10
9
  };
11
- export type InfrastructureFault = Fault & {
10
+ export declare function isInfrastructureFault(error: Fault): error is Fault & {
12
11
  readonly _category: 'infrastructure';
13
12
  };
14
- export type TransientFault = InfrastructureFault & {
13
+ export declare function isTransientFault(error: Fault): error is Fault & {
15
14
  readonly _transient: true;
16
15
  };
17
- export declare function isTransient(error: Fault): error is TransientFault;
18
- export declare function isDomainFault(error: Fault): error is DomainFault;
19
- export declare function isInfrastructureFault(error: Fault): error is InfrastructureFault;
20
- type InferTransient<O> = O extends {
21
- transient: infer T extends boolean;
22
- } ? T : false;
23
- export declare function defineFault<const O extends {
24
- code: string;
25
- category: 'domain' | 'infrastructure';
26
- transient?: boolean;
27
- create: (...args: any[]) => {
28
- message: string;
29
- };
30
- }>(options: O): (...args: Parameters<O['create']>) => Readonly<{
31
- code: O['code'];
32
- _category: O['category'];
33
- _transient: InferTransient<O>;
34
- message: string;
35
- } & Omit<ReturnType<O['create']>, 'message'>>;
36
- export {};
16
+ export declare function Fault<const Code extends string, const Category extends 'domain' | 'infrastructure', const Transient extends boolean = false>(code: Code, category: Category, transient?: Transient): <A extends Record<string, unknown> = Record<string, unknown>>() => abstract new (...args: Record<string, unknown> extends A ? [] : [fields: A]) => Readonly<A> & {
17
+ readonly code: Code;
18
+ readonly _category: Category;
19
+ readonly _transient: Transient;
20
+ readonly message: string;
21
+ };
37
22
  //# sourceMappingURL=fault.d.ts.map
@@ -1,22 +1,27 @@
1
- export function isTransient(error) {
2
- return error._transient && error._category === 'infrastructure';
3
- }
4
1
  export function isDomainFault(error) {
5
2
  return error._category === 'domain';
6
3
  }
7
4
  export function isInfrastructureFault(error) {
8
5
  return error._category === 'infrastructure';
9
6
  }
10
- export function defineFault(options) {
11
- const transient = (options.transient ?? false);
12
- return (...args) => {
13
- const extra = options.create(...args);
14
- return Object.freeze({
15
- code: options.code,
16
- _category: options.category,
17
- _transient: transient,
18
- ...extra,
19
- });
7
+ export function isTransientFault(error) {
8
+ return error._transient && error._category === 'infrastructure';
9
+ }
10
+ export function Fault(code, category, transient) {
11
+ const _transient = (transient ?? false);
12
+ return () => {
13
+ class FaultImpl {
14
+ code = code;
15
+ _category = category;
16
+ _transient = _transient;
17
+ constructor(...args) {
18
+ const fields = args[0];
19
+ if (fields) {
20
+ Object.assign(this, fields);
21
+ }
22
+ }
23
+ }
24
+ return FaultImpl;
20
25
  };
21
26
  }
22
27
  //# sourceMappingURL=fault.js.map
@@ -1,8 +1,7 @@
1
1
  import { ResultAsync as ResultAsyncClass, okAsync, errAsync } from './result.async.js';
2
2
  import { DoAsync } from './do.js';
3
- import { defineFault, isDomainFault, isInfrastructureFault } from './fault.js';
3
+ import { Fault as _Fault, isDomainFault as _isDomainFault, isInfrastructureFault as _isInfrastructureFault, isTransientFault as _isTransientFault } from './fault.js';
4
4
  export { Ok, Err } from './result.js';
5
- export type { DomainFault, InfrastructureFault, TransientFault, } from './fault.js';
6
5
  export type Result<T, E> = import('./result.js').Ok<T, E> | import('./result.js').Err<T, E>;
7
6
  type _Result<T, E> = Result<T, E>;
8
7
  export declare namespace Result {
@@ -24,9 +23,9 @@ export declare namespace ResultAsync {
24
23
  }
25
24
  export type Fault = import('./fault.js').Fault;
26
25
  export declare namespace Fault {
27
- const define: typeof defineFault;
28
- const isTransient: typeof import("./fault.js").isTransient;
29
- const isDomain: typeof isDomainFault;
30
- const isInfrastructure: typeof isInfrastructureFault;
26
+ const Tagged: typeof _Fault;
27
+ const isDomain: typeof _isDomainFault;
28
+ const isInfrastructure: typeof _isInfrastructureFault;
29
+ const isTransient: typeof _isTransientFault;
31
30
  }
32
31
  //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,7 @@ import { ok, err } from './result.js';
2
2
  import { okAsync, errAsync, } from './result.async.js';
3
3
  import { fromPromise, fromThrowable, combine as combineFn, combineWithAllErrors as combineWithAllErrorsFn, } from './helpers.js';
4
4
  import { Do, DoAsync } from './do.js';
5
- import { defineFault, isTransient, isDomainFault, isInfrastructureFault, } from './fault.js';
5
+ import { Fault as _Fault, isDomainFault as _isDomainFault, isInfrastructureFault as _isInfrastructureFault, isTransientFault as _isTransientFault, } from './fault.js';
6
6
  // Re-export instance types (needed in user signatures)
7
7
  export { Ok, Err } from './result.js';
8
8
  // Internal aliases (must precede namespaces that reference them)
@@ -10,7 +10,6 @@ const _ok = ok;
10
10
  const _err = err;
11
11
  const _fromThrowable = fromThrowable;
12
12
  const _fromPromise = fromPromise;
13
- const _isTransient = isTransient;
14
13
  const _Do = Do;
15
14
  // eslint-disable-next-line @typescript-eslint/no-namespace
16
15
  export var Result;
@@ -47,9 +46,9 @@ export var ResultAsync;
47
46
  // eslint-disable-next-line @typescript-eslint/no-namespace
48
47
  export var Fault;
49
48
  (function (Fault) {
50
- Fault.define = defineFault;
51
- Fault.isTransient = _isTransient;
52
- Fault.isDomain = isDomainFault;
53
- Fault.isInfrastructure = isInfrastructureFault;
49
+ Fault.Tagged = _Fault;
50
+ Fault.isDomain = _isDomainFault;
51
+ Fault.isInfrastructure = _isInfrastructureFault;
52
+ Fault.isTransient = _isTransientFault;
54
53
  })(Fault || (Fault = {}));
55
54
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clutchit",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "hexac",
@@ -94,6 +94,9 @@
94
94
  "node": ">=20",
95
95
  "bun": ">=1"
96
96
  },
97
+ "dependencies": {
98
+ "croner": "^10.0.1"
99
+ },
97
100
  "scripts": {
98
101
  "build": "rimraf dist && tsc -p tsconfig.build.json",
99
102
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",