@anishhs/retryq 1.1.0 → 1.2.0

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/README.md +142 -19
  2. package/dist/cjs/index.d.ts +9 -0
  3. package/dist/cjs/index.d.ts.map +1 -0
  4. package/dist/cjs/index.js +13 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/manager.d.ts +139 -0
  7. package/dist/cjs/manager.d.ts.map +1 -0
  8. package/dist/cjs/manager.js +431 -0
  9. package/dist/cjs/manager.js.map +1 -0
  10. package/dist/cjs/package.json +3 -0
  11. package/dist/cjs/types.d.ts +237 -0
  12. package/dist/cjs/types.d.ts.map +1 -0
  13. package/dist/cjs/types.js +3 -0
  14. package/dist/cjs/types.js.map +1 -0
  15. package/dist/cjs/utils.d.ts +43 -0
  16. package/dist/cjs/utils.d.ts.map +1 -0
  17. package/dist/cjs/utils.js +71 -0
  18. package/dist/cjs/utils.js.map +1 -0
  19. package/dist/cjs/validation.d.ts +37 -0
  20. package/dist/cjs/validation.d.ts.map +1 -0
  21. package/dist/cjs/validation.js +69 -0
  22. package/dist/cjs/validation.js.map +1 -0
  23. package/dist/esm/index.d.ts +9 -0
  24. package/dist/esm/index.d.ts.map +1 -0
  25. package/dist/esm/index.js +8 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/manager.d.ts +139 -0
  28. package/dist/esm/manager.d.ts.map +1 -0
  29. package/dist/esm/manager.js +427 -0
  30. package/dist/esm/manager.js.map +1 -0
  31. package/dist/esm/package.json +3 -0
  32. package/dist/esm/types.d.ts +237 -0
  33. package/dist/esm/types.d.ts.map +1 -0
  34. package/dist/esm/types.js +2 -0
  35. package/dist/esm/types.js.map +1 -0
  36. package/dist/esm/utils.d.ts +43 -0
  37. package/dist/esm/utils.d.ts.map +1 -0
  38. package/dist/esm/utils.js +64 -0
  39. package/dist/esm/utils.js.map +1 -0
  40. package/dist/esm/validation.d.ts +37 -0
  41. package/dist/esm/validation.d.ts.map +1 -0
  42. package/dist/esm/validation.js +65 -0
  43. package/dist/esm/validation.js.map +1 -0
  44. package/package.json +29 -12
  45. package/dist/index.d.ts +0 -85
  46. package/dist/index.js +0 -293
  47. package/dist/index.js.map +0 -1
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Lifecycle state of a job managed by {@link RetryQManager}.
3
+ *
4
+ * ```
5
+ * pending → running → completed
6
+ * → failed
7
+ * → cancelled
8
+ * ```
9
+ */
10
+ export type JobState = "pending" | "running" | "completed" | "failed" | "cancelled";
11
+ /**
12
+ * Information passed to {@link RetryQJobOptions.onRetry} and emitted with the
13
+ * manager's `retry` event each time an attempt fails and another is scheduled.
14
+ */
15
+ export interface RetryInfo {
16
+ /** 1-based index of the attempt that just failed. */
17
+ attempt: number;
18
+ /** The error thrown by the failed attempt. */
19
+ error: unknown;
20
+ /** Delay in milliseconds before the next attempt runs (after jitter/caps). */
21
+ nextDelay: number;
22
+ /** Number of attempts still remaining after this failure. */
23
+ retriesLeft: number;
24
+ }
25
+ /**
26
+ * Per-job configuration accepted by {@link RetryQManager.createJob}.
27
+ *
28
+ * @typeParam T - Resolved value produced by the job function.
29
+ */
30
+ export type RetryQJobOptions<T = unknown> = {
31
+ /**
32
+ * Number of retries **after** the initial attempt (total attempts =
33
+ * `retries + 1`). Must be between `0` and `100`.
34
+ * @defaultValue 3
35
+ */
36
+ retries?: number;
37
+ /**
38
+ * Initial delay between attempts, in milliseconds. Must be `>= 0`.
39
+ * @defaultValue 1000
40
+ */
41
+ delay?: number;
42
+ /**
43
+ * Multiplier applied to the delay after each failed attempt (exponential
44
+ * backoff). Must be `>= 1`.
45
+ * @defaultValue 2
46
+ */
47
+ backoff?: number;
48
+ /**
49
+ * Total time budget for the job across all attempts, in milliseconds. Now
50
+ * enforced **during** execution: an in-flight attempt is aborted once the
51
+ * budget is exhausted. Must be `> 0`.
52
+ * @defaultValue 30000
53
+ */
54
+ maxTime?: number;
55
+ /**
56
+ * Upper bound for a single backoff delay, in milliseconds. Prevents the
57
+ * exponential delay from growing without limit. Must be `>= 0`.
58
+ * @defaultValue Infinity
59
+ */
60
+ maxDelay?: number;
61
+ /**
62
+ * Maximum duration of a single attempt, in milliseconds. The attempt is
63
+ * aborted (and counts as a failure) if it exceeds this. The effective bound
64
+ * is `min(attemptTimeout, remaining maxTime)`. Must be `> 0` when provided.
65
+ * @defaultValue Infinity
66
+ */
67
+ attemptTimeout?: number;
68
+ /**
69
+ * Random variation applied to each delay, as a fraction between `0` and `1`
70
+ * (e.g. `0.1` = ±10%).
71
+ * @defaultValue 0.1
72
+ */
73
+ jitter?: number;
74
+ /**
75
+ * Human-readable identifier used for grouping/lookup via
76
+ * {@link RetryQManager.findJobsByLabel}. Defaults to the generated job id.
77
+ */
78
+ label?: string;
79
+ /**
80
+ * Queue priority — higher values are dispatched before lower ones.
81
+ * @defaultValue 1
82
+ */
83
+ priority?: number;
84
+ /**
85
+ * External {@link AbortSignal}. Aborting it force-cancels the job.
86
+ */
87
+ signal?: AbortSignal;
88
+ /**
89
+ * Predicate deciding whether a thrown error should be retried. Return `false`
90
+ * to stop immediately and mark the job `failed`. When omitted, every error is
91
+ * retried until attempts are exhausted.
92
+ *
93
+ * @param error - The error thrown by the attempt.
94
+ * @param attempt - 1-based index of the attempt that just failed.
95
+ */
96
+ shouldRetry?: (error: unknown, attempt: number) => boolean;
97
+ /** Called after each failed attempt that schedules another try. */
98
+ onRetry?: (info: RetryInfo) => void;
99
+ /** Called once when the job completes successfully. */
100
+ onSuccess?: (result: T) => void;
101
+ /** Called once when the job fails after exhausting retries (or `shouldRetry`). */
102
+ onFailure?: (error: unknown) => void;
103
+ /** Called once when the job is cancelled. */
104
+ onCancel?: () => void;
105
+ };
106
+ /** Configuration for the {@link RetryQManager} itself. */
107
+ export type RetryQManagerConfig = {
108
+ /**
109
+ * Maximum number of jobs allowed to run concurrently.
110
+ * @defaultValue Infinity
111
+ */
112
+ maxConcurrent?: number;
113
+ /**
114
+ * Maximum number of jobs retained per terminal-state history map
115
+ * (completed/failed/cancelled) before the oldest are evicted (LRU).
116
+ * @defaultValue 1000
117
+ */
118
+ maxHistorySize?: number;
119
+ };
120
+ /**
121
+ * A unit of work tracked by {@link RetryQManager}.
122
+ *
123
+ * @typeParam T - Resolved value produced by the job function.
124
+ */
125
+ export interface RetryQJob<T = unknown> {
126
+ /** Unique identifier generated at creation. */
127
+ id: string;
128
+ /** Human-readable label (defaults to {@link RetryQJob.id}). */
129
+ label: string;
130
+ /** Current {@link JobState}. */
131
+ state: JobState;
132
+ /** Queue priority (higher runs first). */
133
+ priority: number;
134
+ /** Remaining attempts (initialised to `retries + 1`). */
135
+ retriesLeft: number;
136
+ /** Promise resolving with the job result or rejecting with the last error. */
137
+ promise: Promise<T>;
138
+ /**
139
+ * Cancel this job.
140
+ * @param force - When `true`, aborts the in-flight attempt via its signal.
141
+ */
142
+ cancel: (force?: boolean) => void;
143
+ /** The async function executed (receives an {@link AbortSignal}). */
144
+ fn: (signal?: AbortSignal) => Promise<T>;
145
+ /** Resolved options the job was created with. */
146
+ options: RetryQJobOptions<T>;
147
+ /** Creation timestamp (ms since epoch). */
148
+ createdAt: number;
149
+ /** Timestamp execution started (ms since epoch), if started. */
150
+ startedAt?: number;
151
+ /** Timestamp the job reached a terminal state (ms since epoch), if finished. */
152
+ finishedAt?: number;
153
+ /** Last error encountered, if any. */
154
+ error?: unknown;
155
+ /** Internal controller used to abort in-flight execution. */
156
+ abortController?: AbortController;
157
+ }
158
+ /** Compact view of a job returned by {@link RetryQManager.listJobs}. */
159
+ export interface JobSummary {
160
+ /** Job id. */
161
+ id: string;
162
+ /** Job label. */
163
+ label: string;
164
+ /** Current state. */
165
+ state: JobState;
166
+ /** Remaining attempts. */
167
+ retriesLeft: number;
168
+ /** Queue priority. */
169
+ priority: number;
170
+ }
171
+ /** Snapshot of all jobs grouped by state, returned by {@link RetryQManager.listJobs}. */
172
+ export interface JobListSnapshot {
173
+ /** Jobs queued and waiting for a slot. */
174
+ pending: JobSummary[];
175
+ /** Jobs currently executing. */
176
+ running: JobSummary[];
177
+ /** Jobs that exhausted retries (or were rejected by `shouldRetry`). */
178
+ failed: JobSummary[];
179
+ /** Jobs that completed successfully. */
180
+ completed: JobSummary[];
181
+ /** Jobs that were cancelled. */
182
+ cancelled: JobSummary[];
183
+ }
184
+ /**
185
+ * Internal no-op callback augmented with a {@link CancelableFunction.cancelSleep}
186
+ * hook that interrupts the delay between retries when a job is cancelled.
187
+ */
188
+ export interface CancelableFunction {
189
+ (): void;
190
+ /** Interrupts the in-progress retry delay, if any. */
191
+ cancelSleep?: () => void;
192
+ }
193
+ /**
194
+ * Payload emitted with the manager's `retry` event.
195
+ */
196
+ export interface RetryEvent {
197
+ /** The job being retried. */
198
+ job: RetryQJob;
199
+ /** Details of the failure and the scheduled retry. */
200
+ info: RetryInfo;
201
+ }
202
+ /** Payload emitted with the manager's `success` event. */
203
+ export interface SuccessEvent {
204
+ /** The job that completed. */
205
+ job: RetryQJob;
206
+ /** The resolved result. */
207
+ result: unknown;
208
+ }
209
+ /** Payload emitted with the manager's `failure` event. */
210
+ export interface FailureEvent {
211
+ /** The job that failed. */
212
+ job: RetryQJob;
213
+ /** The terminal error. */
214
+ error: unknown;
215
+ }
216
+ /** Payload emitted with the manager's `cancel` event. */
217
+ export interface CancelEvent {
218
+ /** The cancelled job. */
219
+ job: RetryQJob;
220
+ }
221
+ /**
222
+ * Typed event map for {@link RetryQManager}. Keys are event names; values are
223
+ * the corresponding listener signatures.
224
+ */
225
+ export interface RetryQEvents {
226
+ /** Fired after a failed attempt schedules another try. */
227
+ retry: (payload: RetryEvent) => void;
228
+ /** Fired when a job completes successfully. */
229
+ success: (payload: SuccessEvent) => void;
230
+ /** Fired when a job fails terminally. */
231
+ failure: (payload: FailureEvent) => void;
232
+ /** Fired when a job is cancelled. */
233
+ cancel: (payload: CancelEvent) => void;
234
+ /** Fired when the queue transitions to fully idle (no pending/running jobs). */
235
+ idle: () => void;
236
+ }
237
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import type { CancelableFunction } from "./types.js";
2
+ /**
3
+ * Generate a collision-resistant job id.
4
+ *
5
+ * Format: `job-{timestamp}-{counter}-{random1}{random2}`. The counter plus two
6
+ * independent random segments make same-millisecond collisions practically
7
+ * impossible even under high concurrency.
8
+ *
9
+ * @returns A unique job identifier.
10
+ */
11
+ export declare function randomId(): string;
12
+ /**
13
+ * Clamp a number into the inclusive range `[min, max]`.
14
+ *
15
+ * @param value - Value to clamp.
16
+ * @param min - Lower bound.
17
+ * @param max - Upper bound.
18
+ * @returns The clamped value.
19
+ */
20
+ export declare function clamp(value: number, min: number, max: number): number;
21
+ /**
22
+ * Sleep for `ms` milliseconds. If a {@link CancelableFunction} is supplied, a
23
+ * `cancelSleep` hook is attached to it so the delay can be interrupted early
24
+ * (used to abort the wait between retries when a job is cancelled).
25
+ *
26
+ * @param ms - Duration to sleep.
27
+ * @param cancelFn - Optional handle that receives a `cancelSleep` interrupter.
28
+ * @returns A promise that resolves after `ms`, or rejects if interrupted.
29
+ */
30
+ export declare function sleep(ms: number, cancelFn?: CancelableFunction): Promise<void>;
31
+ /**
32
+ * Error thrown when a single attempt exceeds its timeout (either an explicit
33
+ * {@link RetryQJobOptions.attemptTimeout} or the remaining `maxTime` budget).
34
+ */
35
+ export declare class RetryQTimeoutError extends Error {
36
+ /** The timeout, in milliseconds, that was exceeded. */
37
+ readonly timeoutMs: number;
38
+ /**
39
+ * @param timeoutMs - The timeout that was exceeded, in milliseconds.
40
+ */
41
+ constructor(timeoutMs: number);
42
+ }
43
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKrD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAMjC;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9E;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,uDAAuD;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B;;OAEG;gBACS,SAAS,EAAE,MAAM;CAK9B"}
@@ -0,0 +1,64 @@
1
+ /** Monotonic counter mixed into ids to harden against same-millisecond collisions. */
2
+ let idCounter = 0;
3
+ /**
4
+ * Generate a collision-resistant job id.
5
+ *
6
+ * Format: `job-{timestamp}-{counter}-{random1}{random2}`. The counter plus two
7
+ * independent random segments make same-millisecond collisions practically
8
+ * impossible even under high concurrency.
9
+ *
10
+ * @returns A unique job identifier.
11
+ */
12
+ export function randomId() {
13
+ const timestamp = Date.now();
14
+ const counter = (idCounter++ % 10000).toString(36);
15
+ const random1 = Math.random().toString(36).slice(2, 11);
16
+ const random2 = Math.random().toString(36).slice(2, 11);
17
+ return `job-${timestamp}-${counter}-${random1}${random2}`;
18
+ }
19
+ /**
20
+ * Clamp a number into the inclusive range `[min, max]`.
21
+ *
22
+ * @param value - Value to clamp.
23
+ * @param min - Lower bound.
24
+ * @param max - Upper bound.
25
+ * @returns The clamped value.
26
+ */
27
+ export function clamp(value, min, max) {
28
+ return Math.max(min, Math.min(value, max));
29
+ }
30
+ /**
31
+ * Sleep for `ms` milliseconds. If a {@link CancelableFunction} is supplied, a
32
+ * `cancelSleep` hook is attached to it so the delay can be interrupted early
33
+ * (used to abort the wait between retries when a job is cancelled).
34
+ *
35
+ * @param ms - Duration to sleep.
36
+ * @param cancelFn - Optional handle that receives a `cancelSleep` interrupter.
37
+ * @returns A promise that resolves after `ms`, or rejects if interrupted.
38
+ */
39
+ export function sleep(ms, cancelFn) {
40
+ return new Promise((resolve, reject) => {
41
+ const timer = setTimeout(() => resolve(), ms);
42
+ if (cancelFn) {
43
+ cancelFn.cancelSleep = () => {
44
+ clearTimeout(timer);
45
+ reject(new Error("RetryQ job cancelled"));
46
+ };
47
+ }
48
+ });
49
+ }
50
+ /**
51
+ * Error thrown when a single attempt exceeds its timeout (either an explicit
52
+ * {@link RetryQJobOptions.attemptTimeout} or the remaining `maxTime` budget).
53
+ */
54
+ export class RetryQTimeoutError extends Error {
55
+ /**
56
+ * @param timeoutMs - The timeout that was exceeded, in milliseconds.
57
+ */
58
+ constructor(timeoutMs) {
59
+ super(`RetryQ attempt timed out after ${timeoutMs}ms`);
60
+ this.name = "RetryQTimeoutError";
61
+ this.timeoutMs = timeoutMs;
62
+ }
63
+ }
64
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxD,OAAO,OAAO,SAAS,IAAI,OAAO,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,QAA6B;IAC7D,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,WAAW,GAAG,GAAG,EAAE;gBAC1B,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;YAC5C,CAAC,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAI3C;;OAEG;IACH,YAAY,SAAiB;QAC3B,KAAK,CAAC,kCAAkC,SAAS,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,37 @@
1
+ import type { RetryQJobOptions } from "./types.js";
2
+ /** Default values applied to unspecified {@link RetryQJobOptions}. */
3
+ export declare const DEFAULT_OPTIONS: {
4
+ readonly retries: 3;
5
+ readonly delay: 1000;
6
+ readonly backoff: 2;
7
+ readonly maxTime: 30000;
8
+ readonly maxDelay: number;
9
+ readonly attemptTimeout: number;
10
+ readonly jitter: 0.1;
11
+ readonly priority: 1;
12
+ };
13
+ /** Hard cap on retries to guard against accidental denial-of-service. */
14
+ export declare const MAX_RETRIES = 100;
15
+ /**
16
+ * Fully-resolved numeric options used internally by the execution loop. Every
17
+ * field is guaranteed present after {@link resolveOptions}.
18
+ */
19
+ export interface ResolvedOptions {
20
+ retries: number;
21
+ delay: number;
22
+ backoff: number;
23
+ maxTime: number;
24
+ maxDelay: number;
25
+ attemptTimeout: number;
26
+ jitter: number;
27
+ priority: number;
28
+ }
29
+ /**
30
+ * Validate and apply defaults to user-supplied job options.
31
+ *
32
+ * @param options - Raw options passed to {@link RetryQManager.createJob}.
33
+ * @returns The resolved numeric options with all defaults applied.
34
+ * @throws {Error} If any option is outside its allowed range.
35
+ */
36
+ export declare function resolveOptions(options: RetryQJobOptions<any>): ResolvedOptions;
37
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,sEAAsE;AACtE,eAAO,MAAM,eAAe;;;;;;;;;CASlB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,eAAe,CA6C9E"}
@@ -0,0 +1,65 @@
1
+ /** Default values applied to unspecified {@link RetryQJobOptions}. */
2
+ export const DEFAULT_OPTIONS = {
3
+ retries: 3,
4
+ delay: 1000,
5
+ backoff: 2,
6
+ maxTime: 30000,
7
+ maxDelay: Infinity,
8
+ attemptTimeout: Infinity,
9
+ jitter: 0.1,
10
+ priority: 1,
11
+ };
12
+ /** Hard cap on retries to guard against accidental denial-of-service. */
13
+ export const MAX_RETRIES = 100;
14
+ /**
15
+ * Validate and apply defaults to user-supplied job options.
16
+ *
17
+ * @param options - Raw options passed to {@link RetryQManager.createJob}.
18
+ * @returns The resolved numeric options with all defaults applied.
19
+ * @throws {Error} If any option is outside its allowed range.
20
+ */
21
+ export function resolveOptions(options) {
22
+ const retries = options.retries ?? DEFAULT_OPTIONS.retries;
23
+ const delay = options.delay ?? DEFAULT_OPTIONS.delay;
24
+ const backoff = options.backoff ?? DEFAULT_OPTIONS.backoff;
25
+ const maxTime = options.maxTime ?? DEFAULT_OPTIONS.maxTime;
26
+ const maxDelay = options.maxDelay ?? DEFAULT_OPTIONS.maxDelay;
27
+ const attemptTimeout = options.attemptTimeout ?? DEFAULT_OPTIONS.attemptTimeout;
28
+ const jitter = options.jitter ?? DEFAULT_OPTIONS.jitter;
29
+ const priority = options.priority ?? DEFAULT_OPTIONS.priority;
30
+ if (retries < 0) {
31
+ throw new Error("retries must be >= 0");
32
+ }
33
+ if (retries > MAX_RETRIES) {
34
+ throw new Error(`retries cannot exceed ${MAX_RETRIES} (DoS protection)`);
35
+ }
36
+ if (delay < 0) {
37
+ throw new Error("delay must be >= 0");
38
+ }
39
+ if (backoff < 1) {
40
+ throw new Error("backoff must be >= 1");
41
+ }
42
+ if (maxTime <= 0) {
43
+ throw new Error("maxTime must be > 0");
44
+ }
45
+ if (maxDelay < 0) {
46
+ throw new Error("maxDelay must be >= 0");
47
+ }
48
+ if (attemptTimeout <= 0) {
49
+ throw new Error("attemptTimeout must be > 0");
50
+ }
51
+ if (jitter < 0 || jitter > 1) {
52
+ throw new Error("jitter must be between 0 and 1");
53
+ }
54
+ return {
55
+ retries,
56
+ delay,
57
+ backoff,
58
+ maxTime,
59
+ maxDelay,
60
+ attemptTimeout,
61
+ jitter,
62
+ priority,
63
+ };
64
+ }
65
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAEA,sEAAsE;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,cAAc,EAAE,QAAQ;IACxB,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,CAAC;CACH,CAAC;AAEX,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAiB/B;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC;IAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,KAAK,CAAC;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC;IAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC;IAC3D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC;IAC9D,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,eAAe,CAAC,cAAc,CAAC;IAChF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC;IAE9D,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,yBAAyB,WAAW,mBAAmB,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO;QACL,OAAO;QACP,KAAK;QACL,OAAO;QACP,OAAO;QACP,QAAQ;QACR,cAAc;QACd,MAAM;QACN,QAAQ;KACT,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,21 +1,38 @@
1
1
  {
2
2
  "name": "@anishhs/retryq",
3
- "version": "1.1.0",
4
- "description": "Production-ready retry queue with force cancellation, priorities, and exponential backoff",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
3
+ "version": "1.2.0",
4
+ "description": "Production-ready retry queue with lifecycle events, force cancellation, priorities, and exponential backoff",
5
+ "main": "./dist/cjs/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "types": "./dist/cjs/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/esm/index.d.ts",
12
+ "default": "./dist/esm/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/cjs/index.d.ts",
16
+ "default": "./dist/cjs/index.js"
17
+ }
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
7
21
  "files": [
8
22
  "dist"
9
23
  ],
