@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 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).
@@ -0,0 +1,9 @@
1
+ export default class ApiError extends Error {
2
+ private _code;
3
+ private _details;
4
+ private _status;
5
+ get code(): any;
6
+ get details(): any;
7
+ get status(): any;
8
+ constructor(status: any, err: any);
9
+ }
@@ -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,4 @@
1
+ export const CONTENT_TYPE_NAME = 'Content-Type';
2
+ export const TYPE_JSON = "application/json";
3
+ export const TYPE_HTML = "text/html";
4
+ export const TYPE_TEXT = "text/plain";
@@ -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 {};
@@ -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
@@ -0,0 +1,4 @@
1
+ import ApiError from "./ApiError";
2
+ import { CONTENT_TYPE_NAME, TYPE_JSON, TYPE_HTML, TYPE_TEXT } from "./RestService";
3
+ export { ApiError };
4
+ export { CONTENT_TYPE_NAME, TYPE_JSON, TYPE_HTML, TYPE_TEXT };
@@ -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
+ }