axios-engine 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 +21 -0
- package/README.md +280 -0
- package/dist/createApiEngine.d.ts +2 -0
- package/dist/delayManager.d.ts +19 -0
- package/dist/errorNormalizer.d.ts +14 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.esm.js +255 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +11 -0
- package/dist/types.d.ts +37 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 yunush
|
|
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,280 @@
|
|
|
1
|
+
# @yunush/api-engine
|
|
2
|
+
|
|
3
|
+
A platform-agnostic networking library built on [Axios](https://axios-http.com), designed to work seamlessly across **React Native**, **React (Web)**, **Next.js**, and **Node.js**.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@yunush/api-engine)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🔐 **Token injection** — automatically attaches Bearer tokens to every request
|
|
14
|
+
- ♻️ **Token refresh** — handles 401 responses, retries the original request, and queues concurrent calls during refresh
|
|
15
|
+
- 🔁 **Retry logic** — configurable retry attempts for network failures
|
|
16
|
+
- ⏱️ **Response delay** — runtime-toggleable delays for testing loading states
|
|
17
|
+
- 🖥️ **Backend-controlled delay** — honour `X-API-Delay` response headers from your server
|
|
18
|
+
- 🪵 **Structured logging** — request/response logs with timing, opt-in per environment
|
|
19
|
+
- 🎯 **Normalised errors** — every error follows `{ status, message, data }` regardless of source
|
|
20
|
+
- 🧩 **Fully typed** — complete TypeScript definitions included
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @yunush/api-engine axios
|
|
28
|
+
# or
|
|
29
|
+
yarn add @yunush/api-engine axios
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> `axios` is a peer dependency and must be installed alongside the library.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createApiEngine } from "@yunush/api-engine";
|
|
40
|
+
|
|
41
|
+
export const api = createApiEngine({
|
|
42
|
+
baseURL: "https://api.myapp.com",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Use it anywhere
|
|
46
|
+
const users = await api.get("/users");
|
|
47
|
+
const created = await api.post("/users", { name: "Alice" });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
createApiEngine({
|
|
56
|
+
baseURL: "https://api.myapp.com",
|
|
57
|
+
|
|
58
|
+
// Return the current auth token (sync or async)
|
|
59
|
+
getToken: async () => AsyncStorage.getItem("token"),
|
|
60
|
+
|
|
61
|
+
// Called on 401 — return a fresh token
|
|
62
|
+
refreshToken: async () => {
|
|
63
|
+
const res = await fetch("/auth/refresh");
|
|
64
|
+
const { token } = await res.json();
|
|
65
|
+
return token;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Called when refresh fails
|
|
69
|
+
onLogout: () => router.push("/login"),
|
|
70
|
+
|
|
71
|
+
// Retry network failures up to N times
|
|
72
|
+
retry: 2,
|
|
73
|
+
|
|
74
|
+
// Static delay applied to every response (ms)
|
|
75
|
+
responseDelay: 0,
|
|
76
|
+
|
|
77
|
+
// Log requests and responses
|
|
78
|
+
enableLogging: true,
|
|
79
|
+
|
|
80
|
+
// Any extra Axios defaults
|
|
81
|
+
axiosConfig: { timeout: 15_000 },
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `baseURL` | `string` | — | Base URL for all requests |
|
|
88
|
+
| `getToken` | `() => string \| null` | — | Returns the current auth token |
|
|
89
|
+
| `refreshToken` | `() => Promise<string>` | — | Fetches a new token on 401 |
|
|
90
|
+
| `onLogout` | `() => void` | — | Called when refresh fails |
|
|
91
|
+
| `retry` | `number` | `0` | Network error retry count |
|
|
92
|
+
| `responseDelay` | `number` | `0` | Static delay in ms |
|
|
93
|
+
| `enableLogging` | `boolean` | `false` | Enable console logging |
|
|
94
|
+
| `axiosConfig` | `AxiosRequestConfig` | `{}` | Extra Axios options |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Response Delay
|
|
99
|
+
|
|
100
|
+
Useful for testing loading states, simulating slow networks, and demos.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { setResponseDelay } from "@yunush/api-engine";
|
|
104
|
+
|
|
105
|
+
// Enable a 5-second delay globally
|
|
106
|
+
setResponseDelay(true, 5000);
|
|
107
|
+
|
|
108
|
+
// Disable it
|
|
109
|
+
setResponseDelay(false);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Backend-Controlled Delay
|
|
113
|
+
|
|
114
|
+
Your backend can control delay without a client deploy by setting the `X-API-Delay` header:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// Express example
|
|
118
|
+
app.get("/users", (req, res) => {
|
|
119
|
+
res.set("X-API-Delay", "3000"); // 3-second delay
|
|
120
|
+
res.json({ users: [] });
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Admin Config Pattern
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const config = await api.get<{ apiDelayEnabled: boolean; apiDelayTime: number }>("/app-config");
|
|
128
|
+
setResponseDelay(config.apiDelayEnabled, config.apiDelayTime);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This lets admins toggle API throttling at runtime without redeploying your app.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Platform Examples
|
|
136
|
+
|
|
137
|
+
### React Native (AsyncStorage)
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
141
|
+
import { createApiEngine } from "@yunush/api-engine";
|
|
142
|
+
|
|
143
|
+
export const api = createApiEngine({
|
|
144
|
+
baseURL: "https://api.myapp.com",
|
|
145
|
+
getToken: () => AsyncStorage.getItem("token"),
|
|
146
|
+
refreshToken: async () => {
|
|
147
|
+
const res = await fetch("https://api.myapp.com/auth/refresh");
|
|
148
|
+
const { token } = await res.json();
|
|
149
|
+
await AsyncStorage.setItem("token", token);
|
|
150
|
+
return token;
|
|
151
|
+
},
|
|
152
|
+
onLogout: () => {
|
|
153
|
+
AsyncStorage.removeItem("token");
|
|
154
|
+
// navigate to login screen
|
|
155
|
+
},
|
|
156
|
+
retry: 2,
|
|
157
|
+
enableLogging: __DEV__,
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### React / Next.js (localStorage)
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { createApiEngine } from "@yunush/api-engine";
|
|
165
|
+
|
|
166
|
+
export const api = createApiEngine({
|
|
167
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL!,
|
|
168
|
+
getToken: () => localStorage.getItem("token"),
|
|
169
|
+
refreshToken: async () => {
|
|
170
|
+
const res = await fetch("/api/auth/refresh");
|
|
171
|
+
const { token } = await res.json();
|
|
172
|
+
localStorage.setItem("token", token);
|
|
173
|
+
return token;
|
|
174
|
+
},
|
|
175
|
+
onLogout: () => {
|
|
176
|
+
localStorage.removeItem("token");
|
|
177
|
+
window.location.href = "/login";
|
|
178
|
+
},
|
|
179
|
+
enableLogging: process.env.NODE_ENV === "development",
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Node.js
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { createApiEngine } from "@yunush/api-engine";
|
|
187
|
+
|
|
188
|
+
const api = createApiEngine({
|
|
189
|
+
baseURL: "https://api.partner.com",
|
|
190
|
+
getToken: () => process.env.API_TOKEN ?? null,
|
|
191
|
+
retry: 3,
|
|
192
|
+
enableLogging: true,
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Error Handling
|
|
199
|
+
|
|
200
|
+
All errors are normalised into `ApiError` — a typed subclass of `Error`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { ApiError } from "@yunush/api-engine";
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const data = await api.get("/users");
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (err instanceof ApiError) {
|
|
209
|
+
console.log(err.status); // e.g. 404
|
|
210
|
+
console.log(err.message); // e.g. "Not Found"
|
|
211
|
+
console.log(err.data); // response body (if any)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Field | Type | Description |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `status` | `number` | HTTP status code (`0` = network error, `-1` = unknown) |
|
|
219
|
+
| `message` | `string` | Human-readable error message |
|
|
220
|
+
| `data` | `unknown` | Raw response body from the server |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Token Refresh Flow
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Request → 401 response
|
|
228
|
+
↓
|
|
229
|
+
Call refreshToken()
|
|
230
|
+
↓
|
|
231
|
+
Success?
|
|
232
|
+
Yes → update Authorization header → retry original request
|
|
233
|
+
No → call onLogout() → throw ApiError
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Concurrent requests that arrive while a refresh is in-flight are queued and replayed automatically once the new token is available.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Logging
|
|
241
|
+
|
|
242
|
+
When `enableLogging: true`:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
[API Engine] → GET /users
|
|
246
|
+
[API Engine] ← GET /users [200] 142ms
|
|
247
|
+
[API Engine] Token expired — refreshing...
|
|
248
|
+
[API Engine] ← GET /users [200] 98ms
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Disable in production:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
enableLogging: process.env.NODE_ENV !== "production"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Best Practices
|
|
260
|
+
|
|
261
|
+
**Create a single shared instance**
|
|
262
|
+
```ts
|
|
263
|
+
// lib/api.ts
|
|
264
|
+
export const api = createApiEngine({ ... });
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Fetch backend config on app startup**
|
|
268
|
+
```ts
|
|
269
|
+
// app entry point
|
|
270
|
+
const config = await api.get("/app-config");
|
|
271
|
+
setResponseDelay(config.delayEnabled, config.delayMs);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Don't create multiple instances** — multiple interceptor stacks lead to unpredictable behaviour.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT © yunush
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DelayState } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Enable or disable global response delay.
|
|
4
|
+
*
|
|
5
|
+
* @param enabled - Whether delay should be active
|
|
6
|
+
* @param duration - Delay duration in milliseconds (required when enabling)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Enable a 3-second delay
|
|
10
|
+
* setResponseDelay(true, 3000);
|
|
11
|
+
*
|
|
12
|
+
* // Disable delay
|
|
13
|
+
* setResponseDelay(false);
|
|
14
|
+
*/
|
|
15
|
+
export declare function setResponseDelay(enabled: boolean, duration?: number): void;
|
|
16
|
+
/** Get current delay state (used internally by interceptors) */
|
|
17
|
+
export declare function getDelayState(): Readonly<DelayState>;
|
|
18
|
+
/** Returns a Promise that resolves after the current delay duration */
|
|
19
|
+
export declare function applyDelay(overrideDuration?: number): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NormalizedError } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes any error thrown by Axios into a consistent shape.
|
|
4
|
+
*
|
|
5
|
+
* All API errors conform to:
|
|
6
|
+
* { status: number, message: string, data: unknown }
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeError(error: unknown): NormalizedError;
|
|
9
|
+
/** Creates a typed error object from a normalized error */
|
|
10
|
+
export declare class ApiError extends Error implements NormalizedError {
|
|
11
|
+
status: number;
|
|
12
|
+
data: unknown;
|
|
13
|
+
constructor(normalized: NormalizedError);
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as axios from 'axios';
|
|
2
|
+
import { AxiosRequestConfig } from 'axios';
|
|
3
|
+
|
|
4
|
+
interface ApiEngineConfig {
|
|
5
|
+
/** Base URL for all API requests */
|
|
6
|
+
baseURL: string;
|
|
7
|
+
/** Async function that returns the current auth token */
|
|
8
|
+
getToken?: () => Promise<string | null> | string | null;
|
|
9
|
+
/** Called when a 401 is received; should return a new token */
|
|
10
|
+
refreshToken?: () => Promise<string | null>;
|
|
11
|
+
/** Called when token refresh fails — use to redirect to login */
|
|
12
|
+
onLogout?: () => void;
|
|
13
|
+
/** Number of retry attempts on network failures (default: 0) */
|
|
14
|
+
retry?: number;
|
|
15
|
+
/** Static delay in ms applied to every response (default: 0) */
|
|
16
|
+
responseDelay?: number;
|
|
17
|
+
/** Enable request/response logging (default: false) */
|
|
18
|
+
enableLogging?: boolean;
|
|
19
|
+
/** Additional Axios config defaults */
|
|
20
|
+
axiosConfig?: AxiosRequestConfig;
|
|
21
|
+
}
|
|
22
|
+
interface NormalizedError {
|
|
23
|
+
status: number;
|
|
24
|
+
message: string;
|
|
25
|
+
data: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface DelayState {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
duration: number;
|
|
30
|
+
}
|
|
31
|
+
interface ApiEngine {
|
|
32
|
+
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
33
|
+
post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
34
|
+
put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
35
|
+
patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
36
|
+
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
37
|
+
request<T = unknown>(config: AxiosRequestConfig): Promise<T>;
|
|
38
|
+
getAxiosInstance(): axios.AxiosInstance;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare function createApiEngine(config: ApiEngineConfig): ApiEngine;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Enable or disable global response delay.
|
|
45
|
+
*
|
|
46
|
+
* @param enabled - Whether delay should be active
|
|
47
|
+
* @param duration - Delay duration in milliseconds (required when enabling)
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Enable a 3-second delay
|
|
51
|
+
* setResponseDelay(true, 3000);
|
|
52
|
+
*
|
|
53
|
+
* // Disable delay
|
|
54
|
+
* setResponseDelay(false);
|
|
55
|
+
*/
|
|
56
|
+
declare function setResponseDelay(enabled: boolean, duration?: number): void;
|
|
57
|
+
/** Get current delay state (used internally by interceptors) */
|
|
58
|
+
declare function getDelayState(): Readonly<DelayState>;
|
|
59
|
+
|
|
60
|
+
/** Creates a typed error object from a normalized error */
|
|
61
|
+
declare class ApiError extends Error implements NormalizedError {
|
|
62
|
+
status: number;
|
|
63
|
+
data: unknown;
|
|
64
|
+
constructor(normalized: NormalizedError);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { ApiError, createApiEngine, getDelayState, setResponseDelay };
|
|
68
|
+
export type { ApiEngine, ApiEngineConfig, DelayState, NormalizedError };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
2
|
+
|
|
3
|
+
/** Internal singleton state for response delay */
|
|
4
|
+
const delayState = {
|
|
5
|
+
enabled: false,
|
|
6
|
+
duration: 0,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Enable or disable global response delay.
|
|
10
|
+
*
|
|
11
|
+
* @param enabled - Whether delay should be active
|
|
12
|
+
* @param duration - Delay duration in milliseconds (required when enabling)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Enable a 3-second delay
|
|
16
|
+
* setResponseDelay(true, 3000);
|
|
17
|
+
*
|
|
18
|
+
* // Disable delay
|
|
19
|
+
* setResponseDelay(false);
|
|
20
|
+
*/
|
|
21
|
+
function setResponseDelay(enabled, duration = 0) {
|
|
22
|
+
delayState.enabled = enabled;
|
|
23
|
+
delayState.duration = enabled ? duration : 0;
|
|
24
|
+
}
|
|
25
|
+
/** Get current delay state (used internally by interceptors) */
|
|
26
|
+
function getDelayState() {
|
|
27
|
+
return delayState;
|
|
28
|
+
}
|
|
29
|
+
/** Returns a Promise that resolves after the current delay duration */
|
|
30
|
+
function applyDelay(overrideDuration) {
|
|
31
|
+
const duration = overrideDuration !== null && overrideDuration !== void 0 ? overrideDuration : delayState.duration;
|
|
32
|
+
if (!delayState.enabled && overrideDuration === undefined)
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
if (duration <= 0)
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let loggingEnabled = false;
|
|
40
|
+
function setLogging(enabled) {
|
|
41
|
+
loggingEnabled = enabled;
|
|
42
|
+
}
|
|
43
|
+
function log(level, ...args) {
|
|
44
|
+
if (!loggingEnabled)
|
|
45
|
+
return;
|
|
46
|
+
const prefix = "[API Engine]";
|
|
47
|
+
switch (level) {
|
|
48
|
+
case "info":
|
|
49
|
+
console.log(prefix, ...args);
|
|
50
|
+
break;
|
|
51
|
+
case "warn":
|
|
52
|
+
console.warn(prefix, ...args);
|
|
53
|
+
break;
|
|
54
|
+
case "error":
|
|
55
|
+
console.error(prefix, ...args);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const logger = {
|
|
60
|
+
info: (...args) => log("info", ...args),
|
|
61
|
+
warn: (...args) => log("warn", ...args),
|
|
62
|
+
error: (...args) => log("error", ...args),
|
|
63
|
+
request: (method, url) => log("info", `→ ${method.toUpperCase()} ${url}`),
|
|
64
|
+
response: (method, url, status, durationMs) => log("info", `← ${method.toUpperCase()} ${url} [${status}] ${durationMs}ms`),
|
|
65
|
+
retrying: (attempt, max, url) => log("warn", `Retrying (${attempt}/${max}): ${url}`),
|
|
66
|
+
tokenRefresh: () => log("info", "Token expired — refreshing..."),
|
|
67
|
+
logout: () => log("warn", "Token refresh failed — logging out."),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalizes any error thrown by Axios into a consistent shape.
|
|
72
|
+
*
|
|
73
|
+
* All API errors conform to:
|
|
74
|
+
* { status: number, message: string, data: unknown }
|
|
75
|
+
*/
|
|
76
|
+
function normalizeError(error) {
|
|
77
|
+
var _a, _b, _c;
|
|
78
|
+
if (error instanceof AxiosError) {
|
|
79
|
+
const response = error.response;
|
|
80
|
+
if (response) {
|
|
81
|
+
const data = response.data;
|
|
82
|
+
return {
|
|
83
|
+
status: response.status,
|
|
84
|
+
message: (_c = (_b = (_a = (data && typeof data.message === "string" ? data.message : null)) !== null && _a !== void 0 ? _a : error.message) !== null && _b !== void 0 ? _b : response.statusText) !== null && _c !== void 0 ? _c : "Unknown error",
|
|
85
|
+
data: data !== null && data !== void 0 ? data : {},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Network error / timeout — no response received
|
|
89
|
+
if (error.request) {
|
|
90
|
+
return {
|
|
91
|
+
status: 0,
|
|
92
|
+
message: error.code === "ECONNABORTED" ? "Request timed out" : "Network error — no response received",
|
|
93
|
+
data: {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Non-Axios error
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
return { status: -1, message: error.message, data: {} };
|
|
100
|
+
}
|
|
101
|
+
return { status: -1, message: "An unexpected error occurred", data: {} };
|
|
102
|
+
}
|
|
103
|
+
/** Creates a typed error object from a normalized error */
|
|
104
|
+
class ApiError extends Error {
|
|
105
|
+
constructor(normalized) {
|
|
106
|
+
super(normalized.message);
|
|
107
|
+
this.name = "ApiError";
|
|
108
|
+
this.status = normalized.status;
|
|
109
|
+
this.data = normalized.data;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const RETRY_COUNT_KEY = "__retryCount";
|
|
114
|
+
const START_TIME_KEY = "__startTime";
|
|
115
|
+
function createApiEngine(config) {
|
|
116
|
+
const { baseURL, getToken, refreshToken, onLogout, retry = 0, responseDelay = 0, enableLogging = false, axiosConfig = {}, } = config;
|
|
117
|
+
// Apply initial static delay if provided in config
|
|
118
|
+
if (responseDelay > 0) {
|
|
119
|
+
const { setResponseDelay } = require("./delayManager");
|
|
120
|
+
setResponseDelay(true, responseDelay);
|
|
121
|
+
}
|
|
122
|
+
setLogging(enableLogging);
|
|
123
|
+
const instance = axios.create(Object.assign({ baseURL, timeout: 30000, headers: { "Content-Type": "application/json" } }, axiosConfig));
|
|
124
|
+
let isRefreshing = false;
|
|
125
|
+
let refreshQueue = [];
|
|
126
|
+
function processRefreshQueue(token, error) {
|
|
127
|
+
for (const pending of refreshQueue) {
|
|
128
|
+
if (token) {
|
|
129
|
+
pending.resolve(token);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
pending.reject(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
refreshQueue = [];
|
|
136
|
+
}
|
|
137
|
+
// ─── Request Interceptor ─────────────────────────────────────────────────
|
|
138
|
+
instance.interceptors.request.use(async (req) => {
|
|
139
|
+
var _a, _b, _c;
|
|
140
|
+
// Stamp start time for logging
|
|
141
|
+
req[START_TIME_KEY] = Date.now();
|
|
142
|
+
// Inject auth token
|
|
143
|
+
if (getToken) {
|
|
144
|
+
const token = await getToken();
|
|
145
|
+
if (token) {
|
|
146
|
+
req.headers = (_a = req.headers) !== null && _a !== void 0 ? _a : {};
|
|
147
|
+
req.headers["Authorization"] = `Bearer ${token}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
logger.request((_b = req.method) !== null && _b !== void 0 ? _b : "GET", (_c = req.url) !== null && _c !== void 0 ? _c : "");
|
|
151
|
+
return req;
|
|
152
|
+
}, (error) => Promise.reject(error));
|
|
153
|
+
// ─── Response Interceptor ────────────────────────────────────────────────
|
|
154
|
+
instance.interceptors.response.use(async (response) => {
|
|
155
|
+
var _a, _b;
|
|
156
|
+
const req = response.config;
|
|
157
|
+
const start = req[START_TIME_KEY];
|
|
158
|
+
const duration = start ? Date.now() - start : 0;
|
|
159
|
+
logger.response((_a = response.config.method) !== null && _a !== void 0 ? _a : "GET", (_b = response.config.url) !== null && _b !== void 0 ? _b : "", response.status, duration);
|
|
160
|
+
// ── Backend-controlled delay via X-API-Delay header ──
|
|
161
|
+
const headerDelay = response.headers["x-api-delay"];
|
|
162
|
+
if (headerDelay) {
|
|
163
|
+
const ms = parseInt(headerDelay, 10);
|
|
164
|
+
if (!isNaN(ms) && ms > 0) {
|
|
165
|
+
logger.info(`Applying backend-controlled delay: ${ms}ms`);
|
|
166
|
+
await applyDelay(ms);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── Global runtime delay ──
|
|
170
|
+
else {
|
|
171
|
+
const delayState = getDelayState();
|
|
172
|
+
if (delayState.enabled && delayState.duration > 0) {
|
|
173
|
+
await applyDelay(delayState.duration);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return response;
|
|
177
|
+
}, async (error) => {
|
|
178
|
+
var _a, _b, _c;
|
|
179
|
+
const originalRequest = error.config;
|
|
180
|
+
if (!originalRequest) {
|
|
181
|
+
throw new ApiError(normalizeError(error));
|
|
182
|
+
}
|
|
183
|
+
// ── Retry logic for network failures ──
|
|
184
|
+
const isNetworkError = !error.response && error.request;
|
|
185
|
+
const currentRetry = (_a = originalRequest[RETRY_COUNT_KEY]) !== null && _a !== void 0 ? _a : 0;
|
|
186
|
+
if (isNetworkError && currentRetry < retry) {
|
|
187
|
+
originalRequest[RETRY_COUNT_KEY] = currentRetry + 1;
|
|
188
|
+
logger.retrying(currentRetry + 1, retry, (_b = originalRequest.url) !== null && _b !== void 0 ? _b : "");
|
|
189
|
+
return instance(originalRequest);
|
|
190
|
+
}
|
|
191
|
+
// ── 401 Token Refresh ──
|
|
192
|
+
if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 401 && refreshToken && !originalRequest["__isRetry"]) {
|
|
193
|
+
originalRequest["__isRetry"] = true;
|
|
194
|
+
if (isRefreshing) {
|
|
195
|
+
// Queue this request until refresh completes
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
refreshQueue.push({
|
|
198
|
+
resolve: (newToken) => {
|
|
199
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
200
|
+
resolve(instance(originalRequest));
|
|
201
|
+
},
|
|
202
|
+
reject,
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
isRefreshing = true;
|
|
207
|
+
logger.tokenRefresh();
|
|
208
|
+
try {
|
|
209
|
+
const newToken = await refreshToken();
|
|
210
|
+
isRefreshing = false;
|
|
211
|
+
if (!newToken)
|
|
212
|
+
throw new Error("Refresh returned empty token");
|
|
213
|
+
processRefreshQueue(newToken, null);
|
|
214
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
215
|
+
return instance(originalRequest);
|
|
216
|
+
}
|
|
217
|
+
catch (refreshError) {
|
|
218
|
+
isRefreshing = false;
|
|
219
|
+
processRefreshQueue(null, refreshError);
|
|
220
|
+
logger.logout();
|
|
221
|
+
onLogout === null || onLogout === void 0 ? void 0 : onLogout();
|
|
222
|
+
throw new ApiError(normalizeError(error));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ── Normalize all other errors ──
|
|
226
|
+
throw new ApiError(normalizeError(error));
|
|
227
|
+
});
|
|
228
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
229
|
+
return {
|
|
230
|
+
get(url, config) {
|
|
231
|
+
return instance.get(url, config);
|
|
232
|
+
},
|
|
233
|
+
post(url, data, config) {
|
|
234
|
+
return instance.post(url, data, config);
|
|
235
|
+
},
|
|
236
|
+
put(url, data, config) {
|
|
237
|
+
return instance.put(url, data, config);
|
|
238
|
+
},
|
|
239
|
+
patch(url, data, config) {
|
|
240
|
+
return instance.patch(url, data, config);
|
|
241
|
+
},
|
|
242
|
+
delete(url, config) {
|
|
243
|
+
return instance.delete(url, config);
|
|
244
|
+
},
|
|
245
|
+
request(config) {
|
|
246
|
+
return instance.request(config);
|
|
247
|
+
},
|
|
248
|
+
getAxiosInstance() {
|
|
249
|
+
return instance;
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { ApiError, createApiEngine, getDelayState, setResponseDelay };
|
|
255
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/delayManager.ts","../src/logger.ts","../src/errorNormalizer.ts","../src/createApiEngine.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var axios = require('axios');
|
|
4
|
+
|
|
5
|
+
/** Internal singleton state for response delay */
|
|
6
|
+
const delayState = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
duration: 0,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Enable or disable global response delay.
|
|
12
|
+
*
|
|
13
|
+
* @param enabled - Whether delay should be active
|
|
14
|
+
* @param duration - Delay duration in milliseconds (required when enabling)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Enable a 3-second delay
|
|
18
|
+
* setResponseDelay(true, 3000);
|
|
19
|
+
*
|
|
20
|
+
* // Disable delay
|
|
21
|
+
* setResponseDelay(false);
|
|
22
|
+
*/
|
|
23
|
+
function setResponseDelay(enabled, duration = 0) {
|
|
24
|
+
delayState.enabled = enabled;
|
|
25
|
+
delayState.duration = enabled ? duration : 0;
|
|
26
|
+
}
|
|
27
|
+
/** Get current delay state (used internally by interceptors) */
|
|
28
|
+
function getDelayState() {
|
|
29
|
+
return delayState;
|
|
30
|
+
}
|
|
31
|
+
/** Returns a Promise that resolves after the current delay duration */
|
|
32
|
+
function applyDelay(overrideDuration) {
|
|
33
|
+
const duration = overrideDuration !== null && overrideDuration !== void 0 ? overrideDuration : delayState.duration;
|
|
34
|
+
if (!delayState.enabled && overrideDuration === undefined)
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
if (duration <= 0)
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let loggingEnabled = false;
|
|
42
|
+
function setLogging(enabled) {
|
|
43
|
+
loggingEnabled = enabled;
|
|
44
|
+
}
|
|
45
|
+
function log(level, ...args) {
|
|
46
|
+
if (!loggingEnabled)
|
|
47
|
+
return;
|
|
48
|
+
const prefix = "[API Engine]";
|
|
49
|
+
switch (level) {
|
|
50
|
+
case "info":
|
|
51
|
+
console.log(prefix, ...args);
|
|
52
|
+
break;
|
|
53
|
+
case "warn":
|
|
54
|
+
console.warn(prefix, ...args);
|
|
55
|
+
break;
|
|
56
|
+
case "error":
|
|
57
|
+
console.error(prefix, ...args);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const logger = {
|
|
62
|
+
info: (...args) => log("info", ...args),
|
|
63
|
+
warn: (...args) => log("warn", ...args),
|
|
64
|
+
error: (...args) => log("error", ...args),
|
|
65
|
+
request: (method, url) => log("info", `→ ${method.toUpperCase()} ${url}`),
|
|
66
|
+
response: (method, url, status, durationMs) => log("info", `← ${method.toUpperCase()} ${url} [${status}] ${durationMs}ms`),
|
|
67
|
+
retrying: (attempt, max, url) => log("warn", `Retrying (${attempt}/${max}): ${url}`),
|
|
68
|
+
tokenRefresh: () => log("info", "Token expired — refreshing..."),
|
|
69
|
+
logout: () => log("warn", "Token refresh failed — logging out."),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalizes any error thrown by Axios into a consistent shape.
|
|
74
|
+
*
|
|
75
|
+
* All API errors conform to:
|
|
76
|
+
* { status: number, message: string, data: unknown }
|
|
77
|
+
*/
|
|
78
|
+
function normalizeError(error) {
|
|
79
|
+
var _a, _b, _c;
|
|
80
|
+
if (error instanceof axios.AxiosError) {
|
|
81
|
+
const response = error.response;
|
|
82
|
+
if (response) {
|
|
83
|
+
const data = response.data;
|
|
84
|
+
return {
|
|
85
|
+
status: response.status,
|
|
86
|
+
message: (_c = (_b = (_a = (data && typeof data.message === "string" ? data.message : null)) !== null && _a !== void 0 ? _a : error.message) !== null && _b !== void 0 ? _b : response.statusText) !== null && _c !== void 0 ? _c : "Unknown error",
|
|
87
|
+
data: data !== null && data !== void 0 ? data : {},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Network error / timeout — no response received
|
|
91
|
+
if (error.request) {
|
|
92
|
+
return {
|
|
93
|
+
status: 0,
|
|
94
|
+
message: error.code === "ECONNABORTED" ? "Request timed out" : "Network error — no response received",
|
|
95
|
+
data: {},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Non-Axios error
|
|
100
|
+
if (error instanceof Error) {
|
|
101
|
+
return { status: -1, message: error.message, data: {} };
|
|
102
|
+
}
|
|
103
|
+
return { status: -1, message: "An unexpected error occurred", data: {} };
|
|
104
|
+
}
|
|
105
|
+
/** Creates a typed error object from a normalized error */
|
|
106
|
+
class ApiError extends Error {
|
|
107
|
+
constructor(normalized) {
|
|
108
|
+
super(normalized.message);
|
|
109
|
+
this.name = "ApiError";
|
|
110
|
+
this.status = normalized.status;
|
|
111
|
+
this.data = normalized.data;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const RETRY_COUNT_KEY = "__retryCount";
|
|
116
|
+
const START_TIME_KEY = "__startTime";
|
|
117
|
+
function createApiEngine(config) {
|
|
118
|
+
const { baseURL, getToken, refreshToken, onLogout, retry = 0, responseDelay = 0, enableLogging = false, axiosConfig = {}, } = config;
|
|
119
|
+
// Apply initial static delay if provided in config
|
|
120
|
+
if (responseDelay > 0) {
|
|
121
|
+
const { setResponseDelay } = require("./delayManager");
|
|
122
|
+
setResponseDelay(true, responseDelay);
|
|
123
|
+
}
|
|
124
|
+
setLogging(enableLogging);
|
|
125
|
+
const instance = axios.create(Object.assign({ baseURL, timeout: 30000, headers: { "Content-Type": "application/json" } }, axiosConfig));
|
|
126
|
+
let isRefreshing = false;
|
|
127
|
+
let refreshQueue = [];
|
|
128
|
+
function processRefreshQueue(token, error) {
|
|
129
|
+
for (const pending of refreshQueue) {
|
|
130
|
+
if (token) {
|
|
131
|
+
pending.resolve(token);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
pending.reject(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
refreshQueue = [];
|
|
138
|
+
}
|
|
139
|
+
// ─── Request Interceptor ─────────────────────────────────────────────────
|
|
140
|
+
instance.interceptors.request.use(async (req) => {
|
|
141
|
+
var _a, _b, _c;
|
|
142
|
+
// Stamp start time for logging
|
|
143
|
+
req[START_TIME_KEY] = Date.now();
|
|
144
|
+
// Inject auth token
|
|
145
|
+
if (getToken) {
|
|
146
|
+
const token = await getToken();
|
|
147
|
+
if (token) {
|
|
148
|
+
req.headers = (_a = req.headers) !== null && _a !== void 0 ? _a : {};
|
|
149
|
+
req.headers["Authorization"] = `Bearer ${token}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
logger.request((_b = req.method) !== null && _b !== void 0 ? _b : "GET", (_c = req.url) !== null && _c !== void 0 ? _c : "");
|
|
153
|
+
return req;
|
|
154
|
+
}, (error) => Promise.reject(error));
|
|
155
|
+
// ─── Response Interceptor ────────────────────────────────────────────────
|
|
156
|
+
instance.interceptors.response.use(async (response) => {
|
|
157
|
+
var _a, _b;
|
|
158
|
+
const req = response.config;
|
|
159
|
+
const start = req[START_TIME_KEY];
|
|
160
|
+
const duration = start ? Date.now() - start : 0;
|
|
161
|
+
logger.response((_a = response.config.method) !== null && _a !== void 0 ? _a : "GET", (_b = response.config.url) !== null && _b !== void 0 ? _b : "", response.status, duration);
|
|
162
|
+
// ── Backend-controlled delay via X-API-Delay header ──
|
|
163
|
+
const headerDelay = response.headers["x-api-delay"];
|
|
164
|
+
if (headerDelay) {
|
|
165
|
+
const ms = parseInt(headerDelay, 10);
|
|
166
|
+
if (!isNaN(ms) && ms > 0) {
|
|
167
|
+
logger.info(`Applying backend-controlled delay: ${ms}ms`);
|
|
168
|
+
await applyDelay(ms);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ── Global runtime delay ──
|
|
172
|
+
else {
|
|
173
|
+
const delayState = getDelayState();
|
|
174
|
+
if (delayState.enabled && delayState.duration > 0) {
|
|
175
|
+
await applyDelay(delayState.duration);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return response;
|
|
179
|
+
}, async (error) => {
|
|
180
|
+
var _a, _b, _c;
|
|
181
|
+
const originalRequest = error.config;
|
|
182
|
+
if (!originalRequest) {
|
|
183
|
+
throw new ApiError(normalizeError(error));
|
|
184
|
+
}
|
|
185
|
+
// ── Retry logic for network failures ──
|
|
186
|
+
const isNetworkError = !error.response && error.request;
|
|
187
|
+
const currentRetry = (_a = originalRequest[RETRY_COUNT_KEY]) !== null && _a !== void 0 ? _a : 0;
|
|
188
|
+
if (isNetworkError && currentRetry < retry) {
|
|
189
|
+
originalRequest[RETRY_COUNT_KEY] = currentRetry + 1;
|
|
190
|
+
logger.retrying(currentRetry + 1, retry, (_b = originalRequest.url) !== null && _b !== void 0 ? _b : "");
|
|
191
|
+
return instance(originalRequest);
|
|
192
|
+
}
|
|
193
|
+
// ── 401 Token Refresh ──
|
|
194
|
+
if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 401 && refreshToken && !originalRequest["__isRetry"]) {
|
|
195
|
+
originalRequest["__isRetry"] = true;
|
|
196
|
+
if (isRefreshing) {
|
|
197
|
+
// Queue this request until refresh completes
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
refreshQueue.push({
|
|
200
|
+
resolve: (newToken) => {
|
|
201
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
202
|
+
resolve(instance(originalRequest));
|
|
203
|
+
},
|
|
204
|
+
reject,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
isRefreshing = true;
|
|
209
|
+
logger.tokenRefresh();
|
|
210
|
+
try {
|
|
211
|
+
const newToken = await refreshToken();
|
|
212
|
+
isRefreshing = false;
|
|
213
|
+
if (!newToken)
|
|
214
|
+
throw new Error("Refresh returned empty token");
|
|
215
|
+
processRefreshQueue(newToken, null);
|
|
216
|
+
originalRequest.headers["Authorization"] = `Bearer ${newToken}`;
|
|
217
|
+
return instance(originalRequest);
|
|
218
|
+
}
|
|
219
|
+
catch (refreshError) {
|
|
220
|
+
isRefreshing = false;
|
|
221
|
+
processRefreshQueue(null, refreshError);
|
|
222
|
+
logger.logout();
|
|
223
|
+
onLogout === null || onLogout === void 0 ? void 0 : onLogout();
|
|
224
|
+
throw new ApiError(normalizeError(error));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// ── Normalize all other errors ──
|
|
228
|
+
throw new ApiError(normalizeError(error));
|
|
229
|
+
});
|
|
230
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
231
|
+
return {
|
|
232
|
+
get(url, config) {
|
|
233
|
+
return instance.get(url, config);
|
|
234
|
+
},
|
|
235
|
+
post(url, data, config) {
|
|
236
|
+
return instance.post(url, data, config);
|
|
237
|
+
},
|
|
238
|
+
put(url, data, config) {
|
|
239
|
+
return instance.put(url, data, config);
|
|
240
|
+
},
|
|
241
|
+
patch(url, data, config) {
|
|
242
|
+
return instance.patch(url, data, config);
|
|
243
|
+
},
|
|
244
|
+
delete(url, config) {
|
|
245
|
+
return instance.delete(url, config);
|
|
246
|
+
},
|
|
247
|
+
request(config) {
|
|
248
|
+
return instance.request(config);
|
|
249
|
+
},
|
|
250
|
+
getAxiosInstance() {
|
|
251
|
+
return instance;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
exports.ApiError = ApiError;
|
|
257
|
+
exports.createApiEngine = createApiEngine;
|
|
258
|
+
exports.getDelayState = getDelayState;
|
|
259
|
+
exports.setResponseDelay = setResponseDelay;
|
|
260
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/delayManager.ts","../src/logger.ts","../src/errorNormalizer.ts","../src/createApiEngine.ts"],"sourcesContent":[null,null,null,null],"names":["AxiosError"],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function setLogging(enabled: boolean): void;
|
|
2
|
+
export declare const logger: {
|
|
3
|
+
info: (...args: unknown[]) => void;
|
|
4
|
+
warn: (...args: unknown[]) => void;
|
|
5
|
+
error: (...args: unknown[]) => void;
|
|
6
|
+
request: (method: string, url: string) => void;
|
|
7
|
+
response: (method: string, url: string, status: number, durationMs: number) => void;
|
|
8
|
+
retrying: (attempt: number, max: number, url: string) => void;
|
|
9
|
+
tokenRefresh: () => void;
|
|
10
|
+
logout: () => void;
|
|
11
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AxiosRequestConfig } from "axios";
|
|
2
|
+
export interface ApiEngineConfig {
|
|
3
|
+
/** Base URL for all API requests */
|
|
4
|
+
baseURL: string;
|
|
5
|
+
/** Async function that returns the current auth token */
|
|
6
|
+
getToken?: () => Promise<string | null> | string | null;
|
|
7
|
+
/** Called when a 401 is received; should return a new token */
|
|
8
|
+
refreshToken?: () => Promise<string | null>;
|
|
9
|
+
/** Called when token refresh fails — use to redirect to login */
|
|
10
|
+
onLogout?: () => void;
|
|
11
|
+
/** Number of retry attempts on network failures (default: 0) */
|
|
12
|
+
retry?: number;
|
|
13
|
+
/** Static delay in ms applied to every response (default: 0) */
|
|
14
|
+
responseDelay?: number;
|
|
15
|
+
/** Enable request/response logging (default: false) */
|
|
16
|
+
enableLogging?: boolean;
|
|
17
|
+
/** Additional Axios config defaults */
|
|
18
|
+
axiosConfig?: AxiosRequestConfig;
|
|
19
|
+
}
|
|
20
|
+
export interface NormalizedError {
|
|
21
|
+
status: number;
|
|
22
|
+
message: string;
|
|
23
|
+
data: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface DelayState {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
duration: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ApiEngine {
|
|
30
|
+
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
31
|
+
post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
32
|
+
put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
33
|
+
patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
|
|
34
|
+
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
35
|
+
request<T = unknown>(config: AxiosRequestConfig): Promise<T>;
|
|
36
|
+
getAxiosInstance(): import("axios").AxiosInstance;
|
|
37
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "axios-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A platform-agnostic networking library built on Axios for React Native, React, Next.js, and Node.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "rollup -c",
|
|
13
|
+
"dev": "rollup -c -w",
|
|
14
|
+
"test": "jest --coverage",
|
|
15
|
+
"lint": "eslint src/**/*.ts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"axios",
|
|
20
|
+
"api",
|
|
21
|
+
"http",
|
|
22
|
+
"react-native",
|
|
23
|
+
"react",
|
|
24
|
+
"nextjs",
|
|
25
|
+
"nodejs",
|
|
26
|
+
"networking",
|
|
27
|
+
"interceptors",
|
|
28
|
+
"token-refresh"
|
|
29
|
+
],
|
|
30
|
+
"author": "yunush",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"axios": ">=1.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
37
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
38
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
39
|
+
"@types/jest": "^29.5.14",
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"axios": "^1.13.6",
|
|
42
|
+
"jest": "^29.7.0",
|
|
43
|
+
"rollup": "^3.0.0",
|
|
44
|
+
"rollup-plugin-dts": "^6.0.0",
|
|
45
|
+
"ts-jest": "^29.4.6",
|
|
46
|
+
"tslib": "^2.6.0",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|