@gwash3189/doover 0.0.0-stage → 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gwash3189
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,63 @@
1
- # Temporary Holding Version
1
+ # @gwash3189/doover
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A fluent, zero-dependency TypeScript retry library with configurable backoff strategies and lifecycle event hooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gwash3189/doover
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import Retry from '@gwash3189/doover'
15
+
16
+ const retryable = Retry
17
+ .exponential
18
+ .attempts(3)
19
+ .work(async () => {
20
+ // ... perform work that might throw
21
+ })
22
+ .on('retry', (err) => {
23
+ // Returning true (or void) will cause a retry
24
+ // Returning false will cause the error to bubble immediately
25
+ return true
26
+ })
27
+ .on('exhausted', (err) => {
28
+ // Called when all retries are out before the error bubbles
29
+ })
30
+
31
+ const result = await retryable
32
+ ```
33
+
34
+ ## API
35
+
36
+ ### Backoff Strategies
37
+
38
+ - `Retry.exponential`: Exponential backoff (`delay * 2^(attempt - 1)`).
39
+ - `Retry.linear`: Linear backoff (`delay * attempt`).
40
+ - `Retry.constant`: Constant delay between attempts (`delay`).
41
+ - `Retry.immediate`: Retries immediately with `0ms` delay.
42
+
43
+ ### Builder Methods
44
+
45
+ - `.attempts(count: number)`: Maximum number of attempts (default: `3`).
46
+ - `.delay(ms: number)`: Base delay in milliseconds between retries (default: `100`).
47
+ - `.jitter(enabled?: boolean)`: Applies random jitter to the computed backoff delay.
48
+ - `.work(fn: (attempt: number) => Promise<T> | T)`: The function to execute and retry if it throws.
49
+ - `.on('retry', (err, attempt) => boolean | void | Promise<boolean | void>)`: Invoked when an attempt fails and retries remain. Returning `false` stops retrying and lets the error bubble immediately.
50
+ - `.on('exhausted', (err, attempts) => void | Promise<void>)`: Invoked when all configured attempts have failed.
51
+
52
+ ## Development
53
+
54
+ ```bash
55
+ npm test # Run tests with Vitest
56
+ npm run lint # Run linter with Oxlint
57
+ npm run type-check # Run TypeScript type checker
58
+ npm run build # Build ESM + CJS bundles with tsdown
59
+ ```
60
+
61
+ ## License
62
+
63
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,147 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ // src/retry.ts
3
+ function waitDelay(ms) {
4
+ if (ms <= 0) return Promise.resolve();
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+ function computeDelay(strategy, baseDelayMs, attemptNumber, useJitter) {
8
+ if (baseDelayMs <= 0 || strategy === "none") return 0;
9
+ let delayMs;
10
+ switch (strategy) {
11
+ case "exponential":
12
+ delayMs = baseDelayMs * 2 ** (attemptNumber - 1);
13
+ break;
14
+ case "linear":
15
+ delayMs = baseDelayMs * attemptNumber;
16
+ break;
17
+ case "constant": delayMs = baseDelayMs;
18
+ }
19
+ if (useJitter) return Math.random() * delayMs;
20
+ return delayMs;
21
+ }
22
+ var RetryBuilder = class RetryBuilder {
23
+ config;
24
+ executionPromise;
25
+ constructor(config) {
26
+ this.config = {
27
+ strategy: config?.strategy ?? "exponential",
28
+ maxAttempts: config?.maxAttempts ?? 3,
29
+ baseDelayMs: config?.baseDelayMs ?? 100,
30
+ useJitter: config?.useJitter ?? false,
31
+ workFn: config?.workFn,
32
+ retryHandlers: [...config?.retryHandlers ?? []],
33
+ exhaustedHandlers: [...config?.exhaustedHandlers ?? []]
34
+ };
35
+ }
36
+ get exponential() {
37
+ return new RetryBuilder({
38
+ ...this.config,
39
+ strategy: "exponential"
40
+ });
41
+ }
42
+ get linear() {
43
+ return new RetryBuilder({
44
+ ...this.config,
45
+ strategy: "linear"
46
+ });
47
+ }
48
+ get constant() {
49
+ return new RetryBuilder({
50
+ ...this.config,
51
+ strategy: "constant"
52
+ });
53
+ }
54
+ get immediate() {
55
+ return new RetryBuilder({
56
+ ...this.config,
57
+ strategy: "none",
58
+ baseDelayMs: 0
59
+ });
60
+ }
61
+ attempts(count) {
62
+ if (!Number.isInteger(count) || count < 1) throw new RangeError("attempts must be an integer of at least 1");
63
+ return new RetryBuilder({
64
+ ...this.config,
65
+ maxAttempts: count
66
+ });
67
+ }
68
+ delay(ms) {
69
+ if (ms < 0) throw new RangeError("delay must be a non-negative number");
70
+ return new RetryBuilder({
71
+ ...this.config,
72
+ baseDelayMs: ms
73
+ });
74
+ }
75
+ jitter(enabled = true) {
76
+ return new RetryBuilder({
77
+ ...this.config,
78
+ useJitter: enabled
79
+ });
80
+ }
81
+ work(fn) {
82
+ return new RetryBuilder({
83
+ ...this.config,
84
+ workFn: fn
85
+ });
86
+ }
87
+ on(event, handler) {
88
+ if (event === "retry") return new RetryBuilder({
89
+ ...this.config,
90
+ retryHandlers: [...this.config.retryHandlers, handler]
91
+ });
92
+ if (event === "exhausted") return new RetryBuilder({
93
+ ...this.config,
94
+ exhaustedHandlers: [...this.config.exhaustedHandlers, handler]
95
+ });
96
+ throw new TypeError(`Unsupported event type: ${String(event)}`);
97
+ }
98
+ async run() {
99
+ return this.execute();
100
+ }
101
+ then(onfulfilled, onrejected) {
102
+ if (!this.executionPromise) this.executionPromise = this.execute();
103
+ return this.executionPromise.then(onfulfilled, onrejected);
104
+ }
105
+ catch(onrejected) {
106
+ return this.then(void 0, onrejected);
107
+ }
108
+ finally(onfinally) {
109
+ return this.then((value) => {
110
+ onfinally?.();
111
+ return value;
112
+ }, (reason) => {
113
+ onfinally?.();
114
+ throw reason;
115
+ });
116
+ }
117
+ async execute() {
118
+ const { workFn, maxAttempts, strategy, baseDelayMs, useJitter, retryHandlers, exhaustedHandlers } = this.config;
119
+ if (!workFn) throw new Error("No work function provided. Call .work(fn) before awaiting or running.");
120
+ let attempt = 0;
121
+ while (attempt < maxAttempts) {
122
+ attempt++;
123
+ try {
124
+ return await workFn(attempt);
125
+ } catch (error) {
126
+ if (attempt >= maxAttempts) {
127
+ for (const handler of exhaustedHandlers) await handler(error, attempt);
128
+ throw error;
129
+ }
130
+ let shouldContinue = true;
131
+ for (const handler of retryHandlers) if (await handler(error, attempt) === false) {
132
+ shouldContinue = false;
133
+ break;
134
+ }
135
+ if (!shouldContinue) throw error;
136
+ await waitDelay(computeDelay(strategy, baseDelayMs, attempt, useJitter));
137
+ }
138
+ }
139
+ throw new Error("Unreachable retry state");
140
+ }
141
+ };
142
+ const Retry = new RetryBuilder();
143
+
144
+ exports.Retry = Retry;
145
+ exports.RetryBuilder = RetryBuilder;
146
+ exports.default = Retry;
147
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["export type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'none'\n\nexport type RetryHandler = (\n error: unknown,\n attempt: number\n) => boolean | void | Promise<boolean | void>\n\nexport type ExhaustedHandler = (error: unknown, attempts: number) => void | Promise<void>\n\nexport type WorkFn<T = unknown> = (attempt: number) => Promise<T> | T\n\ninterface RetryConfig<T = unknown> {\n strategy: BackoffStrategy\n maxAttempts: number\n baseDelayMs: number\n useJitter: boolean\n workFn?: WorkFn<T>\n retryHandlers: RetryHandler[]\n exhaustedHandlers: ExhaustedHandler[]\n}\n\nfunction waitDelay(ms: number): Promise<void> {\n if (ms <= 0) {\n return Promise.resolve()\n }\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction computeDelay(\n strategy: BackoffStrategy,\n baseDelayMs: number,\n attemptNumber: number,\n useJitter: boolean\n): number {\n if (baseDelayMs <= 0 || strategy === 'none') {\n return 0\n }\n\n let delayMs: number\n switch (strategy) {\n case 'exponential':\n delayMs = baseDelayMs * 2 ** (attemptNumber - 1)\n break\n case 'linear':\n delayMs = baseDelayMs * attemptNumber\n break\n case 'constant':\n delayMs = baseDelayMs\n break\n }\n\n if (useJitter) {\n return Math.random() * delayMs\n }\n\n return delayMs\n}\n\nexport class RetryBuilder<T = unknown> implements PromiseLike<T> {\n private readonly config: RetryConfig<T>\n private executionPromise?: Promise<T>\n\n constructor(config?: Partial<RetryConfig<T>>) {\n this.config = {\n strategy: config?.strategy ?? 'exponential',\n maxAttempts: config?.maxAttempts ?? 3,\n baseDelayMs: config?.baseDelayMs ?? 100,\n useJitter: config?.useJitter ?? false,\n workFn: config?.workFn,\n retryHandlers: [...(config?.retryHandlers ?? [])],\n exhaustedHandlers: [...(config?.exhaustedHandlers ?? [])],\n }\n }\n\n get exponential(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'exponential' })\n }\n\n get linear(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'linear' })\n }\n\n get constant(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'constant' })\n }\n\n get immediate(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'none', baseDelayMs: 0 })\n }\n\n attempts(count: number): RetryBuilder<T> {\n if (!Number.isInteger(count) || count < 1) {\n throw new RangeError('attempts must be an integer of at least 1')\n }\n return new RetryBuilder<T>({ ...this.config, maxAttempts: count })\n }\n\n delay(ms: number): RetryBuilder<T> {\n if (ms < 0) {\n throw new RangeError('delay must be a non-negative number')\n }\n return new RetryBuilder<T>({ ...this.config, baseDelayMs: ms })\n }\n\n jitter(enabled = true): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, useJitter: enabled })\n }\n\n work<R>(fn: WorkFn<R>): RetryBuilder<R> {\n return new RetryBuilder<R>({\n ...this.config,\n workFn: fn,\n })\n }\n\n on(event: 'retry', handler: RetryHandler): RetryBuilder<T>\n on(event: 'exhausted', handler: ExhaustedHandler): RetryBuilder<T>\n on(event: 'retry' | 'exhausted', handler: RetryHandler | ExhaustedHandler): RetryBuilder<T> {\n if (event === 'retry') {\n return new RetryBuilder<T>({\n ...this.config,\n retryHandlers: [...this.config.retryHandlers, handler as RetryHandler],\n })\n }\n\n if (event === 'exhausted') {\n return new RetryBuilder<T>({\n ...this.config,\n exhaustedHandlers: [...this.config.exhaustedHandlers, handler as ExhaustedHandler],\n })\n }\n\n throw new TypeError(`Unsupported event type: ${String(event)}`)\n }\n\n async run(): Promise<T> {\n return this.execute()\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined\n ): Promise<TResult1 | TResult2> {\n if (!this.executionPromise) {\n this.executionPromise = this.execute()\n }\n return this.executionPromise.then(onfulfilled, onrejected)\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined\n ): Promise<T | TResult> {\n return this.then(undefined, onrejected)\n }\n\n finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n return this.then(\n (value) => {\n onfinally?.()\n return value\n },\n (reason) => {\n onfinally?.()\n throw reason\n }\n )\n }\n\n private async execute(): Promise<T> {\n const {\n workFn,\n maxAttempts,\n strategy,\n baseDelayMs,\n useJitter,\n retryHandlers,\n exhaustedHandlers,\n } = this.config\n\n if (!workFn) {\n throw new Error('No work function provided. Call .work(fn) before awaiting or running.')\n }\n\n let attempt = 0\n\n while (attempt < maxAttempts) {\n attempt++\n\n try {\n return await workFn(attempt)\n } catch (error) {\n if (attempt >= maxAttempts) {\n for (const handler of exhaustedHandlers) {\n await handler(error, attempt)\n }\n throw error\n }\n\n let shouldContinue = true\n for (const handler of retryHandlers) {\n const decision = await handler(error, attempt)\n if (decision === false) {\n shouldContinue = false\n break\n }\n }\n\n if (!shouldContinue) {\n throw error\n }\n\n const waitMs = computeDelay(strategy, baseDelayMs, attempt, useJitter)\n await waitDelay(waitMs)\n }\n }\n\n throw new Error('Unreachable retry state')\n }\n}\n\nexport const Retry: RetryBuilder = new RetryBuilder()\n\nexport default Retry\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'none';
2
+ type RetryHandler = (error: unknown, attempt: number) => boolean | void | Promise<boolean | void>;
3
+ type ExhaustedHandler = (error: unknown, attempts: number) => void | Promise<void>;
4
+ type WorkFn<T = unknown> = (attempt: number) => Promise<T> | T;
5
+ interface RetryConfig<T = unknown> {
6
+ strategy: BackoffStrategy;
7
+ maxAttempts: number;
8
+ baseDelayMs: number;
9
+ useJitter: boolean;
10
+ workFn?: WorkFn<T>;
11
+ retryHandlers: RetryHandler[];
12
+ exhaustedHandlers: ExhaustedHandler[];
13
+ }
14
+ export declare class RetryBuilder<T = unknown> implements PromiseLike<T> {
15
+ private readonly config;
16
+ private executionPromise?;
17
+ constructor(config?: Partial<RetryConfig<T>>);
18
+ get exponential(): RetryBuilder<T>;
19
+ get linear(): RetryBuilder<T>;
20
+ get constant(): RetryBuilder<T>;
21
+ get immediate(): RetryBuilder<T>;
22
+ attempts(count: number): RetryBuilder<T>;
23
+ delay(ms: number): RetryBuilder<T>;
24
+ jitter(enabled?: boolean): RetryBuilder<T>;
25
+ work<R>(fn: WorkFn<R>): RetryBuilder<R>;
26
+ on(event: 'retry', handler: RetryHandler): RetryBuilder<T>;
27
+ on(event: 'exhausted', handler: ExhaustedHandler): RetryBuilder<T>;
28
+ run(): Promise<T>;
29
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
30
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
31
+ finally(onfinally?: (() => void) | null | undefined): Promise<T>;
32
+ private execute;
33
+ }
34
+ export declare const Retry: RetryBuilder;
35
+ export { type BackoffStrategy, type ExhaustedHandler, Retry as default, type RetryHandler, type WorkFn };
36
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/retry.ts"],"mappings":"A,KAAY;KAEA,gBACV,gBACA,qCACoB;KAEV,oBAAoB,gBAAgB,4BAA4B;KAEhE,OAAO,gBAAgB,oBAAoB,QAAQ,KAAK;UAE1D,YAAY;EACpB,UAAU;EACV;EACA;EACA;EACA,SAAS,OAAO;EAChB,eAAe;EACf,mBAAmB;;qBAwCR,aAAa,wBAAwB,YAAY;mBAC3C;UACT;EAER,YAAY,SAAS,QAAQ,YAAY;MAYrC,eAAe,aAAa;MAI5B,UAAU,aAAa;MAIvB,YAAY,aAAa;MAIzB,aAAa,aAAa;EAI9B,SAAS,gBAAgB,aAAa;EAOtC,MAAM,aAAa,aAAa;EAOhC,OAAO,oBAAiB,aAAa;EAIrC,KAAK,GAAG,IAAI,OAAO,KAAK,aAAa;EAOrC,GAAG,gBAAgB,SAAS,eAAe,aAAa;EACxD,GAAG,oBAAoB,SAAS,mBAAmB,aAAa;EAmB1D,OAAO,QAAQ;EAIrB,KAAK,WAAW,GAAG,kBACjB,gBAAgB,OAAO,MAAM,WAAW,YAAY,+BACpD,eAAe,oBAAoB,WAAW,YAAY,gCACzD,QAAQ,WAAW;EAOtB,MAAM,iBACJ,eAAe,oBAAoB,UAAU,YAAY,+BACxD,QAAQ,IAAI;EAIf,QAAQ,8CAA8C,QAAQ;UAahD;;qBAoDH,OAAO"}
@@ -0,0 +1,36 @@
1
+ type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'none';
2
+ type RetryHandler = (error: unknown, attempt: number) => boolean | void | Promise<boolean | void>;
3
+ type ExhaustedHandler = (error: unknown, attempts: number) => void | Promise<void>;
4
+ type WorkFn<T = unknown> = (attempt: number) => Promise<T> | T;
5
+ interface RetryConfig<T = unknown> {
6
+ strategy: BackoffStrategy;
7
+ maxAttempts: number;
8
+ baseDelayMs: number;
9
+ useJitter: boolean;
10
+ workFn?: WorkFn<T>;
11
+ retryHandlers: RetryHandler[];
12
+ exhaustedHandlers: ExhaustedHandler[];
13
+ }
14
+ export declare class RetryBuilder<T = unknown> implements PromiseLike<T> {
15
+ private readonly config;
16
+ private executionPromise?;
17
+ constructor(config?: Partial<RetryConfig<T>>);
18
+ get exponential(): RetryBuilder<T>;
19
+ get linear(): RetryBuilder<T>;
20
+ get constant(): RetryBuilder<T>;
21
+ get immediate(): RetryBuilder<T>;
22
+ attempts(count: number): RetryBuilder<T>;
23
+ delay(ms: number): RetryBuilder<T>;
24
+ jitter(enabled?: boolean): RetryBuilder<T>;
25
+ work<R>(fn: WorkFn<R>): RetryBuilder<R>;
26
+ on(event: 'retry', handler: RetryHandler): RetryBuilder<T>;
27
+ on(event: 'exhausted', handler: ExhaustedHandler): RetryBuilder<T>;
28
+ run(): Promise<T>;
29
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
30
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
31
+ finally(onfinally?: (() => void) | null | undefined): Promise<T>;
32
+ private execute;
33
+ }
34
+ export declare const Retry: RetryBuilder;
35
+ export { type BackoffStrategy, type ExhaustedHandler, Retry as default, type RetryHandler, type WorkFn };
36
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/retry.ts"],"mappings":"A,KAAY;KAEA,gBACV,gBACA,qCACoB;KAEV,oBAAoB,gBAAgB,4BAA4B;KAEhE,OAAO,gBAAgB,oBAAoB,QAAQ,KAAK;UAE1D,YAAY;EACpB,UAAU;EACV;EACA;EACA;EACA,SAAS,OAAO;EAChB,eAAe;EACf,mBAAmB;;qBAwCR,aAAa,wBAAwB,YAAY;mBAC3C;UACT;EAER,YAAY,SAAS,QAAQ,YAAY;MAYrC,eAAe,aAAa;MAI5B,UAAU,aAAa;MAIvB,YAAY,aAAa;MAIzB,aAAa,aAAa;EAI9B,SAAS,gBAAgB,aAAa;EAOtC,MAAM,aAAa,aAAa;EAOhC,OAAO,oBAAiB,aAAa;EAIrC,KAAK,GAAG,IAAI,OAAO,KAAK,aAAa;EAOrC,GAAG,gBAAgB,SAAS,eAAe,aAAa;EACxD,GAAG,oBAAoB,SAAS,mBAAmB,aAAa;EAmB1D,OAAO,QAAQ;EAIrB,KAAK,WAAW,GAAG,kBACjB,gBAAgB,OAAO,MAAM,WAAW,YAAY,+BACpD,eAAe,oBAAoB,WAAW,YAAY,gCACzD,QAAQ,WAAW;EAOtB,MAAM,iBACJ,eAAe,oBAAoB,UAAU,YAAY,+BACxD,QAAQ,IAAI;EAIf,QAAQ,8CAA8C,QAAQ;UAahD;;qBAoDH,OAAO"}
package/dist/index.mjs ADDED
@@ -0,0 +1,144 @@
1
+ // src/retry.ts
2
+ function waitDelay(ms) {
3
+ if (ms <= 0) return Promise.resolve();
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+ function computeDelay(strategy, baseDelayMs, attemptNumber, useJitter) {
7
+ if (baseDelayMs <= 0 || strategy === "none") return 0;
8
+ let delayMs;
9
+ switch (strategy) {
10
+ case "exponential":
11
+ delayMs = baseDelayMs * 2 ** (attemptNumber - 1);
12
+ break;
13
+ case "linear":
14
+ delayMs = baseDelayMs * attemptNumber;
15
+ break;
16
+ case "constant": delayMs = baseDelayMs;
17
+ }
18
+ if (useJitter) return Math.random() * delayMs;
19
+ return delayMs;
20
+ }
21
+ var RetryBuilder = class RetryBuilder {
22
+ config;
23
+ executionPromise;
24
+ constructor(config) {
25
+ this.config = {
26
+ strategy: config?.strategy ?? "exponential",
27
+ maxAttempts: config?.maxAttempts ?? 3,
28
+ baseDelayMs: config?.baseDelayMs ?? 100,
29
+ useJitter: config?.useJitter ?? false,
30
+ workFn: config?.workFn,
31
+ retryHandlers: [...config?.retryHandlers ?? []],
32
+ exhaustedHandlers: [...config?.exhaustedHandlers ?? []]
33
+ };
34
+ }
35
+ get exponential() {
36
+ return new RetryBuilder({
37
+ ...this.config,
38
+ strategy: "exponential"
39
+ });
40
+ }
41
+ get linear() {
42
+ return new RetryBuilder({
43
+ ...this.config,
44
+ strategy: "linear"
45
+ });
46
+ }
47
+ get constant() {
48
+ return new RetryBuilder({
49
+ ...this.config,
50
+ strategy: "constant"
51
+ });
52
+ }
53
+ get immediate() {
54
+ return new RetryBuilder({
55
+ ...this.config,
56
+ strategy: "none",
57
+ baseDelayMs: 0
58
+ });
59
+ }
60
+ attempts(count) {
61
+ if (!Number.isInteger(count) || count < 1) throw new RangeError("attempts must be an integer of at least 1");
62
+ return new RetryBuilder({
63
+ ...this.config,
64
+ maxAttempts: count
65
+ });
66
+ }
67
+ delay(ms) {
68
+ if (ms < 0) throw new RangeError("delay must be a non-negative number");
69
+ return new RetryBuilder({
70
+ ...this.config,
71
+ baseDelayMs: ms
72
+ });
73
+ }
74
+ jitter(enabled = true) {
75
+ return new RetryBuilder({
76
+ ...this.config,
77
+ useJitter: enabled
78
+ });
79
+ }
80
+ work(fn) {
81
+ return new RetryBuilder({
82
+ ...this.config,
83
+ workFn: fn
84
+ });
85
+ }
86
+ on(event, handler) {
87
+ if (event === "retry") return new RetryBuilder({
88
+ ...this.config,
89
+ retryHandlers: [...this.config.retryHandlers, handler]
90
+ });
91
+ if (event === "exhausted") return new RetryBuilder({
92
+ ...this.config,
93
+ exhaustedHandlers: [...this.config.exhaustedHandlers, handler]
94
+ });
95
+ throw new TypeError(`Unsupported event type: ${String(event)}`);
96
+ }
97
+ async run() {
98
+ return this.execute();
99
+ }
100
+ then(onfulfilled, onrejected) {
101
+ if (!this.executionPromise) this.executionPromise = this.execute();
102
+ return this.executionPromise.then(onfulfilled, onrejected);
103
+ }
104
+ catch(onrejected) {
105
+ return this.then(void 0, onrejected);
106
+ }
107
+ finally(onfinally) {
108
+ return this.then((value) => {
109
+ onfinally?.();
110
+ return value;
111
+ }, (reason) => {
112
+ onfinally?.();
113
+ throw reason;
114
+ });
115
+ }
116
+ async execute() {
117
+ const { workFn, maxAttempts, strategy, baseDelayMs, useJitter, retryHandlers, exhaustedHandlers } = this.config;
118
+ if (!workFn) throw new Error("No work function provided. Call .work(fn) before awaiting or running.");
119
+ let attempt = 0;
120
+ while (attempt < maxAttempts) {
121
+ attempt++;
122
+ try {
123
+ return await workFn(attempt);
124
+ } catch (error) {
125
+ if (attempt >= maxAttempts) {
126
+ for (const handler of exhaustedHandlers) await handler(error, attempt);
127
+ throw error;
128
+ }
129
+ let shouldContinue = true;
130
+ for (const handler of retryHandlers) if (await handler(error, attempt) === false) {
131
+ shouldContinue = false;
132
+ break;
133
+ }
134
+ if (!shouldContinue) throw error;
135
+ await waitDelay(computeDelay(strategy, baseDelayMs, attempt, useJitter));
136
+ }
137
+ }
138
+ throw new Error("Unreachable retry state");
139
+ }
140
+ };
141
+ const Retry = new RetryBuilder();
142
+
143
+ export { Retry, Retry as default, RetryBuilder };
144
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["export type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'none'\n\nexport type RetryHandler = (\n error: unknown,\n attempt: number\n) => boolean | void | Promise<boolean | void>\n\nexport type ExhaustedHandler = (error: unknown, attempts: number) => void | Promise<void>\n\nexport type WorkFn<T = unknown> = (attempt: number) => Promise<T> | T\n\ninterface RetryConfig<T = unknown> {\n strategy: BackoffStrategy\n maxAttempts: number\n baseDelayMs: number\n useJitter: boolean\n workFn?: WorkFn<T>\n retryHandlers: RetryHandler[]\n exhaustedHandlers: ExhaustedHandler[]\n}\n\nfunction waitDelay(ms: number): Promise<void> {\n if (ms <= 0) {\n return Promise.resolve()\n }\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction computeDelay(\n strategy: BackoffStrategy,\n baseDelayMs: number,\n attemptNumber: number,\n useJitter: boolean\n): number {\n if (baseDelayMs <= 0 || strategy === 'none') {\n return 0\n }\n\n let delayMs: number\n switch (strategy) {\n case 'exponential':\n delayMs = baseDelayMs * 2 ** (attemptNumber - 1)\n break\n case 'linear':\n delayMs = baseDelayMs * attemptNumber\n break\n case 'constant':\n delayMs = baseDelayMs\n break\n }\n\n if (useJitter) {\n return Math.random() * delayMs\n }\n\n return delayMs\n}\n\nexport class RetryBuilder<T = unknown> implements PromiseLike<T> {\n private readonly config: RetryConfig<T>\n private executionPromise?: Promise<T>\n\n constructor(config?: Partial<RetryConfig<T>>) {\n this.config = {\n strategy: config?.strategy ?? 'exponential',\n maxAttempts: config?.maxAttempts ?? 3,\n baseDelayMs: config?.baseDelayMs ?? 100,\n useJitter: config?.useJitter ?? false,\n workFn: config?.workFn,\n retryHandlers: [...(config?.retryHandlers ?? [])],\n exhaustedHandlers: [...(config?.exhaustedHandlers ?? [])],\n }\n }\n\n get exponential(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'exponential' })\n }\n\n get linear(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'linear' })\n }\n\n get constant(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'constant' })\n }\n\n get immediate(): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, strategy: 'none', baseDelayMs: 0 })\n }\n\n attempts(count: number): RetryBuilder<T> {\n if (!Number.isInteger(count) || count < 1) {\n throw new RangeError('attempts must be an integer of at least 1')\n }\n return new RetryBuilder<T>({ ...this.config, maxAttempts: count })\n }\n\n delay(ms: number): RetryBuilder<T> {\n if (ms < 0) {\n throw new RangeError('delay must be a non-negative number')\n }\n return new RetryBuilder<T>({ ...this.config, baseDelayMs: ms })\n }\n\n jitter(enabled = true): RetryBuilder<T> {\n return new RetryBuilder<T>({ ...this.config, useJitter: enabled })\n }\n\n work<R>(fn: WorkFn<R>): RetryBuilder<R> {\n return new RetryBuilder<R>({\n ...this.config,\n workFn: fn,\n })\n }\n\n on(event: 'retry', handler: RetryHandler): RetryBuilder<T>\n on(event: 'exhausted', handler: ExhaustedHandler): RetryBuilder<T>\n on(event: 'retry' | 'exhausted', handler: RetryHandler | ExhaustedHandler): RetryBuilder<T> {\n if (event === 'retry') {\n return new RetryBuilder<T>({\n ...this.config,\n retryHandlers: [...this.config.retryHandlers, handler as RetryHandler],\n })\n }\n\n if (event === 'exhausted') {\n return new RetryBuilder<T>({\n ...this.config,\n exhaustedHandlers: [...this.config.exhaustedHandlers, handler as ExhaustedHandler],\n })\n }\n\n throw new TypeError(`Unsupported event type: ${String(event)}`)\n }\n\n async run(): Promise<T> {\n return this.execute()\n }\n\n then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined\n ): Promise<TResult1 | TResult2> {\n if (!this.executionPromise) {\n this.executionPromise = this.execute()\n }\n return this.executionPromise.then(onfulfilled, onrejected)\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined\n ): Promise<T | TResult> {\n return this.then(undefined, onrejected)\n }\n\n finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n return this.then(\n (value) => {\n onfinally?.()\n return value\n },\n (reason) => {\n onfinally?.()\n throw reason\n }\n )\n }\n\n private async execute(): Promise<T> {\n const {\n workFn,\n maxAttempts,\n strategy,\n baseDelayMs,\n useJitter,\n retryHandlers,\n exhaustedHandlers,\n } = this.config\n\n if (!workFn) {\n throw new Error('No work function provided. Call .work(fn) before awaiting or running.')\n }\n\n let attempt = 0\n\n while (attempt < maxAttempts) {\n attempt++\n\n try {\n return await workFn(attempt)\n } catch (error) {\n if (attempt >= maxAttempts) {\n for (const handler of exhaustedHandlers) {\n await handler(error, attempt)\n }\n throw error\n }\n\n let shouldContinue = true\n for (const handler of retryHandlers) {\n const decision = await handler(error, attempt)\n if (decision === false) {\n shouldContinue = false\n break\n }\n }\n\n if (!shouldContinue) {\n throw error\n }\n\n const waitMs = computeDelay(strategy, baseDelayMs, attempt, useJitter)\n await waitDelay(waitMs)\n }\n }\n\n throw new Error('Unreachable retry state')\n }\n}\n\nexport const Retry: RetryBuilder = new RetryBuilder()\n\nexport default Retry\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@gwash3189/doover",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "A fluent, lightweight TypeScript retry library with backoff strategies and lifecycle hooks.",
5
+ "license": "MIT",
6
+ "author": "gwash3189",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Gwash3189/doover.git"
10
+ },
11
+ "homepage": "https://github.com/Gwash3189/doover#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Gwash3189/doover/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.mts",
22
+ "exports": {
23
+ ".": {
24
+ "import": {
25
+ "types": "./dist/index.d.mts",
26
+ "default": "./dist/index.mjs"
27
+ },
28
+ "require": {
29
+ "types": "./dist/index.d.cts",
30
+ "default": "./dist/index.cjs"
31
+ }
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsdown",
41
+ "dev": "tsdown --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "test:coverage": "vitest run --coverage",
45
+ "type-check": "tsc --noEmit",
46
+ "lint": "oxlint",
47
+ "lint:fix": "oxlint --fix",
48
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
49
+ "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
50
+ "prepublishOnly": "npm run type-check && npm run lint && npm run test && npm run build",
51
+ "clean": "rm -rf dist"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^26.6.6",
55
+ "@vitest/coverage-v8": "^5.1.0",
56
+ "oxlint": "^1.90.0",
57
+ "prettier": "^3.9.10",
58
+ "tsdown": "^0.23.4",
59
+ "typescript": "^7.0.2",
60
+ "vitest": "^5.1.0"
61
+ },
62
+ "keywords": [
63
+ "retry",
64
+ "doover",
65
+ "backoff",
66
+ "exponential",
67
+ "resilience",
68
+ "typescript"
69
+ ],
70
+ "engines": {
71
+ "node": ">=22.0.0"
72
+ }
73
+ }