@ahoo-wang/fetcher 0.5.6 → 0.6.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/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![License](https://img.shields.io/npm/l/@ahoo-wang/fetcher.svg)](https://github.com/Ahoo-Wang/fetcher/blob/main/LICENSE)
7
7
  [![npm downloads](https://img.shields.io/npm/dm/@ahoo-wang/fetcher.svg)](https://www.npmjs.com/package/@ahoo-wang/fetcher)
8
8
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40ahoo-wang%2Ffetcher)](https://www.npmjs.com/package/@ahoo-wang/fetcher)
9
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Ahoo-Wang/fetcher)
9
10
 
10
11
  A modern, ultra-lightweight (1.9kB) HTTP client with built-in path parameters, query parameters, and Axios-like API. 86%
11
12
  smaller than Axios while providing the same powerful features.
@@ -53,7 +54,7 @@ const response = await fetcher.get('/users/{id}', {
53
54
  path: { id: 123 },
54
55
  query: { include: 'profile' },
55
56
  });
56
- const userData = await response.json();
57
+ const userData = await response.json<User>();
57
58
 
58
59
  // POST request with automatic JSON conversion
59
60
  const createUserResponse = await fetcher.post('/users', {
@@ -100,12 +101,46 @@ import { fetcher } from '@ahoo-wang/fetcher';
100
101
 
101
102
  // Use the default fetcher directly
102
103
  const response = await fetcher.get('/users');
103
- const data = await response.json();
104
+ const data = await response.json<User>();
104
105
  ```
105
106
 
106
107
  ## 🔗 Interceptor System
107
108
 
108
- ### Request Interceptors
109
+ ### Interceptor
110
+
111
+ Interceptor interface that defines the basic structure of interceptors.
112
+
113
+ **Properties:**
114
+
115
+ - `name: string` - The name of the interceptor, used to identify it, must be unique
116
+ - `order: number` - The execution order of the interceptor, smaller values have higher priority
117
+
118
+ **Methods:**
119
+
120
+ - `intercept(exchange: FetchExchange): FetchExchange | Promise<FetchExchange>` - Intercept and process data
121
+
122
+ ### InterceptorManager
123
+
124
+ Interceptor manager for managing multiple interceptors of the same type.
125
+
126
+ **Methods:**
127
+
128
+ - `use(interceptor: Interceptor): boolean` - Add interceptor, returns whether the addition was successful
129
+ - `eject(name: string): boolean` - Remove interceptor by name, returns whether the removal was successful
130
+ - `clear(): void` - Clear all interceptors
131
+ - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - Execute all interceptors in sequence
132
+
133
+ ### FetcherInterceptors
134
+
135
+ Fetcher interceptor collection, including request, response, and error interceptor managers.
136
+
137
+ **Properties:**
138
+
139
+ - `request: InterceptorManager` - Request interceptor manager
140
+ - `response: InterceptorManager` - Response interceptor manager
141
+ - `error: InterceptorManager` - Error interceptor manager
142
+
143
+ ### Using Interceptors
109
144
 
110
145
  ```typescript
111
146
  import { Fetcher } from '@ahoo-wang/fetcher';
@@ -113,7 +148,9 @@ import { Fetcher } from '@ahoo-wang/fetcher';
113
148
  const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
114
149
 
115
150
  // Add request interceptor (e.g., for authentication)
116
- const interceptorId = fetcher.interceptors.request.use({
151
+ const success = fetcher.interceptors.request.use({
152
+ name: 'auth-interceptor',
153
+ order: 100,
117
154
  intercept(exchange) {
118
155
  return {
119
156
  ...exchange,
@@ -128,8 +165,84 @@ const interceptorId = fetcher.interceptors.request.use({
128
165
  },
129
166
  });
130
167
 
131
- // Remove interceptor
132
- fetcher.interceptors.request.eject(interceptorId);
168
+ // Add response interceptor (e.g., for logging)
169
+ fetcher.interceptors.response.use({
170
+ name: 'logging-interceptor',
171
+ order: 10,
172
+ intercept(exchange) {
173
+ console.log('Response received:', exchange.response?.status);
174
+ return exchange;
175
+ },
176
+ });
177
+
178
+ // Add error interceptor (e.g., for unified error handling)
179
+ fetcher.interceptors.error.use({
180
+ name: 'error-interceptor',
181
+ order: 50,
182
+ intercept(exchange) {
183
+ if (exchange.error?.name === 'FetchTimeoutError') {
184
+ console.error('Request timeout:', exchange.error.message);
185
+ } else {
186
+ console.error('Network error:', exchange.error?.message);
187
+ }
188
+ return exchange;
189
+ },
190
+ });
191
+
192
+ // Remove interceptor by name
193
+ fetcher.interceptors.request.eject('auth-interceptor');
194
+ ```
195
+
196
+ ### Ordered Execution
197
+
198
+ The `OrderedCapable` system allows you to control the execution order of interceptors and other components.
199
+
200
+ #### Ordering Concept
201
+
202
+ ```typescript
203
+ import { OrderedCapable } from '@ahoo-wang/fetcher';
204
+
205
+ // Lower order values have higher priority
206
+ const highPriority: OrderedCapable = { order: 1 }; // Executes first
207
+ const mediumPriority: OrderedCapable = { order: 10 }; // Executes second
208
+ const lowPriority: OrderedCapable = { order: 100 }; // Executes last
209
+ ```
210
+
211
+ #### Interceptor Ordering
212
+
213
+ ```typescript
214
+ // Add interceptors with different orders
215
+ fetcher.interceptors.request.use({
216
+ name: 'timing-interceptor',
217
+ order: 5, // Executes very early
218
+ intercept(exchange) {
219
+ console.log('Very early timing');
220
+ return exchange;
221
+ },
222
+ });
223
+
224
+ fetcher.interceptors.request.use({
225
+ name: 'logging-interceptor',
226
+ order: 10, // Executes early
227
+ intercept(exchange) {
228
+ console.log('Early logging');
229
+ return exchange;
230
+ },
231
+ });
232
+
233
+ fetcher.interceptors.request.use({
234
+ name: 'auth-interceptor',
235
+ order: 50, // Executes later
236
+ intercept(exchange) {
237
+ // Add auth headers
238
+ return exchange;
239
+ },
240
+ });
241
+
242
+ // Execution order will be:
243
+ // 1. timing-interceptor (order: 5)
244
+ // 2. logging-interceptor (order: 10)
245
+ // 3. auth-interceptor (order: 50)
133
246
  ```
134
247
 
135
248
  ### Response Interceptors
@@ -189,6 +302,16 @@ new Fetcher(options ? : FetcherOptions);
189
302
  - `head(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - HEAD request
190
303
  - `options(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - OPTIONS request
191
304
 
305
+ ### Response Extension
306
+
307
+ To provide better TypeScript support, we extend the native Response interface with a type-safe json() method:
308
+
309
+ ```typescript
310
+ // Now you can use it with type safety
311
+ const response = await fetcher.get('/users/123');
312
+ const userData = await response.json<User>(); // Type is Promise<User>
313
+ ```
314
+
192
315
  ### NamedFetcher Class
193
316
 
194
317
  An extension of the Fetcher class that automatically registers itself with the global fetcherRegistrar.
@@ -221,16 +344,29 @@ Global instance for managing multiple Fetcher instances by name.
221
344
 
222
345
  ### Interceptor System
223
346
 
347
+ #### Interceptor
348
+
349
+ Interceptor interface that defines the basic structure of interceptors.
350
+
351
+ **Properties:**
352
+
353
+ - `name: string` - The name of the interceptor, used to identify it, must be unique
354
+ - `order: number` - The execution order of the interceptor, smaller values have higher priority
355
+
356
+ **Methods:**
357
+
358
+ - `intercept(exchange: FetchExchange): FetchExchange | Promise<FetchExchange>` - Intercept and process data
359
+
224
360
  #### InterceptorManager
225
361
 
226
362
  Interceptor manager for managing multiple interceptors of the same type.
227
363
 
228
364
  **Methods:**
229
365
 
230
- - `use(interceptor: Interceptor): number` - Add interceptor, returns interceptor ID
231
- - `eject(index: number): void` - Remove interceptor by ID
366
+ - `use(interceptor: Interceptor): boolean` - Add interceptor, returns whether the addition was successful
367
+ - `eject(name: string): boolean` - Remove interceptor by name, returns whether the removal was successful
232
368
  - `clear(): void` - Clear all interceptors
233
- - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - Execute all interceptors sequentially
369
+ - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - Execute all interceptors in sequence
234
370
 
235
371
  #### FetcherInterceptors
236
372
 
@@ -242,16 +378,178 @@ Fetcher interceptor collection, including request, response, and error intercept
242
378
  - `response: InterceptorManager` - Response interceptor manager
243
379
  - `error: InterceptorManager` - Error interceptor manager
244
380
 
245
- ## 🛠️ Development
381
+ **Options:**
246
382
 
247
- ### Testing
383
+ - `baseURL`: Base URL for all requests
384
+ - `timeout`: Request timeout in milliseconds
385
+ - `headers`: Default request headers
248
386
 
249
- ```bash
250
- # Run tests
251
- pnpm test
387
+ #### Methods
388
+
389
+ - `fetch(url: string, request?: FetcherRequest): Promise<Response>` - Generic HTTP request method
390
+ - `get(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - GET request
391
+ - `post(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - POST request
392
+ - `put(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PUT request
393
+ - `delete(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - DELETE request
394
+ - `patch(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PATCH request
395
+ - `head(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - HEAD request
396
+ - `options(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - OPTIONS request
397
+
398
+ ### Response Extension
399
+
400
+ To provide better TypeScript support, we extend the native Response interface with a type-safe json() method:
401
+
402
+ ```typescript
403
+ // Now you can use it with type safety
404
+ const response = await fetcher.get('/users/123');
405
+ const userData = await response.json<User>(); // Type is Promise<User>
406
+ ```
407
+
408
+ ### NamedFetcher Class
409
+
410
+ An extension of the Fetcher class that automatically registers itself with the global fetcherRegistrar.
411
+
412
+ #### Constructor
413
+
414
+ ```typescript
415
+ new NamedFetcher(name
416
+ :
417
+ string, options ? : FetcherOptions
418
+ )
419
+ ;
420
+ ```
421
+
422
+ ### FetcherRegistrar
423
+
424
+ Global instance for managing multiple Fetcher instances by name.
425
+
426
+ #### Properties
427
+
428
+ - `default`: Get or set the default fetcher instance
429
+
430
+ #### Methods
431
+
432
+ - `register(name: string, fetcher: Fetcher): void` - Register a fetcher with a name
433
+ - `unregister(name: string): boolean` - Unregister a fetcher by name
434
+ - `get(name: string): Fetcher | undefined` - Get a fetcher by name
435
+ - `requiredGet(name: string): Fetcher` - Get a fetcher by name, throws if not found
436
+ - `fetchers: Map<string, Fetcher>` - Get all registered fetchers
437
+
438
+ ### Interceptor System
439
+
440
+ #### Interceptor
441
+
442
+ Interceptor interface that defines the basic structure of interceptors.
443
+
444
+ **Properties:**
445
+
446
+ - `name: string` - The name of the interceptor, used to identify it, must be unique
447
+ - `order: number` - The execution order of the interceptor, smaller values have higher priority
448
+
449
+ **Methods:**
450
+
451
+ - `intercept(exchange: FetchExchange): FetchExchange | Promise<FetchExchange>` - Intercept and process data
452
+
453
+ #### InterceptorManager
454
+
455
+ Interceptor manager for managing multiple interceptors of the same type.
456
+
457
+ **Methods:**
458
+
459
+ - `use(interceptor: Interceptor): boolean` - Add interceptor, returns whether the addition was successful
460
+ - `eject(name: string): boolean` - Remove interceptor by name, returns whether the removal was successful
461
+ - `clear(): void` - Clear all interceptors
462
+ - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - Execute all interceptors in sequence
463
+
464
+ #### FetcherInterceptors
465
+
466
+ Fetcher interceptor collection, including request, response, and error interceptor managers.
467
+
468
+ **Properties:**
469
+
470
+ - `request: InterceptorManager` - Request interceptor manager
471
+ - `response: InterceptorManager` - Response interceptor manager
472
+ - `error: InterceptorManager` - Error interceptor manager
473
+
474
+ #### Request Interceptors
475
+
476
+ ```typescript
477
+ import { Fetcher } from '@ahoo-wang/fetcher';
478
+
479
+ const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
480
+
481
+ // Add request interceptor (e.g., for authentication)
482
+ const success = fetcher.interceptors.request.use({
483
+ name: 'auth-interceptor',
484
+ order: 100,
485
+ intercept(exchange) {
486
+ return {
487
+ ...exchange,
488
+ request: {
489
+ ...exchange.request,
490
+ headers: {
491
+ ...exchange.request.headers,
492
+ Authorization: 'Bearer ' + getAuthToken(),
493
+ },
494
+ },
495
+ };
496
+ },
497
+ });
498
+
499
+ // Remove interceptor
500
+ fetcher.interceptors.request.eject('auth-interceptor');
501
+ ```
502
+
503
+ ### OrderedCapable System
504
+
505
+ The `OrderedCapable` system allows you to control the execution order of interceptors and other components.
506
+
507
+ #### Ordering Concept
508
+
509
+ ```typescript
510
+ import { OrderedCapable } from '@ahoo-wang/fetcher';
511
+
512
+ // Lower order values have higher priority
513
+ const highPriority: OrderedCapable = { order: 1 }; // Executes first
514
+ const mediumPriority: OrderedCapable = { order: 10 }; // Executes second
515
+ const lowPriority: OrderedCapable = { order: 100 }; // Executes last
516
+ ```
517
+
518
+ #### Interceptor Ordering
519
+
520
+ ```typescript
521
+ // Add interceptors with different orders
522
+ fetcher.interceptors.request.use({
523
+ name: 'logging-interceptor',
524
+ order: 10, // Executes early
525
+ intercept(exchange) {
526
+ console.log('Early logging');
527
+ return exchange;
528
+ },
529
+ });
530
+
531
+ fetcher.interceptors.request.use({
532
+ name: 'auth-interceptor',
533
+ order: 50, // Executes later
534
+ intercept(exchange) {
535
+ // Add auth headers
536
+ return exchange;
537
+ },
538
+ });
539
+
540
+ fetcher.interceptors.request.use({
541
+ name: 'timing-interceptor',
542
+ order: 5, // Executes very early
543
+ intercept(exchange) {
544
+ console.log('Very early timing');
545
+ return exchange;
546
+ },
547
+ });
252
548
 
253
- # Run tests with coverage
254
- pnpm test --coverage
549
+ // Execution order will be:
550
+ // 1. timing-interceptor (order: 5)
551
+ // 2. logging-interceptor (order: 10)
552
+ // 3. auth-interceptor (order: 50)
255
553
  ```
256
554
 
257
555
  ## 🤝 Contributing
package/README.zh-CN.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![License](https://img.shields.io/npm/l/@ahoo-wang/fetcher.svg)](https://github.com/Ahoo-Wang/fetcher/blob/main/LICENSE)
7
7
  [![npm downloads](https://img.shields.io/npm/dm/@ahoo-wang/fetcher.svg)](https://www.npmjs.com/package/@ahoo-wang/fetcher)
8
8
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40ahoo-wang%2Ffetcher)](https://www.npmjs.com/package/@ahoo-wang/fetcher)
9
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Ahoo-Wang/fetcher)
9
10
 
10
11
  一个现代、超轻量级(1.9kB)的 HTTP 客户端,内置路径参数、查询参数和类似 Axios 的 API。比 Axios 小 86%,同时提供相同的强大功能。
11
12
 
@@ -52,7 +53,7 @@ const response = await fetcher.get('/users/{id}', {
52
53
  path: { id: 123 },
53
54
  query: { include: 'profile' },
54
55
  });
55
- const userData = await response.json();
56
+ const userData = await response.json<User>();
56
57
 
57
58
  // 带自动 JSON 转换的 POST 请求
58
59
  const createUserResponse = await fetcher.post('/users', {
@@ -99,12 +100,46 @@ import { fetcher } from '@ahoo-wang/fetcher';
99
100
 
100
101
  // 直接使用默认 fetcher
101
102
  const response = await fetcher.get('/users');
102
- const data = await response.json();
103
+ const data = await response.json<User>();
103
104
  ```
104
105
 
105
106
  ## 🔗 拦截器系统
106
107
 
107
- ### 请求拦截器
108
+ ### Interceptor
109
+
110
+ 拦截器接口,定义了拦截器的基本结构。
111
+
112
+ **属性:**
113
+
114
+ - `name: string` - 拦截器的名称,用于标识拦截器,不可重复
115
+ - `order: number` - 拦截器的执行顺序,数值越小优先级越高
116
+
117
+ **方法:**
118
+
119
+ - `intercept(exchange: FetchExchange): FetchExchange | Promise<FetchExchange>` - 拦截并处理数据
120
+
121
+ ### InterceptorManager
122
+
123
+ 用于管理同一类型多个拦截器的拦截器管理器。
124
+
125
+ **方法:**
126
+
127
+ - `use(interceptor: Interceptor): boolean` - 添加拦截器,返回是否添加成功
128
+ - `eject(name: string): boolean` - 按名称移除拦截器,返回是否移除成功
129
+ - `clear(): void` - 清除所有拦截器
130
+ - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - 顺序执行所有拦截器
131
+
132
+ ### FetcherInterceptors
133
+
134
+ Fetcher 拦截器集合,包括请求、响应和错误拦截器管理器。
135
+
136
+ **属性:**
137
+
138
+ - `request: InterceptorManager` - 请求拦截器管理器
139
+ - `response: InterceptorManager` - 响应拦截器管理器
140
+ - `error: InterceptorManager` - 错误拦截器管理器
141
+
142
+ ### 使用拦截器
108
143
 
109
144
  ```typescript
110
145
  import { Fetcher } from '@ahoo-wang/fetcher';
@@ -112,7 +147,9 @@ import { Fetcher } from '@ahoo-wang/fetcher';
112
147
  const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
113
148
 
114
149
  // 添加请求拦截器(例如用于认证)
115
- const interceptorId = fetcher.interceptors.request.use({
150
+ const success = fetcher.interceptors.request.use({
151
+ name: 'auth-interceptor',
152
+ order: 100,
116
153
  intercept(exchange) {
117
154
  return {
118
155
  ...exchange,
@@ -127,27 +164,20 @@ const interceptorId = fetcher.interceptors.request.use({
127
164
  },
128
165
  });
129
166
 
130
- // 移除拦截器
131
- fetcher.interceptors.request.eject(interceptorId);
132
- ```
133
-
134
- ### 响应拦截器
135
-
136
- ```typescript
137
167
  // 添加响应拦截器(例如用于日志记录)
138
168
  fetcher.interceptors.response.use({
169
+ name: 'logging-interceptor',
170
+ order: 10,
139
171
  intercept(exchange) {
140
- console.log('收到响应:', exchange.response.status);
172
+ console.log('收到响应:', exchange.response?.status);
141
173
  return exchange;
142
174
  },
143
175
  });
144
- ```
145
-
146
- ### 错误拦截器
147
176
 
148
- ```typescript
149
177
  // 添加错误拦截器(例如用于统一错误处理)
150
178
  fetcher.interceptors.error.use({
179
+ name: 'error-interceptor',
180
+ order: 50,
151
181
  intercept(exchange) {
152
182
  if (exchange.error?.name === 'FetchTimeoutError') {
153
183
  console.error('请求超时:', exchange.error.message);
@@ -157,89 +187,62 @@ fetcher.interceptors.error.use({
157
187
  return exchange;
158
188
  },
159
189
  });
160
- ```
161
-
162
- ## 📚 API 参考
163
190
 
164
- ### Fetcher 类
165
-
166
- 提供各种 HTTP 方法的核心 HTTP 客户端类。
167
-
168
- #### 构造函数
169
-
170
- ```typescript
171
- new Fetcher(options ? : FetcherOptions);
191
+ // 按名称移除拦截器
192
+ fetcher.interceptors.request.eject('auth-interceptor');
172
193
  ```
173
194
 
174
- **选项:**
195
+ ### 有序执行
175
196
 
176
- - `baseURL`:基础 URL
177
- - `timeout`:以毫秒为单位的请求超时
178
- - `headers`:默认请求头部
197
+ `OrderedCapable` 系统允许您控制拦截器和其他组件的执行顺序。
179
198
 
180
- #### 方法
181
-
182
- - `fetch(url: string, request?: FetcherRequest): Promise<Response>` - 通用 HTTP 请求方法
183
- - `get(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - GET 请求
184
- - `post(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - POST 请求
185
- - `put(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PUT 请求
186
- - `delete(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - DELETE 请求
187
- - `patch(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PATCH 请求
188
- - `head(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - HEAD 请求
189
- - `options(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - OPTIONS 请求
190
-
191
- ### NamedFetcher 类
192
-
193
- Fetcher 类的扩展,它会自动使用提供的名称在全局 fetcherRegistrar 中注册自己。
194
-
195
- #### 构造函数
199
+ #### 排序概念
196
200
 
197
201
  ```typescript
198
- new NamedFetcher(name
199
- :
200
- string, options ? : FetcherOptions
201
- )
202
- ;
203
- ```
204
-
205
- ### FetcherRegistrar
206
-
207
- 用于按名称管理多个 Fetcher 实例的全局实例。
208
-
209
- #### 属性
210
-
211
- - `default`:获取或设置默认 fetcher 实例
212
-
213
- #### 方法
202
+ import { OrderedCapable } from '@ahoo-wang/fetcher';
214
203
 
215
- - `register(name: string, fetcher: Fetcher): void` - 使用名称注册 fetcher
216
- - `unregister(name: string): boolean` - 按名称注销 fetcher
217
- - `get(name: string): Fetcher | undefined` - 按名称获取 fetcher
218
- - `requiredGet(name: string): Fetcher` - 按名称获取 fetcher,如果未找到则抛出错误
219
- - `fetchers: Map<string, Fetcher>` - 获取所有已注册的 fetcher
220
-
221
- ### 拦截器系统
222
-
223
- #### InterceptorManager
224
-
225
- 用于管理同一类型多个拦截器的拦截器管理器。
226
-
227
- **方法:**
204
+ // 数值越小优先级越高
205
+ const highPriority: OrderedCapable = { order: 1 }; // 首先执行
206
+ const mediumPriority: OrderedCapable = { order: 10 }; // 其次执行
207
+ const lowPriority: OrderedCapable = { order: 100 }; // 最后执行
208
+ ```
228
209
 
229
- - `use(interceptor: Interceptor): number` - 添加拦截器,返回拦截器 ID
230
- - `eject(index: number): void` - 按 ID 移除拦截器
231
- - `clear(): void` - 清除所有拦截器
232
- - `intercept(exchange: FetchExchange): Promise<FetchExchange>` - 顺序执行所有拦截器
210
+ #### 拦截器排序
233
211
 
234
- #### FetcherInterceptors
212
+ ```typescript
213
+ // 添加具有不同顺序的拦截器
214
+ fetcher.interceptors.request.use({
215
+ name: 'timing-interceptor',
216
+ order: 5, // 很早执行
217
+ intercept(exchange) {
218
+ console.log('很早的计时');
219
+ return exchange;
220
+ },
221
+ });
235
222
 
236
- Fetcher 拦截器集合,包括请求、响应和错误拦截器管理器。
223
+ fetcher.interceptors.request.use({
224
+ name: 'logging-interceptor',
225
+ order: 10, // 较早执行
226
+ intercept(exchange) {
227
+ console.log('较早的日志');
228
+ return exchange;
229
+ },
230
+ });
237
231
 
238
- **属性:**
232
+ fetcher.interceptors.request.use({
233
+ name: 'auth-interceptor',
234
+ order: 50, // 较晚执行
235
+ intercept(exchange) {
236
+ // 添加认证头部
237
+ return exchange;
238
+ },
239
+ });
239
240
 
240
- - `request: InterceptorManager` - 请求拦截器管理器
241
- - `response: InterceptorManager` - 响应拦截器管理器
242
- - `error: InterceptorManager` - 错误拦截器管理器
241
+ // 执行顺序将是:
242
+ // 1. timing-interceptor (order: 5)
243
+ // 2. logging-interceptor (order: 10)
244
+ // 3. auth-interceptor (order: 50)
245
+ ```
243
246
 
244
247
  ## 🛠️ 开发
245
248
 
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export * from './fetcher';
2
2
  export * from './fetcherRegistrar';
3
3
  export * from './interceptor';
4
4
  export * from './namedFetcher';
5
+ export * from './orderedCapable';
6
+ export * from './requestBodyInterceptor';
5
7
  export * from './timeout';
6
8
  export * from './types';
7
9
  export * from './urlBuilder';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC"}
package/dist/index.es.js CHANGED
@@ -1,8 +1,8 @@
1
- function b(r) {
1
+ function w(r) {
2
2
  return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(r);
3
3
  }
4
- function g(r, e) {
5
- return b(e) ? e : e ? r.replace(/\/?\/$/, "") + "/" + e.replace(/^\/+/, "") : r;
4
+ function I(r, e) {
5
+ return w(e) ? e : e ? r.replace(/\/?\/$/, "") + "/" + e.replace(/^\/+/, "") : r;
6
6
  }
7
7
  class P {
8
8
  /**
@@ -23,7 +23,7 @@ class P {
23
23
  * @throws 当路径参数中缺少必需的占位符时抛出错误
24
24
  */
25
25
  build(e, t, s) {
26
- const o = g(this.baseURL, e);
26
+ const o = I(this.baseURL, e);
27
27
  let i = this.interpolateUrl(o, t);
28
28
  if (s) {
29
29
  const n = new URLSearchParams(s).toString();
@@ -48,21 +48,30 @@ class P {
48
48
  }) : e;
49
49
  }
50
50
  }
51
- function O(r, e) {
51
+ function A(r, e) {
52
52
  return typeof r < "u" ? r : e;
53
53
  }
54
- class l extends Error {
54
+ class m extends Error {
55
55
  constructor(e, t) {
56
56
  const s = e.request?.method || "GET", o = `Request timeout of ${t}ms exceeded for ${s} ${e.url}`;
57
- super(o), this.name = "FetchTimeoutError", this.exchange = e, Object.setPrototypeOf(this, l.prototype);
57
+ super(o), this.name = "FetchTimeoutError", this.exchange = e, Object.setPrototypeOf(this, m.prototype);
58
58
  }
59
59
  }
60
- var c = /* @__PURE__ */ ((r) => (r.GET = "GET", r.POST = "POST", r.PUT = "PUT", r.DELETE = "DELETE", r.PATCH = "PATCH", r.HEAD = "HEAD", r.OPTIONS = "OPTIONS", r))(c || {}), A = /* @__PURE__ */ ((r) => (r.METHOD = "method", r.BODY = "body", r))(A || {});
61
- const d = "Content-Type";
60
+ var c = /* @__PURE__ */ ((r) => (r.GET = "GET", r.POST = "POST", r.PUT = "PUT", r.DELETE = "DELETE", r.PATCH = "PATCH", r.HEAD = "HEAD", r.OPTIONS = "OPTIONS", r))(c || {}), F = /* @__PURE__ */ ((r) => (r.METHOD = "method", r.BODY = "body", r))(F || {});
61
+ const f = "Content-Type";
62
62
  var p = /* @__PURE__ */ ((r) => (r.APPLICATION_JSON = "application/json", r.TEXT_EVENT_STREAM = "text/event-stream", r))(p || {});
63
- class h {
64
- constructor() {
65
- this.interceptors = [];
63
+ function h(r, e) {
64
+ return e ? r.filter(e).sort((t, s) => t.order - s.order) : [...r].sort((t, s) => t.order - s.order);
65
+ }
66
+ class d {
67
+ constructor(e = []) {
68
+ this.sortedInterceptors = [], this.sortedInterceptors = h(e);
69
+ }
70
+ get name() {
71
+ return this.constructor.name;
72
+ }
73
+ get order() {
74
+ return Number.MIN_SAFE_INTEGER;
66
75
  }
67
76
  /**
68
77
  * 添加拦截器到管理器中
@@ -70,21 +79,21 @@ class h {
70
79
  * @returns 拦截器在管理器中的索引位置
71
80
  */
72
81
  use(e) {
73
- const t = this.interceptors.length;
74
- return this.interceptors.push(e), t;
82
+ return this.sortedInterceptors.some((t) => t.name === e.name) ? !1 : (this.sortedInterceptors = h([...this.sortedInterceptors, e]), !0);
75
83
  }
76
84
  /**
77
- * 根据索引移除拦截器
78
- * @param index - 要移除的拦截器索引
85
+ * 根据名称移除拦截器
86
+ * @param name 要移除的拦截器名称
79
87
  */
80
88
  eject(e) {
81
- this.interceptors[e] && (this.interceptors[e] = null);
89
+ const t = this.sortedInterceptors;
90
+ return this.sortedInterceptors = h(t, (s) => s.name !== e), t.length !== this.sortedInterceptors.length;
82
91
  }
83
92
  /**
84
93
  * 清空所有拦截器
85
94
  */
86
95
  clear() {
87
- this.interceptors = [];
96
+ this.sortedInterceptors = [];
88
97
  }
89
98
  /**
90
99
  * 依次执行所有拦截器对数据的处理
@@ -93,17 +102,20 @@ class h {
93
102
  */
94
103
  async intercept(e) {
95
104
  let t = e;
96
- for (const s of this.interceptors)
97
- s && (t = await s.intercept(t));
105
+ for (const s of this.sortedInterceptors)
106
+ t = await s.intercept(t);
98
107
  return t;
99
108
  }
100
109
  }
101
- class F {
110
+ class O {
102
111
  constructor() {
103
- this.request = new h(), this.response = new h(), this.error = new h();
112
+ this.request = new d(), this.response = new d(), this.error = new d();
104
113
  }
105
114
  }
106
- class q {
115
+ class S {
116
+ constructor() {
117
+ this.name = "RequestBodyInterceptor", this.order = Number.MIN_SAFE_INTEGER;
118
+ }
107
119
  /**
108
120
  * 尝试转换请求体为合法的 fetch API body 类型
109
121
  *
@@ -134,23 +146,23 @@ class q {
134
146
  const s = { ...t };
135
147
  s.body = JSON.stringify(t.body), s.headers || (s.headers = {});
136
148
  const o = s.headers;
137
- return o[d] || (o[d] = p.APPLICATION_JSON), { ...e, request: s };
149
+ return o[f] || (o[f] = p.APPLICATION_JSON), { ...e, request: s };
138
150
  }
139
151
  }
140
- const y = {
141
- [d]: p.APPLICATION_JSON
142
- }, T = {
152
+ const T = {
153
+ [f]: p.APPLICATION_JSON
154
+ }, y = {
143
155
  baseURL: "",
144
- headers: y
156
+ headers: T
145
157
  };
146
- class S {
158
+ class q {
147
159
  /**
148
160
  * Create a Fetcher instance
149
161
  *
150
162
  * @param options - Fetcher configuration options
151
163
  */
152
- constructor(e = T) {
153
- this.headers = y, this.interceptors = new F(), this.urlBuilder = new P(e.baseURL), e.headers !== void 0 && (this.headers = e.headers), this.timeout = e.timeout, this.interceptors.request.use(new q());
164
+ constructor(e = y) {
165
+ this.headers = T, this.interceptors = new O(), this.urlBuilder = new P(e.baseURL), e.headers !== void 0 && (this.headers = e.headers), this.timeout = e.timeout, this.interceptors.request.use(new S());
154
166
  }
155
167
  /**
156
168
  * Make an HTTP request
@@ -190,16 +202,16 @@ class S {
190
202
  error: void 0
191
203
  };
192
204
  try {
193
- const u = {
205
+ const a = {
194
206
  ...n
195
207
  };
196
- n = await this.interceptors.request.intercept(u), n.response = await this.timeoutFetch(n);
197
- const a = {
208
+ n = await this.interceptors.request.intercept(a), n.response = await this.timeoutFetch(n);
209
+ const u = {
198
210
  ...n
199
211
  };
200
- return n = await this.interceptors.response.intercept(a), n;
201
- } catch (u) {
202
- if (n.error = u, n = await this.interceptors.error.intercept(n), n.response)
212
+ return n = await this.interceptors.response.intercept(u), n;
213
+ } catch (a) {
214
+ if (n.error = a, n = await this.interceptors.error.intercept(n), n.response)
203
215
  return n;
204
216
  throw n.error;
205
217
  }
@@ -216,28 +228,28 @@ class S {
216
228
  * @throws FetchTimeoutError Thrown when the request times out
217
229
  */
218
230
  async timeoutFetch(e) {
219
- const t = e.url, s = e.request, o = s.timeout, i = O(o, this.timeout);
231
+ const t = e.url, s = e.request, o = s.timeout, i = A(o, this.timeout);
220
232
  if (!i)
221
233
  return fetch(t, s);
222
- const n = new AbortController(), u = {
234
+ const n = new AbortController(), a = {
223
235
  ...s,
224
236
  signal: n.signal
225
237
  };
226
- let a = null;
227
- const E = new Promise((L, w) => {
228
- a = setTimeout(() => {
229
- a && clearTimeout(a);
230
- const m = new l(e, i);
231
- n.abort(m), w(m);
238
+ let u = null;
239
+ const g = new Promise((L, b) => {
240
+ u = setTimeout(() => {
241
+ u && clearTimeout(u);
242
+ const E = new m(e, i);
243
+ n.abort(E), b(E);
232
244
  }, i);
233
245
  });
234
246
  try {
235
247
  return await Promise.race([
236
- fetch(t, u),
237
- E
248
+ fetch(t, a),
249
+ g
238
250
  ]);
239
251
  } finally {
240
- a && clearTimeout(a);
252
+ u && clearTimeout(u);
241
253
  }
242
254
  }
243
255
  /**
@@ -325,8 +337,8 @@ class S {
325
337
  return this.methodFetch(c.OPTIONS, e, t);
326
338
  }
327
339
  }
328
- const f = "default";
329
- class R {
340
+ const l = "default";
341
+ class N {
330
342
  constructor() {
331
343
  this.registrar = /* @__PURE__ */ new Map();
332
344
  }
@@ -399,7 +411,7 @@ class R {
399
411
  * const defaultFetcher = fetcherRegistrar.default;
400
412
  */
401
413
  get default() {
402
- return this.requiredGet(f);
414
+ return this.requiredGet(l);
403
415
  }
404
416
  /**
405
417
  * Set the default Fetcher instance
@@ -410,7 +422,7 @@ class R {
410
422
  * fetcherRegistrar.default = fetcher;
411
423
  */
412
424
  set default(e) {
413
- this.register(f, e);
425
+ this.register(l, e);
414
426
  }
415
427
  /**
416
428
  * Get a copy of all registered fetchers
@@ -426,8 +438,8 @@ class R {
426
438
  return new Map(this.registrar);
427
439
  }
428
440
  }
429
- const U = new R();
430
- class I extends S {
441
+ const R = new N();
442
+ class U extends q {
431
443
  /**
432
444
  * Create a NamedFetcher instance and automatically register it with the global fetcherRegistrar
433
445
  *
@@ -445,28 +457,30 @@ class I extends S {
445
457
  * headers: { 'Authorization': 'Bearer token' }
446
458
  * });
447
459
  */
448
- constructor(e, t = T) {
449
- super(t), this.name = e, U.register(e, this);
460
+ constructor(e, t = y) {
461
+ super(t), this.name = e, R.register(e, this);
450
462
  }
451
463
  }
452
- const N = new I(f);
464
+ const _ = new U(l);
453
465
  export {
454
- d as ContentTypeHeader,
466
+ f as ContentTypeHeader,
455
467
  p as ContentTypeValues,
456
- f as DEFAULT_FETCHER_NAME,
457
- T as DEFAULT_OPTIONS,
458
- l as FetchTimeoutError,
459
- S as Fetcher,
460
- F as FetcherInterceptors,
461
- R as FetcherRegistrar,
468
+ l as DEFAULT_FETCHER_NAME,
469
+ y as DEFAULT_OPTIONS,
470
+ m as FetchTimeoutError,
471
+ q as Fetcher,
472
+ O as FetcherInterceptors,
473
+ N as FetcherRegistrar,
462
474
  c as HttpMethod,
463
- h as InterceptorManager,
464
- I as NamedFetcher,
465
- A as RequestField,
475
+ d as InterceptorManager,
476
+ U as NamedFetcher,
477
+ S as RequestBodyInterceptor,
478
+ F as RequestField,
466
479
  P as UrlBuilder,
467
- g as combineURLs,
468
- N as fetcher,
469
- U as fetcherRegistrar,
470
- b as isAbsoluteURL,
471
- O as resolveTimeout
480
+ I as combineURLs,
481
+ _ as fetcher,
482
+ R as fetcherRegistrar,
483
+ w as isAbsoluteURL,
484
+ A as resolveTimeout,
485
+ h as toSorted
472
486
  };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(n,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(n=typeof globalThis<"u"?globalThis:n||self,h(n.Fetcher={}))})(this,(function(n){"use strict";function h(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function p(r,e){return h(e)?e:e?r.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""):r}class b{constructor(e){this.baseURL=e}build(e,t,s){const o=p(this.baseURL,e);let c=this.interpolateUrl(o,t);if(s){const i=new URLSearchParams(s).toString();i&&(c+="?"+i)}return c}interpolateUrl(e,t){return t?e.replace(/{([^}]+)}/g,(s,o)=>{const c=t[o];if(c===void 0)throw new Error(`Missing required path parameter: ${o}`);return String(c)}):e}}function g(r,e){return typeof r<"u"?r:e}class f extends Error{constructor(e,t){const s=e.request?.method||"GET",o=`Request timeout of ${t}ms exceeded for ${s} ${e.url}`;super(o),this.name="FetchTimeoutError",this.exchange=e,Object.setPrototypeOf(this,f.prototype)}}var u=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.HEAD="HEAD",r.OPTIONS="OPTIONS",r))(u||{}),F=(r=>(r.METHOD="method",r.BODY="body",r))(F||{});const l="Content-Type";var m=(r=>(r.APPLICATION_JSON="application/json",r.TEXT_EVENT_STREAM="text/event-stream",r))(m||{});class T{constructor(){this.interceptors=[]}use(e){const t=this.interceptors.length;return this.interceptors.push(e),t}eject(e){this.interceptors[e]&&(this.interceptors[e]=null)}clear(){this.interceptors=[]}async intercept(e){let t=e;for(const s of this.interceptors)s&&(t=await s.intercept(t));return t}}class w{constructor(){this.request=new T,this.response=new T,this.error=new T}}class q{intercept(e){const t=e.request;if(t.body===void 0||t.body===null||typeof t.body!="object"||t.body instanceof ArrayBuffer||ArrayBuffer.isView(t.body)||t.body instanceof Blob||t.body instanceof File||t.body instanceof URLSearchParams||t.body instanceof FormData||t.body instanceof ReadableStream)return e;const s={...t};s.body=JSON.stringify(t.body),s.headers||(s.headers={});const o=s.headers;return o[l]||(o[l]=m.APPLICATION_JSON),{...e,request:s}}}const P={[l]:m.APPLICATION_JSON},E={baseURL:"",headers:P};class A{constructor(e=E){this.headers=P,this.interceptors=new w,this.urlBuilder=new b(e.baseURL),e.headers!==void 0&&(this.headers=e.headers),this.timeout=e.timeout,this.interceptors.request.use(new q)}async fetch(e,t={}){const s=await this.request(e,t);if(!s.response)throw new Error(`Request to ${s.url} failed with no response`);return s.response}async request(e,t={}){const s={...this.headers||{},...t.headers||{}},o={...t,headers:Object.keys(s).length>0?s:void 0},c=this.urlBuilder.build(e,t.path,t.query);let i={fetcher:this,url:c,request:o,response:void 0,error:void 0};try{const d={...i};i=await this.interceptors.request.intercept(d),i.response=await this.timeoutFetch(i);const a={...i};return i=await this.interceptors.response.intercept(a),i}catch(d){if(i.error=d,i=await this.interceptors.error.intercept(i),i.response)return i;throw i.error}}async timeoutFetch(e){const t=e.url,s=e.request,o=s.timeout,c=g(o,this.timeout);if(!c)return fetch(t,s);const i=new AbortController,d={...s,signal:i.signal};let a=null;const I=new Promise((D,N)=>{a=setTimeout(()=>{a&&clearTimeout(a);const U=new f(e,c);i.abort(U),N(U)},c)});try{return await Promise.race([fetch(t,d),I])}finally{a&&clearTimeout(a)}}async methodFetch(e,t,s={}){return this.fetch(t,{...s,method:e})}async get(e,t={}){return this.methodFetch(u.GET,e,t)}async post(e,t={}){return this.methodFetch(u.POST,e,t)}async put(e,t={}){return this.methodFetch(u.PUT,e,t)}async delete(e,t={}){return this.methodFetch(u.DELETE,e,t)}async patch(e,t={}){return this.methodFetch(u.PATCH,e,t)}async head(e,t={}){return this.methodFetch(u.HEAD,e,t)}async options(e,t={}){return this.methodFetch(u.OPTIONS,e,t)}}const y="default";class O{constructor(){this.registrar=new Map}register(e,t){this.registrar.set(e,t)}unregister(e){return this.registrar.delete(e)}get(e){return this.registrar.get(e)}requiredGet(e){const t=this.get(e);if(!t)throw new Error(`Fetcher ${e} not found`);return t}get default(){return this.requiredGet(y)}set default(e){this.register(y,e)}get fetchers(){return new Map(this.registrar)}}const R=new O;class S extends A{constructor(e,t=E){super(t),this.name=e,R.register(e,this)}}const L=new S(y);n.ContentTypeHeader=l,n.ContentTypeValues=m,n.DEFAULT_FETCHER_NAME=y,n.DEFAULT_OPTIONS=E,n.FetchTimeoutError=f,n.Fetcher=A,n.FetcherInterceptors=w,n.FetcherRegistrar=O,n.HttpMethod=u,n.InterceptorManager=T,n.NamedFetcher=S,n.RequestField=F,n.UrlBuilder=b,n.combineURLs=p,n.fetcher=L,n.fetcherRegistrar=R,n.isAbsoluteURL=h,n.resolveTimeout=g,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(n,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(n=typeof globalThis<"u"?globalThis:n||self,h(n.Fetcher={}))})(this,(function(n){"use strict";function h(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function g(r,e){return h(e)?e:e?r.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""):r}class b{constructor(e){this.baseURL=e}build(e,t,s){const i=g(this.baseURL,e);let c=this.interpolateUrl(i,t);if(s){const o=new URLSearchParams(s).toString();o&&(c+="?"+o)}return c}interpolateUrl(e,t){return t?e.replace(/{([^}]+)}/g,(s,i)=>{const c=t[i];if(c===void 0)throw new Error(`Missing required path parameter: ${i}`);return String(c)}):e}}function F(r,e){return typeof r<"u"?r:e}class f extends Error{constructor(e,t){const s=e.request?.method||"GET",i=`Request timeout of ${t}ms exceeded for ${s} ${e.url}`;super(i),this.name="FetchTimeoutError",this.exchange=e,Object.setPrototypeOf(this,f.prototype)}}var u=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.HEAD="HEAD",r.OPTIONS="OPTIONS",r))(u||{}),I=(r=>(r.METHOD="method",r.BODY="body",r))(I||{});const l="Content-Type";var m=(r=>(r.APPLICATION_JSON="application/json",r.TEXT_EVENT_STREAM="text/event-stream",r))(m||{});function T(r,e){return e?r.filter(e).sort((t,s)=>t.order-s.order):[...r].sort((t,s)=>t.order-s.order)}class E{constructor(e=[]){this.sortedInterceptors=[],this.sortedInterceptors=T(e)}get name(){return this.constructor.name}get order(){return Number.MIN_SAFE_INTEGER}use(e){return this.sortedInterceptors.some(t=>t.name===e.name)?!1:(this.sortedInterceptors=T([...this.sortedInterceptors,e]),!0)}eject(e){const t=this.sortedInterceptors;return this.sortedInterceptors=T(t,s=>s.name!==e),t.length!==this.sortedInterceptors.length}clear(){this.sortedInterceptors=[]}async intercept(e){let t=e;for(const s of this.sortedInterceptors)t=await s.intercept(t);return t}}class w{constructor(){this.request=new E,this.response=new E,this.error=new E}}class A{constructor(){this.name="RequestBodyInterceptor",this.order=Number.MIN_SAFE_INTEGER}intercept(e){const t=e.request;if(t.body===void 0||t.body===null||typeof t.body!="object"||t.body instanceof ArrayBuffer||ArrayBuffer.isView(t.body)||t.body instanceof Blob||t.body instanceof File||t.body instanceof URLSearchParams||t.body instanceof FormData||t.body instanceof ReadableStream)return e;const s={...t};s.body=JSON.stringify(t.body),s.headers||(s.headers={});const i=s.headers;return i[l]||(i[l]=m.APPLICATION_JSON),{...e,request:s}}}const P={[l]:m.APPLICATION_JSON},p={baseURL:"",headers:P};class O{constructor(e=p){this.headers=P,this.interceptors=new w,this.urlBuilder=new b(e.baseURL),e.headers!==void 0&&(this.headers=e.headers),this.timeout=e.timeout,this.interceptors.request.use(new A)}async fetch(e,t={}){const s=await this.request(e,t);if(!s.response)throw new Error(`Request to ${s.url} failed with no response`);return s.response}async request(e,t={}){const s={...this.headers||{},...t.headers||{}},i={...t,headers:Object.keys(s).length>0?s:void 0},c=this.urlBuilder.build(e,t.path,t.query);let o={fetcher:this,url:c,request:i,response:void 0,error:void 0};try{const d={...o};o=await this.interceptors.request.intercept(d),o.response=await this.timeoutFetch(o);const a={...o};return o=await this.interceptors.response.intercept(a),o}catch(d){if(o.error=d,o=await this.interceptors.error.intercept(o),o.response)return o;throw o.error}}async timeoutFetch(e){const t=e.url,s=e.request,i=s.timeout,c=F(i,this.timeout);if(!c)return fetch(t,s);const o=new AbortController,d={...s,signal:o.signal};let a=null;const L=new Promise((D,_)=>{a=setTimeout(()=>{a&&clearTimeout(a);const q=new f(e,c);o.abort(q),_(q)},c)});try{return await Promise.race([fetch(t,d),L])}finally{a&&clearTimeout(a)}}async methodFetch(e,t,s={}){return this.fetch(t,{...s,method:e})}async get(e,t={}){return this.methodFetch(u.GET,e,t)}async post(e,t={}){return this.methodFetch(u.POST,e,t)}async put(e,t={}){return this.methodFetch(u.PUT,e,t)}async delete(e,t={}){return this.methodFetch(u.DELETE,e,t)}async patch(e,t={}){return this.methodFetch(u.PATCH,e,t)}async head(e,t={}){return this.methodFetch(u.HEAD,e,t)}async options(e,t={}){return this.methodFetch(u.OPTIONS,e,t)}}const y="default";class R{constructor(){this.registrar=new Map}register(e,t){this.registrar.set(e,t)}unregister(e){return this.registrar.delete(e)}get(e){return this.registrar.get(e)}requiredGet(e){const t=this.get(e);if(!t)throw new Error(`Fetcher ${e} not found`);return t}get default(){return this.requiredGet(y)}set default(e){this.register(y,e)}get fetchers(){return new Map(this.registrar)}}const S=new R;class N extends O{constructor(e,t=p){super(t),this.name=e,S.register(e,this)}}const U=new N(y);n.ContentTypeHeader=l,n.ContentTypeValues=m,n.DEFAULT_FETCHER_NAME=y,n.DEFAULT_OPTIONS=p,n.FetchTimeoutError=f,n.Fetcher=O,n.FetcherInterceptors=w,n.FetcherRegistrar=R,n.HttpMethod=u,n.InterceptorManager=E,n.NamedFetcher=N,n.RequestBodyInterceptor=A,n.RequestField=I,n.UrlBuilder=b,n.combineURLs=g,n.fetcher=U,n.fetcherRegistrar=S,n.isAbsoluteURL=h,n.resolveTimeout=F,n.toSorted=T,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
@@ -1,4 +1,6 @@
1
1
  import { Fetcher, FetcherRequest } from './fetcher';
2
+ import { NamedCapable } from './types';
3
+ import { OrderedCapable } from './orderedCapable';
2
4
  export interface FetchExchange {
3
5
  fetcher: Fetcher;
4
6
  url: string;
@@ -10,7 +12,11 @@ export interface FetchExchange {
10
12
  * 拦截器接口,定义了拦截器的基本结构
11
13
  * @template T - 拦截器处理的数据类型
12
14
  */
13
- export interface Interceptor {
15
+ export interface Interceptor extends NamedCapable, OrderedCapable {
16
+ /**
17
+ * 拦截器的名称,用于标识拦截器,不可重复
18
+ */
19
+ name: string;
14
20
  /**
15
21
  * 拦截并处理数据
16
22
  * @param exchange - 需要处理的数据
@@ -22,18 +28,21 @@ export interface Interceptor {
22
28
  * 拦截器管理器类,用于管理同一类型的多个拦截器
23
29
  */
24
30
  export declare class InterceptorManager implements Interceptor {
25
- private interceptors;
31
+ get name(): string;
32
+ get order(): number;
33
+ private sortedInterceptors;
34
+ constructor(interceptors?: Interceptor[]);
26
35
  /**
27
36
  * 添加拦截器到管理器中
28
37
  * @param interceptor - 要添加的拦截器
29
38
  * @returns 拦截器在管理器中的索引位置
30
39
  */
31
- use(interceptor: Interceptor): number;
40
+ use(interceptor: Interceptor): boolean;
32
41
  /**
33
- * 根据索引移除拦截器
34
- * @param index - 要移除的拦截器索引
42
+ * 根据名称移除拦截器
43
+ * @param name 要移除的拦截器名称
35
44
  */
36
- eject(index: number): void;
45
+ eject(name: string): boolean;
37
46
  /**
38
47
  * 清空所有拦截器
39
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,KAAK,GAAG,GAAG,GAAG,SAAS,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5E;AAED;;GAEG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,YAAY,CAAiC;IAErD;;;;OAIG;IACH,GAAG,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM;IAMrC;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM1B;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;;OAIG;IACG,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;CAUjE;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAA4B;IAEvD;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAA4B;IAExD;;OAEG;IACH,KAAK,EAAE,kBAAkB,CAA4B;CACtD"}
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,cAAc,EAAY,MAAM,kBAAkB,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,KAAK,GAAG,GAAG,GAAG,SAAS,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,YAAY,EAAE,cAAc;IAC/D;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5E;AAED;;GAEG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,YAAY,GAAE,WAAW,EAAO;IAI5C;;;;OAIG;IACH,GAAG,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAQtC;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAM5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;;OAIG;IACG,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;CAQjE;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAA4B;IAEvD;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAA4B;IAExD;;OAEG;IACH,KAAK,EAAE,kBAAkB,CAA4B;CACtD"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 具备排序能力的接口
3
+ *
4
+ * 实现该接口的类型需要提供一个排序属性,用于确定执行顺序。
5
+ * 数值越小优先级越高,相同数值的元素保持原有相对顺序。
6
+ */
7
+ export interface OrderedCapable {
8
+ /**
9
+ * 排序值
10
+ *
11
+ * 数值越小优先级越高。负数、零和正数都支持。
12
+ * 当多个元素具有相同的order值时,它们的相对顺序将保持不变(稳定排序)。
13
+ */
14
+ order: number;
15
+ }
16
+ /**
17
+ * 对实现了OrderedCapable接口的数组进行排序
18
+ *
19
+ * 该函数创建并返回一个新的排序数组,不会修改原始数组。
20
+ * 支持可选的过滤函数来筛选需要排序的元素。
21
+ *
22
+ * @template T - 数组元素类型,必须实现OrderedCapable接口
23
+ * @param array - 需要排序的数组
24
+ * @param filter - 可选的过滤函数,用于筛选需要参与排序的元素
25
+ * @returns 排序后的新数组,按照order值升序排列
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const items: OrderedCapable[] = [
30
+ * { order: 10 },
31
+ * { order: 5 },
32
+ * { order: 1 },
33
+ * ];
34
+ *
35
+ * const sortedItems = toSorted(items);
36
+ * // 结果: [{ order: 1 }, { order: 5 }, { order: 10 }]
37
+ *
38
+ * // 使用过滤函数
39
+ * const filteredAndSorted = toSorted(items, item => item.order > 3);
40
+ * // 结果: [{ order: 5 }, { order: 10 }]
41
+ * ```
42
+ */
43
+ export declare function toSorted<T extends OrderedCapable>(array: T[], filter?: (item: T) => boolean): T[];
44
+ //# sourceMappingURL=orderedCapable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orderedCapable.d.ts","sourceRoot":"","sources":["../src/orderedCapable.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,cAAc,EAC/C,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAC5B,CAAC,EAAE,CAKL"}
@@ -3,6 +3,8 @@ import { FetchExchange, Interceptor } from './interceptor';
3
3
  * 请求体拦截器,负责将普通对象转换为JSON字符串
4
4
  */
5
5
  export declare class RequestBodyInterceptor implements Interceptor {
6
+ name: string;
7
+ order: number;
6
8
  /**
7
9
  * 尝试转换请求体为合法的 fetch API body 类型
8
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"requestBodyInterceptor.d.ts","sourceRoot":"","sources":["../src/requestBodyInterceptor.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG3D;;GAEG;AACH,qBAAa,sBAAuB,YAAW,WAAW;IACxD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa;CA2ClD"}
1
+ {"version":3,"file":"requestBodyInterceptor.d.ts","sourceRoot":"","sources":["../src/requestBodyInterceptor.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG3D;;GAEG;AACH,qBAAa,sBAAuB,YAAW,WAAW;IACxD,IAAI,SAA4B;IAChC,KAAK,SAA2B;IAEhC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa;CA2ClD"}
package/dist/types.d.ts CHANGED
@@ -32,7 +32,28 @@ export declare enum ContentTypeValues {
32
32
  APPLICATION_JSON = "application/json",
33
33
  TEXT_EVENT_STREAM = "text/event-stream"
34
34
  }
35
+ /**
36
+ * 具备名称能力的接口
37
+ * 实现该接口的类型需要提供一个名称属性
38
+ */
35
39
  export interface NamedCapable {
40
+ /**
41
+ * 名称
42
+ */
36
43
  name: string;
37
44
  }
45
+ /**
46
+ * Global extension of Response interface
47
+ * Adds type-safe json() method support to Response objects
48
+ */
49
+ declare global {
50
+ interface Response {
51
+ /**
52
+ * Parse response body as JSON in a type-safe manner
53
+ * @template T The type of returned data, defaults to any
54
+ * @returns Promise<T> The parsed JSON data
55
+ */
56
+ json<T = any>(): Promise<T>;
57
+ }
58
+ }
38
59
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,oBAAY,UAAU;IACpB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,OAAO,YAAY;CACpB;AAED,oBAAY,YAAY;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;CACd;AAED,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,oBAAY,iBAAiB;IAC3B,gBAAgB,qBAAqB;IACrC,iBAAiB,sBAAsB;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,oBAAY,UAAU;IACpB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,OAAO,YAAY;CACpB;AAED,oBAAY,YAAY;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;CACd;AAED,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,oBAAY,iBAAiB;IAC3B,gBAAgB,qBAAqB;IACrC,iBAAiB,sBAAsB;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,QAAQ;QAChB;;;;WAIG;QACH,IAAI,CAAC,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;KAC7B;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahoo-wang/fetcher",
3
- "version": "0.5.6",
3
+ "version": "0.6.1",
4
4
  "description": "Ultra-lightweight (1.9kB) HTTP client with built-in path parameters and Axios-like API",
5
5
  "keywords": [
6
6
  "fetch",