@codingaryan/smoothapi 1.0.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.
package/README.md CHANGED
@@ -1,123 +1,174 @@
1
- # @codingaryan/smoothapi
2
-
3
- API resilience library for TypeScript/JavaScript. It wraps the native `fetch` API with **exponential backoff, full jitter, and a finite-state machine circuit breaker** to protect against cascading failures.
4
-
5
- Zero dependencies. Small bundle size. Built for modern ESM.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @codingaryan/smoothapi
11
- ```
12
-
13
- ## Features
14
-
15
- - **Exponential Backoff with Full Jitter:** Prevents the "thundering herd" problem by randomizing retry delays.
16
- - **Circuit Breaker (FSM):** Isolated per-domain state machine (`CLOSED` → `OPEN` → `HALF_OPEN`).
17
- - **Smart Retries:** Automatically retries on specific HTTP status codes (e.g., 429, 500, 502, 503, 504) while throwing immediately on client errors (400, 401, 404).
18
- - **Graceful Fallbacks:** Optionally serve cached or default data instantly when the circuit is `OPEN`.
19
-
20
- ## Usage
21
-
22
- ### Basic Usage (Defaults)
23
-
24
- If you don't need custom configurations, you can instantiate the resilient fetch with its defaults by simply passing an empty object.
25
-
26
- ```typescript
27
- import { createResilientFetch } from '@codingaryan/smoothapi';
28
-
29
- // Create it with default settings
30
- const fetchWithRetry = createResilientFetch({});
31
-
32
- async function main() {
33
- try {
34
- // Drop-in replacement for native fetch
35
- const response = await fetchWithRetry('https://api.example.com/data');
36
- const data = await response.json();
37
- console.log(data);
38
- } catch (err) {
39
- console.error("Request failed completely:", err);
40
- }
41
- }
42
- ```
43
-
44
- **Default Settings provided automatically:**
45
- - **Retries**: 3 attempts
46
- - **Backoff Base Delay**: 100 milliseconds
47
- - **Circuit Failure Threshold**: Trips after 3 consecutive failures
48
- - **Circuit Cooldown**: Stays open for 10 seconds before probing
49
- - **Status Codes to Retry**: `429`, `500`, `502`, `503`, and `504`
50
-
51
- ### Advanced Usage (Custom Settings)
52
-
53
- You can override any of the defaults to suit your application's needs, such as adding a fallback object.
54
-
55
- ```typescript
56
- import { createResilientFetch } from '@codingaryan/smoothapi';
57
-
58
- const fetchWithRetry = createResilientFetch({
59
- backoff: {
60
- baseDelay: 100, // ms to wait before first retry
61
- maxDelay: 30000, // cap on exponential growth
62
- maxRetries: 3 // max number of retry attempts
63
- },
64
- circuitBreaker: {
65
- failureThreshold: 3, // trip OPEN after 3 consecutive failures
66
- cooldownMs: 10000 // stay OPEN for 10 seconds before probing
67
- },
68
- // Optional: Return this instead of throwing when the circuit is OPEN
69
- fallback: { error: "Service degraded, returning stale data." },
70
- // Optional: Custom status codes to retry on
71
- retryOn: [429, 500, 502, 503, 504]
72
- });
73
-
74
- async function main() {
75
- try {
76
- const response = await fetchWithRetry('https://api.example.com/data');
77
-
78
- // If fallback triggered, it returns your fallback object directly
79
- if ('error' in response) {
80
- console.log("Fallback triggered:", response.error);
81
- return;
82
- }
83
-
84
- // Otherwise it's a standard Response object
85
- const data = await response.json();
86
- console.log(data);
87
- } catch (err) {
88
- console.error("Request failed completely:", err);
89
- }
90
- }
91
- ```
92
-
93
- ### Client Error Handling & Alerts
94
-
95
- By default, client errors (e.g. `400`, `401`, `403`, `404`, `405`) resolve immediately and bypass the retry loop. If you want to handle these errors gracefully and alert users:
96
-
97
- ```typescript
98
- import { createResilientFetch } from '@codingaryan/smoothapi';
99
-
100
- const fetchWithRetry = createResilientFetch({
101
- fallbackOnNonRetryable: true,
102
- // Optional: Trigger custom UI logic when a client error happens
103
- onNonRetryableError: (status, message) => {
104
- console.log(`Custom callback: Received status ${status}`);
105
- },
106
- // Optional: Fallback returned on non-retryable errors
107
- fallback: { error: "Page not found." }
108
- });
109
- ```
110
-
111
- * **Default Alerting**: If `fallbackOnNonRetryable` is `true` and no custom `onNonRetryableError` is provided, running in a browser environment will trigger a standard `window.alert("Non-retryable HTTP error: [status]")`. In backend/Node environments, it logs the warning to `console.error`.
112
- * **Graceful Return**: If no custom `fallback` is configured, it returns a mock `Response` wrapper with the status code and a JSON error body: `{ error: true, status: 404, message: "..." }`. Callers can safely call `.json()`, `.status`, or `.ok` on it without crashing.
113
-
114
- ## How It Works
115
-
116
- 1. **Host Extraction:** The domain is automatically extracted from the URL. The circuit breaker state is isolated per host (e.g., `api.github.com` failing won't trip the circuit for `api.stripe.com`).
117
- 2. **Circuit Check:** Before making a network request, the breaker checks the state. If it's `OPEN`, the request is blocked instantly (returning your fallback, or throwing a `CircuitOpenError`).
118
- 3. **Execution & Retries:** If the response status is in your `retryOn` list, it's counted as a failure and retried with backoff.
119
- 4. **Recovery:** After `cooldownMs`, the breaker enters `HALF_OPEN` state. The next request acts as a probe. If it succeeds, the circuit closes. If it fails, it snaps back to `OPEN` immediately.
120
-
121
- ## License
122
-
123
- MIT
1
+ # @codingaryan/smoothapi
2
+
3
+ API protection library for TypeScript/JavaScript. It wraps the native `fetch` API with state of the art protections like **exponential backoff, full jitter, and a finite-state machine circuit breaker** to protect against cascading failures.
4
+
5
+ Zero dependencies. Small bundle size. Built for modern ESM.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @codingaryan/smoothapi
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Exponential Backoff with Full Jitter:** Prevents the "thundering herd" problem by randomizing retry delays.
16
+ - **Circuit Breaker (FSM):** Isolated per-domain state machine (`CLOSED` → `OPEN` → `HALF_OPEN`).
17
+ - **Smart Retries:** Automatically retries on specific HTTP status codes (e.g., 429, 500, 502, 503, 504) while throwing immediately on client errors (400, 401, 404).
18
+ - **Graceful Fallbacks:** Optionally serve cached or default data instantly when the circuit is `OPEN`.
19
+ - **Request Deduplication:** Automatically couples concurrent identical requests into a single network call.
20
+
21
+ ## Usage
22
+
23
+ ### Basic Usage (Defaults)
24
+
25
+ If you don't need custom configurations, you can use the smooth fetch with its defaults by simply passing an empty object.
26
+
27
+ ```typescript
28
+ import { createSmoothFetch } from '@codingaryan/smoothapi';
29
+
30
+ // Create it with default settings
31
+ const fetchWithRetry = createSmoothFetch({});
32
+
33
+ async function main() {
34
+ try {
35
+ // Drop-in replacement for native fetch
36
+ const response = await fetchWithRetry('https://api.example.com/data');
37
+ const data = await response.json();
38
+ console.log(data);
39
+ } catch (err) {
40
+ console.error("Request failed completely:", err);
41
+ }
42
+ }
43
+ ```
44
+
45
+ **Default Settings provided automatically:**
46
+ - **Retries**: 3 attempts
47
+ - **Backoff Base Delay**: 100 milliseconds
48
+ - **Circuit Failure Threshold**: Trips after 3 consecutive failures
49
+ - **Circuit Cooldown**: Stays open for 10 seconds before probing
50
+ - **Status Codes to Retry**: `429`, `500`, `502`, `503`, and `504`
51
+
52
+ ### Advanced Usage (Custom Settings)
53
+
54
+ You can override any of the defaults to suit your application's needs, such as adding a fallback object.
55
+
56
+ ```typescript
57
+ import { createSmoothFetch } from '@codingaryan/smoothapi';
58
+
59
+ const fetchWithRetry = createSmoothFetch({
60
+ backoff: {
61
+ baseDelay: 100, // ms to wait before first retry
62
+ maxDelay: 30000, // cap on exponential growth
63
+ maxRetries: 3 // max number of retry attempts
64
+ },
65
+ circuitBreaker: {
66
+ failureThreshold: 3, // trip OPEN after 3 consecutive failures
67
+ cooldownMs: 10000 // stay OPEN for 10 seconds before probing
68
+ },
69
+ // Optional: Return this instead of throwing when the circuit is OPEN
70
+ fallback: { error: "Service degraded, returning stale data." },
71
+ // Optional: Custom status codes to retry on
72
+ retryOn: [429, 500, 502, 503, 504]
73
+ });
74
+
75
+ async function main() {
76
+ try {
77
+ const response = await fetchWithRetry('https://api.example.com/data');
78
+
79
+ // If fallback triggered, it returns your fallback object directly
80
+ if ('error' in response) {
81
+ console.log("Fallback triggered:", response.error);
82
+ return;
83
+ }
84
+
85
+ // Otherwise it's a standard Response object
86
+ const data = await response.json();
87
+ console.log(data);
88
+ } catch (err) {
89
+ console.error("Request failed completely:", err);
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Client Error Handling & Alerts
95
+
96
+ By default, client errors (e.g. `400`, `401`, `403`, `404`, `405`) resolve immediately and bypass the retry loop. If you want to handle these errors gracefully and alert users:
97
+
98
+ ```typescript
99
+ import { createSmoothFetch } from '@codingaryan/smoothapi';
100
+
101
+ const fetchWithRetry = createSmoothFetch({
102
+ fallbackOnNonRetryable: true,
103
+ // Optional: Trigger custom UI logic when a client error happens
104
+ onNonRetryableError: (status, message) => {
105
+ console.log(`Custom callback: Received status ${status}`);
106
+ },
107
+ // Optional: Fallback returned on non-retryable errors
108
+ fallback: { error: "Page not found." }
109
+ });
110
+ ```
111
+
112
+ * **Default Alerting**: If `fallbackOnNonRetryable` is `true` and no custom `onNonRetryableError` is provided, running in a browser environment will trigger a standard `window.alert("Non-retryable HTTP error: [status]")`. In backend/Node environments, it logs the warning to `console.error`.
113
+ * **Graceful Return**: If no custom `fallback` is configured, it returns a mock `Response` wrapper with the status code and a JSON error body: `{ error: true, status: 404, message: "..." }`. Callers can safely call `.json()`, `.status`, or `.ok` on it without crashing.
114
+
115
+ ### Request Deduplication
116
+
117
+ When multiple identical requests are made concurrently, SmoothAPI will execute only one network call and share the result with all callers. This reduces unnecessary load on downstream services and prevents exausting computing resources.
118
+
119
+ **Enable with default key function** (deduplicates by URL):
120
+
121
+ ```typescript
122
+ import { createSmoothFetch } from '@codingaryan/smoothapi';
123
+
124
+ const fetchWithRetry = createSmoothFetch({
125
+ deduplication: {} // Empty object activates deduplication
126
+ });
127
+
128
+ // All three calls share a single network request
129
+ const [a, b, c] = await Promise.all([
130
+ fetchWithRetry('http://api.example.com/users/1'),
131
+ fetchWithRetry('http://api.example.com/users/1'),
132
+ fetchWithRetry('http://api.example.com/users/1'),
133
+ ]);
134
+ ```
135
+
136
+ **Custom key function** for advanced coalescing:
137
+
138
+ ```typescript
139
+ const fetchWithRetry = createSmoothFetch({
140
+ deduplication: {
141
+ // Deduplicate by method + URL (ignores headers/body)
142
+ keyFn: (url, options) => `${options?.method ?? 'GET'}:${url.toString()}`
143
+ }
144
+ });
145
+ ```
146
+
147
+ **Opt out of deduplication** for specific requests:
148
+
149
+ ```typescript
150
+ const fetchWithRetry = createSmoothFetch({
151
+ deduplication: {
152
+ keyFn: (url, options) => {
153
+ // Skip dedup for POST requests
154
+ if (options?.method === 'POST') return null;
155
+ return url.toString();
156
+ }
157
+ }
158
+ });
159
+ ```
160
+
161
+ * **Default Behavior**: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are merged.
162
+ * **Error Propagation**: If the network call fails, all waiting callers receive the same error.
163
+ * **Settlement**: Once a request completes, the next call to the same URL triggers a fresh network request.
164
+
165
+ ## How It Works
166
+
167
+ 1. **Host Extraction:** The domain is automatically extracted from the URL. The circuit breaker state is isolated per host (e.g., `api.github.com` failing won't trip the circuit for `api.stripe.com`).
168
+ 2. **Circuit Check:** Before making a network request, the breaker checks the state. If it's `OPEN`, the request is blocked instantly (returning your fallback, or throwing a `CircuitOpenError`).
169
+ 3. **Execution & Retries:** If the response status is in your `retryOn` list, it's counted as a failure and retried with backoff.
170
+ 4. **Recovery:** After `cooldownMs`, the breaker enters `HALF_OPEN` state. The next request acts as a probe. If it succeeds, the circuit closes. If it fails, it snaps back to `OPEN` immediately.
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,44 @@
1
+ import type { DeduplicationKeyFn } from "./types.js";
2
+ /**
3
+ * Tracks in-flight requests and coalesces identical concurrent calls
4
+ * into a single shared Promise.
5
+ *
6
+ * Lifecycle
7
+ * ---------
8
+ * 1. First caller for key K → starts the network call, stores the raw
9
+ * Promise in the map, and returns a `.then(clone)` chain so the
10
+ * original `Response` body stream is never consumed by anyone.
11
+ * 2. Subsequent callers for K (while the first is still pending) →
12
+ * attach their own `.then(clone)` to the same shared Promise, each
13
+ * receiving an independent cloned `Response`.
14
+ * 3. Once the shared Promise settles the entry is deleted, so the *next*
15
+ * caller after settlement triggers a fresh network request.
16
+ *
17
+ * Response cloning
18
+ * ----------------
19
+ * `fetch()` returns a `Response` whose body is a one-time-readable stream.
20
+ * If two callers received the *same* `Response` object, whichever reads
21
+ * `.json()` / `.text()` first would disturb the body for the other.
22
+ * To avoid this, the raw result is kept in the inflight map and every
23
+ * caller — including the first one — receives `response.clone()`, leaving
24
+ * the stored original unconsumed and safe to clone again.
25
+ */
26
+ export declare class RequestDeduplicator {
27
+ /** Stores the raw (un-cloned) shared Promise for each in-flight key. */
28
+ private readonly inflight;
29
+ private readonly keyFn;
30
+ constructor(keyFn?: DeduplicationKeyFn);
31
+ /**
32
+ * Execute `fetcher` if no identical request is already in-flight,
33
+ * otherwise attach to the existing Promise. In either case the caller
34
+ * receives `Response.clone()` so body streams are independent.
35
+ *
36
+ * @param url - Same value passed to the outer resilientFetch.
37
+ * @param options - Same value passed to the outer resilientFetch.
38
+ * @param fetcher - A thunk that performs the actual network call.
39
+ */
40
+ execute<R>(url: string | URL, options: RequestInit | undefined, fetcher: () => Promise<R>): Promise<R>;
41
+ /** Number of in-flight deduplicated requests. Useful for tests. */
42
+ get size(): number;
43
+ }
44
+ //# sourceMappingURL=dedup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.d.ts","sourceRoot":"","sources":["../src/dedup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAyBrD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,mBAAmB;IAC9B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAE/B,KAAK,CAAC,EAAE,kBAAkB;IAItC;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,EACP,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,CAAC;IAyBb,mEAAmE;IACnE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
package/dist/dedup.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Default key derivation: stringified URL only.
3
+ * This intentionally ignores `options` (e.g. headers, body) so that
4
+ * concurrent GET /users/1 calls are always collapsed, even when the
5
+ * caller did not customise the key function.
6
+ *
7
+ * For mutation-safe deduplication (POST, PUT …) supply a custom
8
+ * `keyFn` via `DeduplicationConfig.keyFn`.
9
+ */
10
+ const defaultKeyFn = (url) => url.toString();
11
+ /**
12
+ * Clone `result` if it is a `Response` — necessary because `Response` bodies
13
+ * are single-consumption streams. Non-Response values (fallback objects, etc.)
14
+ * are returned as-is.
15
+ */
16
+ function cloneIfResponse(result) {
17
+ if (result instanceof Response) {
18
+ return result.clone();
19
+ }
20
+ return result;
21
+ }
22
+ /**
23
+ * Tracks in-flight requests and coalesces identical concurrent calls
24
+ * into a single shared Promise.
25
+ *
26
+ * Lifecycle
27
+ * ---------
28
+ * 1. First caller for key K → starts the network call, stores the raw
29
+ * Promise in the map, and returns a `.then(clone)` chain so the
30
+ * original `Response` body stream is never consumed by anyone.
31
+ * 2. Subsequent callers for K (while the first is still pending) →
32
+ * attach their own `.then(clone)` to the same shared Promise, each
33
+ * receiving an independent cloned `Response`.
34
+ * 3. Once the shared Promise settles the entry is deleted, so the *next*
35
+ * caller after settlement triggers a fresh network request.
36
+ *
37
+ * Response cloning
38
+ * ----------------
39
+ * `fetch()` returns a `Response` whose body is a one-time-readable stream.
40
+ * If two callers received the *same* `Response` object, whichever reads
41
+ * `.json()` / `.text()` first would disturb the body for the other.
42
+ * To avoid this, the raw result is kept in the inflight map and every
43
+ * caller — including the first one — receives `response.clone()`, leaving
44
+ * the stored original unconsumed and safe to clone again.
45
+ */
46
+ export class RequestDeduplicator {
47
+ /** Stores the raw (un-cloned) shared Promise for each in-flight key. */
48
+ inflight = new Map();
49
+ keyFn;
50
+ constructor(keyFn) {
51
+ this.keyFn = keyFn ?? defaultKeyFn;
52
+ }
53
+ /**
54
+ * Execute `fetcher` if no identical request is already in-flight,
55
+ * otherwise attach to the existing Promise. In either case the caller
56
+ * receives `Response.clone()` so body streams are independent.
57
+ *
58
+ * @param url - Same value passed to the outer resilientFetch.
59
+ * @param options - Same value passed to the outer resilientFetch.
60
+ * @param fetcher - A thunk that performs the actual network call.
61
+ */
62
+ execute(url, options, fetcher) {
63
+ const key = this.keyFn(url, options);
64
+ // null means: "skip deduplication for this request"
65
+ if (key === null) {
66
+ return fetcher();
67
+ }
68
+ if (this.inflight.has(key)) {
69
+ // Attach to the existing in-flight promise and return a fresh clone
70
+ // so this caller's Response stream is fully independent.
71
+ return this.inflight.get(key).then(cloneIfResponse);
72
+ }
73
+ const raw = fetcher().finally(() => {
74
+ this.inflight.delete(key);
75
+ });
76
+ this.inflight.set(key, raw);
77
+ // The first caller also gets a clone so the raw result stored in the
78
+ // map is never body-consumed, keeping it safe to clone for latecomers.
79
+ return raw.then(cloneIfResponse);
80
+ }
81
+ /** Number of in-flight deduplicated requests. Useful for tests. */
82
+ get size() {
83
+ return this.inflight.size;
84
+ }
85
+ }
86
+ //# sourceMappingURL=dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.js","sourceRoot":"","sources":["../src/dedup.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAuB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEjE;;;;GAIG;AACH,SAAS,eAAe,CAAI,MAAS;IACnC,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC,KAAK,EAAkB,CAAC;IACxC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,mBAAmB;IAC9B,wEAAwE;IACvD,QAAQ,GAAkC,IAAI,GAAG,EAAE,CAAC;IACpD,KAAK,CAAqB;IAE3C,YAAY,KAA0B;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,YAAY,CAAC;IACrC,CAAC;IAED;;;;;;;;OAQG;IACH,OAAO,CACL,GAAiB,EACjB,OAAgC,EAChC,OAAyB;QAEzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAErC,oDAAoD;QACpD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,OAAO,EAAE,CAAC;QACnB,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,oEAAoE;YACpE,yDAAyD;YACzD,OAAQ,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YACjC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAE5B,qEAAqE;QACrE,uEAAuE;QACvE,OAAO,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,mEAAmE;IACnE,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  import { ResilientFetchConfig } from "./types.js";
2
- export declare function createResilientFetch<T>(globalConfig: ResilientFetchConfig<T>): (url: string | URL, options?: RequestInit) => Promise<Response | T>;
2
+ export declare function createSmoothFetch<T>(globalConfig: ResilientFetchConfig<T>): (url: string | URL, options?: RequestInit) => Promise<Response | T>;
3
+ /** @deprecated use createSmoothFetch instead */
4
+ export declare const createResilientFetch: typeof createSmoothFetch;
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAUpE,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,YAAY,EAAE,oBAAoB,CAAC,CAAC,CAAC,IAMzE,KAAK,MAAM,GAAG,GAAG,EACjB,UAAU,WAAW,KACpB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAyEzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAWpE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,oBAAoB,CAAC,CAAC,CAAC,IAStE,KAAK,MAAM,GAAG,GAAG,EACjB,UAAU,WAAW,KACpB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAuFzB;AAED,gDAAgD;AAChD,eAAO,MAAM,oBAAoB,0BAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -1,17 +1,21 @@
1
1
  import { CircuitBreakerState } from "./state.js";
2
2
  import { calculateBackoff, sleep } from "./utils/backoff.js";
3
3
  import { CircuitOpenError } from "./types.js";
4
+ import { RequestDeduplicator } from "./dedup.js";
4
5
  const BACKOFF_DEFAULTS = {
5
6
  baseDelay: 100,
6
7
  maxDelay: 30_000,
7
8
  maxRetries: 3,
8
9
  };
9
10
  const DEFAULT_RETRY_ON = [429, 500, 502, 503, 504];
10
- export function createResilientFetch(globalConfig) {
11
+ export function createSmoothFetch(globalConfig) {
11
12
  const backoffConfig = { ...BACKOFF_DEFAULTS, ...globalConfig.backoff };
12
13
  const retryOn = globalConfig.retryOn ?? DEFAULT_RETRY_ON;
13
14
  const breaker = new CircuitBreakerState(globalConfig.circuitBreaker);
14
- return async function resilientFetch(url, options) {
15
+ const deduplicator = globalConfig.deduplication
16
+ ? new RequestDeduplicator(globalConfig.deduplication.keyFn)
17
+ : null;
18
+ return async function smoothFetch(url, options) {
15
19
  const domain = new URL(url).hostname;
16
20
  // Block before any network IO if the circuit is OPEN.
17
21
  if (!breaker.canRequest(domain)) {
@@ -20,58 +24,71 @@ export function createResilientFetch(globalConfig) {
20
24
  }
21
25
  throw new CircuitOpenError(domain);
22
26
  }
23
- let lastError;
24
- for (let attempt = 0; attempt <= backoffConfig.maxRetries; attempt++) {
25
- try {
26
- const response = await fetch(url, options);
27
- // fetch() resolves for any HTTP status. Retryable codes need to be
28
- // treated as failures manually.
29
- if (retryOn.includes(response.status)) {
30
- breaker.recordFailure(domain);
31
- if (attempt < backoffConfig.maxRetries) {
32
- await sleep(calculateBackoff(attempt, backoffConfig));
33
- continue;
27
+ // The core fetch-with-retry logic extracted into a thunk so the
28
+ // deduplicator can decide whether to run it or share an existing Promise.
29
+ const executeRequest = () => {
30
+ let lastError;
31
+ const run = async () => {
32
+ for (let attempt = 0; attempt <= backoffConfig.maxRetries; attempt++) {
33
+ try {
34
+ const response = await fetch(url, options);
35
+ // fetch() resolves for any HTTP status. Retryable codes need to be
36
+ // treated as failures manually.
37
+ if (retryOn.includes(response.status)) {
38
+ breaker.recordFailure(domain);
39
+ if (attempt < backoffConfig.maxRetries) {
40
+ await sleep(calculateBackoff(attempt, backoffConfig));
41
+ continue;
42
+ }
43
+ return response;
44
+ }
45
+ if (response.status >= 400 && globalConfig.fallbackOnNonRetryable) {
46
+ const message = `Non-retryable HTTP error: ${response.status}${response.statusText ? ' ' + response.statusText : ''}`;
47
+ if (globalConfig.onNonRetryableError) {
48
+ globalConfig.onNonRetryableError(response.status, message);
49
+ }
50
+ else if (typeof window !== 'undefined') {
51
+ window.alert(message);
52
+ }
53
+ else {
54
+ console.error(message);
55
+ }
56
+ breaker.recordSuccess(domain);
57
+ if (globalConfig.fallback !== undefined) {
58
+ return globalConfig.fallback;
59
+ }
60
+ return new Response(JSON.stringify({
61
+ error: true,
62
+ status: response.status,
63
+ message,
64
+ }), {
65
+ status: response.status,
66
+ statusText: response.statusText,
67
+ headers: { "Content-Type": "application/json" }
68
+ });
69
+ }
70
+ breaker.recordSuccess(domain);
71
+ return response;
34
72
  }
35
- return response;
36
- }
37
- if (response.status >= 400 && globalConfig.fallbackOnNonRetryable) {
38
- const message = `Non-retryable HTTP error: ${response.status}${response.statusText ? ' ' + response.statusText : ''}`;
39
- if (globalConfig.onNonRetryableError) {
40
- globalConfig.onNonRetryableError(response.status, message);
41
- }
42
- else if (typeof window !== 'undefined') {
43
- window.alert(message);
44
- }
45
- else {
46
- console.error(message);
47
- }
48
- breaker.recordSuccess(domain);
49
- if (globalConfig.fallback !== undefined) {
50
- return globalConfig.fallback;
73
+ catch (err) {
74
+ lastError = err;
75
+ breaker.recordFailure(domain);
76
+ // Don't sleep after the final attempt
77
+ if (attempt < backoffConfig.maxRetries) {
78
+ await sleep(calculateBackoff(attempt, backoffConfig));
79
+ }
51
80
  }
52
- return new Response(JSON.stringify({
53
- error: true,
54
- status: response.status,
55
- message,
56
- }), {
57
- status: response.status,
58
- statusText: response.statusText,
59
- headers: { "Content-Type": "application/json" }
60
- });
61
81
  }
62
- breaker.recordSuccess(domain);
63
- return response;
64
- }
65
- catch (err) {
66
- lastError = err;
67
- breaker.recordFailure(domain);
68
- // Don't sleep after the final attempt
69
- if (attempt < backoffConfig.maxRetries) {
70
- await sleep(calculateBackoff(attempt, backoffConfig));
71
- }
72
- }
82
+ throw lastError;
83
+ };
84
+ return run();
85
+ };
86
+ if (deduplicator) {
87
+ return deduplicator.execute(url, options, executeRequest);
73
88
  }
74
- throw lastError;
89
+ return executeRequest();
75
90
  };
76
91
  }
92
+ /** @deprecated use createSmoothFetch instead */
93
+ export const createResilientFetch = createSmoothFetch;
77
94
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAwB,MAAM,YAAY,CAAC;AAEpE,MAAM,gBAAgB,GAAG;IACvB,SAAS,EAAE,GAAG;IACd,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEnD,MAAM,UAAU,oBAAoB,CAAI,YAAqC;IAC3E,MAAM,aAAa,GAAG,EAAE,GAAG,gBAAgB,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;IACvE,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;IAErE,OAAO,KAAK,UAAU,cAAc,CAClC,GAAiB,EACjB,OAAqB;QAErB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QAErC,sDAAsD;QACtD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACxC,OAAO,YAAY,CAAC,QAAa,CAAC;YACpC,CAAC;YACD,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,SAAkB,CAAC;QAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;YACrE,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBAE3C,mEAAmE;gBACnE,gCAAgC;gBAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;oBAC9B,IAAI,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE,CAAC;wBACvC,MAAM,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;wBACtD,SAAS;oBACX,CAAC;oBACD,OAAO,QAAQ,CAAC;gBAClB,CAAC;gBAED,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,sBAAsB,EAAE,CAAC;oBAClE,MAAM,OAAO,GAAG,6BAA6B,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBACtH,IAAI,YAAY,CAAC,mBAAmB,EAAE,CAAC;wBACrC,YAAY,CAAC,mBAAmB,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;oBAC7D,CAAC;yBAAM,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;wBACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACzB,CAAC;oBAED,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;oBAE9B,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;wBACxC,OAAO,YAAY,CAAC,QAAa,CAAC;oBACpC,CAAC;oBAED,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;wBACb,KAAK,EAAE,IAAI;wBACX,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,OAAO;qBACR,CAAC,EACF;wBACE,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;qBAChD,CACF,CAAC;gBACJ,CAAC;gBAED,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBAC9B,OAAO,QAAQ,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,SAAS,GAAG,GAAG,CAAC;gBAChB,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBAE9B,sCAAsC;gBACtC,IAAI,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE,CAAC;oBACvC,MAAM,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;gBACxD,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,SAAS,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAwB,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,gBAAgB,GAAG;IACvB,SAAS,EAAE,GAAG;IACd,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEnD,MAAM,UAAU,iBAAiB,CAAI,YAAqC;IACxE,MAAM,aAAa,GAAG,EAAE,GAAG,gBAAgB,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;IACvE,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa;QAC7C,CAAC,CAAC,IAAI,mBAAmB,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC;QAC3D,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,KAAK,UAAU,WAAW,CAC/B,GAAiB,EACjB,OAAqB;QAErB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QAErC,sDAAsD;QACtD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACxC,OAAO,YAAY,CAAC,QAAa,CAAC;YACpC,CAAC;YACD,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;QAED,gEAAgE;QAChE,0EAA0E;QAC1E,MAAM,cAAc,GAAG,GAA0B,EAAE;YACjD,IAAI,SAAkB,CAAC;YAEvB,MAAM,GAAG,GAAG,KAAK,IAA2B,EAAE;gBAC5C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;oBACrE,IAAI,CAAC;wBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;wBAE3C,mEAAmE;wBACnE,gCAAgC;wBAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;4BACtC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;4BAC9B,IAAI,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE,CAAC;gCACvC,MAAM,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;gCACtD,SAAS;4BACX,CAAC;4BACD,OAAO,QAAQ,CAAC;wBAClB,CAAC;wBAED,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,sBAAsB,EAAE,CAAC;4BAClE,MAAM,OAAO,GAAG,6BAA6B,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;4BACtH,IAAI,YAAY,CAAC,mBAAmB,EAAE,CAAC;gCACrC,YAAY,CAAC,mBAAmB,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;4BAC7D,CAAC;iCAAM,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;gCACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;4BACxB,CAAC;iCAAM,CAAC;gCACN,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;4BACzB,CAAC;4BAED,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;4BAE9B,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gCACxC,OAAO,YAAY,CAAC,QAAa,CAAC;4BACpC,CAAC;4BAED,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gCACb,KAAK,EAAE,IAAI;gCACX,MAAM,EAAE,QAAQ,CAAC,MAAM;gCACvB,OAAO;6BACR,CAAC,EACF;gCACE,MAAM,EAAE,QAAQ,CAAC,MAAM;gCACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;gCAC/B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;6BAChD,CACF,CAAC;wBACJ,CAAC;wBAED,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;wBAC9B,OAAO,QAAQ,CAAC;oBAClB,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,SAAS,GAAG,GAAG,CAAC;wBAChB,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;wBAE9B,sCAAsC;wBACtC,IAAI,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE,CAAC;4BACvC,MAAM,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;wBACxD,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,MAAM,SAAS,CAAC;YAClB,CAAC,CAAC;YAEF,OAAO,GAAG,EAAE,CAAC;QACf,CAAC,CAAC;QAEF,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,cAAc,EAAE,CAAC;IAC1B,CAAC,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,18 @@
1
1
  export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
2
+ /**
3
+ * Optional function that derives a cache key from a request.
4
+ * Defaults to `url.toString()` when not provided.
5
+ * Return `null` to opt this specific request out of deduplication.
6
+ */
7
+ export type DeduplicationKeyFn = (url: string | URL, options?: RequestInit) => string | null;
8
+ export interface DeduplicationConfig {
9
+ /**
10
+ * Custom function to compute the deduplication key.
11
+ * Receives the same (url, options) passed to resilientFetch.
12
+ * Defaults to the stringified URL (method-agnostic).
13
+ */
14
+ keyFn?: DeduplicationKeyFn;
15
+ }
2
16
  export interface CircuitEntry {
3
17
  state: CircuitState;
4
18
  failureCount: number;
@@ -20,6 +34,11 @@ export interface ResilientFetchConfig<T = unknown> {
20
34
  retryOn?: number[];
21
35
  fallbackOnNonRetryable?: boolean;
22
36
  onNonRetryableError?: (status: number, message: string) => void;
37
+ /**
38
+ * When set, enables request deduplication.
39
+ * Pass an empty object `{}` to activate with the default key function.
40
+ */
41
+ deduplication?: DeduplicationConfig;
23
42
  }
24
43
  export declare class CircuitOpenError extends Error {
25
44
  readonly domain: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,OAAO;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;AAGD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM;CAO3B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,CAAC,EAAE,WAAW,KAClB,MAAM,GAAG,IAAI,CAAC;AAEnB,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAGD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,OAAO;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAGD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM;CAO3B"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA8BA,iEAAiE;AACjE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,MAAM,CAAS;IAExB,YAAY,MAAc;QACxB,KAAK,CAAC,uCAAuC,MAAM,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,kEAAkE;QAClE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAsDA,iEAAiE;AACjE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,MAAM,CAAS;IAExB,YAAY,MAAc;QACxB,KAAK,CAAC,uCAAuC,MAAM,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,kEAAkE;QAClE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codingaryan/smoothapi",
3
- "version": "1.0.0",
4
- "description": "API resilience library — exponential backoff and circuit breaker for fetch",
3
+ "version": "1.2.0",
4
+ "description": "API protection library — exponential backoff and circuit breaker for fetch",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",