@ticatec/restful_service_api 0.6.1 → 0.8.0

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-CN.md CHANGED
@@ -18,6 +18,60 @@
18
18
  - 🌐 **浏览器优先**: 专为前端应用程序设计
19
19
  - ✨ **PATCH 支持**: 完整支持 HTTP PATCH 方法进行部分更新
20
20
 
21
+ ## ⚠️ v0.8.0 重大变更
22
+
23
+ **`ErrorHandler` 已变更为返回 `Promise`(支持异步处理)。**
24
+
25
+ ### 对您的影响
26
+
27
+ - `ErrorHandler` 函数类型现为 `(ex: Error) => Promise<boolean | void>`。
28
+ - 错误处理器现在应定义为 `async` 异步函数或返回 `Promise`。
29
+ - `UploadCallback.handleError` 统一采用 Promise 异步的 `ErrorHandler` 定义。
30
+
31
+ ### 迁移指南
32
+
33
+ **之前(v0.7.x 及更早版本):**
34
+ ```typescript
35
+ const errorHandler: ErrorHandler = (error: Error) => {
36
+ console.error('API 错误:', error);
37
+ return true;
38
+ };
39
+ ```
40
+
41
+ **之后(v0.8.0+):**
42
+ ```typescript
43
+ const errorHandler: ErrorHandler = async (error: Error) => {
44
+ console.error('API 错误:', error);
45
+ return true;
46
+ };
47
+ ```
48
+
49
+ ## ⚠️ v0.7.0 重大变更
50
+
51
+ **`RestfulOptions.contentType` 已被移除,改为使用通用的 `headers` 字段。**
52
+
53
+ ### 对您的影响
54
+
55
+ - `RestfulOptions`(用于 `post`/`put`/`del`/`patch`)不再识别 `contentType` 字段,
56
+ 请把 `{ contentType: 'multipart/form-data' }` 改为
57
+ `{ headers: { 'Content-Type': 'multipart/form-data' } }`。
58
+ - `get()` 新增了一个可选的第 4 个参数:`get(url, params?, dataProcessor?, headers?)`。
59
+ 已有的按位置调用方式(`get(url)`、`get(url, params)`、`get(url, params, dataProcessor)`)不受影响。
60
+ - 这也让单次请求携带任意自定义请求头成为可能(不仅限于 Content-Type),例如通过
61
+ `If-None-Match` 实现条件 GET —— 参见"自定义请求头(例如条件请求)"一节。
62
+
63
+ ### 迁移指南
64
+
65
+ **之前(v0.6.x 及更早版本):**
66
+ ```typescript
67
+ await api.post('/upload', formData, { contentType: 'multipart/form-data' });
68
+ ```
69
+
70
+ **之后(v0.7.0+):**
71
+ ```typescript
72
+ await api.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } });
73
+ ```
74
+
21
75
  ## ⚠️ v0.5.0 重大变更
22
76
 
23
77
  **从 0.5.0 版本开始,此包已迁移到 ESM (ECMAScript Modules) 格式。**
@@ -59,7 +113,7 @@ import RestService, { FileService } from '@ticatec/restful_service_api';
59
113
 
60
114
  // 实现 RestService 接口