24
+ "engines": {
25
+ "node": ">=16"
26
+ },
10
27
  "scripts": {
28
+ "clean": "rm -rf dist",
29
+ "build": "npm run clean && npm run build:cjs && npm run build:esm && node scripts/postbuild.js",
30
+ "build:cjs": "tsc -p tsconfig.cjs.json",
31
+ "build:esm": "tsc -p tsconfig.esm.json",
32
+ "typecheck": "tsc -p tsconfig.json --noEmit",
11
33
  "prepare": "npm run build",
12
- "build": "tsc",
13
- "start": "node dist/index.js",
14
- "dev": "ts-node src/index.ts",
15
- "test": "npm run test:verification && npm run test:force-cancel && npm run test:cancellation",
16
- "test:verification": "node tests/verification.test.js",
17
- "test:force-cancel": "node tests/force-cancellation.test.js",
18
- "test:cancellation": "node tests/cancellation-proof.test.js"
34
+ "pretest": "npm run build",
35
+ "test": "node --test tests/"
19
36
  },
20
37
  "keywords": [
21
38
  "retryq",
@@ -24,6 +41,7 @@
24
41
  "manager",
25
42
  "priority",
26
43
  "jobs",
44
+ "events",
27
45
  "asynchronous",
28
46
  "concurrent",
29
47
  "typescript",
@@ -33,7 +51,6 @@
33
51
  "license": "ISC",
34
52
  "devDependencies": {
35
53
  "@types/node": "^24.6.1",
36
- "ts-node": "^10.9.2",
37
54
  "typescript": "^5.9.3"
38
55
  }
39
56
  }
