@ahriknow/afetch 0.0.1
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 +452 -0
- package/README_zh-CN.md +454 -0
- package/dist/afetch.d.ts +15 -0
- package/dist/afetch.d.ts.map +1 -0
- package/dist/afetch.js +284 -0
- package/dist/afetch.js.map +1 -0
- package/dist/error.d.ts +47 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +75 -0
- package/dist/error.js.map +1 -0
- package/dist/events.d.ts +31 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +63 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +42 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +46 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/event-bus.d.ts +19 -0
- package/dist/plugins/event-bus.d.ts.map +1 -0
- package/dist/plugins/event-bus.js +27 -0
- package/dist/plugins/event-bus.js.map +1 -0
- package/dist/plugins/index.d.ts +8 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +6 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/retry.d.ts +18 -0
- package/dist/plugins/retry.d.ts.map +1 -0
- package/dist/plugins/retry.js +151 -0
- package/dist/plugins/retry.js.map +1 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +57 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +378 -0
- package/dist/utils.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ahriknow
|
|
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,452 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# afetch
|
|
4
|
+
|
|
5
|
+
**English** | [δΈζ](./README_zh-CN.md)
|
|
6
|
+
|
|
7
|
+
A lightweight, type-safe, plugin-based fetch API wrapper for modern JavaScript/TypeScript.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@ahriknow/afetch)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://codecov.io/gh/ahriknow/afetch)
|
|
12
|
+
[](https://www.typescriptlang.org/)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- π **Lightweight** β Zero dependencies, minimal bundle size
|
|
21
|
+
- π **Type-safe** β Full TypeScript support with strict types
|
|
22
|
+
- π§© **Plugin System** β Extensible via `beforeRequest`, `afterResponse`, `onError` hooks
|
|
23
|
+
- π **Retry Plugin** β Automatic retry with exponential backoff, status matching, and custom hooks
|
|
24
|
+
- π‘ **Event Bus Plugin** β Observe request lifecycle via events
|
|
25
|
+
- β±οΈ **Timeout** β Request timeout with automatic abort
|
|
26
|
+
- β **Cancellation** β AbortController support + Task API for fine-grained control
|
|
27
|
+
- π **Progress** β Upload and download progress tracking
|
|
28
|
+
- ποΈ **Instances** β Create pre-configured instances for different APIs
|
|
29
|
+
- π§ **Transforms** β Request and response data transformation
|
|
30
|
+
- π **Universal** β Works in browsers (Chrome 42+, Firefox 39+, Safari 10.1+) and Node.js 18+
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install afetch
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { afetch } from 'afetch';
|
|
42
|
+
|
|
43
|
+
// GET request
|
|
44
|
+
const { data } = await afetch.get<User[]>('/api/users');
|
|
45
|
+
|
|
46
|
+
// POST request
|
|
47
|
+
const { data: user } = await afetch.post<User>('/api/users', {
|
|
48
|
+
name: 'John Doe',
|
|
49
|
+
email: 'john@example.com',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// With options
|
|
53
|
+
const { data: item } = await afetch.get<Item>('/api/items/1', {
|
|
54
|
+
headers: { Authorization: 'Bearer token' },
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
params: { fields: 'name,email' },
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Creating Instances
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { createInstance } from 'afetch';
|
|
64
|
+
|
|
65
|
+
const api = createInstance({
|
|
66
|
+
baseURL: 'https://api.example.com',
|
|
67
|
+
timeout: 10000,
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const { data: users } = await api.get<User[]>('/users');
|
|
72
|
+
const { data: user } = await api.post<User>('/users', { name: 'John' });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Plugin System
|
|
76
|
+
|
|
77
|
+
afetch uses a plugin architecture. Core functionality is minimal β features like retry and event observation are provided as plugins.
|
|
78
|
+
|
|
79
|
+
### Built-in Plugins
|
|
80
|
+
|
|
81
|
+
#### Retry Plugin
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createRetryPlugin } from 'afetch';
|
|
85
|
+
|
|
86
|
+
const api = createInstance({ baseURL: 'https://api.example.com' });
|
|
87
|
+
api.use(createRetryPlugin());
|
|
88
|
+
|
|
89
|
+
// Basic retry
|
|
90
|
+
await api.get('/api/data', {
|
|
91
|
+
meta: { retry: { maxRetries: 3, delay: 1000 } },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Retry on specific status codes
|
|
95
|
+
await api.get('/api/data', {
|
|
96
|
+
meta: {
|
|
97
|
+
retry: {
|
|
98
|
+
maxRetries: 3,
|
|
99
|
+
delay: 1000,
|
|
100
|
+
retryOn: [500, 502, 503, 504],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Exponential backoff
|
|
106
|
+
await api.get('/api/data', {
|
|
107
|
+
meta: {
|
|
108
|
+
retry: {
|
|
109
|
+
maxRetries: 5,
|
|
110
|
+
delay: (attempt) => Math.pow(2, attempt) * 1000,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Advanced: hook + call for token refresh on 401
|
|
116
|
+
await api.get('/api/protected', {
|
|
117
|
+
meta: {
|
|
118
|
+
retry: {
|
|
119
|
+
maxRetries: 3,
|
|
120
|
+
delay: 1000,
|
|
121
|
+
retryOn: [
|
|
122
|
+
500,
|
|
123
|
+
{
|
|
124
|
+
hook: async (error) => error.status === 401,
|
|
125
|
+
retryDelay: 0,
|
|
126
|
+
call: async () => {
|
|
127
|
+
const token = await refreshToken();
|
|
128
|
+
api.defaults.headers!['Authorization'] = `Bearer ${token}`;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Custom condition function
|
|
137
|
+
await api.get('/api/data', {
|
|
138
|
+
meta: {
|
|
139
|
+
retry: {
|
|
140
|
+
maxRetries: 3,
|
|
141
|
+
condition: (attempt, error) => error.status === 503 && attempt < 2,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Event Bus Plugin
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createEventBusPlugin } from 'afetch';
|
|
151
|
+
|
|
152
|
+
const api = createInstance({ baseURL: 'https://api.example.com' });
|
|
153
|
+
const eventBus = createEventBusPlugin();
|
|
154
|
+
api.use(eventBus);
|
|
155
|
+
|
|
156
|
+
// Listen to lifecycle events
|
|
157
|
+
const unsub = eventBus.on('request', ({ config }) => {
|
|
158
|
+
console.log(`β ${config.method} ${config.url}`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
eventBus.on('response', ({ config, response }) => {
|
|
162
|
+
console.log(`β ${response.status} ${config.url}`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
eventBus.on('error', ({ config, error }) => {
|
|
166
|
+
console.error(`β ${error.code} ${config.url}`);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Unsubscribe
|
|
170
|
+
unsub();
|
|
171
|
+
|
|
172
|
+
// Remove all listeners for an event
|
|
173
|
+
eventBus.off('response');
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Writing Custom Plugins
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import type { AFetchPlugin } from 'afetch';
|
|
180
|
+
|
|
181
|
+
const loggerPlugin: AFetchPlugin = {
|
|
182
|
+
name: 'logger',
|
|
183
|
+
install(api) {
|
|
184
|
+
api.addHook('beforeRequest', ({ config }) => {
|
|
185
|
+
console.log(`[REQ] ${config.method} ${config.baseURL}${config.url}`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
api.addHook('afterResponse', ({ response }) => {
|
|
189
|
+
console.log(`[RES] ${response.status} ${response.statusText}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
api.addHook('onError', ({ error }) => {
|
|
193
|
+
console.error(`[ERR] ${error.code}: ${error.message}`);
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
api.use(loggerPlugin);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Plugin Lifecycle Hooks
|
|
202
|
+
|
|
203
|
+
| Hook | When | Return |
|
|
204
|
+
|------|------|--------|
|
|
205
|
+
| `beforeRequest` | Before sending request | `void` |
|
|
206
|
+
| `afterResponse` | After receiving response | `AResponse` (replace) or `void` |
|
|
207
|
+
| `onError` | On request error | `AResponse` (retry/replace) or `void` (propagate) |
|
|
208
|
+
|
|
209
|
+
Plugins are installed once per instance β calling `use()` with the same plugin name is a no-op.
|
|
210
|
+
|
|
211
|
+
## Error Handling
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { AFetchError, AFetchErrorType } from 'afetch';
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
await api.get('/api/data');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error instanceof AFetchError) {
|
|
220
|
+
switch (error.code) {
|
|
221
|
+
case AFetchErrorType.TIMEOUT:
|
|
222
|
+
console.log('Request timed out');
|
|
223
|
+
break;
|
|
224
|
+
case AFetchErrorType.HTTP:
|
|
225
|
+
console.log(`HTTP ${error.status}: ${error.message}`);
|
|
226
|
+
break;
|
|
227
|
+
case AFetchErrorType.NETWORK:
|
|
228
|
+
console.log('Network error');
|
|
229
|
+
break;
|
|
230
|
+
case AFetchErrorType.ABORT:
|
|
231
|
+
console.log('Request aborted');
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Request Cancellation
|
|
239
|
+
|
|
240
|
+
### Task API (Recommended)
|
|
241
|
+
|
|
242
|
+
The Task API starts the request immediately and returns a handle to control it:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Create a task β request starts immediately
|
|
246
|
+
const task = api.task.get('/api/data', {
|
|
247
|
+
headers: { Authorization: 'Bearer token' },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Check status
|
|
251
|
+
console.log(task.done); // false
|
|
252
|
+
console.log(task.aborted); // false
|
|
253
|
+
|
|
254
|
+
// Cancel the request
|
|
255
|
+
task.abort();
|
|
256
|
+
console.log(task.aborted); // true
|
|
257
|
+
|
|
258
|
+
// Wait for response β throws AFetchError(ABORT) if cancelled
|
|
259
|
+
try {
|
|
260
|
+
const response = await task.wait();
|
|
261
|
+
console.log(response.data);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error.code === 'EABORT') {
|
|
264
|
+
console.log('Request was aborted');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Supports all HTTP methods:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const getTask = api.task.get('/api/users');
|
|
273
|
+
const postTask = api.task.post('/api/users', { name: 'John' });
|
|
274
|
+
const putTask = api.task.put('/api/users/1', { name: 'Updated' });
|
|
275
|
+
const deleteTask = api.task.delete('/api/users/1');
|
|
276
|
+
const patchTask = api.task.patch('/api/users/1', { name: 'Patched' });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### AbortController
|
|
280
|
+
|
|
281
|
+
You can also use the standard `AbortController` approach:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const controller = new AbortController();
|
|
285
|
+
|
|
286
|
+
const { data } = await api.get('/api/data', {
|
|
287
|
+
signal: controller.signal,
|
|
288
|
+
timeout: 5000, // auto-abort after 5s
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Cancel manually
|
|
292
|
+
controller.abort();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Request & Response Transforms
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Transform request data before sending
|
|
299
|
+
await api.post('/api/data', rawData, {
|
|
300
|
+
transformRequest: (data) => ({
|
|
301
|
+
...(data as object),
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Transform response data after receiving
|
|
307
|
+
const { data } = await api.get<Item[]>('/api/items', {
|
|
308
|
+
transformResponse: (data) => (data as any).items,
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## TypeScript Support
|
|
313
|
+
|
|
314
|
+
Full generic type support:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
interface User {
|
|
318
|
+
id: number;
|
|
319
|
+
name: string;
|
|
320
|
+
email: string;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Response data is fully typed
|
|
324
|
+
const { data } = await api.get<User[]>('/users');
|
|
325
|
+
// ^ User[]
|
|
326
|
+
|
|
327
|
+
const { data: user } = await api.post<User>('/users', { name: 'John' });
|
|
328
|
+
// ^ User
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Configuration Options
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const api = createInstance({
|
|
335
|
+
baseURL: 'https://api.example.com', // Base URL for all requests
|
|
336
|
+
timeout: 10000, // Default timeout (ms)
|
|
337
|
+
headers: { // Default headers
|
|
338
|
+
'Content-Type': 'application/json',
|
|
339
|
+
},
|
|
340
|
+
responseType: 'json', // Default response type
|
|
341
|
+
cache: 'default', // Request cache mode
|
|
342
|
+
credentials: 'same-origin', // Credentials mode
|
|
343
|
+
throwOnError: true, // Throw on non-2xx (default: true)
|
|
344
|
+
fetchAdapter: customFetch, // Custom fetch implementation
|
|
345
|
+
plugins: [createRetryPlugin()], // Plugins to install
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Per-request Options
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
await api.get('/data', {
|
|
353
|
+
headers: { 'X-Custom': 'value' },
|
|
354
|
+
params: { page: 1, limit: 20 },
|
|
355
|
+
timeout: 3000,
|
|
356
|
+
signal: controller.signal,
|
|
357
|
+
responseType: 'text',
|
|
358
|
+
cache: 'no-cache',
|
|
359
|
+
throwOnError: false,
|
|
360
|
+
meta: { requestId: '123' },
|
|
361
|
+
transformRequest: fn,
|
|
362
|
+
transformResponse: fn,
|
|
363
|
+
onUploadProgress: fn,
|
|
364
|
+
onDownloadProgress: fn,
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## API Reference
|
|
369
|
+
|
|
370
|
+
### Instance Methods
|
|
371
|
+
|
|
372
|
+
| Method | Description |
|
|
373
|
+
|--------|-------------|
|
|
374
|
+
| `afetch.get<T>(url, options?)` | GET request |
|
|
375
|
+
| `afetch.post<T>(url, data?, options?)` | POST request |
|
|
376
|
+
| `afetch.put<T>(url, data?, options?)` | PUT request |
|
|
377
|
+
| `afetch.delete<T>(url, options?)` | DELETE request |
|
|
378
|
+
| `afetch.patch<T>(url, data?, options?)` | PATCH request |
|
|
379
|
+
| `afetch.head<T>(url, options?)` | HEAD request |
|
|
380
|
+
| `afetch.options<T>(url, options?)` | OPTIONS request |
|
|
381
|
+
| `afetch.request<T>(url, options?)` | Custom method request |
|
|
382
|
+
| `afetch.create(config?)` | Create a new instance |
|
|
383
|
+
| `afetch.use(plugin)` | Install a plugin |
|
|
384
|
+
| `afetch.task` | Task API for cancellable requests |
|
|
385
|
+
| `afetch.defaults` | Default configuration |
|
|
386
|
+
|
|
387
|
+
### Response Object (`AResponse<T>`)
|
|
388
|
+
|
|
389
|
+
| Property | Type | Description |
|
|
390
|
+
|----------|------|-------------|
|
|
391
|
+
| `data` | `T` | Parsed response data |
|
|
392
|
+
| `status` | `number` | HTTP status code |
|
|
393
|
+
| `statusText` | `string` | HTTP status text |
|
|
394
|
+
| `headers` | `Headers` | Response headers |
|
|
395
|
+
| `config` | `ResolvedRequestConfig` | Request config |
|
|
396
|
+
| `raw` | `Response` | Original Response object |
|
|
397
|
+
| `ok` | `boolean` | `status >= 200 && status < 300` |
|
|
398
|
+
|
|
399
|
+
### Request Task (`RequestTask<T>`)
|
|
400
|
+
|
|
401
|
+
| Property / Method | Type | Description |
|
|
402
|
+
|-------------------|------|-------------|
|
|
403
|
+
| `abort()` | `() => void` | Cancel the request |
|
|
404
|
+
| `wait()` | `() => Promise<AResponse<T>>` | Wait for the response (throws if aborted) |
|
|
405
|
+
| `aborted` | `boolean` | Whether the request has been aborted |
|
|
406
|
+
| `done` | `boolean` | Whether the request has completed |
|
|
407
|
+
|
|
408
|
+
### Error Types (`AFetchErrorType`)
|
|
409
|
+
|
|
410
|
+
| Code | Description |
|
|
411
|
+
|------|-------------|
|
|
412
|
+
| `TIMEOUT` | Request timed out |
|
|
413
|
+
| `NETWORK` | Network error |
|
|
414
|
+
| `ABORT` | Request aborted |
|
|
415
|
+
| `HTTP` | Non-2xx response |
|
|
416
|
+
| `PARSE` | Response parse error |
|
|
417
|
+
| `CONFIG` | Configuration error |
|
|
418
|
+
|
|
419
|
+
## Project Structure
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
afetch/
|
|
423
|
+
βββ src/
|
|
424
|
+
β βββ index.ts # Entry point
|
|
425
|
+
β βββ afetch.ts # Core implementation
|
|
426
|
+
β βββ types.ts # Type definitions
|
|
427
|
+
β βββ plugin.ts # Plugin system (HookRunner)
|
|
428
|
+
β βββ events.ts # Event emitter
|
|
429
|
+
β βββ error.ts # AFetchError class
|
|
430
|
+
β βββ utils.ts # Utilities
|
|
431
|
+
β βββ plugins/
|
|
432
|
+
β βββ index.ts # Plugin exports
|
|
433
|
+
β βββ retry.ts # Retry plugin
|
|
434
|
+
β βββ event-bus.ts # Event bus plugin
|
|
435
|
+
βββ test/
|
|
436
|
+
β βββ afetch.test.ts # Unit tests
|
|
437
|
+
β βββ coverage.test.ts # Coverage tests
|
|
438
|
+
βββ examples/
|
|
439
|
+
β βββ basic.ts # Basic usage
|
|
440
|
+
β βββ plugins.ts # Plugin examples
|
|
441
|
+
β βββ task.ts # Task API examples
|
|
442
|
+
βββ .github/
|
|
443
|
+
β βββ workflows/
|
|
444
|
+
β βββ publish.yml # CI/CD
|
|
445
|
+
βββ package.json
|
|
446
|
+
βββ tsconfig.json
|
|
447
|
+
βββ README.md
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## License
|
|
451
|
+
|
|
452
|
+
[MIT](./LICENSE)
|