@ticatec/restful_service_api 0.1.5
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-CN.md +410 -0
- package/README.md +376 -0
- package/dist/ApiError.d.ts +9 -0
- package/dist/ApiError.js +18 -0
- package/dist/RestService.d.ts +89 -0
- package/dist/RestService.js +4 -0
- package/dist/UploadCallback.d.ts +51 -0
- package/dist/UploadCallback.js +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +4 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +103 -0
- package/package.json +54 -0
- package/tsconfig.json +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ticatec
|
|
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-CN.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# @ticatec/restful_service_api
|
|
2
|
+
|
|
3
|
+
中文版 | [English](README.md)
|
|
4
|
+
|
|
5
|
+
一个轻量级的 TypeScript RESTful API 浏览器客户端,具有完善的错误处理和拦截器支持。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- 🚀 **轻量级**: 零依赖,专为现代浏览器构建
|
|
10
|
+
- 🔧 **TypeScript 支持**: 完整的类型安全和 TypeScript 定义
|
|
11
|
+
- 🛡️ **错误处理**: 内置错误处理机制,包含自定义 ApiError 类
|
|
12
|
+
- ⚡ **拦截器**: 支持请求前后拦截器,用于身份验证和数据处理
|
|
13
|
+
- 🎯 **灵活性**: 支持自定义请求头、超时设置和数据处理器
|
|
14
|
+
- 🌐 **浏览器优先**: 专为前端应用程序设计
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ticatec/restful_service_api
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 快速开始
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import RestService from '@ticatec/restful_service_api';
|
|
26
|
+
|
|
27
|
+
// 实现 RestService 接口
|
|
28
|
+
class MyApiClient implements RestService {
|
|
29
|
+
async get(url: string, params?: any) {
|
|
30
|
+
// 在这里实现
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async post(url: string, data: any, params?: any, contentType?: string) {
|
|
34
|
+
// 在这里实现
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ... 其他方法
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const api = new MyApiClient();
|
|
41
|
+
|
|
42
|
+
// 发起请求
|
|
43
|
+
const users = await api.get('/users');
|
|
44
|
+
const newUser = await api.post('/users', { name: '张三' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API 参考
|
|
48
|
+
|
|
49
|
+
### RestService 接口
|
|
50
|
+
|
|
51
|
+
定义 REST 操作契约的主要接口:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface RestService {
|
|
55
|
+
get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
|
|
56
|
+
post(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
57
|
+
put(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
58
|
+
del(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
59
|
+
upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
60
|
+
asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress>;
|
|
61
|
+
download(url: string, filename: string, params: any, method?: string, formData?: any): Promise<any>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 方法说明
|
|
66
|
+
|
|
67
|
+
#### `get(url, params?, dataProcessor?)`
|
|
68
|
+
- **url**: 接口端点 URL
|
|
69
|
+
- **params**: 查询参数(可选)
|
|
70
|
+
- **dataProcessor**: 处理响应数据的函数(可选)
|
|
71
|
+
|
|
72
|
+
#### `post(url, data, params?, contentType?, dataProcessor?)`
|
|
73
|
+
- **url**: 接口端点 URL
|
|
74
|
+
- **data**: 请求载荷
|
|
75
|
+
- **params**: 查询参数(可选)
|
|
76
|
+
- **contentType**: Content-Type 请求头(可选,默认为 application/json)
|
|
77
|
+
- **dataProcessor**: 处理响应数据的函数(可选)
|
|
78
|
+
|
|
79
|
+
#### `put(url, data, params?, contentType?, dataProcessor?)`
|
|
80
|
+
类似于 POST,但用于更新操作。
|
|
81
|
+
|
|
82
|
+
#### `del(url, data, params?, contentType?, dataProcessor?)`
|
|
83
|
+
用于删除操作,支持可选的请求体。
|
|
84
|
+
|
|
85
|
+
#### `upload(url, params, file, fileKey?, dataProcessor?)`
|
|
86
|
+
- **url**: 上传端点 URL
|
|
87
|
+
- **params**: 上传请求的附加参数
|
|
88
|
+
- **file**: 要上传的文件对象
|
|
89
|
+
- **fileKey**: 可选的文件表单字段名(默认为 'file')
|
|
90
|
+
- **dataProcessor**: 处理响应数据的函数(可选)
|
|
91
|
+
|
|
92
|
+
#### `asyncUpload(url, params, file, callback, fileKey?)`
|
|
93
|
+
- **url**: 上传端点 URL
|
|
94
|
+
- **params**: 上传请求的附加参数
|
|
95
|
+
- **file**: 要上传的文件对象
|
|
96
|
+
- **callback**: 上传回调对象,包含进度、错误和完成处理函数
|
|
97
|
+
- **fileKey**: 可选的文件表单字段名(默认为 'file')
|
|
98
|
+
- **返回值**: 返回 Promise,解析为 UploadProgress 对象,可用于取消上传
|
|
99
|
+
|
|
100
|
+
#### `download(url, filename, params, method?, formData?)`
|
|
101
|
+
- **url**: 下载端点 URL
|
|
102
|
+
- **filename**: 保存下载文件的名称
|
|
103
|
+
- **params**: 下载请求参数
|
|
104
|
+
- **method**: 可选的 HTTP 方法(默认为 GET)
|
|
105
|
+
- **formData**: 可选的表单数据,用于 POST 下载
|
|
106
|
+
|
|
107
|
+
### 错误处理
|
|
108
|
+
|
|
109
|
+
库包含自定义的 `ApiError` 类用于处理 API 错误:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { ApiError } from '@ticatec/restful_service_api';
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const result = await api.get('/users');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof ApiError) {
|
|
118
|
+
console.log('状态码:', error.status);
|
|
119
|
+
console.log('错误代码:', error.code);
|
|
120
|
+
console.log('错误详情:', error.details);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 类型和拦截器
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
PreInterceptor,
|
|
130
|
+
PostInterceptor,
|
|
131
|
+
ErrorHandler,
|
|
132
|
+
DataProcessor
|
|
133
|
+
} from '@ticatec/restful_service_api';
|
|
134
|
+
|
|
135
|
+
// 请求前拦截器,用于添加认证头
|
|
136
|
+
const preInterceptor: PreInterceptor = (method: string, url: string) => {
|
|
137
|
+
return {
|
|
138
|
+
headers: {
|
|
139
|
+
'Authorization': 'Bearer ' + getToken(),
|
|
140
|
+
'X-Request-ID': generateRequestId()
|
|
141
|
+
},
|
|
142
|
+
timeout: 30000
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// 响应后拦截器,用于处理响应数据
|
|
147
|
+
const postInterceptor: PostInterceptor = async (data: any) => {
|
|
148
|
+
// 处理响应数据
|
|
149
|
+
return data;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// 错误处理器
|
|
153
|
+
const errorHandler: ErrorHandler = (error: Error) => {
|
|
154
|
+
console.error('API 错误:', error);
|
|
155
|
+
return true; // 返回 true 表示错误已处理
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// 数据处理器
|
|
159
|
+
const dataProcessor: DataProcessor = (data: any) => {
|
|
160
|
+
// 转换响应数据
|
|
161
|
+
return data.results || data;
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 内容类型
|
|
166
|
+
|
|
167
|
+
预定义的内容类型常量:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import {
|
|
171
|
+
CONTENT_TYPE_NAME,
|
|
172
|
+
TYPE_JSON,
|
|
173
|
+
TYPE_HTML,
|
|
174
|
+
TYPE_TEXT
|
|
175
|
+
} from '@ticatec/restful_service_api';
|
|
176
|
+
|
|
177
|
+
// 使用示例
|
|
178
|
+
await api.post('/upload', data, {}, TYPE_JSON);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 实现示例
|
|
182
|
+
|
|
183
|
+
以下是使用原生 fetch API 的完整实现示例:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import RestService, {
|
|
187
|
+
ApiError,
|
|
188
|
+
PreInterceptor,
|
|
189
|
+
PostInterceptor,
|
|
190
|
+
TYPE_JSON
|
|
191
|
+
} from '@ticatec/restful_service_api';
|
|
192
|
+
|
|
193
|
+
class FetchRestService implements RestService {
|
|
194
|
+
private baseURL: string;
|
|
195
|
+
private preInterceptor?: PreInterceptor;
|
|
196
|
+
private postInterceptor?: PostInterceptor;
|
|
197
|
+
|
|
198
|
+
constructor(baseURL: string, preInterceptor?: PreInterceptor, postInterceptor?: PostInterceptor) {
|
|
199
|
+
this.baseURL = baseURL;
|
|
200
|
+
this.preInterceptor = preInterceptor;
|
|
201
|
+
this.postInterceptor = postInterceptor;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any> {
|
|
205
|
+
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
|
|
206
|
+
return this.request('GET', url + queryString, null, undefined, dataProcessor);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async post(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
210
|
+
return this.request('POST', url, data, contentType, dataProcessor);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async put(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
214
|
+
return this.request('PUT', url, data, contentType, dataProcessor);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async del(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
218
|
+
return this.request('DELETE', url, data, contentType, dataProcessor);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async request(method: string, url: string, body?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any> {
|
|
222
|
+
const fullUrl = this.baseURL + url;
|
|
223
|
+
|
|
224
|
+
// 应用请求前拦截器
|
|
225
|
+
const interceptorResult = this.preInterceptor?.(method, fullUrl) || { headers: {} };
|
|
226
|
+
|
|
227
|
+
const headers = {
|
|
228
|
+
...interceptorResult.headers,
|
|
229
|
+
...(contentType && { 'Content-Type': contentType })
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(fullUrl, {
|
|
234
|
+
method,
|
|
235
|
+
headers,
|
|
236
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
237
|
+
signal: interceptorResult.timeout ? AbortSignal.timeout(interceptorResult.timeout) : undefined
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
const errorData = await response.json().catch(() => ({}));
|
|
242
|
+
throw new ApiError(response.status, errorData);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let data = await response.json();
|
|
246
|
+
|
|
247
|
+
// 应用响应后拦截器
|
|
248
|
+
if (this.postInterceptor) {
|
|
249
|
+
data = await this.postInterceptor(data);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 应用数据处理器
|
|
253
|
+
if (dataProcessor) {
|
|
254
|
+
data = dataProcessor(data);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return data;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error instanceof ApiError) {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
throw new ApiError(0, { code: 'NETWORK_ERROR', message: error.message });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 使用示例
|
|
268
|
+
const api = new FetchRestService('https://api.example.com');
|
|
269
|
+
const users = await api.get('/users');
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 文件上传和下载示例
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import RestService, {
|
|
276
|
+
UploadCallback,
|
|
277
|
+
UploadProgress
|
|
278
|
+
} from '@ticatec/restful_service_api';
|
|
279
|
+
|
|
280
|
+
// 简单文件上传
|
|
281
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
282
|
+
const file = fileInput.files[0];
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const result = await api.upload('/upload', { userId: 123 }, file);
|
|
286
|
+
console.log('上传成功:', result);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('上传失败:', error);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 带进度跟踪的异步上传
|
|
292
|
+
const uploadCallback: UploadCallback = {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
progressUpdate: (uploadedBytes: number) => {
|
|
295
|
+
console.log(`已上传: ${uploadedBytes} 字节`);
|
|
296
|
+
},
|
|
297
|
+
handleError: (error: Error) => {
|
|
298
|
+
console.error('上传错误:', error);
|
|
299
|
+
},
|
|
300
|
+
onCompleted: (data: any) => {
|
|
301
|
+
console.log('上传完成:', data);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const uploadProgress = await api.asyncUpload('/upload', { userId: 123 }, file, uploadCallback);
|
|
306
|
+
|
|
307
|
+
// 需要时可以取消上传
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
uploadProgress.cancel();
|
|
310
|
+
}, 5000);
|
|
311
|
+
|
|
312
|
+
// 文件下载
|
|
313
|
+
try {
|
|
314
|
+
await api.download('/files/document.pdf', 'my-document.pdf', { userId: 123 });
|
|
315
|
+
console.log('下载完成');
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('下载失败:', error);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 使用表单数据的 POST 下载
|
|
321
|
+
const formData = { reportType: 'monthly', format: 'pdf' };
|
|
322
|
+
await api.download('/reports/generate', 'monthly-report.pdf', {}, 'POST', formData);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## 工具函数
|
|
326
|
+
|
|
327
|
+
该库包含用于常见操作的工具函数:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import utils from '@ticatec/restful_service_api/utils';
|
|
331
|
+
|
|
332
|
+
// 将对象转换为查询字符串
|
|
333
|
+
const queryString = utils.toQueryString({ name: 'John', age: 30 });
|
|
334
|
+
// 返回: "name=John&age=30"
|
|
335
|
+
|
|
336
|
+
// 将 URL 与参数组合
|
|
337
|
+
const fullUrl = utils.combineUrl('/api/users', { page: 1, limit: 10 });
|
|
338
|
+
// 返回: "/api/users?page=1&limit=10"
|
|
339
|
+
|
|
340
|
+
// 生成 HTTP 请求选项
|
|
341
|
+
const options = utils.generateRequestOptions('POST', { id: 1 }, { name: 'John' });
|
|
342
|
+
// 返回: { method: 'POST', headers: {}, params: { id: 1 }, data: { name: 'John' } }
|
|
343
|
+
|
|
344
|
+
// 清理参数(移除 null、undefined、空字符串,修剪字符串值)
|
|
345
|
+
const cleanedParams = utils.cleanParams({ name: ' John ', age: null, email: '' });
|
|
346
|
+
// 返回: { name: 'John' }
|
|
347
|
+
|
|
348
|
+
// 函数工具
|
|
349
|
+
utils.invokeFunction(callback, arg1, arg2); // 安全地调用函数(如果存在)
|
|
350
|
+
const isFunc = utils.isFunction(someValue); // 检查值是否为函数
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 可用的工具函数
|
|
354
|
+
|
|
355
|
+
- **`toQueryString(obj)`**: 将对象转换为 URL 查询字符串
|
|
356
|
+
- **`combineUrl(url, params)`**: 将 URL 与查询参数组合
|
|
357
|
+
- **`generateRequestOptions(method, params, data)`**: 生成 HTTP 请求选项
|
|
358
|
+
- **`cleanParams(params)`**: 通过移除 null/空值和修剪字符串来清理参数
|
|
359
|
+
- **`isFunction(value)`**: 检查值是否为函数
|
|
360
|
+
- **`invokeFunction(func, ...args)`**: 安全地调用函数(如果存在)
|
|
361
|
+
|
|
362
|
+
## 高级用法
|
|
363
|
+
|
|
364
|
+
### 带认证的完整示例
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// 设置认证拦截器
|
|
368
|
+
const authInterceptor: PreInterceptor = (method, url) => ({
|
|
369
|
+
headers: {
|
|
370
|
+
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
|
371
|
+
'Accept': 'application/json'
|
|
372
|
+
},
|
|
373
|
+
timeout: 10000
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 设置响应处理拦截器
|
|
377
|
+
const responseInterceptor: PostInterceptor = async (data) => {
|
|
378
|
+
// 处理通用响应格式
|
|
379
|
+
if (data.success === false) {
|
|
380
|
+
throw new Error(data.message);
|
|
381
|
+
}
|
|
382
|
+
return data.data || data;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const api = new FetchRestService(
|
|
386
|
+
'https://api.example.com',
|
|
387
|
+
authInterceptor,
|
|
388
|
+
responseInterceptor
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// 现在所有请求都会自动包含认证头
|
|
392
|
+
const profile = await api.get('/user/profile');
|
|
393
|
+
const updated = await api.put('/user/profile', { name: '新名称' });
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 贡献
|
|
397
|
+
|
|
398
|
+
欢迎贡献代码!请随时提交 Pull Request。
|
|
399
|
+
|
|
400
|
+
## 许可证
|
|
401
|
+
|
|
402
|
+
MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
|
|
403
|
+
|
|
404
|
+
## 作者
|
|
405
|
+
|
|
406
|
+
Henry Feng
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
更多示例和高级用法,请查看[文档](https://github.com/ticatec/restful_service_api)。
|
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# @ticatec/restful_service_api
|
|
2
|
+
|
|
3
|
+
[中文版](README-CN.md) | English
|
|
4
|
+
|
|
5
|
+
A lightweight TypeScript RESTful API client for browsers with comprehensive error handling and interceptor support.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Lightweight**: Zero dependencies, built for modern browsers
|
|
10
|
+
- 🔧 **TypeScript Support**: Full type safety with TypeScript definitions
|
|
11
|
+
- 🛡️ **Error Handling**: Built-in error handling with custom ApiError class
|
|
12
|
+
- ⚡ **Interceptors**: Pre and post request interceptors for authentication and data processing
|
|
13
|
+
- 🎯 **Flexible**: Support for custom headers, timeouts, and data processors
|
|
14
|
+
- 🌐 **Browser-First**: Designed specifically for frontend applications
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ticatec/restful_service_api
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import RestService from '@ticatec/restful_service_api';
|
|
26
|
+
|
|
27
|
+
// Your implementation of RestService interface
|
|
28
|
+
class MyApiClient implements RestService {
|
|
29
|
+
async get(url: string, params?: any) {
|
|
30
|
+
// Implementation here
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async post(url: string, data: any, params?: any, contentType?: string) {
|
|
34
|
+
// Implementation here
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ... other methods
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const api = new MyApiClient();
|
|
41
|
+
|
|
42
|
+
// Make requests
|
|
43
|
+
const users = await api.get('/users');
|
|
44
|
+
const newUser = await api.post('/users', { name: 'John Doe' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API Reference
|
|
48
|
+
|
|
49
|
+
### RestService Interface
|
|
50
|
+
|
|
51
|
+
The main interface that defines the contract for REST operations:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface RestService {
|
|
55
|
+
get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
|
|
56
|
+
post(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
57
|
+
put(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
58
|
+
del(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
59
|
+
upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
60
|
+
asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress>;
|
|
61
|
+
download(url: string, filename: string, params: any, method?: string, formData?: any): Promise<any>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Methods
|
|
66
|
+
|
|
67
|
+
#### `get(url, params?, dataProcessor?)`
|
|
68
|
+
- **url**: The endpoint URL
|
|
69
|
+
- **params**: Query parameters (optional)
|
|
70
|
+
- **dataProcessor**: Function to process response data (optional)
|
|
71
|
+
|
|
72
|
+
#### `post(url, data, params?, contentType?, dataProcessor?)`
|
|
73
|
+
- **url**: The endpoint URL
|
|
74
|
+
- **data**: Request payload
|
|
75
|
+
- **params**: Query parameters (optional)
|
|
76
|
+
- **contentType**: Content-Type header (optional, defaults to application/json)
|
|
77
|
+
- **dataProcessor**: Function to process response data (optional)
|
|
78
|
+
|
|
79
|
+
#### `put(url, data, params?, contentType?, dataProcessor?)`
|
|
80
|
+
Similar to POST but for update operations.
|
|
81
|
+
|
|
82
|
+
#### `del(url, data, params?, contentType?, dataProcessor?)`
|
|
83
|
+
For delete operations with optional request body.
|
|
84
|
+
|
|
85
|
+
#### `upload(url, params, file, fileKey?, dataProcessor?)`
|
|
86
|
+
- **url**: The upload endpoint URL
|
|
87
|
+
- **params**: Additional parameters for the upload request
|
|
88
|
+
- **file**: The File object to upload
|
|
89
|
+
- **fileKey**: Optional form field name for the file (defaults to 'file')
|
|
90
|
+
- **dataProcessor**: Function to process response data (optional)
|
|
91
|
+
|
|
92
|
+
#### `asyncUpload(url, params, file, callback, fileKey?)`
|
|
93
|
+
- **url**: The upload endpoint URL
|
|
94
|
+
- **params**: Additional parameters for the upload request
|
|
95
|
+
- **file**: The File object to upload
|
|
96
|
+
- **callback**: Upload callback object with progress, error, and completion handlers
|
|
97
|
+
- **fileKey**: Optional form field name for the file (defaults to 'file')
|
|
98
|
+
- **Returns**: Promise that resolves to UploadProgress object for cancellation
|
|
99
|
+
|
|
100
|
+
#### `download(url, filename, params, method?, formData?)`
|
|
101
|
+
- **url**: The download endpoint URL
|
|
102
|
+
- **filename**: The name to save the downloaded file
|
|
103
|
+
- **params**: Download request parameters
|
|
104
|
+
- **method**: Optional HTTP method (defaults to GET)
|
|
105
|
+
- **formData**: Optional form data for POST downloads
|
|
106
|
+
|
|
107
|
+
### Error Handling
|
|
108
|
+
|
|
109
|
+
The library includes a custom `ApiError` class for handling API errors:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { ApiError } from '@ticatec/restful_service_api';
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const result = await api.get('/users');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof ApiError) {
|
|
118
|
+
console.log('Status:', error.status);
|
|
119
|
+
console.log('Code:', error.code);
|
|
120
|
+
console.log('Details:', error.details);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Types and Interceptors
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
PreInterceptor,
|
|
130
|
+
PostInterceptor,
|
|
131
|
+
ErrorHandler,
|
|
132
|
+
DataProcessor
|
|
133
|
+
} from '@ticatec/restful_service_api';
|
|
134
|
+
|
|
135
|
+
// Pre-interceptor for adding authentication headers
|
|
136
|
+
const preInterceptor: PreInterceptor = (method: string, url: string) => {
|
|
137
|
+
return {
|
|
138
|
+
headers: {
|
|
139
|
+
'Authorization': 'Bearer ' + getToken(),
|
|
140
|
+
'X-Request-ID': generateRequestId()
|
|
141
|
+
},
|
|
142
|
+
timeout: 30000
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Post-interceptor for response processing
|
|
147
|
+
const postInterceptor: PostInterceptor = async (data: any) => {
|
|
148
|
+
// Process response data
|
|
149
|
+
return data;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Error handler
|
|
153
|
+
const errorHandler: ErrorHandler = (error: Error) => {
|
|
154
|
+
console.error('API Error:', error);
|
|
155
|
+
return true; // Return true if error is handled
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Data processor
|
|
159
|
+
const dataProcessor: DataProcessor = (data: any) => {
|
|
160
|
+
// Transform response data
|
|
161
|
+
return data.results || data;
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Content Types
|
|
166
|
+
|
|
167
|
+
Pre-defined content type constants:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import {
|
|
171
|
+
CONTENT_TYPE_NAME,
|
|
172
|
+
TYPE_JSON,
|
|
173
|
+
TYPE_HTML,
|
|
174
|
+
TYPE_TEXT
|
|
175
|
+
} from '@ticatec/restful_service_api';
|
|
176
|
+
|
|
177
|
+
// Usage
|
|
178
|
+
await api.post('/upload', data, {}, TYPE_JSON);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Implementation Example
|
|
182
|
+
|
|
183
|
+
Here's a complete example implementation using the native fetch API:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import RestService, {
|
|
187
|
+
ApiError,
|
|
188
|
+
PreInterceptor,
|
|
189
|
+
PostInterceptor,
|
|
190
|
+
TYPE_JSON
|
|
191
|
+
} from '@ticatec/restful_service_api';
|
|
192
|
+
|
|
193
|
+
class FetchRestService implements RestService {
|
|
194
|
+
private baseURL: string;
|
|
195
|
+
private preInterceptor?: PreInterceptor;
|
|
196
|
+
private postInterceptor?: PostInterceptor;
|
|
197
|
+
|
|
198
|
+
constructor(baseURL: string, preInterceptor?: PreInterceptor, postInterceptor?: PostInterceptor) {
|
|
199
|
+
this.baseURL = baseURL;
|
|
200
|
+
this.preInterceptor = preInterceptor;
|
|
201
|
+
this.postInterceptor = postInterceptor;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any> {
|
|
205
|
+
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
|
|
206
|
+
return this.request('GET', url + queryString, null, undefined, dataProcessor);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async post(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
210
|
+
return this.request('POST', url, data, contentType, dataProcessor);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async put(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
214
|
+
return this.request('PUT', url, data, contentType, dataProcessor);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async del(url: string, data: any, params?: any, contentType: string = TYPE_JSON, dataProcessor?: DataProcessor): Promise<any> {
|
|
218
|
+
return this.request('DELETE', url, data, contentType, dataProcessor);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async request(method: string, url: string, body?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any> {
|
|
222
|
+
const fullUrl = this.baseURL + url;
|
|
223
|
+
|
|
224
|
+
// Apply pre-interceptor
|
|
225
|
+
const interceptorResult = this.preInterceptor?.(method, fullUrl) || { headers: {} };
|
|
226
|
+
|
|
227
|
+
const headers = {
|
|
228
|
+
...interceptorResult.headers,
|
|
229
|
+
...(contentType && { 'Content-Type': contentType })
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(fullUrl, {
|
|
234
|
+
method,
|
|
235
|
+
headers,
|
|
236
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
237
|
+
signal: interceptorResult.timeout ? AbortSignal.timeout(interceptorResult.timeout) : undefined
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
const errorData = await response.json().catch(() => ({}));
|
|
242
|
+
throw new ApiError(response.status, errorData);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let data = await response.json();
|
|
246
|
+
|
|
247
|
+
// Apply post-interceptor
|
|
248
|
+
if (this.postInterceptor) {
|
|
249
|
+
data = await this.postInterceptor(data);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Apply data processor
|
|
253
|
+
if (dataProcessor) {
|
|
254
|
+
data = dataProcessor(data);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return data;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error instanceof ApiError) {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
throw new ApiError(0, { code: 'NETWORK_ERROR', message: error.message });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Usage
|
|
268
|
+
const api = new FetchRestService('https://api.example.com');
|
|
269
|
+
const users = await api.get('/users');
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### File Upload and Download Examples
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import RestService, {
|
|
276
|
+
UploadCallback,
|
|
277
|
+
UploadProgress
|
|
278
|
+
} from '@ticatec/restful_service_api';
|
|
279
|
+
|
|
280
|
+
// Simple file upload
|
|
281
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
282
|
+
const file = fileInput.files[0];
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const result = await api.upload('/upload', { userId: 123 }, file);
|
|
286
|
+
console.log('Upload successful:', result);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Upload failed:', error);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Async upload with progress tracking
|
|
292
|
+
const uploadCallback: UploadCallback = {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
progressUpdate: (uploadedBytes: number) => {
|
|
295
|
+
console.log(`Uploaded: ${uploadedBytes} bytes`);
|
|
296
|
+
},
|
|
297
|
+
handleError: (error: Error) => {
|
|
298
|
+
console.error('Upload error:', error);
|
|
299
|
+
},
|
|
300
|
+
onCompleted: (data: any) => {
|
|
301
|
+
console.log('Upload completed:', data);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const uploadProgress = await api.asyncUpload('/upload', { userId: 123 }, file, uploadCallback);
|
|
306
|
+
|
|
307
|
+
// Cancel upload if needed
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
uploadProgress.cancel();
|
|
310
|
+
}, 5000);
|
|
311
|
+
|
|
312
|
+
// File download
|
|
313
|
+
try {
|
|
314
|
+
await api.download('/files/document.pdf', 'my-document.pdf', { userId: 123 });
|
|
315
|
+
console.log('Download completed');
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('Download failed:', error);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// POST download with form data
|
|
321
|
+
const formData = { reportType: 'monthly', format: 'pdf' };
|
|
322
|
+
await api.download('/reports/generate', 'monthly-report.pdf', {}, 'POST', formData);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Utility Functions
|
|
326
|
+
|
|
327
|
+
The library includes utility functions for common operations:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import utils from '@ticatec/restful_service_api/utils';
|
|
331
|
+
|
|
332
|
+
// Convert object to query string
|
|
333
|
+
const queryString = utils.toQueryString({ name: 'John', age: 30 });
|
|
334
|
+
// Returns: "name=John&age=30"
|
|
335
|
+
|
|
336
|
+
// Combine URL with parameters
|
|
337
|
+
const fullUrl = utils.combineUrl('/api/users', { page: 1, limit: 10 });
|
|
338
|
+
// Returns: "/api/users?page=1&limit=10"
|
|
339
|
+
|
|
340
|
+
// Generate HTTP request options
|
|
341
|
+
const options = utils.generateRequestOptions('POST', { id: 1 }, { name: 'John' });
|
|
342
|
+
// Returns: { method: 'POST', headers: {}, params: { id: 1 }, data: { name: 'John' } }
|
|
343
|
+
|
|
344
|
+
// Clean parameters (removes null, undefined, empty strings, trims values)
|
|
345
|
+
const cleanedParams = utils.cleanParams({ name: ' John ', age: null, email: '' });
|
|
346
|
+
// Returns: { name: 'John' }
|
|
347
|
+
|
|
348
|
+
// Function utilities
|
|
349
|
+
utils.invokeFunction(callback, arg1, arg2); // Safely invoke function if it exists
|
|
350
|
+
const isFunc = utils.isFunction(someValue); // Check if value is a function
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Available Utility Functions
|
|
354
|
+
|
|
355
|
+
- **`toQueryString(obj)`**: Converts an object to URL query string
|
|
356
|
+
- **`combineUrl(url, params)`**: Combines URL with query parameters
|
|
357
|
+
- **`generateRequestOptions(method, params, data)`**: Generates HTTP request options
|
|
358
|
+
- **`cleanParams(params)`**: Cleans parameters by removing null/empty values and trimming strings
|
|
359
|
+
- **`isFunction(value)`**: Checks if a value is a function
|
|
360
|
+
- **`invokeFunction(func, ...args)`**: Safely invokes a function if it exists
|
|
361
|
+
|
|
362
|
+
## Contributing
|
|
363
|
+
|
|
364
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
369
|
+
|
|
370
|
+
## Author
|
|
371
|
+
|
|
372
|
+
Henry Feng
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
For more examples and advanced usage, please check the [documentation](https://github.com/ticatec/restful_service_api).
|
package/dist/ApiError.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default class ApiError extends Error {
|
|
2
|
+
get code() {
|
|
3
|
+
return this._code;
|
|
4
|
+
}
|
|
5
|
+
get details() {
|
|
6
|
+
return this._details;
|
|
7
|
+
}
|
|
8
|
+
get status() {
|
|
9
|
+
return this._status;
|
|
10
|
+
}
|
|
11
|
+
constructor(status, err) {
|
|
12
|
+
super(err.code);
|
|
13
|
+
this.name = this.constructor.name;
|
|
14
|
+
this._code = err.code;
|
|
15
|
+
this._status = status;
|
|
16
|
+
this._details = err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import UploadCallback, { UploadProgress } from "./UploadCallback";
|
|
2
|
+
export interface PreInterceptorResult {
|
|
3
|
+
/**
|
|
4
|
+
* 待增加的headers
|
|
5
|
+
*/
|
|
6
|
+
headers: any;
|
|
7
|
+
/**
|
|
8
|
+
* 访问过期时间
|
|
9
|
+
*/
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}
|
|
12
|
+
export type DataProcessor = (data: any) => any;
|
|
13
|
+
export type PreInterceptor = (method: string, url: string) => PreInterceptorResult;
|
|
14
|
+
export type PostInterceptor = (data: any) => Promise<any>;
|
|
15
|
+
export type ErrorHandler = (ex: Error) => boolean;
|
|
16
|
+
export declare const CONTENT_TYPE_NAME = "Content-Type";
|
|
17
|
+
export declare const TYPE_JSON = "application/json";
|
|
18
|
+
export declare const TYPE_HTML = "text/html";
|
|
19
|
+
export declare const TYPE_TEXT = "text/plain";
|
|
20
|
+
export default interface RestService {
|
|
21
|
+
/**
|
|
22
|
+
* 执行HTTP GET请求获取资源
|
|
23
|
+
* @param url 请求的目标URL路径
|
|
24
|
+
* @param params 查询参数对象,将被转换为URL查询字符串
|
|
25
|
+
* @param dataProcessor 可选的数据处理函数,用于在返回前转换响应数据
|
|
26
|
+
* @returns 返回Promise,解析为服务器响应数据
|
|
27
|
+
*/
|
|
28
|
+
get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* 执行HTTP POST请求创建新资源
|
|
31
|
+
* @param url 请求的目标URL路径
|
|
32
|
+
* @param data 要发送的请求体数据,通常为对象或字符串
|
|
33
|
+
* @param params 可选的查询参数对象,将被转换为URL查询字符串
|
|
34
|
+
* @param contentType 可选的Content-Type头部,默认为application/json
|
|
35
|
+
* @param dataProcessor 可选的数据处理函数,用于在返回前转换响应数据
|
|
36
|
+
* @returns 返回Promise,解析为服务器响应数据
|
|
37
|
+
*/
|
|
38
|
+
post(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
39
|
+
/**
|
|
40
|
+
* 执行HTTP PUT请求更新现有资源
|
|
41
|
+
* @param url 请求的目标URL路径
|
|
42
|
+
* @param data 要发送的请求体数据,用于更新资源
|
|
43
|
+
* @param params 可选的查询参数对象,将被转换为URL查询字符串
|
|
44
|
+
* @param contentType 可选的Content-Type头部,默认为application/json
|
|
45
|
+
* @param dataProcessor 可选的数据处理函数,用于在返回前转换响应数据
|
|
46
|
+
* @returns 返回Promise,解析为服务器响应数据
|
|
47
|
+
*/
|
|
48
|
+
put(url: string, data: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
49
|
+
/**
|
|
50
|
+
* 执行HTTP DELETE请求删除资源
|
|
51
|
+
* @param url 请求的目标URL路径
|
|
52
|
+
* @param data 可选的请求体数据,某些DELETE操作可能需要发送数据
|
|
53
|
+
* @param params 可选的查询参数对象,将被转换为URL查询字符串
|
|
54
|
+
* @param contentType 可选的Content-Type头部,默认为application/json
|
|
55
|
+
* @param dataProcessor 可选的数据处理函数,用于在返回前转换响应数据
|
|
56
|
+
* @returns 返回Promise,解析为服务器响应数据
|
|
57
|
+
*/
|
|
58
|
+
del(url: string, data?: any, params?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
59
|
+
/**
|
|
60
|
+
* 执行同步文件上传操作
|
|
61
|
+
* @param url 上传的目标URL路径
|
|
62
|
+
* @param params 上传请求的参数对象
|
|
63
|
+
* @param file 要上传的文件对象
|
|
64
|
+
* @param fileKey 可选的文件字段名,默认为'file'
|
|
65
|
+
* @param dataProcessor 可选的数据处理函数,用于在返回前转换响应数据
|
|
66
|
+
* @returns 返回Promise,解析为服务器响应数据
|
|
67
|
+
*/
|
|
68
|
+
upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor): Promise<any>;
|
|
69
|
+
/**
|
|
70
|
+
* 执行异步文件上传操作,支持进度监控和取消功能
|
|
71
|
+
* @param url 上传的目标URL路径
|
|
72
|
+
* @param params 上传请求的参数对象
|
|
73
|
+
* @param file 要上传的文件对象
|
|
74
|
+
* @param callback 上传过程中的回调函数,包含进度更新、错误处理等
|
|
75
|
+
* @param fileKey 可选的文件字段名,默认为'file'
|
|
76
|
+
* @returns 返回Promise,解析为UploadProgress对象,可用于取消上传
|
|
77
|
+
*/
|
|
78
|
+
asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress>;
|
|
79
|
+
/**
|
|
80
|
+
* 执行文件下载操作
|
|
81
|
+
* @param url 下载文件的URL路径
|
|
82
|
+
* @param filename 保存的文件名
|
|
83
|
+
* @param params 下载请求的参数对象
|
|
84
|
+
* @param method 可选的HTTP方法,默认为GET
|
|
85
|
+
* @param formData 可选的表单数据,用于POST下载
|
|
86
|
+
* @returns 返回Promise,解析为下载结果
|
|
87
|
+
*/
|
|
88
|
+
download(url: string, filename: string, params: any, method?: string, formData?: any): Promise<any>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上传进度更新回调函数类型
|
|
3
|
+
* @param uploadBytes 已上传的字节数
|
|
4
|
+
*/
|
|
5
|
+
export type ProgressUpdate = (uploadBytes: number) => void;
|
|
6
|
+
/**
|
|
7
|
+
* 上传错误处理回调函数类型
|
|
8
|
+
* @param e 上传过程中发生的错误对象
|
|
9
|
+
*/
|
|
10
|
+
export type ErrorHandler = (e: Error) => void;
|
|
11
|
+
/**
|
|
12
|
+
* 上传完成回调函数类型
|
|
13
|
+
* @param data 服务器返回的响应数据
|
|
14
|
+
*/
|
|
15
|
+
export type OnCompleted = (data: any) => void;
|
|
16
|
+
/**
|
|
17
|
+
* 上传进度控制接口,提供上传过程中的控制功能
|
|
18
|
+
*/
|
|
19
|
+
export interface UploadProgress {
|
|
20
|
+
/**
|
|
21
|
+
* 取消正在进行的上传操作
|
|
22
|
+
*/
|
|
23
|
+
cancel: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 文件上传完成后的回调函数类型
|
|
27
|
+
* @param url 上传后文件的访问URL
|
|
28
|
+
* @param thumbnail 可选的缩略图URL
|
|
29
|
+
*/
|
|
30
|
+
export type OnUploaded = (url: string, thumbnail?: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* 文件上传回调接口,定义上传过程中的各种回调函数
|
|
33
|
+
*/
|
|
34
|
+
export default interface UploadCallback {
|
|
35
|
+
/**
|
|
36
|
+
* HTTP请求方法,默认是POST,特殊情况允许PUT等方法
|
|
37
|
+
*/
|
|
38
|
+
method: string;
|
|
39
|
+
/**
|
|
40
|
+
* 可选的上传进度更新回调函数
|
|
41
|
+
*/
|
|
42
|
+
progressUpdate?: ProgressUpdate;
|
|
43
|
+
/**
|
|
44
|
+
* 可选的错误处理回调函数
|
|
45
|
+
*/
|
|
46
|
+
handleError?: ErrorHandler;
|
|
47
|
+
/**
|
|
48
|
+
* 必需的上传完成回调函数
|
|
49
|
+
*/
|
|
50
|
+
onCompleted: OnCompleted;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ApiError from "./ApiError";
|
|
2
|
+
import RestService from "./RestService";
|
|
3
|
+
import { ErrorHandler, PostInterceptor, PreInterceptor, DataProcessor } from "./RestService";
|
|
4
|
+
import { PreInterceptorResult } from "./RestService";
|
|
5
|
+
import { CONTENT_TYPE_NAME, TYPE_JSON, TYPE_HTML, TYPE_TEXT } from "./RestService";
|
|
6
|
+
import UploadCallback, { UploadProgress, ProgressUpdate, OnCompleted, OnUploaded } from "./UploadCallback";
|
|
7
|
+
export default RestService;
|
|
8
|
+
export { ApiError };
|
|
9
|
+
export type { ErrorHandler, PostInterceptor, PreInterceptor, DataProcessor };
|
|
10
|
+
export type { PreInterceptorResult };
|
|
11
|
+
export { CONTENT_TYPE_NAME, TYPE_JSON, TYPE_HTML, TYPE_TEXT };
|
|
12
|
+
export { UploadCallback };
|
|
13
|
+
export type { UploadProgress, ProgressUpdate, OnCompleted, OnUploaded };
|
package/dist/index.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for HTTP requests and URL manipulation
|
|
3
|
+
*/
|
|
4
|
+
declare const _default: {
|
|
5
|
+
toQueryString: (obj: Record<string, any>) => string | null;
|
|
6
|
+
combineUrl: (url: string, params: Record<string, any> | null) => string;
|
|
7
|
+
generateRequestOptions: (method: string, params?: any, data?: any) => any;
|
|
8
|
+
isFunction: (fun: any) => fun is Function;
|
|
9
|
+
invokeFunction: (fun: Function | null | undefined, ...args: any[]) => void;
|
|
10
|
+
cleanParams: (params: Record<string, any> | null | undefined) => Record<string, any> | null;
|
|
11
|
+
};
|
|
12
|
+
export default _default;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an object to a URL query string
|
|
3
|
+
*
|
|
4
|
+
* @param obj - Object containing key-value pairs to convert
|
|
5
|
+
* @returns Query string without leading '?' or null if no valid parameters
|
|
6
|
+
*/
|
|
7
|
+
const toQueryString = (obj) => {
|
|
8
|
+
let list = [];
|
|
9
|
+
Object.keys(obj).forEach(key => {
|
|
10
|
+
if (obj[key] != null && obj[key] !== '') {
|
|
11
|
+
list.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
return list.length === 0 ? null : list.join('&');
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Combines a URL with query parameters
|
|
18
|
+
*
|
|
19
|
+
* @param url - Base URL string
|
|
20
|
+
* @param params - Object containing query parameters
|
|
21
|
+
* @returns Complete URL with query string appended
|
|
22
|
+
*/
|
|
23
|
+
const combineUrl = (url, params) => {
|
|
24
|
+
let qs = params == null ? null : toQueryString(params);
|
|
25
|
+
return qs == null ? url : `${url}?${qs}`;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Generates HTTP request options object
|
|
29
|
+
*
|
|
30
|
+
* @param method - HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
31
|
+
* @param params - Optional query parameters
|
|
32
|
+
* @param data - Optional request body data
|
|
33
|
+
* @returns Request options object with method, headers, params, and data
|
|
34
|
+
*/
|
|
35
|
+
const generateRequestOptions = (method, params = null, data = null) => {
|
|
36
|
+
return {
|
|
37
|
+
method: method,
|
|
38
|
+
headers: {},
|
|
39
|
+
params,
|
|
40
|
+
data
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Checks if a value is a function
|
|
45
|
+
*
|
|
46
|
+
* @param fun - Value to check
|
|
47
|
+
* @returns True if the value is a function, false otherwise
|
|
48
|
+
*/
|
|
49
|
+
const isFunction = (fun) => {
|
|
50
|
+
return fun != null && fun instanceof Function;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Safely invokes a function if it exists and is callable
|
|
54
|
+
*
|
|
55
|
+
* @param fun - Function to invoke (can be null/undefined)
|
|
56
|
+
* @param args - Arguments to pass to the function
|
|
57
|
+
*/
|
|
58
|
+
const invokeFunction = (fun, ...args) => {
|
|
59
|
+
if (isFunction(fun)) {
|
|
60
|
+
fun(...args);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Cleans parameters object by removing null values, empty strings, and trimming string values
|
|
65
|
+
*
|
|
66
|
+
* @param params - Object containing parameters to clean
|
|
67
|
+
* @returns New object with cleaned parameters, or null if no valid parameters remain
|
|
68
|
+
*/
|
|
69
|
+
const cleanParams = (params) => {
|
|
70
|
+
if (params == null) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const cleaned = {};
|
|
74
|
+
let hasValidParams = false;
|
|
75
|
+
Object.keys(params).forEach(key => {
|
|
76
|
+
let value = params[key];
|
|
77
|
+
// Skip null and undefined values
|
|
78
|
+
if (value == null) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Trim string values and skip empty strings
|
|
82
|
+
if (typeof value === 'string') {
|
|
83
|
+
value = value.trim();
|
|
84
|
+
if (value === '') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
cleaned[key] = value;
|
|
89
|
+
hasValidParams = true;
|
|
90
|
+
});
|
|
91
|
+
return hasValidParams ? cleaned : null;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Utility functions for HTTP requests and URL manipulation
|
|
95
|
+
*/
|
|
96
|
+
export default {
|
|
97
|
+
toQueryString,
|
|
98
|
+
combineUrl,
|
|
99
|
+
generateRequestOptions,
|
|
100
|
+
isFunction,
|
|
101
|
+
invokeFunction,
|
|
102
|
+
cleanParams
|
|
103
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ticatec/restful_service_api",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "A lightweight TypeScript RESTful API client for browsers with error handling.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./ApiError": {
|
|
15
|
+
"import": "./dist/ApiError.js",
|
|
16
|
+
"require": "./dist/ApiError.js",
|
|
17
|
+
"types": "./dist/ApiError.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./utils": {
|
|
20
|
+
"import": "./dist/utils.js",
|
|
21
|
+
"require": "./dist/utils.js",
|
|
22
|
+
"types": "./dist/utils.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"prepare": "npm run clean && npm run build",
|
|
29
|
+
"publish:public": "npm publish --access public"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"rest",
|
|
33
|
+
"typescript",
|
|
34
|
+
"axios",
|
|
35
|
+
"browser",
|
|
36
|
+
"frontend",
|
|
37
|
+
"api-client"
|
|
38
|
+
],
|
|
39
|
+
"author": "Henry Feng",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/ticatec/rest_service_api.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/ticatec/rest_service_api/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/ticatec/rest_service_api/tree/main/README.md",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"typescript": "^5.4.5"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["DOM", "ES2019"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|