clutchit 0.0.3

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 (58) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -0
  4. package/dist/circuit/circuit-breaker.d.ts +47 -0
  5. package/dist/circuit/circuit-breaker.js +114 -0
  6. package/dist/circuit/index.d.ts +7 -0
  7. package/dist/circuit/index.js +7 -0
  8. package/dist/concurrency/bulkhead.d.ts +27 -0
  9. package/dist/concurrency/bulkhead.js +62 -0
  10. package/dist/concurrency/index.d.ts +16 -0
  11. package/dist/concurrency/index.js +13 -0
  12. package/dist/concurrency/rate-limiter.d.ts +25 -0
  13. package/dist/concurrency/rate-limiter.js +40 -0
  14. package/dist/concurrency/ref.d.ts +11 -0
  15. package/dist/concurrency/ref.js +26 -0
  16. package/dist/concurrency/semaphore.d.ts +15 -0
  17. package/dist/concurrency/semaphore.js +48 -0
  18. package/dist/queue/base.queue.d.ts +18 -0
  19. package/dist/queue/base.queue.js +66 -0
  20. package/dist/queue/bounded.queue.d.ts +11 -0
  21. package/dist/queue/bounded.queue.js +64 -0
  22. package/dist/queue/dropping.queue.d.ts +7 -0
  23. package/dist/queue/dropping.queue.js +20 -0
  24. package/dist/queue/faults.d.ts +20 -0
  25. package/dist/queue/faults.js +17 -0
  26. package/dist/queue/index.d.ts +11 -0
  27. package/dist/queue/index.js +11 -0
  28. package/dist/queue/sliding.queue.d.ts +7 -0
  29. package/dist/queue/sliding.queue.js +19 -0
  30. package/dist/retry/index.d.ts +6 -0
  31. package/dist/retry/index.js +7 -0
  32. package/dist/retry/retry.d.ts +21 -0
  33. package/dist/retry/retry.js +58 -0
  34. package/dist/schedule/index.d.ts +30 -0
  35. package/dist/schedule/index.js +29 -0
  36. package/dist/schedule/operators.d.ts +22 -0
  37. package/dist/schedule/operators.js +106 -0
  38. package/dist/schedule/runner.d.ts +30 -0
  39. package/dist/schedule/runner.js +57 -0
  40. package/dist/schedule/schedule.d.ts +31 -0
  41. package/dist/schedule/schedule.js +94 -0
  42. package/dist/timeout/index.d.ts +16 -0
  43. package/dist/timeout/index.js +9 -0
  44. package/dist/timeout/timeout.d.ts +14 -0
  45. package/dist/timeout/timeout.js +37 -0
  46. package/dist/unthrow/do.d.ts +5 -0
  47. package/dist/unthrow/do.js +23 -0
  48. package/dist/unthrow/fault.d.ts +37 -0
  49. package/dist/unthrow/fault.js +22 -0
  50. package/dist/unthrow/helpers.d.ts +9 -0
  51. package/dist/unthrow/helpers.js +56 -0
  52. package/dist/unthrow/index.d.ts +32 -0
  53. package/dist/unthrow/index.js +55 -0
  54. package/dist/unthrow/result.async.d.ts +19 -0
  55. package/dist/unthrow/result.async.js +63 -0
  56. package/dist/unthrow/result.d.ts +38 -0
  57. package/dist/unthrow/result.js +80 -0
  58. package/package.json +109 -0