package/dist/index.d.ts DELETED
@@ -1,85 +0,0 @@
1
- export type RetryQJobOptions = {
2
- retries?: number;
3
- delay?: number;
4
- backoff?: number;
5
- maxTime?: number;
6
- jitter?: number;
7
- label?: string;
8
- priority?: number;
9
- signal?: AbortSignal;
10
- };
11
- export type RetryQManagerConfig = {
12
- maxConcurrent?: number;
13
- maxHistorySize?: number;
14
- };
15
- export type JobState = "pending" | "running" | "completed" | "failed" | "cancelled";
16
- export interface RetryQJob {
17
- id: string;
18
- label: string;
19
- state: JobState;
20
- priority: number;
21
- retriesLeft: number;
22
- promise: Promise<any>;
23
- cancel: (force?: boolean) => void;
24
- fn: (signal?: AbortSignal) => Promise<any>;
25
- options: RetryQJobOptions;
26
- createdAt: number;
27
- startedAt?: number;
28
- finishedAt?: number;
29
- error?: any;
30
- abortController?: AbortController;
31
- }
32
- export interface CancelableFunction {
33
- (): void;
34
- cancelSleep?: () => void;
35
- }
36
- export declare class RetryQManager {
37
- private pendingQueue;
38
- private runningJobs;
39
- private failedJobs;
40
- private completedJobs;
41
- private maxConcurrent;
42
- private maxHistorySize;
43
- private registry;
44
- constructor(config?: RetryQManagerConfig | number);
45
- private _evictOldest;
46
- clearHistory(state?: JobState): void;
47
- createJob(fn: (signal?: AbortSignal) => Promise<any>, options?: RetryQJobOptions): RetryQJob;
48
- private _sortQueue;
49
- private _processQueue;
50
- private _runJob;
51
- cancelJob(id: string, force?: boolean): void;
52
- listJobs(): {
53
- pending: {
54
- id: string;
55
- label: string;
56
- state: JobState;
57
- retriesLeft: number;
58
- priority: number;
59
- }[];
60
- running: {
61
- id: string;
62
- label: string;
63
- state: JobState;
64
- retriesLeft: number;
65
- priority: number;
66
- }[];
67
- failed: {
68
- id: string;
69
- label: string;
70
- state: JobState;
71
- retriesLeft: number;
72
- priority: number;
73
- }[];
74
- completed: {
75
- id: string;
76
- label: string;
77
- state: JobState;
78
- retriesLeft: number;
79
- priority: number;
80
- }[];
81
- };
82
- findJobById(id: string): RetryQJob | null;
83
- findJobsByLabel(label: string): RetryQJob[];
84
- private _jobSummary;
85
- }