@ahoo-wang/fetcher 0.0.1 → 0.0.3
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 +191 -57
- package/dist/index.umd.js +1 -1
- package/dist/urls.d.ts +13 -0
- package/dist/urls.d.ts.map +1 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -1,36 +1,68 @@
|
|
|
1
|
-
# fetcher
|
|
1
|
+
# @ahoo-wang/fetcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modern HTTP client library based on the Fetch API, designed to simplify and optimize interactions with backend RESTful APIs. It provides an Axios-like API with support for path parameters, query parameters, timeout settings, and request/response interceptors.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- **Fetch API Compatible**: Fetcher's API is fully compatible with the native Fetch API, making it easy to get started.
|
|
8
|
+
- **Path and Query Parameters**: Supports path parameters and query parameters in requests, with path parameters wrapped in `{}`.
|
|
9
|
+
- **Timeout Settings**: Request timeout can be configured.
|
|
10
|
+
- **Request Interceptors**: Supports modifying requests before they are sent.
|
|
11
|
+
- **Response Interceptors**: Supports processing responses after they are returned.
|
|
12
|
+
- **Modular Design**: Clear code structure for easy maintenance and extension.
|
|
13
|
+
- **TypeScript Support**: Complete TypeScript type definitions.
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Installation
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Using pnpm:
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
pnpm add @fetcher
|
|
20
|
+
pnpm add @ahoo-wang/fetcher
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Using npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @ahoo-wang/fetcher
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Using yarn:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @ahoo-wang/fetcher
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Basic Usage
|
|
23
38
|
|
|
24
39
|
```typescript
|
|
25
|
-
import { Fetcher } from '@fetcher
|
|
40
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
26
41
|
|
|
27
42
|
const fetcher = new Fetcher({
|
|
28
43
|
baseURL: 'https://api.example.com',
|
|
29
44
|
timeout: 5000,
|
|
30
45
|
});
|
|
31
46
|
|
|
47
|
+
// GET request with path parameters and query parameters
|
|
48
|
+
fetcher
|
|
49
|
+
.get('/users/{id}', {
|
|
50
|
+
pathParams: { id: 123 },
|
|
51
|
+
queryParams: { include: 'profile' },
|
|
52
|
+
})
|
|
53
|
+
.then(response => {
|
|
54
|
+
console.log(response.data);
|
|
55
|
+
})
|
|
56
|
+
.catch(error => {
|
|
57
|
+
console.error(error);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// POST request with JSON body
|
|
32
61
|
fetcher
|
|
33
|
-
.
|
|
62
|
+
.post('/users', {
|
|
63
|
+
body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' }),
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
})
|
|
34
66
|
.then(response => {
|
|
35
67
|
console.log(response.data);
|
|
36
68
|
})
|
|
@@ -39,79 +71,181 @@ fetcher
|
|
|
39
71
|
});
|
|
40
72
|
```
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
### Interceptor Usage
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
78
|
+
|
|
79
|
+
const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
|
|
80
|
+
|
|
81
|
+
// Add request interceptor
|
|
82
|
+
const requestInterceptorId = fetcher.interceptors.request.use({
|
|
83
|
+
intercept(request) {
|
|
84
|
+
// Modify request configuration, e.g., add auth header
|
|
85
|
+
return {
|
|
86
|
+
...request,
|
|
87
|
+
headers: {
|
|
88
|
+
...request.headers,
|
|
89
|
+
Authorization: 'Bearer token',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Add response interceptor
|
|
96
|
+
const responseInterceptorId = fetcher.interceptors.response.use({
|
|
97
|
+
intercept(response) {
|
|
98
|
+
// Process response data, e.g., parse JSON
|
|
99
|
+
return response;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Add error interceptor
|
|
104
|
+
const errorInterceptorId = fetcher.interceptors.error.use({
|
|
105
|
+
intercept(error) {
|
|
106
|
+
// Handle errors, e.g., log them
|
|
107
|
+
console.error('Request failed:', error);
|
|
108
|
+
return error;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### Fetcher Class
|
|
116
|
+
|
|
117
|
+
Core HTTP client class that provides various HTTP methods.
|
|
118
|
+
|
|
119
|
+
#### Constructor
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
new Fetcher(options?: FetcherOptions)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Parameters:**
|
|
126
|
+
|
|
127
|
+
- `options.baseURL`: Base URL
|
|
128
|
+
- `options.timeout`: Request timeout in milliseconds
|
|
129
|
+
- `options.headers`: Default request headers
|
|
130
|
+
|
|
131
|
+
#### Methods
|
|
132
|
+
|
|
133
|
+
- `fetch(url: string, request?: FetcherRequest): Promise<Response>` - Generic HTTP request method
|
|
134
|
+
- `get(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - GET request
|
|
135
|
+
- `post(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - POST request
|
|
136
|
+
- `put(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PUT request
|
|
137
|
+
- `delete(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - DELETE request
|
|
138
|
+
- `patch(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>` - PATCH request
|
|
139
|
+
- `head(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - HEAD request
|
|
140
|
+
- `options(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>` - OPTIONS request
|
|
141
|
+
|
|
142
|
+
### UrlBuilder Class
|
|
143
|
+
|
|
144
|
+
URL builder for constructing complete URLs with parameters.
|
|
145
|
+
|
|
146
|
+
#### Methods
|
|
147
|
+
|
|
148
|
+
- `build(path: string, pathParams?: Record<string, any>, queryParams?: Record<string, any>): string` - Build complete URL
|
|
43
149
|
|
|
44
|
-
###
|
|
150
|
+
### InterceptorManager Class
|
|
45
151
|
|
|
46
|
-
|
|
47
|
-
- `get(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>`
|
|
48
|
-
- `post(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>`
|
|
49
|
-
- `put(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>`
|
|
50
|
-
- `delete(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>`
|
|
51
|
-
- `patch(url: string, request?: Omit<FetcherRequest, 'method'>): Promise<Response>`
|
|
52
|
-
- `head(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>`
|
|
53
|
-
- `options(url: string, request?: Omit<FetcherRequest, 'method' | 'body'>): Promise<Response>`
|
|
152
|
+
Interceptor manager for managing multiple interceptors of the same type.
|
|
54
153
|
|
|
55
|
-
|
|
154
|
+
#### Methods
|
|
56
155
|
|
|
57
|
-
- `
|
|
156
|
+
- `use(interceptor: T): number` - Add interceptor, returns interceptor ID
|
|
157
|
+
- `eject(index: number): void` - Remove interceptor by ID
|
|
158
|
+
- `clear(): void` - Clear all interceptors
|
|
159
|
+
- `intercept(data: R): Promise<R>` - Execute all interceptors sequentially
|
|
58
160
|
|
|
59
|
-
###
|
|
161
|
+
### FetcherInterceptors Class
|
|
60
162
|
|
|
61
|
-
|
|
62
|
-
- `eject(index: number): void`
|
|
63
|
-
- `clear(): void`
|
|
64
|
-
- `intercept(data: R): Promise<R>`
|
|
163
|
+
Fetcher interceptor collection, including request, response, and error interceptor managers.
|
|
65
164
|
|
|
66
|
-
|
|
165
|
+
#### Properties
|
|
67
166
|
|
|
68
|
-
- `request: RequestInterceptorManager`
|
|
69
|
-
- `response: ResponseInterceptorManager`
|
|
70
|
-
- `error: ErrorInterceptorManager`
|
|
167
|
+
- `request: RequestInterceptorManager` - Request interceptor manager
|
|
168
|
+
- `response: ResponseInterceptorManager` - Response interceptor manager
|
|
169
|
+
- `error: ErrorInterceptorManager` - Error interceptor manager
|
|
71
170
|
|
|
72
|
-
|
|
171
|
+
## Complete Example
|
|
73
172
|
|
|
74
173
|
```typescript
|
|
75
|
-
import { Fetcher } from '@fetcher
|
|
174
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
76
175
|
|
|
77
|
-
|
|
176
|
+
// Create fetcher instance
|
|
177
|
+
const fetcher = new Fetcher({
|
|
178
|
+
baseURL: 'https://api.example.com',
|
|
179
|
+
timeout: 10000,
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
78
184
|
|
|
79
|
-
//
|
|
80
|
-
|
|
185
|
+
// Add request interceptor - Add auth header
|
|
186
|
+
fetcher.interceptors.request.use({
|
|
81
187
|
intercept(request) {
|
|
82
|
-
// 修改请求配置
|
|
83
188
|
return {
|
|
84
189
|
...request,
|
|
85
190
|
headers: {
|
|
86
191
|
...request.headers,
|
|
87
|
-
Authorization: 'Bearer
|
|
192
|
+
Authorization: 'Bearer ' + getAuthToken(),
|
|
88
193
|
},
|
|
89
194
|
};
|
|
90
195
|
},
|
|
91
196
|
});
|
|
92
197
|
|
|
93
|
-
//
|
|
94
|
-
|
|
198
|
+
// Add response interceptor - Auto-parse JSON
|
|
199
|
+
fetcher.interceptors.response.use({
|
|
95
200
|
intercept(response) {
|
|
96
|
-
|
|
201
|
+
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
202
|
+
return response.json().then(data => {
|
|
203
|
+
return new Response(JSON.stringify(data), response);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
97
206
|
return response;
|
|
98
207
|
},
|
|
99
208
|
});
|
|
100
209
|
|
|
101
|
-
//
|
|
102
|
-
fetcher.interceptors.
|
|
103
|
-
|
|
210
|
+
// Add error interceptor - Unified error handling
|
|
211
|
+
fetcher.interceptors.error.use({
|
|
212
|
+
intercept(error) {
|
|
213
|
+
if (error.name === 'FetchTimeoutError') {
|
|
214
|
+
console.error('Request timeout:', error.message);
|
|
215
|
+
} else {
|
|
216
|
+
console.error('Network error:', error.message);
|
|
217
|
+
}
|
|
218
|
+
return error;
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Use fetcher to make requests
|
|
223
|
+
fetcher
|
|
224
|
+
.get('/users/{id}', {
|
|
225
|
+
pathParams: { id: 123 },
|
|
226
|
+
queryParams: { include: 'profile,posts' },
|
|
227
|
+
})
|
|
228
|
+
.then(response => response.json())
|
|
229
|
+
.then(data => {
|
|
230
|
+
console.log('User data:', data);
|
|
231
|
+
})
|
|
232
|
+
.catch(error => {
|
|
233
|
+
console.error('Failed to fetch user:', error);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Testing
|
|
104
238
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
239
|
+
Run tests:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pnpm test
|
|
109
243
|
```
|
|
110
244
|
|
|
111
|
-
##
|
|
245
|
+
## Contributing
|
|
112
246
|
|
|
113
|
-
|
|
247
|
+
Contributions of any kind are welcome! Please see the [contributing guide](https://github.com/Ahoo-Wang/fetcher/blob/main/CONTRIBUTING.md) for more details.
|
|
114
248
|
|
|
115
|
-
##
|
|
249
|
+
## License
|
|
116
250
|
|
|
117
|
-
|
|
251
|
+
This project is licensed under the [Apache-2.0 License](https://opensource.org/licenses/Apache-2.0).
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(i,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(i=typeof globalThis<"u"?globalThis:i||self,u(i.
|
|
1
|
+
(function(i,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(i=typeof globalThis<"u"?globalThis:i||self,u(i.Fetcher={}))})(this,(function(i){"use strict";function u(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function f(r,e){return u(e)?e:e?r.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""):r}class d{constructor(e){this.baseURL=e}build(e,t,s){let n=f(this.baseURL,e),h=this.interpolateUrl(n,t);if(s){const o=new URLSearchParams(s).toString();o&&(h+="?"+o)}return h}interpolateUrl(e,t){return t?e.replace(/{([^}]+)}/g,(s,n)=>{const h=t[n];if(h===void 0)throw new Error(`Missing required path parameter: ${n}`);return String(h)}):e}}function m(r,e){return typeof r<"u"?r:e}class a extends Error{constructor(e,t,s){const n=`Request timeout of ${s}ms exceeded for ${t?.method||"GET"} ${e}`;super(n),this.name="FetchTimeoutError",this.url=e,this.request=t,Object.setPrototypeOf(this,a.prototype)}}var c=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.HEAD="HEAD",r.OPTIONS="OPTIONS",r))(c||{}),p=(r=>(r.METHOD="method",r.BODY="body",r))(p||{});class l{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(let s of this.interceptors)s&&(t=await s.intercept(t));return t}}class E{constructor(){this.request=new l,this.response=new l,this.error=new l}}const y={baseURL:""};class b{constructor(e=y){this.interceptors=new E,this.urlBuilder=new d(e.baseURL),this.headers=e.headers,this.timeout=e.timeout}async fetch(e,t={}){const s={...this.headers||{},...t.headers||{}};let n={...t,headers:Object.keys(s).length>0?s:void 0};const h=this.urlBuilder.build(e,t.pathParams,t.queryParams);n=await this.interceptors.request.intercept(n);try{let o=await this.timeoutFetch(h,n);return o=await this.interceptors.response.intercept(o),o}catch(o){throw await this.interceptors.error.intercept(o)}}async timeoutFetch(e,t){const s=m(t.timeout,this.timeout);if(!s)return fetch(e,t);const n=new AbortController,h={...t,signal:n.signal};let o=null;const T=new Promise((P,w)=>{o=setTimeout(()=>{n.abort(),w(new a(e,t,s))},s)});try{return await Promise.race([fetch(e,h),T])}finally{o&&clearTimeout(o)}}async get(e,t={}){return this.fetch(e,{...t,method:c.GET})}async post(e,t={}){return this.fetch(e,{...t,method:c.POST})}async put(e,t={}){return this.fetch(e,{...t,method:c.PUT})}async delete(e,t={}){return this.fetch(e,{...t,method:c.DELETE})}async patch(e,t={}){return this.fetch(e,{...t,method:c.PATCH})}async head(e,t={}){return this.fetch(e,{...t,method:c.HEAD})}async options(e,t={}){return this.fetch(e,{...t,method:c.OPTIONS})}}i.FetchTimeoutError=a,i.Fetcher=b,i.HttpMethod=c,i.RequestField=p,i.UrlBuilder=d,i.combineURLs=f,i.isAbsoluteURL=u,i.resolveTimeout=m,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/urls.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 检查给定的 URL 是否为绝对 URL
|
|
3
|
+
*
|
|
4
|
+
* @param url - 需要检查的 URL 字符串
|
|
5
|
+
* @returns boolean - 如果是绝对 URL 返回 true,否则返回 false
|
|
6
|
+
*/
|
|
1
7
|
export declare function isAbsoluteURL(url: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 将基础 URL 和相对 URL 组合成完整的 URL
|
|
10
|
+
*
|
|
11
|
+
* @param baseURL - 基础 URL
|
|
12
|
+
* @param relativeURL - 相对 URL
|
|
13
|
+
* @returns string - 组合后的完整 URL
|
|
14
|
+
*/
|
|
2
15
|
export declare function combineURLs(baseURL: string, relativeURL: string): string;
|
|
3
16
|
//# sourceMappingURL=urls.d.ts.map
|
package/dist/urls.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../src/urls.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,WAExC;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAQ/D"}
|
|
1
|
+
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../src/urls.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,WAExC;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAQ/D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahoo-wang/fetcher",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Core library providing basic HTTP client functionality for Fetcher",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"main": "./dist/index.umd.js",
|
|
23
23
|
"module": "./dist/index.es.js",
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./dist/index.es.js",
|
|
28
|
+
"require": "./dist/index.umd.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
25
31
|
"files": [
|
|
26
32
|
"dist"
|
|
27
33
|
],
|