@wowistudio/grit 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeroen Huisman
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 ADDED
@@ -0,0 +1,304 @@
1
+ # Grit
2
+
3
+ > ⚠️ **Work in Progress**: This library is currently under active development. The API may change and some features may be incomplete.
4
+
5
+ A powerful, fluent retry utility for TypeScript/JavaScript that makes handling transient failures elegant and configurable.
6
+
7
+ ## Features
8
+
9
+ - **🔄 Configurable Retries**: Set the number of retry attempts with a simple API
10
+ - **🎯 Error Filtering**: Retry only on specific errors or skip certain error types
11
+ - **⏱️ Flexible Backoff Strategies**: Choose from fixed delays, exponential backoff, or random jitter
12
+ - **🔗 Fluent API**: Chain methods for readable, declarative retry logic
13
+ - **📊 Attempt Tracking**: Access attempt numbers in your retry logic
14
+ - **🎣 Before Retry Hooks**: Execute custom logic before each retry attempt
15
+ - **✅ TypeScript First**: Full type safety with TypeScript support
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ # Using npm
21
+ npm install @wowistudio/grit
22
+ # Using pnpm
23
+ pnpm add @wowistudio/grit
24
+ # Using yarn
25
+ yarn add @wowistudio/grit
26
+ ```
27
+
28
+ ## Usage Examples
29
+
30
+ ### Basic
31
+
32
+ ```typescript
33
+ import { Grit } from '@wowistudio/grit';
34
+
35
+ // Simple retry with 3 attempts
36
+ await Grit.retry(3)
37
+ .attempt(async (attempt) => {
38
+ console.log(`Attempt ${attempt}`);
39
+ // Your code that might fail
40
+ await fetchData();
41
+ });
42
+ ```
43
+
44
+ ### Error Filtering
45
+
46
+ ```typescript
47
+ import { Grit } from '@wowistudio/grit';
48
+
49
+ // Retry only on specific errors - only retry when NetworkError or TimeoutError occur
50
+ class NetworkError extends Error {}
51
+ class TimeoutError extends Error {}
52
+
53
+ await Grit.retry(5)
54
+ .onlyErrors([NetworkError, TimeoutError])
55
+ .attempt(async () => {
56
+ await makeNetworkRequest();
57
+ });
58
+
59
+ // Skip certain errors - retry on all errors except ValidationError
60
+ class ValidationError extends Error {}
61
+
62
+ await Grit.retry(3)
63
+ .skipErrors([ValidationError])
64
+ .attempt(async () => {
65
+ await processData();
66
+ });
67
+ ```
68
+
69
+ ### Delay
70
+
71
+ ```typescript
72
+ import { Grit } from 'grit';
73
+
74
+ // Fixed delay between retries - add a 1 second delay between attempts
75
+ await Grit.retry(3)
76
+ .withDelay(1000)
77
+ .attempt(async () => {
78
+ await apiCall();
79
+ });
80
+
81
+ // Exponential backoff - delays: 100ms, 200ms, 400ms, 800ms, 1600ms
82
+ await Grit.retry(5)
83
+ .withDelay({
84
+ delay: 100, // Initial delay: 100ms
85
+ factor: 2 // Multiply by 2 each time
86
+ })
87
+ .attempt(async () => {
88
+ await apiCall();
89
+ });
90
+
91
+ // Random jitter with exponential backoff - prevents thundering herd problems
92
+ await Grit.retry(4)
93
+ .withDelay({
94
+ minDelay: 100, // Minimum delay: 100ms
95
+ maxDelay: 500, // Maximum delay: 500ms
96
+ factor: 1.5 // Optional: multiply by factor each attempt
97
+ })
98
+ .attempt(async () => {
99
+ await distributedServiceCall();
100
+ });
101
+
102
+ // Custom delay array - specify exact delays for each retry attempt (500ms, 1s, 2s)
103
+ await Grit.retry(3)
104
+ .withDelay([500, 1000, 2000])
105
+ .attempt(async () => {
106
+ await apiCall();
107
+ });
108
+ ```
109
+
110
+ #### Reusable Builder Instances
111
+
112
+ You can define a builder instance once and reuse it. Each call to `.attempt()` creates a new execution context, so retry state and attempt counts are independent between calls:
113
+
114
+ ```typescript
115
+ import { Grit } from '@wowistudio/grit';
116
+
117
+ // Define a shared builder configuration
118
+ const grit = Grit.retry(3)
119
+ .onlyErrors([NetworkError])
120
+ .beforeRetry((retryCount) => {
121
+ console.log(`Retrying (attempt ${retryCount})...`);
122
+ });
123
+
124
+ // Use the same builder for multiple different operations
125
+ const userData = await grit.attempt(() => fetchUserData(userId));
126
+ const orderData = await grit.attempt(() => fetchOrderData(orderId));
127
+ ```
128
+
129
+ ### Complete Example
130
+
131
+ ```typescript
132
+ import { Grit } from 'grit';
133
+
134
+ // Combining multiple features for a real-world API call
135
+ class RateLimitError extends Error {}
136
+ class NetworkError extends Error {}
137
+
138
+ async function fetchUserData(userId: string) {
139
+ return await Grit.retry(5)
140
+ .onlyErrors([RateLimitError, NetworkError])
141
+ .withDelay(500)
142
+ .beforeRetry(async (retryCount) => {
143
+ console.warn(`Retry ${retryCount} for user ${userId}`);
144
+ })
145
+ .attempt(async (attempt) => {
146
+ const response = await fetch(`/api/users/${userId}`);
147
+
148
+ if (response.status === 429) {
149
+ throw new RateLimitError('Rate limited');
150
+ }
151
+
152
+ if (!response.ok) {
153
+ throw new NetworkError('Network error');
154
+ }
155
+
156
+ return response.json();
157
+ });
158
+ }
159
+ ```
160
+
161
+ ## API Reference
162
+
163
+ ### `Grit.retry(retryCount: number)`
164
+
165
+ Creates a new Grit instance with the specified number of retries.
166
+
167
+ **Parameters:**
168
+ - `retryCount` (number): Maximum number of retry attempts
169
+
170
+ **Returns:** A new `Grit` instance
171
+
172
+ ### `.attempt(fn: FunctionToExecute)`
173
+
174
+ Executes the provided function with retry logic immediately.
175
+
176
+ **Parameters:**
177
+ - `fn` (Function): An async function that receives the current attempt number as a parameter
178
+
179
+ **Returns:** Promise that resolves with the function's return value or rejects if all retries are exhausted
180
+
181
+ ### `.onlyErrors(errors: ErrorConstructor[])`
182
+
183
+ Configures retries to only occur for specific error types.
184
+
185
+ **Parameters:**
186
+ - `errors` (ErrorConstructor[]): Array of error constructors to retry on
187
+
188
+ **Returns:** `Grit` instance for chaining
189
+
190
+ ### `.skipErrors(errors: ErrorConstructor[])`
191
+
192
+ Configures retries to skip specific error types (retry on all others).
193
+
194
+ **Parameters:**
195
+ - `errors` (ErrorConstructor[]): Array of error constructors to skip
196
+
197
+ **Returns:** `Grit` instance for chaining
198
+
199
+ ### `.withDelay(config: DelayConfig)`
200
+
201
+ Configures the delay strategy between retries.
202
+
203
+ **Parameters:**
204
+ - `config` (DelayConfig): One of:
205
+ - `{ delay: number, factor?: number }`: Fixed delay with optional exponential factor
206
+ - `{ minDelay: number, maxDelay: number, factor?: number }`: Random delay between min/max with optional factor
207
+ - `number[]`: Array of specific delays for each retry (length must match retry count)
208
+
209
+ **Returns:** `Grit` instance for chaining
210
+
211
+ **Throws:** `GritError` if configuration is invalid
212
+
213
+ ### `.beforeRetry(fn: (retryCount: number) => void | Promise<void>)`
214
+
215
+ Sets a callback to execute before each retry attempt.
216
+
217
+ **Parameters:**
218
+ - `fn` (Function): Async or sync function that receives the current retry count
219
+
220
+ **Returns:** `Grit` instance for chaining
221
+
222
+ ## Delay Configuration
223
+
224
+ ### Fixed Delay
225
+
226
+ ```typescript
227
+ .withDelay(1000) // 1 second between retries
228
+ ```
229
+
230
+ ### Exponential Backoff
231
+
232
+ ```typescript
233
+ .withDelay({
234
+ delay: 100, // Start with 100ms
235
+ factor: 2 // Double each time: 100ms, 200ms, 400ms, 800ms...
236
+ })
237
+ ```
238
+
239
+ ### Random Jitter
240
+
241
+ ```typescript
242
+ .withDelay({
243
+ minDelay: 100, // Minimum 100ms
244
+ maxDelay: 500, // Maximum 500ms
245
+ factor: 1.5 // Optional: scale by 1.5x each attempt
246
+ })
247
+ ```
248
+
249
+ ### Custom Delay Array
250
+
251
+ ```typescript
252
+ .withDelay([100, 200, 500, 1000]) // Exact delays for each retry
253
+ // Array length must equal retry count
254
+ ```
255
+
256
+ ## Error Handling
257
+
258
+ Grit provides two error classes:
259
+
260
+ - **`GritError`**: Thrown for configuration errors
261
+ - **`MaxRetriesError`**: Thrown when maximum retries are exceeded (if configured)
262
+
263
+ ```typescript
264
+ import { Grit, GritError } from 'grit';
265
+
266
+ try {
267
+ await Grit.retry(3).attempt(async () => {
268
+ // Your code
269
+ });
270
+ } catch (error) {
271
+ if (error instanceof GritError) {
272
+ console.error('Configuration error:', error.message);
273
+ } else {
274
+ console.error('Operation failed after retries:', error);
275
+ }
276
+ }
277
+ ```
278
+
279
+ ## Best Practices
280
+
281
+ 1. **Use `onlyErrors` for specific error types**: Don't retry on validation errors or other non-transient failures
282
+ 2. **Use exponential backoff for API calls**: Prevents overwhelming services
283
+ 3. **Use random jitter for distributed systems**: Prevents thundering herd problems
284
+ 4. **Log retry attempts**: Use `beforeRetry` to track retry behavior
285
+ 5. **Set reasonable retry counts**: Too many retries can cause long delays
286
+
287
+ ## TypeScript Support
288
+
289
+ Grit is written in TypeScript and provides full type safety:
290
+
291
+ ```typescript
292
+ import { Grit, DelayConfig, FunctionToExecute } from 'grit';
293
+
294
+ // All types are exported for your use
295
+ const delayConfig: DelayConfig = { delay: 1000 };
296
+ const fn: FunctionToExecute = async (attempt) => {
297
+ // attempt is typed as number
298
+ };
299
+ ```
300
+
301
+ ## License
302
+
303
+ This project is licensed under the MIT License.
304
+
@@ -0,0 +1,20 @@
1
+ import { GritError } from "./exceptions.ts";
2
+ import type { DelayConfig, FunctionToExecute } from "./types.ts";
3
+ declare class GritBuilder {
4
+ #private;
5
+ private retryCount?;
6
+ _onlyErrors: (typeof GritError)[];
7
+ _skipErrors: (typeof GritError)[];
8
+ fn: FunctionToExecute<any> | undefined;
9
+ $delay: DelayConfig | undefined;
10
+ beforeRetryFn?: (retryCount: number) => void;
11
+ logging: boolean;
12
+ constructor(retryCount: number);
13
+ onlyErrors(errors: (typeof GritError)[]): this;
14
+ skipErrors(errors: (typeof GritError)[]): this;
15
+ withLogging(enabled?: boolean): this;
16
+ withDelay(config: DelayConfig): this;
17
+ beforeRetry(fn: (retryCount: number) => void): this;
18
+ attempt<T>(fn: FunctionToExecute<T>): Promise<T> | T;
19
+ }
20
+ export { GritBuilder };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _GritBuilder_instances, _GritBuilder_build;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.GritBuilder = void 0;
10
+ const grit_ts_1 = require("./grit.ts");
11
+ class GritBuilder {
12
+ constructor(retryCount) {
13
+ _GritBuilder_instances.add(this);
14
+ this._onlyErrors = [];
15
+ this._skipErrors = [];
16
+ this.fn = undefined;
17
+ this.logging = false;
18
+ this.retryCount = retryCount;
19
+ }
20
+ onlyErrors(errors) {
21
+ this._onlyErrors = errors;
22
+ return this;
23
+ }
24
+ skipErrors(errors) {
25
+ this._skipErrors = errors;
26
+ return this;
27
+ }
28
+ withLogging(enabled = true) {
29
+ this.logging = enabled;
30
+ return this;
31
+ }
32
+ withDelay(config) {
33
+ this.$delay = config;
34
+ return this;
35
+ }
36
+ beforeRetry(fn) {
37
+ this.beforeRetryFn = fn;
38
+ return this;
39
+ }
40
+ attempt(fn) {
41
+ return __classPrivateFieldGet(this, _GritBuilder_instances, "m", _GritBuilder_build).call(this).attempt(fn);
42
+ }
43
+ }
44
+ exports.GritBuilder = GritBuilder;
45
+ _GritBuilder_instances = new WeakSet(), _GritBuilder_build = function _GritBuilder_build() {
46
+ return new grit_ts_1.Grit({
47
+ retryCount: this.retryCount,
48
+ onlyErrors: this._onlyErrors,
49
+ skipErrors: this._skipErrors,
50
+ delay: this.$delay,
51
+ beforeRetryFn: this.beforeRetryFn,
52
+ logging: this.logging,
53
+ });
54
+ };
@@ -0,0 +1,7 @@
1
+ declare class GritError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ declare class MaxRetriesError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export { GritError, MaxRetriesError };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaxRetriesError = exports.GritError = void 0;
4
+ class GritError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "GritError";
8
+ }
9
+ }
10
+ exports.GritError = GritError;
11
+ class MaxRetriesError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "MaxRetriesError";
15
+ }
16
+ }
17
+ exports.MaxRetriesError = MaxRetriesError;
package/dist/grit.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { GritBuilder } from "./builder.ts";
2
+ import type { GritProps } from "./types.ts";
3
+ declare class Grit {
4
+ #private;
5
+ private retryCount?;
6
+ private attempts;
7
+ private retries;
8
+ private onlyErrors;
9
+ private skipErrors;
10
+ private delay;
11
+ private beforeRetryFn?;
12
+ private logging;
13
+ constructor(props: GritProps);
14
+ attemptSync<T>(fn: (attempts: number) => T): T;
15
+ attempt<T>(fn: (attempts: number) => Promise<T>): Promise<T>;
16
+ static retry(retryCount: number): GritBuilder;
17
+ }
18
+ export { Grit };
package/dist/grit.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _Grit_instances, _Grit_currentDelay_get, _Grit_handleBeforeRetry, _Grit_backoff, _Grit_backoffSync, _Grit_processError, _Grit_execute, _Grit_executeSync;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.Grit = void 0;
10
+ const builder_ts_1 = require("./builder.ts");
11
+ const exceptions_ts_1 = require("./exceptions.ts");
12
+ const utils_ts_1 = require("./utils.ts");
13
+ class Grit {
14
+ constructor(props) {
15
+ _Grit_instances.add(this);
16
+ this.attempts = 1;
17
+ this.retries = 0;
18
+ this.logging = false;
19
+ const { retryCount, fn, onlyErrors, skipErrors, delay, beforeRetryFn, logging } = props;
20
+ this.retryCount = retryCount;
21
+ this.onlyErrors = onlyErrors || [];
22
+ this.skipErrors = skipErrors || [];
23
+ this.delay = delay;
24
+ this.beforeRetryFn = beforeRetryFn;
25
+ this.logging = logging || false;
26
+ (0, utils_ts_1.validateGritConfig)(delay, retryCount);
27
+ }
28
+ attemptSync(fn) {
29
+ return __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_executeSync).call(this, fn);
30
+ }
31
+ async attempt(fn) {
32
+ return __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_execute).call(this, fn);
33
+ }
34
+ static retry(retryCount) {
35
+ return new builder_ts_1.GritBuilder(retryCount);
36
+ }
37
+ }
38
+ exports.Grit = Grit;
39
+ _Grit_instances = new WeakSet(), _Grit_currentDelay_get = function _Grit_currentDelay_get() {
40
+ let delay = undefined;
41
+ if (typeof this.delay === "number") {
42
+ delay = this.delay;
43
+ }
44
+ else if (Array.isArray(this.delay)) {
45
+ delay = this.delay.shift();
46
+ }
47
+ else if ((0, utils_ts_1.isObject)(this.delay) && "delay" in this.delay) {
48
+ const { factor, delay: initialDelay } = this.delay;
49
+ delay = initialDelay * Math.pow(factor || 1, this.attempts - 2);
50
+ }
51
+ else if ((0, utils_ts_1.isObject)(this.delay) && "minDelay" in this.delay) {
52
+ const { minDelay, maxDelay, factor } = this.delay;
53
+ const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay;
54
+ delay = factor ? randomDelay * Math.pow(factor, this.attempts - 2) : randomDelay;
55
+ }
56
+ if (!delay)
57
+ throw new exceptions_ts_1.GritError("No delay. Should never happen.");
58
+ if (this.logging)
59
+ console.debug("Delaying for", delay, "ms");
60
+ return delay;
61
+ }, _Grit_handleBeforeRetry = function _Grit_handleBeforeRetry() {
62
+ if (this.retries > 0 && this.beforeRetryFn)
63
+ this.beforeRetryFn(this.retries);
64
+ }, _Grit_backoff = async function _Grit_backoff() {
65
+ return new Promise((resolve) => setTimeout(resolve, __classPrivateFieldGet(this, _Grit_instances, "a", _Grit_currentDelay_get)));
66
+ }, _Grit_backoffSync = function _Grit_backoffSync() {
67
+ setTimeout(() => { }, __classPrivateFieldGet(this, _Grit_instances, "a", _Grit_currentDelay_get));
68
+ }, _Grit_processError = function _Grit_processError(error) {
69
+ if (error && typeof error === 'object' && 'constructor' in error) {
70
+ if (this.skipErrors.length > 0 && this.skipErrors.includes(error.constructor))
71
+ throw error;
72
+ if (this.onlyErrors.length > 0 && !this.onlyErrors.includes(error.constructor))
73
+ throw error;
74
+ }
75
+ this.attempts++;
76
+ this.retries++;
77
+ }, _Grit_execute = async function _Grit_execute(fn) {
78
+ try {
79
+ __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_handleBeforeRetry).call(this);
80
+ return await fn(this.attempts);
81
+ }
82
+ catch (error) {
83
+ __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_processError).call(this, error);
84
+ if (this.delay)
85
+ return __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_backoff).call(this).then(() => __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_execute).call(this, fn));
86
+ return await __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_execute).call(this, fn);
87
+ }
88
+ }, _Grit_executeSync = function _Grit_executeSync(fn) {
89
+ try {
90
+ __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_handleBeforeRetry).call(this);
91
+ return fn(this.attempts);
92
+ }
93
+ catch (error) {
94
+ __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_processError).call(this, error);
95
+ if (this.delay)
96
+ __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_backoffSync).call(this);
97
+ return __classPrivateFieldGet(this, _Grit_instances, "m", _Grit_executeSync).call(this, fn);
98
+ }
99
+ };
@@ -0,0 +1,3 @@
1
+ export { GritBuilder } from "./builder.ts";
2
+ export { Grit } from "./grit.ts";
3
+ export type { DelayConfig, FunctionToExecute } from "./types.ts";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Grit = exports.GritBuilder = void 0;
4
+ var builder_ts_1 = require("./builder.ts");
5
+ Object.defineProperty(exports, "GritBuilder", { enumerable: true, get: function () { return builder_ts_1.GritBuilder; } });
6
+ var grit_ts_1 = require("./grit.ts");
7
+ Object.defineProperty(exports, "Grit", { enumerable: true, get: function () { return grit_ts_1.Grit; } });
@@ -0,0 +1,19 @@
1
+ export type GritProps = {
2
+ retryCount?: number;
3
+ fn?: FunctionToExecute<any>;
4
+ onlyErrors?: (typeof Error)[];
5
+ skipErrors?: (typeof Error)[];
6
+ delay?: DelayConfig;
7
+ isConstructor?: boolean;
8
+ beforeRetryFn?: (retryCount: number) => void;
9
+ logging?: boolean;
10
+ };
11
+ export type DelayConfig = number | number[] | {
12
+ factor?: number;
13
+ delay: number;
14
+ } | {
15
+ factor?: number;
16
+ minDelay: number;
17
+ maxDelay: number;
18
+ };
19
+ export type FunctionToExecute<T> = (attempts: number) => (T | Promise<T>);
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import type { DelayConfig } from "./types.ts";
2
+ export declare const isObject: (o: any) => o is Record<string, any>;
3
+ export declare const isPromise: <T>(value: any) => value is Promise<T>;
4
+ export declare const validateGritConfig: (config: DelayConfig | undefined, retryCount: number | undefined) => void;
package/dist/utils.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateGritConfig = exports.isPromise = exports.isObject = void 0;
4
+ const exceptions_ts_1 = require("./exceptions.ts");
5
+ const isObject = (o) => {
6
+ return typeof o === 'object' && !Array.isArray(o) && o !== null;
7
+ };
8
+ exports.isObject = isObject;
9
+ const isPromise = (value) => {
10
+ return value !== null && typeof value === "object" && typeof value.then === "function";
11
+ };
12
+ exports.isPromise = isPromise;
13
+ const validateGritConfig = (config, retryCount) => {
14
+ if (!config)
15
+ return;
16
+ if (!retryCount)
17
+ throw new exceptions_ts_1.GritError("Missing retry config (Grit.retry(<count>))");
18
+ // validate array config
19
+ if (Array.isArray(config)) {
20
+ if (config.length !== retryCount)
21
+ throw new exceptions_ts_1.GritError("Delay array length must be equal to retry count");
22
+ for (const delay of config) {
23
+ if (delay <= 0)
24
+ throw new exceptions_ts_1.GritError("delay must be greater than 0");
25
+ }
26
+ return;
27
+ }
28
+ if (typeof config === "number") {
29
+ if (config <= 0)
30
+ throw new exceptions_ts_1.GritError("delay must be greater than 0");
31
+ return;
32
+ }
33
+ if (!(0, exports.isObject)(config))
34
+ throw new exceptions_ts_1.GritError("Invalid backoff config");
35
+ // validate delay config
36
+ if ("delay" in config) {
37
+ if (typeof config.delay !== "number")
38
+ throw new exceptions_ts_1.GritError("delay must be a number");
39
+ if (config.delay <= 0)
40
+ throw new exceptions_ts_1.GritError("delay must be greater than 0");
41
+ return;
42
+ }
43
+ // validate random delay config
44
+ const { minDelay, maxDelay } = config;
45
+ if (!minDelay || typeof minDelay !== "number")
46
+ throw new exceptions_ts_1.GritError("minDelay must be a number");
47
+ if (!maxDelay || typeof maxDelay !== "number")
48
+ throw new exceptions_ts_1.GritError("maxDelay must be a number");
49
+ if (minDelay >= maxDelay)
50
+ throw new exceptions_ts_1.GritError("minDelay must be less than maxDelay");
51
+ if (minDelay <= 0)
52
+ throw new exceptions_ts_1.GritError("minDelay must be greater than 0");
53
+ if (maxDelay <= 0)
54
+ throw new exceptions_ts_1.GritError("maxDelay must be greater than 0");
55
+ };
56
+ exports.validateGritConfig = validateGritConfig;
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@wowistudio/grit",
3
+ "description": "A library for retry.",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": [
7
+ "/dist"
8
+ ],
9
+ "version": "1.0.0",
10
+ "license": "MIT",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/index.ts",
14
+ "default": "./src/index.ts"
15
+ }
16
+ },
17
+ "type": "module",
18
+ "scripts": {
19
+ "test": "vitest run",
20
+ "test:watch": "vitest"
21
+ },
22
+ "dependencies": {},
23
+ "devDependencies": {
24
+ "vitest": "^2.1.8",
25
+ "@types/node": "^20.9.0"
26
+ }
27
+ }