@codingaryan/smoothapi 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @codingaryan/smoothapi
|
|
2
2
|
|
|
3
|
-
API
|
|
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
4
|
|
|
5
5
|
Zero dependencies. Small bundle size. Built for modern ESM.
|
|
6
6
|
|
|
@@ -16,19 +16,19 @@ npm install @codingaryan/smoothapi
|
|
|
16
16
|
- **Circuit Breaker (FSM):** Isolated per-domain state machine (`CLOSED` → `OPEN` → `HALF_OPEN`).
|
|
17
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
18
|
- **Graceful Fallbacks:** Optionally serve cached or default data instantly when the circuit is `OPEN`.
|
|
19
|
-
- **Request Deduplication:** Automatically
|
|
19
|
+
- **Request Deduplication:** Automatically couples concurrent identical requests into a single network call.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
23
|
### Basic Usage (Defaults)
|
|
24
24
|
|
|
25
|
-
If you don't need custom configurations, you can
|
|
25
|
+
If you don't need custom configurations, you can use the smooth fetch with its defaults by simply passing an empty object.
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import {
|
|
28
|
+
import { createSmoothFetch } from '@codingaryan/smoothapi';
|
|
29
29
|
|
|
30
30
|
// Create it with default settings
|
|
31
|
-
const fetchWithRetry =
|
|
31
|
+
const fetchWithRetry = createSmoothFetch({});
|
|
32
32
|
|
|
33
33
|
async function main() {
|
|
34
34
|
try {
|
|
@@ -54,9 +54,9 @@ async function main() {
|
|
|
54
54
|
You can override any of the defaults to suit your application's needs, such as adding a fallback object.
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
|
-
import {
|
|
57
|
+
import { createSmoothFetch } from '@codingaryan/smoothapi';
|
|
58
58
|
|
|
59
|
-
const fetchWithRetry =
|
|
59
|
+
const fetchWithRetry = createSmoothFetch({
|
|
60
60
|
backoff: {
|
|
61
61
|
baseDelay: 100, // ms to wait before first retry
|
|
62
62
|
maxDelay: 30000, // cap on exponential growth
|
|
@@ -96,9 +96,9 @@ async function main() {
|
|
|
96
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
97
|
|
|
98
98
|
```typescript
|
|
99
|
-
import {
|
|
99
|
+
import { createSmoothFetch } from '@codingaryan/smoothapi';
|
|
100
100
|
|
|
101
|
-
const fetchWithRetry =
|
|
101
|
+
const fetchWithRetry = createSmoothFetch({
|
|
102
102
|
fallbackOnNonRetryable: true,
|
|
103
103
|
// Optional: Trigger custom UI logic when a client error happens
|
|
104
104
|
onNonRetryableError: (status, message) => {
|
|
@@ -114,14 +114,14 @@ const fetchWithRetry = createResilientFetch({
|
|
|
114
114
|
|
|
115
115
|
### Request Deduplication
|
|
116
116
|
|
|
117
|
-
When multiple identical requests are made concurrently, SmoothAPI
|
|
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
118
|
|
|
119
119
|
**Enable with default key function** (deduplicates by URL):
|
|
120
120
|
|
|
121
121
|
```typescript
|
|
122
|
-
import {
|
|
122
|
+
import { createSmoothFetch } from '@codingaryan/smoothapi';
|
|
123
123
|
|
|
124
|
-
const fetchWithRetry =
|
|
124
|
+
const fetchWithRetry = createSmoothFetch({
|
|
125
125
|
deduplication: {} // Empty object activates deduplication
|
|
126
126
|
});
|
|
127
127
|
|
|
@@ -136,7 +136,7 @@ const [a, b, c] = await Promise.all([
|
|
|
136
136
|
**Custom key function** for advanced coalescing:
|
|
137
137
|
|
|
138
138
|
```typescript
|
|
139
|
-
const fetchWithRetry =
|
|
139
|
+
const fetchWithRetry = createSmoothFetch({
|
|
140
140
|
deduplication: {
|
|
141
141
|
// Deduplicate by method + URL (ignores headers/body)
|
|
142
142
|
keyFn: (url, options) => `${options?.method ?? 'GET'}:${url.toString()}`
|
|
@@ -147,7 +147,7 @@ const fetchWithRetry = createResilientFetch({
|
|
|
147
147
|
**Opt out of deduplication** for specific requests:
|
|
148
148
|
|
|
149
149
|
```typescript
|
|
150
|
-
const fetchWithRetry =
|
|
150
|
+
const fetchWithRetry = createSmoothFetch({
|
|
151
151
|
deduplication: {
|
|
152
152
|
keyFn: (url, options) => {
|
|
153
153
|
// Skip dedup for POST requests
|
|
@@ -158,7 +158,7 @@ const fetchWithRetry = createResilientFetch({
|
|
|
158
158
|
});
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
* **Default Behavior**: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are
|
|
161
|
+
* **Default Behavior**: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are merged.
|
|
162
162
|
* **Error Propagation**: If the network call fails, all waiting callers receive the same error.
|
|
163
163
|
* **Settlement**: Once a request completes, the next call to the same URL triggers a fresh network request.
|
|
164
164
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { ResilientFetchConfig } from "./types.js";
|
|
2
|
-
export declare function
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAWpE,wBAAgB,
|
|
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
|
@@ -8,14 +8,14 @@ const BACKOFF_DEFAULTS = {
|
|
|
8
8
|
maxRetries: 3,
|
|
9
9
|
};
|
|
10
10
|
const DEFAULT_RETRY_ON = [429, 500, 502, 503, 504];
|
|
11
|
-
export function
|
|
11
|
+
export function createSmoothFetch(globalConfig) {
|
|
12
12
|
const backoffConfig = { ...BACKOFF_DEFAULTS, ...globalConfig.backoff };
|
|
13
13
|
const retryOn = globalConfig.retryOn ?? DEFAULT_RETRY_ON;
|
|
14
14
|
const breaker = new CircuitBreakerState(globalConfig.circuitBreaker);
|
|
15
15
|
const deduplicator = globalConfig.deduplication
|
|
16
16
|
? new RequestDeduplicator(globalConfig.deduplication.keyFn)
|
|
17
17
|
: null;
|
|
18
|
-
return async function
|
|
18
|
+
return async function smoothFetch(url, options) {
|
|
19
19
|
const domain = new URL(url).hostname;
|
|
20
20
|
// Block before any network IO if the circuit is OPEN.
|
|
21
21
|
if (!breaker.canRequest(domain)) {
|
|
@@ -89,4 +89,6 @@ export function createResilientFetch(globalConfig) {
|
|
|
89
89
|
return executeRequest();
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
/** @deprecated use createSmoothFetch instead */
|
|
93
|
+
export const createResilientFetch = createSmoothFetch;
|
|
92
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;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,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codingaryan/smoothapi",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "API
|
|
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",
|