61
115
  class MyApiClient implements RestService {
62
- async get(url: string, params?: any, dataProcessor?: DataProcessor) {
116
+ async get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>) {
63
117
  // 在这里实现
64
118
  }
65
119
 
@@ -109,7 +163,7 @@ const result = await fileApi.upload('/upload', { userId: 123 }, file);
109
163
 
110
164
  ```typescript
111
165
  interface RestService {
112
- get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
166
+ get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>): Promise<any>;
113
167
  post(url: string, data?: any, options?: RestfulOptions): Promise<any>;
114
168
  put(url: string, data?: any, options?: RestfulOptions): Promise<any>;
115
169
  patch(url: string, data?: any, options?: RestfulOptions): Promise<any>;
@@ -131,11 +185,12 @@ interface FileService {
131
185
 
132
186
  ### RestService 方法
133
187
 
134
- #### `get(url, params?, dataProcessor?)`
188
+ #### `get(url, params?, dataProcessor?, headers?)`
135
189
  执行 HTTP GET 请求获取资源。
136
190
  - **url**: 接口端点 URL
137
191
  - **params**: 查询参数(可选)
138
192
  - **dataProcessor**: 处理响应数据的函数(可选)
193
+ - **headers**: 本次调用的自定义请求头(可选),例如条件请求:`{ 'If-None-Match': contentHash }`
139
194
 
140
195
  #### `post(url, data?, options?)`
141
196
  执行 HTTP POST 请求创建新资源。
@@ -143,7 +198,8 @@ interface FileService {
143
198
  - **data**: 请求载荷(可选)
144
199
  - **options**: 可选的配置对象,包含:
145
200
  - **params**: 查询参数(可选)
146
- - **contentType**: Content-Type 请求头(可选,默认为 application/json)
201
+ - **headers**: 本次调用的自定义请求头(可选)。可用于覆盖默认的 `Content-Type`
202
+ (`application/json`),例如 `{ 'Content-Type': 'multipart/form-data' }`
147
203
  - **dataProcessor**: 处理响应数据的函数(可选)
148
204
 
149
205
  #### `put(url, data?, options?)`
@@ -152,7 +208,7 @@ interface FileService {
152
208
  - **data**: 请求载荷(可选)
153
209
  - **options**: 可选的配置对象,包含:
154
210
  - **params**: 查询参数(可选)
155
- - **contentType**: Content-Type 请求头(可选,默认为 application/json)
211
+ - **headers**: 本次调用的自定义请求头(可选)
156
212
  - **dataProcessor**: 处理响应数据的函数(可选)
157
213
 
158
214
  #### `patch(url, data?, options?)`
@@ -161,7 +217,7 @@ interface FileService {
161
217
  - **data**: 包含要更新字段的请求载荷(可选)
162
218
  - **options**: 可选的配置对象,包含:
163
219
  - **params**: 查询参数(可选)
164
- - **contentType**: Content-Type 请求头(可选,默认为 application/json)
220
+ - **headers**: 本次调用的自定义请求头(可选)
165
221
  - **dataProcessor**: 处理响应数据的函数(可选)
166
222
 
167
223
  #### `del(url, data?, options?)`
@@ -170,7 +226,7 @@ interface FileService {
170
226
  - **data**: 请求体数据(可选)
171
227
  - **options**: 可选的配置对象,包含:
172
228
  - **params**: 查询参数(可选)
173
- - **contentType**: Content-Type 请求头(可选,默认为 application/json)
229
+ - **headers**: 本次调用的自定义请求头(可选)
174
230
  - **dataProcessor**: 处理响应数据的函数(可选)
175
231
 
176
232
  ### FileService 方法
@@ -232,7 +288,7 @@ import {
232
288
  // RestfulOptions 用于配置请求
233
289
  const options: RestfulOptions = {
234
290
  params: { page: 1, limit: 10 },
235
- contentType: 'application/json',
291
+ headers: { 'Content-Type': 'application/json' },
236
292
  dataProcessor: (data: any) => data.results || data
237
293
  };
238
294
 
@@ -254,7 +310,7 @@ const postInterceptor: PostInterceptor = async (data: any) => {
254
310
  };
255
311
 
256
312
  // 错误处理器
257
- const errorHandler: ErrorHandler = (error: Error) => {
313
+ const errorHandler: ErrorHandler = async (error: Error) => {
258
314
  console.error('API 错误:', error);
259
315
  return true; // 返回 true 表示错误已处理
260
316
  };
@@ -278,8 +334,34 @@ import {
278
334
  TYPE_TEXT
279
335
  } from '@ticatec/restful_service_api';
280
336
 
281
- // 使用示例
282
- await api.post('/upload', data, {}, { contentType: TYPE_JSON });
337
+ // 使用示例 - 通过 RestfulOptions.headers 显式设置请求头
338
+ await api.post('/upload', data, { headers: { [CONTENT_TYPE_NAME]: TYPE_JSON } });
339
+ ```
340
+
341
+ ### 自定义请求头(例如条件请求)
342
+
343
+ `RestfulOptions.headers`(以及 `get()` 的第 4 个参数 `headers`)允许为单次请求附加自定义请求头。
344
+ 一个常见场景是基于本地缓存的 ETag/hash 发起条件 GET 请求:
345
+
346
+ ```typescript
347
+ // 第一次请求:还没有缓存的 hash
348
+ const pack = await api.get('/messages/node1');
349
+ localStorage.setItem('node1-hash', pack.contentHash);
350
+
351
+ // 之后:把缓存的 hash 作为 If-None-Match 发送
352
+ const cachedHash = localStorage.getItem('node1-hash');
353
+ try {
354
+ const updated = await api.get('/messages/node1', undefined, undefined, {
355
+ 'If-None-Match': cachedHash ?? ''
356
+ });
357
+ // 服务端返回 200 —— 内容有变化,使用 `updated`
358
+ } catch (error) {
359
+ if (error instanceof ApiError && Number(error.status) === 304) {
360
+ // 服务端返回 304 Not Modified —— 本地缓存仍然有效
361
+ } else {
362
+ throw error;
363
+ }
364
+ }
283
365
  ```
284
366
 
285
367
  ## 实现示例
@@ -309,48 +391,45 @@ class FetchRestService implements RestService {
309
391
  this.postInterceptor = postInterceptor;
310
392
  }
311
393
 
312
- async get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any> {
394
+ async get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>): Promise<any> {
313
395
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
314
- return this.request('GET', url + queryString, null, undefined, dataProcessor);
396
+ return this.request('GET', url + queryString, null, headers, dataProcessor);
315
397
  }
316
398
 
317
399
  async post(url: string, data?: any, options?: RestfulOptions): Promise<any> {
318
- const contentType = options?.contentType || TYPE_JSON;
319
400
  const params = options?.params;
320
401
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
321
- return this.request('POST', url + queryString, data, contentType, options?.dataProcessor);
402
+ return this.request('POST', url + queryString, data, options?.headers, options?.dataProcessor);
322
403
  }
323
404
 
324
405
  async put(url: string, data?: any, options?: RestfulOptions): Promise<any> {
325
- const contentType = options?.contentType || TYPE_JSON;
326
406
  const params = options?.params;
327
407
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
328
- return this.request('PUT', url + queryString, data, contentType, options?.dataProcessor);
408
+ return this.request('PUT', url + queryString, data, options?.headers, options?.dataProcessor);
329
409
  }
330
410
 
331
411
  async patch(url: string, data?: any, options?: RestfulOptions): Promise<any> {
332
- const contentType = options?.contentType || TYPE_JSON;
333
412
  const params = options?.params;
334
413
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
335
- return this.request('PATCH', url + queryString, data, contentType, options?.dataProcessor);
414
+ return this.request('PATCH', url + queryString, data, options?.headers, options?.dataProcessor);
336
415
  }
337
416
 
338
417
  async del(url: string, data?: any, options?: RestfulOptions): Promise<any> {
339
- const contentType = options?.contentType || TYPE_JSON;
340
418
  const params = options?.params;
341
419
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
342
- return this.request('DELETE', url + queryString, data, contentType, options?.dataProcessor);
420
+ return this.request('DELETE', url + queryString, data, options?.headers, options?.dataProcessor);
343
421
  }
344
422
 
345
- private async request(method: string, url: string, body?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any> {
423
+ private async request(method: string, url: string, body?: any, customHeaders?: Record<string, string>, dataProcessor?: DataProcessor): Promise<any> {
346
424
  const fullUrl = this.baseURL + url;
347
425
 
348
426
  // 应用请求前拦截器
349
427
  const interceptorResult = this.preInterceptor?.(method, fullUrl) || { headers: {} };
350
428
 
351
429
  const headers = {
430
+ 'Content-Type': TYPE_JSON,
352
431
  ...interceptorResult.headers,
353
- ...(contentType && { 'Content-Type': contentType })
432
+ ...customHeaders
354
433
  };
355
434
 
356
435
  try {
@@ -447,18 +526,18 @@ class FetchFileService implements FileService {
447
526
  }
448
527
  });
449
528
 
450
- xhr.addEventListener('load', () => {
529
+ xhr.addEventListener('load', async () => {
451
530
  if (xhr.status >= 200 && xhr.status < 300) {
452
531
  const data = JSON.parse(xhr.responseText);
453
532
  callback.onCompleted(data);
454
533
  } else if (callback.handleError) {
455
- callback.handleError(new Error(`上传失败: ${xhr.statusText}`));
534
+ await callback.handleError(new Error(`上传失败: ${xhr.statusText}`));
456
535
  }
457
536
  });
458
537
 
459
- xhr.addEventListener('error', () => {
538
+ xhr.addEventListener('error', async () => {
460
539
  if (callback.handleError) {
461
- callback.handleError(new Error('网络错误'));
540
+ await callback.handleError(new Error('网络错误'));
462
541
  }
463
542
  });
464
543
 
@@ -521,7 +600,7 @@ const progress = await fileApi.asyncUpload('/upload', { userId: 123 }, file, {
521
600
  method: 'POST',
522
601
  progressUpdate: (loaded) => console.log(`已上传: ${loaded} 字节`),
523
602
  onCompleted: (data) => console.log('完成!', data),
524
- handleError: (err) => console.error('错误!', err)
603
+ handleError: async (err) => console.error('错误!', err)
525
604
  });
526
605
 
527
606
  // 下载文件
package/README.md CHANGED
@@ -18,6 +18,59 @@ A lightweight TypeScript RESTful API client for browsers with comprehensive erro
18
18
  - 🌐 **Browser-First**: Designed specifically for frontend applications
19
19
  - ✨ **PATCH Support**: Full support for HTTP PATCH method for partial updates
20
20
 
21
+ ## ⚠️ Breaking Changes in v0.8.0
22
+
23
+ **`ErrorHandler` has been changed to return a `Promise` (async support).**
24
+
25
+ ### What This Means for You
26
+
27
+ - The `ErrorHandler` function type is now `(ex: Error) => Promise<boolean | void>`.
28
+ - Error handler functions should now be `async` functions or return a `Promise`.
29
+ - `UploadCallback.handleError` now adopts the unified Promise-based `ErrorHandler` type.
30
+
31
+ ### Migration Guide
32
+
33
+ **Before (v0.7.x and earlier):**
34
+ ```typescript
35
+ const errorHandler: ErrorHandler = (error: Error) => {
36
+ console.error('API Error:', error);
37
+ return true;
38
+ };
39
+ ```
40
+
41
+ **After (v0.8.0+):**
42
+ ```typescript
43
+ const errorHandler: ErrorHandler = async (error: Error) => {
44
+ console.error('API Error:', error);
45
+ return true;
46
+ };
47
+ ```
48
+
49
+ ## ⚠️ Breaking Changes in v0.7.0
50
+
51
+ **`RestfulOptions.contentType` has been removed in favor of a general-purpose `headers` field.**
52
+
53
+ ### What This Means for You
54
+
55
+ - `contentType` is no longer a recognized field on `RestfulOptions` (used by `post`/`put`/`del`/`patch`).
56
+ Replace `{ contentType: 'multipart/form-data' }` with `{ headers: { 'Content-Type': 'multipart/form-data' } }`.
57
+ - `get()` gained a new, optional 4th parameter: `get(url, params?, dataProcessor?, headers?)`. Existing
58
+ positional calls (`get(url)`, `get(url, params)`, `get(url, params, dataProcessor)`) are unaffected.
59
+ - This also enables custom per-call headers in general (not just Content-Type), e.g. a conditional
60
+ GET via `If-None-Match` — see [Custom Headers (e.g. Conditional Requests)](#custom-headers-eg-conditional-requests).
61
+
62
+ ### Migration Guide
63
+
64
+ **Before (v0.6.x and earlier):**
65
+ ```typescript
66
+ await api.post('/upload', formData, { contentType: 'multipart/form-data' });
67
+ ```
68
+
69
+ **After (v0.7.0+):**
70
+ ```typescript
71
+ await api.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } });
72
+ ```
73
+
21
74
  ## ⚠️ Breaking Changes in v0.5.0
22
75
 
23
76
  **Starting from version 0.5.0, this package has migrated to ESM (ECMAScript Modules) format.**
@@ -58,7 +111,7 @@ import RestService, { FileService } from '@ticatec/restful_service_api';
58
111
 
59
112
  // Your implementation of RestService interface
60
113
  class MyApiClient implements RestService {
61
- async get(url: string, params?: any, dataProcessor?: DataProcessor) {
114
+ async get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>) {
62
115
  // Implementation here
63
116
  }
64
117
 
@@ -108,7 +161,7 @@ The main interface for standard HTTP REST operations:
108
161
 
109
162
  ```typescript
110
163
  interface RestService {
111
- get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
164
+ get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>): Promise<any>;
112
165
  post(url: string, data?: any, options?: RestfulOptions): Promise<any>;
113
166
  put(url: string, data?: any, options?: RestfulOptions): Promise<any>;
114
167
  patch(url: string, data?: any, options?: RestfulOptions): Promise<any>;
@@ -130,11 +183,13 @@ interface FileService {
130
183
 
131
184
  ### RestService Methods
132
185
 
133
- #### `get(url, params?, dataProcessor?)`
186
+ #### `get(url, params?, dataProcessor?, headers?)`
134
187
  Performs HTTP GET request to retrieve a resource.
135
188
  - **url**: The endpoint URL
136
189
  - **params**: Query parameters (optional)
137
190
  - **dataProcessor**: Function to process response data (optional)
191
+ - **headers**: Custom request headers for this single call (optional), e.g. for a conditional
192
+ request: `{ 'If-None-Match': contentHash }`
138
193
 
139
194
  #### `post(url, data?, options?)`
140
195
  Performs HTTP POST request to create a new resource.
@@ -142,7 +197,8 @@ Performs HTTP POST request to create a new resource.
142
197
  - **data**: Request payload (optional)
143
198
  - **options**: Optional configuration object containing:
144
199
  - **params**: Query parameters (optional)
145
- - **contentType**: Content-Type header (optional, defaults to application/json)
200
+ - **headers**: Custom request headers for this single call (optional). Use this to override
201
+ the default `Content-Type` (`application/json`), e.g. `{ 'Content-Type': 'multipart/form-data' }`
146
202
  - **dataProcessor**: Function to process response data (optional)
147
203
 
148
204
  #### `put(url, data?, options?)`
@@ -151,7 +207,7 @@ Performs HTTP PUT request to update an entire resource.
151
207
  - **data**: Request payload (optional)
152
208
  - **options**: Optional configuration object containing:
153
209
  - **params**: Query parameters (optional)
154
- - **contentType**: Content-Type header (optional, defaults to application/json)
210
+ - **headers**: Custom request headers for this single call (optional)
155
211
  - **dataProcessor**: Function to process response data (optional)
156
212
 
157
213
  #### `patch(url, data?, options?)`
@@ -160,7 +216,7 @@ Performs HTTP PATCH request to partially update a resource.
160
216
  - **data**: Request payload with fields to update (optional)
161
217
  - **options**: Optional configuration object containing:
162
218
  - **params**: Query parameters (optional)
163
- - **contentType**: Content-Type header (optional, defaults to application/json)
219
+ - **headers**: Custom request headers for this single call (optional)
164
220
  - **dataProcessor**: Function to process response data (optional)
165
221
 
166
222
  #### `del(url, data?, options?)`
@@ -169,7 +225,7 @@ Performs HTTP DELETE request to delete a resource.
169
225
  - **data**: Request body data (optional)
170
226
  - **options**: Optional configuration object containing:
171
227
  - **params**: Query parameters (optional)
172
- - **contentType**: Content-Type header (optional, defaults to application/json)
228
+ - **headers**: Custom request headers for this single call (optional)
173
229
  - **dataProcessor**: Function to process response data (optional)
174
230
 
175
231
  ### FileService Methods
@@ -231,7 +287,7 @@ import {
231
287
  // RestfulOptions for configuring requests
232
288
  const options: RestfulOptions = {
233
289
  params: { page: 1, limit: 10 },
234
- contentType: 'application/json',
290
+ headers: { 'Content-Type': 'application/json' },
235
291
  dataProcessor: (data: any) => data.results || data
236
292
  };
237
293
 
@@ -253,7 +309,7 @@ const postInterceptor: PostInterceptor = async (data: any) => {
253
309
  };
254
310
 
255
311
  // Error handler
256
- const errorHandler: ErrorHandler = (error: Error) => {
312
+ const errorHandler: ErrorHandler = async (error: Error) => {
257
313
  console.error('API Error:', error);
258
314
  return true; // Return true if error is handled
259
315
  };
@@ -277,8 +333,35 @@ import {
277
333
  TYPE_TEXT
278
334
  } from '@ticatec/restful_service_api';
279
335
 
280
- // Usage
281
- await api.post('/upload', data, {}, { contentType: TYPE_JSON });
336
+ // Usage - set a header explicitly via RestfulOptions.headers
337
+ await api.post('/upload', data, { headers: { [CONTENT_TYPE_NAME]: TYPE_JSON } });
338
+ ```
339
+
340
+ ### Custom Headers (e.g. Conditional Requests)
341
+
342
+ `RestfulOptions.headers` (and the 4th `headers` parameter of `get()`) let you attach custom
343
+ request headers to a single call. A common use case is a conditional GET based on a
344
+ previously-cached ETag/hash:
345
+
346
+ ```typescript
347
+ // First request: no cached hash yet
348
+ const pack = await api.get('/messages/node1');
349
+ localStorage.setItem('node1-hash', pack.contentHash);
350
+
351
+ // Later: send the cached hash as If-None-Match
352
+ const cachedHash = localStorage.getItem('node1-hash');
353
+ try {
354
+ const updated = await api.get('/messages/node1', undefined, undefined, {
355
+ 'If-None-Match': cachedHash ?? ''
356
+ });
357
+ // Server responded 200 - content changed, use `updated`
358
+ } catch (error) {
359
+ if (error instanceof ApiError && Number(error.status) === 304) {
360
+ // Server responded 304 Not Modified - the local cache is still valid
361
+ } else {
362
+ throw error;
363
+ }
364
+ }
282
365
  ```
283
366
 
284
367
  ## Implementation Example
@@ -308,48 +391,45 @@ class FetchRestService implements RestService {
308
391
  this.postInterceptor = postInterceptor;
309
392
  }
310
393
 
311
- async get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any> {
394
+ async get(url: string, params?: any, dataProcessor?: DataProcessor, headers?: Record<string, string>): Promise<any> {
312
395
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
313
- return this.request('GET', url + queryString, null, undefined, dataProcessor);
396
+ return this.request('GET', url + queryString, null, headers, dataProcessor);
314
397
  }
315
398
 
316
399
  async post(url: string, data?: any, options?: RestfulOptions): Promise<any> {
317
- const contentType = options?.contentType || TYPE_JSON;
318
400
  const params = options?.params;
319
401
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
320
- return this.request('POST', url + queryString, data, contentType, options?.dataProcessor);
402
+ return this.request('POST', url + queryString, data, options?.headers, options?.dataProcessor);
321
403
  }
322
404
 
323
405
  async put(url: string, data?: any, options?: RestfulOptions): Promise<any> {
324
- const contentType = options?.contentType || TYPE_JSON;
325
406
  const params = options?.params;
326
407
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
327
- return this.request('PUT', url + queryString, data, contentType, options?.dataProcessor);
408
+ return this.request('PUT', url + queryString, data, options?.headers, options?.dataProcessor);
328
409
  }
329
410
 
330
411
  async patch(url: string, data?: any, options?: RestfulOptions): Promise<any> {
331
- const contentType = options?.contentType || TYPE_JSON;
332
412
  const params = options?.params;
333
413
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
334
- return this.request('PATCH', url + queryString, data, contentType, options?.dataProcessor);
414
+ return this.request('PATCH', url + queryString, data, options?.headers, options?.dataProcessor);
335
415
  }
336
416
 
337
417
  async del(url: string, data?: any, options?: RestfulOptions): Promise<any> {
338
- const contentType = options?.contentType || TYPE_JSON;
339
418
  const params = options?.params;
340
419
  const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
341
- return this.request('DELETE', url + queryString, data, contentType, options?.dataProcessor);
420
+ return this.request('DELETE', url + queryString, data, options?.headers, options?.dataProcessor);
342
421
  }
343
422
 
344
- private async request(method: string, url: string, body?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any> {
423
+ private async request(method: string, url: string, body?: any, customHeaders?: Record<string, string>, dataProcessor?: DataProcessor): Promise<any> {
345
424
  const fullUrl = this.baseURL + url;
346
425
 
347
426
  // Apply pre-interceptor
348
427
  const interceptorResult = this.preInterceptor?.(method, fullUrl) || { headers: {} };
349
428
 
350
429
  const headers = {
430
+ 'Content-Type': TYPE_JSON,
351
431
  ...interceptorResult.headers,
352
- ...(contentType && { 'Content-Type': contentType })
432
+ ...customHeaders
353
433
  };
354
434
 
355
435
  try {
@@ -446,18 +526,18 @@ class FetchFileService implements FileService {
446
526
  }
447
527
  });
448
528
 
449
- xhr.addEventListener('load', () => {
529
+ xhr.addEventListener('load', async () => {
450
530
  if (xhr.status >= 200 && xhr.status < 300) {
451
531
  const data = JSON.parse(xhr.responseText);
452
532
  callback.onCompleted(data);
453
533
  } else if (callback.handleError) {
454
- callback.handleError(new Error(`Upload failed: ${xhr.statusText}`));
534
+ await callback.handleError(new Error(`Upload failed: ${xhr.statusText}`));
455
535
  }
456
536
  });
457
537
 
458
- xhr.addEventListener('error', () => {
538
+ xhr.addEventListener('error', async () => {
459
539
  if (callback.handleError) {
460
- callback.handleError(new Error('Network error'));
540
+ await callback.handleError(new Error('Network error'));
461
541
  }
462
542
  });
463
543
 
@@ -520,7 +600,7 @@ const progress = await fileApi.asyncUpload('/upload', { userId: 123 }, file, {
520
600
  method: 'POST',
521
601
  progressUpdate: (loaded) => console.log(`Uploaded: ${loaded} bytes`),
522
602
  onCompleted: (data) => console.log('Done!', data),
523
- handleError: (err) => console.error('Error!', err)
603
+ handleError: async (err) => console.error('Error!', err)
524
604
  });
525
605
 
526
606
  // Download file
@@ -45,20 +45,20 @@ export default interface FileService {
45
45
  * { userId: 123 },
46
46
  * file,
47
47
  * {
48
- * onProgress: (loaded, total, percent) => {
49
- * console.log(`Upload: ${percent}%`);
48
+ * progressUpdate: (uploadBytes) => {
49
+ * console.log(`Uploaded: ${uploadBytes} bytes`);
50
50
  * },
51
51
  * onCompleted: (data) => {
52
52
  * console.log('Upload complete:', data);
53
53
  * },
54
- * onError: (error) => {
54
+ * handleError: async (error) => {
55
55
  * console.error('Upload failed:', error);
56
56
  * }
57
57
  * }
58
58
  * );
59
59
  *
60
60
  * // Cancel the upload
61
- * progress.abort();
61
+ * progress.cancel();
62
62
  * ```
63
63
  */
64
64
  asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress>;
@@ -31,17 +31,13 @@ export type PostInterceptor = (data: any) => Promise<any>;
31
31
  /**
32
32
  * Error handler function type for handling request errors
33
33
  * @param ex The error object
34
- * @returns Optional boolean or void. Errors always reject the returned Promise.
34
+ * @returns Promise that resolves to boolean or void. Errors always reject the returned Promise.
35
35
  */
36
- export type ErrorHandler = (ex: Error) => boolean | void;
36
+ export type ErrorHandler = (ex: Error) => Promise<boolean | void>;
37
37
  /**
38
38
  * RESTful API request options
39
39
  */
40
40
  export type RestfulOptions<T = any> = {
41
- /**
42
- * Optional Content-Type header, defaults to 'application/json'
43
- */
44
- contentType?: string;
45
41
  /**
46
42
  * Optional data processing function to transform response data before returning
47
43
  */
@@ -50,6 +46,13 @@ export type RestfulOptions<T = any> = {
50
46
  * Optional query parameters object that will be converted to URL query string
51
47
  */
52
48
  params?: any;
49
+ /**
50
+ * Optional custom request headers for this single call (e.g. { 'If-None-Match': contentHash },
51
+ * or { 'Content-Type': 'multipart/form-data' } to override the default JSON content type).
52
+ * These are merged on top of any headers produced by the PreInterceptor, and take precedence
53
+ * over them when the same header name is used.
54
+ */
55
+ headers?: Record<string, string>;
53
56
  };
54
57
  /**
55
58
  * HTTP header name for Content-Type
@@ -76,6 +79,7 @@ export default interface RestService {
76
79
  * @param url The target URL path for the request
77
80
  * @param params Query parameters object that will be converted to URL query string
78
81
  * @param dataProcessor Optional data processing function to transform response data before returning
82
+ * @param headers Optional custom request headers for this single call (e.g. conditional GET via If-None-Match)
79
83
  * @returns Promise that resolves to the server response data
80
84
  *
81
85
  * @example
@@ -88,14 +92,17 @@ export default interface RestService {
88
92
  *
89
93
  * // GET with data processor
90
94
  * const users = await restService.get('/api/users', null, (data) => data.items);
95
+ *
96
+ * // GET with a custom header (e.g. conditional GET)
97
+ * const pack = await restService.get('/api/messages/node1', null, undefined, { 'If-None-Match': contentHash });
91
98
  * ```
92
99
  */
93
- get<T = any>(url: string, params?: any, dataProcessor?: DataProcessor<T>): Promise<T>;
100
+ get<T = any>(url: string, params?: any, dataProcessor?: DataProcessor<T>, headers?: Record<string, string>): Promise<T>;
94
101
  /**
95
102
  * Performs an HTTP POST request to create a new resource
96
103
  * @param url The target URL path for the request
97
104
  * @param data Request body data to be sent, typically an object or string
98
- * @param options Optional request configuration options, including contentType and dataProcessor
105
+ * @param options Optional request configuration options, including headers and dataProcessor
99
106
  * @returns Promise that resolves to the server response data
100
107
  *
101
108
  * @example
@@ -103,9 +110,9 @@ export default interface RestService {
103
110
  * // Simple POST request
104
111
  * const newUser = await restService.post('/api/users', { name: 'John', age: 30 });
105
112
  *
106
- * // POST with custom content type
113
+ * // POST with a custom Content-Type header
107
114
  * const result = await restService.post('/api/data', formData, {
108
- * contentType: 'multipart/form-data'
115
+ * headers: { 'Content-Type': 'multipart/form-data' }
109
116
  * });
110
117
  *
111
118
  * // POST with data processor
@@ -119,7 +126,7 @@ export default interface RestService {
119
126
  * Performs an HTTP PUT request to update an existing resource
120
127
  * @param url The target URL path for the request
121
128
  * @param data Optional request body data to be sent for updating the resource
122
- * @param options Optional request configuration options, including contentType and dataProcessor
129
+ * @param options Optional request configuration options, including headers and dataProcessor
123
130
  * @returns Promise that resolves to the server response data
124
131
  *
125
132
  * @example
@@ -136,7 +143,7 @@ export default interface RestService {
136
143
  * Performs an HTTP DELETE request to delete a resource
137
144
  * @param url The target URL path for the request
138
145
  * @param data Optional request body data, some DELETE operations may require sending data
139
- * @param options Optional request configuration options, including contentType and dataProcessor
146
+ * @param options Optional request configuration options, including headers and dataProcessor
140
147
  * @returns Promise that resolves to the server response data
141
148
  *
142
149
  * @example
@@ -153,7 +160,7 @@ export default interface RestService {
153
160
  * Performs an HTTP PATCH request to partially update a resource
154
161
  * @param url The target URL path for the request
155
162
  * @param data Optional request body data to be sent for partially updating the resource
156
- * @param options Optional request configuration options, including contentType and dataProcessor
163
+ * @param options Optional request configuration options, including headers and dataProcessor
157
164
  * @returns Promise that resolves to the server response data
158
165
  *
159
166
  * @example
@@ -1,3 +1,4 @@
1
+ import type { ErrorHandler } from "./RestService.js";
1
2
  /**
2
3
  * Upload progress update callback function type
3
4
  * @param uploadBytes Number of bytes uploaded
@@ -5,9 +6,8 @@
5
6
  export type ProgressUpdate = (uploadBytes: number) => void;
6
7
  /**
7
8
  * Upload error handling callback function type
8
- * @param e Error object that occurred during upload
9
9
  */
10
- export type ErrorHandler = (e: Error) => void;
10
+ export type { ErrorHandler };
11
11
  /**
12
12
  * Upload completion callback function type
13
13
  * @param data Response data returned from the server
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@ticatec/restful_service_api",
4
- "version": "0.6.1",
4
+ "version": "0.8.0",
5
5
  "description": "A lightweight TypeScript RESTful API client for browsers with error handling. (ESM only, v0.6.0+)",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",