@@ -0,0 +1,20 @@
1
+ import { BaseQueue } from './base.queue.js';
2
+ export class DroppingQueue extends BaseQueue {
3
+ constructor(options) {
4
+ super(options);
5
+ }
6
+ /** Always succeeds. Drops the item if full. Returns whether item was accepted. */
7
+ offer(item) {
8
+ const waiter = this._takeWaiters.shift();
9
+ if (waiter) {
10
+ waiter(item);
11
+ return true;
12
+ }
13
+ if (this._buffer.length >= this._capacity) {
14
+ return false;
15
+ }
16
+ this._buffer.push(item);
17
+ return true;
18
+ }
19
+ }
20
+ //# sourceMappingURL=dropping.queue.js.map
@@ -0,0 +1,20 @@
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;
8
+ 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>;
20
+ //# sourceMappingURL=faults.d.ts.map
@@ -0,0 +1,17 @@
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
+ });
17
+ //# sourceMappingURL=faults.js.map
@@ -0,0 +1,11 @@
1
+ import { BoundedQueue as _BoundedQueue } from './bounded.queue.js';
2
+ import { DroppingQueue as _DroppingQueue } from './dropping.queue.js';
3
+ import { SlidingQueue as _SlidingQueue } from './sliding.queue.js';
4
+ export type { QueueOptions } from './base.queue.js';
5
+ export type { QueueFullFault, QueueEmptyFault } from './faults.js';
6
+ export declare namespace Queue {
7
+ const Bounded: typeof _BoundedQueue;
8
+ const Dropping: typeof _DroppingQueue;
9
+ const Sliding: typeof _SlidingQueue;
10
+ }
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { BoundedQueue as _BoundedQueue } from './bounded.queue.js';
2
+ import { DroppingQueue as _DroppingQueue } from './dropping.queue.js';
3
+ import { SlidingQueue as _SlidingQueue } from './sliding.queue.js';
4
+ // eslint-disable-next-line @typescript-eslint/no-namespace
5
+ export var Queue;
6
+ (function (Queue) {
7
+ Queue.Bounded = _BoundedQueue;
8
+ Queue.Dropping = _DroppingQueue;
9
+ Queue.Sliding = _SlidingQueue;
10
+ })(Queue || (Queue = {}));
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { BaseQueue, type QueueOptions } from './base.queue.js';
2
+ export declare class SlidingQueue<T> extends BaseQueue<T> {
3
+ constructor(options: QueueOptions);
4
+ /** Always succeeds. Drops oldest if full. */
5
+ offer(item: T): void;
6
+ }
7
+ //# sourceMappingURL=sliding.queue.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { BaseQueue } from './base.queue.js';
2
+ export class SlidingQueue extends BaseQueue {
3
+ constructor(options) {
4
+ super(options);
5
+ }
6
+ /** Always succeeds. Drops oldest if full. */
7
+ offer(item) {
8
+ const waiter = this._takeWaiters.shift();
9
+ if (waiter) {
10
+ waiter(item);
11
+ return;
12
+ }
13
+ if (this._buffer.length >= this._capacity) {
14
+ this._buffer.shift();
15
+ }
16
+ this._buffer.push(item);
17
+ }
18
+ }
19
+ //# sourceMappingURL=sliding.queue.js.map
@@ -0,0 +1,6 @@
1
+ import { RetryPolicy as _RetryPolicy } from './retry.js';
2
+ export type { RetryOptions } from './retry.js';
3
+ export declare namespace Retry {
4
+ const Policy: typeof _RetryPolicy;
5
+ }
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { RetryPolicy as _RetryPolicy } from './retry.js';
2
+ // eslint-disable-next-line @typescript-eslint/no-namespace
3
+ export var Retry;
4
+ (function (Retry) {
5
+ Retry.Policy = _RetryPolicy;
6
+ })(Retry || (Retry = {}));
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ import { Schedule } from '../schedule/index.js';
3
+ export interface RetryOptions<E = unknown> {
4
+ /** Schedule for backoff timing. Default: exponential(200) | recurs(3) */
5
+ schedule?: Schedule;
6
+ /**
7
+ * Predicate to decide if an error should be retried.
8
+ * Default: retries transient errors (error._transient === true).
9
+ */
10
+ retryOn?: (error: E) => boolean;
11
+ /** Called on each retry attempt (for logging/observability). */
12
+ onRetry?: (error: E, attempt: number, delay: number) => void;
13
+ /** External cancellation signal. Aborts retrying when signalled. */
14
+ signal?: AbortSignal;
15
+ }
16
+ export declare class RetryPolicy {
17
+ private readonly defaults?;
18
+ constructor(defaults?: RetryOptions | undefined);
19
+ execute<T, E>(fn: (signal: AbortSignal) => ResultAsync<T, E>, options?: RetryOptions<E>): ResultAsync<T, E>;
20
+ }
21
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
2
+ import { Schedule } from '../schedule/index.js';
3
+ const NEVER_ABORT = new AbortController().signal;
4
+ const DEFAULT_SCHEDULE = Schedule.exponential(200).pipe(Schedule.recurs(3));
5
+ const defaultRetryOn = (error) => typeof error === 'object' &&
6
+ error !== null &&
7
+ '_transient' in error &&
8
+ error['_transient'] === true;
9
+ function sleep(ms, signal) {
10
+ return new Promise((resolve) => {
11
+ if (ms <= 0 || signal?.aborted) {
12
+ resolve();
13
+ return;
14
+ }
15
+ const timer = setTimeout(resolve, ms);
16
+ signal?.addEventListener('abort', () => {
17
+ clearTimeout(timer);
18
+ resolve();
19
+ }, { once: true });
20
+ });
21
+ }
22
+ export class RetryPolicy {
23
+ defaults;
24
+ constructor(defaults) {
25
+ this.defaults = defaults;
26
+ }
27
+ execute(fn, options) {
28
+ const schedule = options?.schedule ?? this.defaults?.schedule ?? DEFAULT_SCHEDULE;
29
+ const retryOn = options?.retryOn ??
30
+ this.defaults?.retryOn ??
31
+ defaultRetryOn;
32
+ const onRetry = options?.onRetry ??
33
+ this.defaults?.onRetry;
34
+ const signal = options?.signal ?? this.defaults?.signal;
35
+ return ResultAsync.fromPromise((async () => {
36
+ let lastError;
37
+ for (let attempt = 0;; attempt++) {
38
+ const result = await fn(signal ?? NEVER_ABORT);
39
+ if (result.isOk())
40
+ return result.value;
41
+ lastError = result.error;
42
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
43
+ if (signal?.aborted)
44
+ throw lastError;
45
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
46
+ if (!retryOn(lastError))
47
+ throw lastError;
48
+ const step = schedule.next(lastError, attempt);
49
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
50
+ if (!step.continue)
51
+ throw lastError;
52
+ onRetry?.(lastError, attempt, step.delay);
53
+ await sleep(step.delay, signal);
54
+ }
55
+ })(), (e) => e);
56
+ }
57
+ }
58
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1,30 @@
1
+ import { Schedule as ScheduleClass } from './schedule.js';
2
+ import { recurs as _recurs, cappedDelay as _cappedDelay, jittered as _jittered, upTo as _upTo, andThen as _andThen, union as _union, intersect as _intersect, whileInput as _whileInput, whileOutput as _whileOutput, map as _map } from './operators.js';
3
+ import { repeat as _repeat } from './runner.js';
4
+ export type { ScheduleStep, ScheduleOperator } from './schedule.js';
5
+ export { ScheduleInterruptedFault } from './runner.js';
6
+ export type { RepeatOptions } from './runner.js';
7
+ export type Schedule<In = unknown, Out = number> = ScheduleClass<In, Out>;
8
+ export declare namespace Schedule {
9
+ const spaced: (ms: number) => ScheduleClass;
10
+ const exponential: (initialDelay: number, multiplier?: number) => ScheduleClass;
11
+ const linear: (initialDelay: number) => ScheduleClass;
12
+ const constant: (delay: number) => ScheduleClass;
13
+ const fixed: (interval: number) => ScheduleClass;
14
+ const windowed: (interval: number) => ScheduleClass;
15
+ const forever: ScheduleClass<unknown, number>;
16
+ const once: ScheduleClass<unknown, number>;
17
+ const fibonacci: (one: number) => ScheduleClass;
18
+ const recurs: typeof _recurs;
19
+ const cappedDelay: typeof _cappedDelay;
20
+ const jittered: typeof _jittered;
21
+ const upTo: typeof _upTo;
22
+ const andThen: typeof _andThen;
23
+ const union: typeof _union;
24
+ const intersect: typeof _intersect;
25
+ const whileInput: typeof _whileInput;
26
+ const whileOutput: typeof _whileOutput;
27
+ const map: typeof _map;
28
+ const repeat: typeof _repeat;
29
+ }
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { Schedule as ScheduleClass } from './schedule.js';
2
+ import { recurs as _recurs, cappedDelay as _cappedDelay, jittered as _jittered, upTo as _upTo, andThen as _andThen, union as _union, intersect as _intersect, whileInput as _whileInput, whileOutput as _whileOutput, map as _map, } from './operators.js';
3
+ import { repeat as _repeat } from './runner.js';
4
+ export { ScheduleInterruptedFault } from './runner.js';
5
+ // eslint-disable-next-line @typescript-eslint/no-namespace
6
+ export var Schedule;
7
+ (function (Schedule) {
8
+ Schedule.spaced = ScheduleClass.spaced;
9
+ Schedule.exponential = ScheduleClass.exponential;
10
+ Schedule.linear = ScheduleClass.linear;
11
+ Schedule.constant = ScheduleClass.constant;
12
+ Schedule.fixed = ScheduleClass.fixed;
13
+ Schedule.windowed = ScheduleClass.windowed;
14
+ Schedule.forever = ScheduleClass.forever;
15
+ Schedule.once = ScheduleClass.once;
16
+ Schedule.fibonacci = ScheduleClass.fibonacci;
17
+ Schedule.recurs = _recurs;
18
+ Schedule.cappedDelay = _cappedDelay;
19
+ Schedule.jittered = _jittered;
20
+ Schedule.upTo = _upTo;
21
+ Schedule.andThen = _andThen;
22
+ Schedule.union = _union;
23
+ Schedule.intersect = _intersect;
24
+ Schedule.whileInput = _whileInput;
25
+ Schedule.whileOutput = _whileOutput;
26
+ Schedule.map = _map;
27
+ Schedule.repeat = _repeat;
28
+ })(Schedule || (Schedule = {}));
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,22 @@
1
+ import { Schedule, type ScheduleOperator } from './schedule.js';
2
+ /** Limit total repetitions to n */
3
+ export declare function recurs(n: number): ScheduleOperator;
4
+ /** Cap individual delay to maxDelay ms */
5
+ export declare function cappedDelay(maxDelay: number): ScheduleOperator;
6
+ /** Add random jitter (±factor, default ±0.25) */
7
+ export declare function jittered(factor?: number): ScheduleOperator;
8
+ /** Stop after total elapsed time exceeds duration ms */
9
+ export declare function upTo(duration: number): ScheduleOperator;
10
+ /** Sequential composition: run base until it stops, then switch to next */
11
+ export declare function andThen(next: Schedule): ScheduleOperator;
12
+ /** Continue while either wants to; use shorter delay */
13
+ export declare function union(other: Schedule): ScheduleOperator;
14
+ /** Continue while both want to; use longer delay */
15
+ export declare function intersect(other: Schedule): ScheduleOperator;
16
+ /** Continue while the input satisfies the predicate */
17
+ export declare function whileInput<In>(predicate: (input: In) => boolean): <Out>(schedule: Schedule<In, Out>) => Schedule<In, Out>;
18
+ /** Continue while the output satisfies the predicate */
19
+ export declare function whileOutput<Out>(predicate: (output: Out) => boolean): <In>(schedule: Schedule<In, Out>) => Schedule<In, Out>;
20
+ /** Transform the output value */
21
+ export declare function map<Out, Out2>(f: (output: Out) => Out2): <In>(schedule: Schedule<In, Out>) => Schedule<In, Out2>;
22
+ //# sourceMappingURL=operators.d.ts.map
@@ -0,0 +1,106 @@
1
+ import { Schedule } from './schedule.js';
2
+ /** Limit total repetitions to n */
3
+ export function recurs(n) {
4
+ return (schedule) => new Schedule((input, attempt) => {
5
+ const step = schedule.next(input, attempt);
6
+ if (attempt >= n)
7
+ return { ...step, delay: 0, continue: false };
8
+ return step;
9
+ });
10
+ }
11
+ /** Cap individual delay to maxDelay ms */
12
+ export function cappedDelay(maxDelay) {
13
+ return (schedule) => new Schedule((input, attempt) => {
14
+ const step = schedule.next(input, attempt);
15
+ return { ...step, delay: Math.min(step.delay, maxDelay) };
16
+ });
17
+ }
18
+ /** Add random jitter (±factor, default ±0.25) */
19
+ export function jittered(factor = 0.25) {
20
+ return (schedule) => new Schedule((input, attempt) => {
21
+ const step = schedule.next(input, attempt);
22
+ const jitter = 1 + (Math.random() * 2 - 1) * factor;
23
+ return { ...step, delay: Math.round(step.delay * jitter) };
24
+ });
25
+ }
26
+ /** Stop after total elapsed time exceeds duration ms */
27
+ export function upTo(duration) {
28
+ return (schedule) => {
29
+ const startTime = Date.now();
30
+ return new Schedule((input, attempt) => {
31
+ const step = schedule.next(input, attempt);
32
+ const elapsed = Date.now() - startTime;
33
+ if (elapsed >= duration)
34
+ return { ...step, delay: 0, continue: false };
35
+ return step;
36
+ });
37
+ };
38
+ }
39
+ /** Sequential composition: run base until it stops, then switch to next */
40
+ export function andThen(next) {
41
+ return (schedule) => {
42
+ let switched = false;
43
+ let switchAttempt = 0;
44
+ return new Schedule((input, attempt) => {
45
+ if (!switched) {
46
+ const step = schedule.next(input, attempt);
47
+ if (step.continue)
48
+ return step;
49
+ switched = true;
50
+ switchAttempt = attempt;
51
+ }
52
+ const nextStep = next.next(input, attempt - switchAttempt);
53
+ return { ...nextStep, output: nextStep.output };
54
+ });
55
+ };
56
+ }
57
+ /** Continue while either wants to; use shorter delay */
58
+ export function union(other) {
59
+ return (schedule) => new Schedule((input, attempt) => {
60
+ const a = schedule.next(input, attempt);
61
+ const b = other.next(input, attempt);
62
+ return {
63
+ delay: Math.min(a.delay, b.delay),
64
+ continue: a.continue || b.continue,
65
+ output: a.output,
66
+ };
67
+ });
68
+ }
69
+ /** Continue while both want to; use longer delay */
70
+ export function intersect(other) {
71
+ return (schedule) => new Schedule((input, attempt) => {
72
+ const a = schedule.next(input, attempt);
73
+ const b = other.next(input, attempt);
74
+ return {
75
+ delay: Math.max(a.delay, b.delay),
76
+ continue: a.continue && b.continue,
77
+ output: a.output,
78
+ };
79
+ });
80
+ }
81
+ /** Continue while the input satisfies the predicate */
82
+ export function whileInput(predicate) {
83
+ return (schedule) => new Schedule((input, attempt) => {
84
+ const step = schedule.next(input, attempt);
85
+ if (!predicate(input))
86
+ return { ...step, delay: 0, continue: false };
87
+ return step;
88
+ });
89
+ }
90
+ /** Continue while the output satisfies the predicate */
91
+ export function whileOutput(predicate) {
92
+ return (schedule) => new Schedule((input, attempt) => {
93
+ const step = schedule.next(input, attempt);
94
+ if (!predicate(step.output))
95
+ return { ...step, delay: 0, continue: false };
96
+ return step;
97
+ });
98
+ }
99
+ /** Transform the output value */
100
+ export function map(f) {
101
+ return (schedule) => new Schedule((input, attempt) => {
102
+ const step = schedule.next(input, attempt);
103
+ return { ...step, output: f(step.output) };
104
+ });
105
+ }
106
+ //# sourceMappingURL=operators.js.map
@@ -0,0 +1,30 @@
1
+ import { ResultAsync } from '../unthrow/index.js';
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>;
12
+ export interface RepeatOptions {
13
+ /** External cancellation signal. Aborts the loop when signalled. */
14
+ signal?: AbortSignal;
15
+ /** Called after each execution (for logging/observability). */
16
+ onExecution?: (attempt: number, delay: number) => void;
17
+ }
18
+ /**
19
+ * Execute `fn` repeatedly according to the given schedule's timing.
20
+ *
21
+ * - Executes `fn` immediately (no initial delay).
22
+ * - After each successful execution, consults schedule.next() for the delay and continuation.
23
+ * - If the schedule returns `continue: false`, resolves with Ok(output).
24
+ * - If `fn` returns an Err, stops immediately and surfaces the error.
25
+ * - If the signal is aborted, stops and returns Err(ScheduleInterruptedFault).
26
+ *
27
+ * Returns the last schedule output on completion.
28
+ */
29
+ export declare function repeat<Out, E>(schedule: Schedule<unknown, Out>, fn: (signal: AbortSignal) => ResultAsync<void, E>, options?: RepeatOptions): ResultAsync<Out, E | ScheduleInterruptedFault>;
30
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1,57 @@
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
+ });
9
+ const NEVER_ABORT = new AbortController().signal;
10
+ function sleep(ms, signal) {
11
+ return new Promise((resolve) => {
12
+ if (ms <= 0 || signal?.aborted) {
13
+ resolve();
14
+ return;
15
+ }
16
+ const timer = setTimeout(resolve, ms);
17
+ signal?.addEventListener('abort', () => {
18
+ clearTimeout(timer);
19
+ resolve();
20
+ }, { once: true });
21
+ });
22
+ }
23
+ /**
24
+ * Execute `fn` repeatedly according to the given schedule's timing.
25
+ *
26
+ * - Executes `fn` immediately (no initial delay).
27
+ * - After each successful execution, consults schedule.next() for the delay and continuation.
28
+ * - If the schedule returns `continue: false`, resolves with Ok(output).
29
+ * - If `fn` returns an Err, stops immediately and surfaces the error.
30
+ * - If the signal is aborted, stops and returns Err(ScheduleInterruptedFault).
31
+ *
32
+ * Returns the last schedule output on completion.
33
+ */
34
+ export function repeat(schedule, fn, options) {
35
+ const signal = options?.signal;
36
+ const onExecution = options?.onExecution;
37
+ return ResultAsync.fromPromise((async () => {
38
+ for (let attempt = 0;; attempt++) {
39
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
40
+ if (signal?.aborted)
41
+ throw ScheduleInterruptedFault();
42
+ const result = await fn(signal ?? NEVER_ABORT);
43
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
44
+ if (result.isErr())
45
+ throw result.error;
46
+ const step = schedule.next(undefined, attempt);
47
+ onExecution?.(attempt, step.delay);
48
+ if (!step.continue)
49
+ return step.output;
50
+ await sleep(step.delay, signal);
51
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional: fromPromise catches and maps E
52
+ if (signal?.aborted)
53
+ throw ScheduleInterruptedFault();
54
+ }
55
+ })(), (e) => e);
56
+ }
57
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1,31 @@
1
+ export interface ScheduleStep<Out = number> {
2
+ readonly delay: number;
3
+ readonly continue: boolean;
4
+ readonly output: Out;
5
+ }
6
+ export type ScheduleOperator = <In, Out>(schedule: Schedule<In, Out>) => Schedule<In, Out>;
7
+ export declare class Schedule<In = unknown, Out = number> {
8
+ private readonly _next;
9
+ constructor(_next: (input: In, attempt: number) => ScheduleStep<Out>);
10
+ next(input: In, attempt: number): ScheduleStep<Out>;
11
+ pipe<Out2 = Out>(operator: (schedule: Schedule<In, Out>) => Schedule<In, Out2>): Schedule<In, Out2>;
12
+ /** Fixed delay between executions (from completion to next start) */
13
+ static spaced: (ms: number) => Schedule;
14
+ /** Exponential backoff: initialDelay * multiplier^attempt */
15
+ static exponential: (initialDelay: number, multiplier?: number) => Schedule;
16
+ /** Linear backoff: initialDelay * (attempt + 1) */
17
+ static linear: (initialDelay: number) => Schedule;
18
+ /** Constant delay (alias for spaced) */
19
+ static constant: (delay: number) => Schedule;
20
+ /** Fixed interval from start to start (compensates for execution time) */
21
+ static fixed: (interval: number) => Schedule;
22
+ /** Aligned to wall-clock time windows */
23
+ static windowed: (interval: number) => Schedule;
24
+ /** Zero-delay infinite schedule */
25
+ static forever: Schedule;
26
+ /** Continue once then stop. With repeat: initial + 1 repetition = 2 total executions */
27
+ static once: Schedule;
28
+ /** Fibonacci delay sequence: one, one, 2*one, 3*one, 5*one, 8*one, ... */
29
+ static fibonacci: (one: number) => Schedule;
30
+ }
31
+ //# sourceMappingURL=schedule.d.ts.map
@@ -0,0 +1,94 @@
1
+ export class Schedule {
2
+ _next;
3
+ constructor(_next) {
4
+ this._next = _next;
5
+ }
6
+ next(input, attempt) {
7
+ return this._next(input, attempt);
8
+ }
9
+ pipe(operator) {
10
+ return operator(this);
11
+ }
12
+ // ── Static Factories ─────────────────────────────────────────────
13
+ /** Fixed delay between executions (from completion to next start) */
14
+ static spaced = (ms) => new Schedule((_input, attempt) => ({
15
+ delay: ms,
16
+ continue: true,
17
+ output: attempt,
18
+ }));
19
+ /** Exponential backoff: initialDelay * multiplier^attempt */
20
+ static exponential = (initialDelay, multiplier = 2) => new Schedule((_input, attempt) => ({
21
+ delay: initialDelay * multiplier ** attempt,
22
+ continue: true,
23
+ output: attempt,
24
+ }));
25
+ /** Linear backoff: initialDelay * (attempt + 1) */
26
+ static linear = (initialDelay) => new Schedule((_input, attempt) => ({
27
+ delay: initialDelay * (attempt + 1),
28
+ continue: true,
29
+ output: attempt,
30
+ }));
31
+ /** Constant delay (alias for spaced) */
32
+ static constant = (delay) => new Schedule((_input, attempt) => ({
33
+ delay,
34
+ continue: true,
35
+ output: attempt,
36
+ }));
37
+ /** Fixed interval from start to start (compensates for execution time) */
38
+ static fixed = (interval) => {
39
+ let lastCallTime = null;
40
+ let lastDelay = 0;
41
+ return new Schedule((_input, attempt) => {
42
+ const now = Date.now();
43
+ if (lastCallTime === null) {
44
+ lastCallTime = now;
45
+ lastDelay = interval;
46
+ return { delay: interval, continue: true, output: attempt };
47
+ }
48
+ const elapsed = now - lastCallTime;
49
+ const execTime = Math.max(0, elapsed - lastDelay);
50
+ const delay = Math.max(0, interval - execTime);
51
+ lastCallTime = now;
52
+ lastDelay = delay;
53
+ return { delay, continue: true, output: attempt };
54
+ });
55
+ };
56
+ /** Aligned to wall-clock time windows */
57
+ static windowed = (interval) => {
58
+ return new Schedule((_input, attempt) => {
59
+ const now = Date.now();
60
+ const nextWindow = Math.ceil(now / interval) * interval;
61
+ const delay = nextWindow - now;
62
+ return {
63
+ delay: delay === 0 ? interval : delay,
64
+ continue: true,
65
+ output: attempt,
66
+ };
67
+ });
68
+ };
69
+ /** Zero-delay infinite schedule */
70
+ static forever = new Schedule((_input, attempt) => ({
71
+ delay: 0,
72
+ continue: true,
73
+ output: attempt,
74
+ }));
75
+ /** Continue once then stop. With repeat: initial + 1 repetition = 2 total executions */
76
+ static once = new Schedule((_input, attempt) => ({
77
+ delay: 0,
78
+ continue: attempt < 1,
79
+ output: attempt,
80
+ }));
81
+ /** Fibonacci delay sequence: one, one, 2*one, 3*one, 5*one, 8*one, ... */
82
+ static fibonacci = (one) => {
83
+ let prev = 0;
84
+ let curr = one;
85
+ return new Schedule((_input, attempt) => {
86
+ const delay = curr;
87
+ const next = prev + curr;
88
+ prev = curr;
89
+ curr = next;
90
+ return { delay, continue: true, output: attempt };
91
+ });
92
+ };
93
+ }
94
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1,16 @@
1
+ import { withTimeout as _withTimeout, createTimeout as _createTimeout } from './timeout.js';
2
+ export type { TimeoutFault } from './timeout.js';
3
+ export declare namespace Timeout {
4
+ const wrap: typeof _withTimeout;
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">>;
15
+ }
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { TimeoutFault as _TimeoutFault, withTimeout as _withTimeout, createTimeout as _createTimeout, } from './timeout.js';
2
+ // eslint-disable-next-line @typescript-eslint/no-namespace
3
+ export var Timeout;
4
+ (function (Timeout) {
5
+ Timeout.wrap = _withTimeout;
6
+ Timeout.create = _createTimeout;
7
+ Timeout.Fault = _TimeoutFault;
8
+ })(Timeout || (Timeout = {}));
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
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;
9
+ duration: number;
10
+ }, "message">>;
11
+ export type TimeoutFault = ReturnType<typeof TimeoutFault>;
12
+ export declare function withTimeout<T, E>(fn: (signal: AbortSignal) => ResultAsync<T, E>, duration: number): ResultAsync<T, E | TimeoutFault>;
13
+ export declare function createTimeout(duration: number): <T, E>(fn: (signal: AbortSignal) => ResultAsync<T, E>) => ResultAsync<T, E | TimeoutFault>;
14
+ //# sourceMappingURL=timeout.d.ts.map