@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 CHANGED
@@ -1,36 +1,68 @@
1
- # fetcher-core
1
+ # @ahoo-wang/fetcher
2
2
 
3
- Fetcher-core 是一个基于现代 Fetch API HTTP 客户端库,旨在简化和优化与后端 RESTful API 的交互。它提供了类似于 Axios API,支持路径参数、查询参数、超时设置以及请求和响应拦截器。
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
- - **兼容 Fetch API**: fetcher API 完全兼容原生 Fetch API,易于上手。
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
- 使用 pnpm 安装:
17
+ Using pnpm:
17
18
 
18
19
  ```bash
19
- pnpm add @fetcher/core
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/core';
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
- .get('/users/{id}', { pathParams: { id: 123 } })
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
- ## API 参考
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
- ### Fetcher
150
+ ### InterceptorManager Class
45
151
 
46
- - `fetch(url: string, request?: FetcherRequest): Promise<Response>`
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
- ### UrlBuilder
154
+ #### Methods
56
155
 
57
- - `build(path: string, pathParams?: Record<string, any>, queryParams?: Record<string, any>): string`
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
- ### InterceptorManager
161
+ ### FetcherInterceptors Class
60
162
 
61
- - `use(interceptor: T): number`
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
- ### FetcherInterceptors
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/core';
174
+ import { Fetcher } from '@ahoo-wang/fetcher';
76
175
 
77
- const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
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
- const requestInterceptorId = fetcher.interceptors.request.use({
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 token',
192
+ Authorization: 'Bearer ' + getAuthToken(),
88
193
  },
89
194
  };
90
195
  },
91
196
  });
92
197
 
93
- // 添加响应拦截器
94
- const responseInterceptorId = fetcher.interceptors.response.use({
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.request.eject(requestInterceptorId);
103
- fetcher.interceptors.response.eject(responseInterceptorId);
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
- fetcher.interceptors.request.clear();
107
- fetcher.interceptors.response.clear();
108
- fetcher.interceptors.error.clear();
239
+ Run tests:
240
+
241
+ ```bash
242
+ pnpm test
109
243
  ```
110
244
 
111
- ## 贡献
245
+ ## Contributing
112
246
 
113
- 欢迎任何形式的贡献!请查看 [贡献指南](https://github.com/Ahoo-Wang/fetcher/blob/main/CONTRIBUTING.md) 了解更多信息。
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
- 本项目采用 [Apache-2.0](https://opensource.org/licenses/Apache-2.0) 许可证。
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.FetcherCore={}))})(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"})}));
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
@@ -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.1",
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
  ],