@codingaryan/smoothapi 0.1.1 → 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/README.md +53 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,7 +19,38 @@ npm install @codingaryan/smoothapi
|
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
22
|
-
|
|
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.
|
|
23
54
|
|
|
24
55
|
```typescript
|
|
25
56
|
import { createResilientFetch } from '@codingaryan/smoothapi';
|
|
@@ -42,7 +73,6 @@ const fetchWithRetry = createResilientFetch({
|
|
|
42
73
|
|
|
43
74
|
async function main() {
|
|
44
75
|
try {
|
|
45
|
-
// Drop-in replacement for native fetch
|
|
46
76
|
const response = await fetchWithRetry('https://api.example.com/data');
|
|
47
77
|
|
|
48
78
|
// If fallback triggered, it returns your fallback object directly
|
|
@@ -60,6 +90,27 @@ async function main() {
|
|
|
60
90
|
}
|
|
61
91
|
```
|
|
62
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
|
+
|
|
63
114
|
## How It Works
|
|
64
115
|
|
|
65
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`).
|
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;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,
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -25,9 +25,39 @@ export function createResilientFetch(globalConfig) {
|
|
|
25
25
|
try {
|
|
26
26
|
const response = await fetch(url, options);
|
|
27
27
|
// fetch() resolves for any HTTP status. Retryable codes need to be
|
|
28
|
-
// treated as failures manually
|
|
28
|
+
// treated as failures manually.
|
|
29
29
|
if (retryOn.includes(response.status)) {
|
|
30
|
-
|
|
30
|
+
breaker.recordFailure(domain);
|
|
31
|
+
if (attempt < backoffConfig.maxRetries) {
|
|
32
|
+
await sleep(calculateBackoff(attempt, backoffConfig));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
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;
|
|
51
|
+
}
|
|
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
|
+
});
|
|
31
61
|
}
|
|
32
62
|
breaker.recordSuccess(domain);
|
|
33
63
|
return response;
|
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,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ResilientFetchConfig<T = unknown> {
|
|
|
18
18
|
circuitBreaker?: Partial<CircuitBreakerConfig>;
|
|
19
19
|
fallback?: T;
|
|
20
20
|
retryOn?: number[];
|
|
21
|
+
fallbackOnNonRetryable?: boolean;
|
|
22
|
+
onNonRetryableError?: (status: number, message: string) => void;
|
|
21
23
|
}
|
|
22
24
|
export declare class CircuitOpenError extends Error {
|
|
23
25
|
readonly domain: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codingaryan/smoothapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "API resilience library — exponential backoff and circuit breaker for fetch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"build:watch": "tsc --watch",
|
|
20
|
-
"test": "tsc -p tsconfig.test.json && node --test dist-test/tests
|
|
20
|
+
"test": "tsc -p tsconfig.test.json && node --test dist-test/tests/*.test.js",
|
|
21
21
|
"prepublishOnly": "npm run build"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|