@happy-ts/fetch-t 1.3.3 → 1.4.1

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
@@ -1,82 +1,170 @@
1
1
  # fetchT
2
2
 
3
- [![NPM version](http://img.shields.io/npm/v/@happy-ts/fetch-t.svg)](https://npmjs.org/package/@happy-ts/fetch-t)
3
+ [![License](https://img.shields.io/npm/l/@happy-ts/fetch-t.svg)](LICENSE)
4
+ [![Build Status](https://github.com/JiangJie/fetch-t/actions/workflows/test.yml/badge.svg)](https://github.com/JiangJie/fetch-t/actions/workflows/test.yml)
5
+ [![codecov](https://codecov.io/gh/JiangJie/fetch-t/graph/badge.svg)](https://codecov.io/gh/JiangJie/fetch-t)
6
+ [![NPM version](https://img.shields.io/npm/v/@happy-ts/fetch-t.svg)](https://npmjs.org/package/@happy-ts/fetch-t)
7
+ [![NPM downloads](https://badgen.net/npm/dm/@happy-ts/fetch-t)](https://npmjs.org/package/@happy-ts/fetch-t)
4
8
  [![JSR Version](https://jsr.io/badges/@happy-ts/fetch-t)](https://jsr.io/@happy-ts/fetch-t)
5
9
  [![JSR Score](https://jsr.io/badges/@happy-ts/fetch-t/score)](https://jsr.io/@happy-ts/fetch-t/score)
6
- [![Build Status](https://github.com/jiangjie/fetch-t/actions/workflows/test.yml/badge.svg)](https://github.com/jiangjie/fetch-t/actions/workflows/test.yml)
7
- [![codecov](https://codecov.io/gh/JiangJie/fetch-t/graph/badge.svg)](https://codecov.io/gh/JiangJie/fetch-t)
8
-
9
- ---
10
-
11
- ## 可终止 && 可预测
12
10
 
13
- fetchT 的返回值包含一个可以 abort 的方法。
11
+ [English](README.md) | [API 文档](https://jiangjie.github.io/fetch-t/)
14
12
 
15
- fetchT 的返回数据是一个明确的类型,可以是 `string` `ArrayBuffer` `Blob` `<T>(泛型)`。
13
+ 类型安全的 Fetch API 封装,支持可中止请求、超时、进度追踪和 Rust 风格的 Result 错误处理。
16
14
 
17
- 支持超时自动取消请求。
15
+ ## 特性
18
16
 
19
- 支持进度回调。
17
+ - **可中止请求** - 随时通过 `FetchTask.abort()` 取消请求
18
+ - **类型安全响应** - 通过 `responseType` 参数指定返回类型
19
+ - **超时支持** - 指定毫秒数后自动中止请求
20
+ - **进度追踪** - 通过 `onProgress` 回调监控下载进度
21
+ - **数据流处理** - 通过 `onChunk` 回调访问原始数据块
22
+ - **Result 错误处理** - Rust 风格的 `Result` 类型实现显式错误处理
23
+ - **跨平台** - 支持 Deno、Node.js、Bun 和浏览器
20
24
 
21
25
  ## 安装
22
26
 
23
27
  ```sh
24
- # via pnpm
25
- pnpm add @happy-ts/fetch-t
26
- # or via yarn
28
+ # npm
29
+ npm install @happy-ts/fetch-t
30
+
31
+ # yarn
27
32
  yarn add @happy-ts/fetch-t
28
- # or just from npm
29
- npm install --save @happy-ts/fetch-t
30
- # via JSR
31
- jsr add @happy-ts/fetch-t
32
- # for deno
33
+
34
+ # pnpm
35
+ pnpm add @happy-ts/fetch-t
36
+
37
+ # JSR (Deno)
33
38
  deno add @happy-ts/fetch-t
34
- # for bun
39
+
40
+ # JSR (Bun)
35
41
  bunx jsr add @happy-ts/fetch-t
36
42
  ```
37
43
 
38
- ## 为什么会有 fetchT
44
+ ## 快速开始
39
45
 
40
- fetchT 是对 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API 的简单封装,主要包括两点修改:
46
+ ### 基础用法
41
47
 
42
- * 增加 `abortable` 参数,如果传入 `abortable: true`,则 fetchT 会返回一个 `FetchTask`,可以通过调用 `FetchTask.abort()` 来终止请求。
43
- * 支持泛型返回值,增加 `responseType` 参数,可选值包括 `'text' | 'arraybuffer' | 'blob' | 'json'`,返回值根据参数不同,对应返回 `string | ArrayBuffer | Blob | T`,并且返回值都是 [Result](https://github.com/JiangJie/happy-rusty) 类型,便于错误处理。
48
+ ```ts
49
+ import { fetchT } from '@happy-ts/fetch-t';
44
50
 
45
- 如果你没有以上需求,推荐使用原版 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)。
51
+ // GET JSON 数据
52
+ const result = await fetchT<{ id: number; title: string }>('https://api.example.com/data', {
53
+ responseType: 'json',
54
+ });
46
55
 
47
- ## 示例
56
+ result.inspect(data => {
57
+ console.log(data.title);
58
+ }).inspectErr(err => {
59
+ console.error('请求失败:', err.message);
60
+ });
61
+ ```
48
62
 
49
- ```ts
50
- import { fetchT } from '@happy-ts/fetch-t';
63
+ ### 可中止请求
51
64
 
52
- const fetchTask = fetchT('https://example.com', {
65
+ ```ts
66
+ const task = fetchT('https://api.example.com/large-file', {
53
67
  abortable: true,
68
+ responseType: 'arraybuffer',
69
+ });
70
+
71
+ // 5 秒后中止
72
+ setTimeout(() => {
73
+ task.abort('用户取消');
74
+ }, 5000);
75
+
76
+ const result = await task.response;
77
+ ```
78
+
79
+ ### 超时控制
80
+
81
+ ```ts
82
+ const result = await fetchT('https://api.example.com/data', {
54
83
  responseType: 'json',
55
- timeout: 3000,
56
- onChunk(chunk): void {
57
- console.assert(chunk instanceof Uint8Array);
58
- },
59
- onProgress(progressResult): void {
84
+ timeout: 3000, // 3 秒后自动中止
85
+ });
86
+ ```
87
+
88
+ ### 进度追踪
89
+
90
+ ```ts
91
+ const result = await fetchT('https://api.example.com/large-file', {
92
+ responseType: 'blob',
93
+ onProgress(progressResult) {
60
94
  progressResult.inspect(progress => {
61
- console.log(`${ progress.completedByteLength }/${ progress.totalByteLength }`);
62
- }).inspectErr(err => {
63
- console.error(err);
95
+ const percent = (progress.completedByteLength / progress.totalByteLength * 100).toFixed(1);
96
+ console.log(`下载进度: ${percent}%`);
64
97
  });
65
98
  },
66
99
  });
100
+ ```
67
101
 
68
- somethingHappenAsync(() => {
69
- fetchTask.abort('cancel');
70
- });
102
+ ### 错误处理
71
103
 
72
- const res = await fetchTask.response;
73
- res.inspect(data => {
74
- console.log(data);
75
- }).inspectErr(err => {
76
- console.assert(err === 'cancel');
104
+ ```ts
105
+ import { fetchT, ABORT_ERROR, TIMEOUT_ERROR } from '@happy-ts/fetch-t';
106
+
107
+ const result = await fetchT('https://api.example.com/data', {
108
+ responseType: 'json',
109
+ timeout: 3000,
77
110
  });
111
+
112
+ if (result.isErr()) {
113
+ const err = result.unwrapErr();
114
+ if (err.name === TIMEOUT_ERROR) {
115
+ console.log('请求超时');
116
+ } else if (err.name === ABORT_ERROR) {
117
+ console.log('请求已中止');
118
+ } else {
119
+ console.log('请求失败:', err.message);
120
+ }
121
+ } else {
122
+ console.log('数据:', result.unwrap());
123
+ }
78
124
  ```
79
125
 
80
- 更多示例可参见测试用例 <a href="tests/fetch.test.ts">fetch.test.ts</a>。
126
+ ## API
127
+
128
+ ### `fetchT(url, options?)`
129
+
130
+ | 参数 | 类型 | 描述 |
131
+ |------|------|------|
132
+ | `url` | `string \| URL` | 请求 URL |
133
+ | `options` | `FetchInit` | 扩展的 fetch 选项 |
134
+
135
+ ### `FetchInit` 选项
136
+
137
+ 继承标准 `RequestInit`,额外支持:
138
+
139
+ | 选项 | 类型 | 描述 |
140
+ |------|------|------|
141
+ | `abortable` | `boolean` | 如为 `true`,返回 `FetchTask` 而非 `FetchResponse` |
142
+ | `responseType` | `'text' \| 'arraybuffer' \| 'blob' \| 'json'` | 指定返回数据类型 |
143
+ | `timeout` | `number` | 指定毫秒数后自动中止 |
144
+ | `onProgress` | `(result: IOResult<FetchProgress>) => void` | 下载进度回调 |
145
+ | `onChunk` | `(chunk: Uint8Array) => void` | 原始数据块回调 |
146
+
147
+ ### `FetchTask<T>`
148
+
149
+ 当 `abortable: true` 时返回:
150
+
151
+ | 属性/方法 | 类型 | 描述 |
152
+ |-----------|------|------|
153
+ | `response` | `FetchResponse<T>` | 响应 Promise |
154
+ | `abort(reason?)` | `void` | 中止请求 |
155
+ | `aborted` | `boolean` | 请求是否已中止 |
156
+
157
+ ### 常量
158
+
159
+ | 常量 | 描述 |
160
+ |------|------|
161
+ | `ABORT_ERROR` | 中止请求的错误名称 |
162
+ | `TIMEOUT_ERROR` | 超时请求的错误名称 |
163
+
164
+ ## 示例
165
+
166
+ 更多示例请参见 [examples](examples/) 目录。
167
+
168
+ ## 许可证
81
169
 
82
- ## [文档](docs/README.md)
170
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,87 +1,170 @@
1
1
  # fetchT
2
2
 
3
+ [![License](https://img.shields.io/npm/l/@happy-ts/fetch-t.svg)](LICENSE)
4
+ [![Build Status](https://github.com/JiangJie/fetch-t/actions/workflows/test.yml/badge.svg)](https://github.com/JiangJie/fetch-t/actions/workflows/test.yml)
5
+ [![codecov](https://codecov.io/gh/JiangJie/fetch-t/graph/badge.svg)](https://codecov.io/gh/JiangJie/fetch-t)
3
6
  [![NPM version](https://img.shields.io/npm/v/@happy-ts/fetch-t.svg)](https://npmjs.org/package/@happy-ts/fetch-t)
4
7
  [![NPM downloads](https://badgen.net/npm/dm/@happy-ts/fetch-t)](https://npmjs.org/package/@happy-ts/fetch-t)
5
8
  [![JSR Version](https://jsr.io/badges/@happy-ts/fetch-t)](https://jsr.io/@happy-ts/fetch-t)
6
9
  [![JSR Score](https://jsr.io/badges/@happy-ts/fetch-t/score)](https://jsr.io/@happy-ts/fetch-t/score)
7
- [![Build Status](https://github.com/jiangjie/fetch-t/actions/workflows/test.yml/badge.svg)](https://github.com/jiangjie/fetch-t/actions/workflows/test.yml)
8
- [![codecov](https://codecov.io/gh/JiangJie/fetch-t/graph/badge.svg)](https://codecov.io/gh/JiangJie/fetch-t)
9
-
10
- ---
11
-
12
- ## [中文](README.cn.md)
13
10
 
14
- ---
11
+ [中文](README.cn.md) | [API Documentation](https://jiangjie.github.io/fetch-t/)
15
12
 
16
- ## Abortable && Predictable
13
+ Type-safe Fetch API wrapper with abortable requests, timeout support, progress tracking, and Rust-like Result error handling.
17
14
 
18
- The return value of fetchT includes an `abort` method.
15
+ ## Features
19
16
 
20
- The return data of fetchT is of a specific type, which can be either `string`, `ArrayBuffer`, `Blob`, or `<T>(generic)`.
21
-
22
- Support `timeout`.
23
-
24
- Support `progress`.
17
+ - **Abortable Requests** - Cancel requests anytime via `FetchTask.abort()`
18
+ - **Type-safe Responses** - Specify return type with `responseType` parameter
19
+ - **Timeout Support** - Auto-abort requests after specified milliseconds
20
+ - **Progress Tracking** - Monitor download progress with `onProgress` callback
21
+ - **Chunk Streaming** - Access raw data chunks via `onChunk` callback
22
+ - **Result Error Handling** - Rust-like `Result` type for explicit error handling
23
+ - **Cross-platform** - Works with Deno, Node.js, Bun, and browsers
25
24
 
26
25
  ## Installation
27
26
 
28
27
  ```sh
29
- # via pnpm
30
- pnpm add @happy-ts/fetch-t
31
- # or via yarn
28
+ # npm
29
+ npm install @happy-ts/fetch-t
30
+
31
+ # yarn
32
32
  yarn add @happy-ts/fetch-t
33
- # or just from npm
34
- npm install --save @happy-ts/fetch-t
35
- # via JSR
36
- jsr add @happy-ts/fetch-t
37
- # for deno
33
+
34
+ # pnpm
35
+ pnpm add @happy-ts/fetch-t
36
+
37
+ # JSR (Deno)
38
38
  deno add @happy-ts/fetch-t
39
- # for bun
39
+
40
+ # JSR (Bun)
40
41
  bunx jsr add @happy-ts/fetch-t
41
42
  ```
42
43
 
43
- ## Why fetchT
44
+ ## Quick Start
44
45
 
45
- fetchT is a simple encapsulation of the [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API, with two main modifications:
46
+ ### Basic Usage
47
+
48
+ ```ts
49
+ import { fetchT } from '@happy-ts/fetch-t';
46
50
 
47
- - It adds the `abortable` parameter. If `abortable: true` is passed, fetchT will return a `FetchTask` object that allows you to abort the request by calling `FetchTask.abort()`.
48
- - It supports generic return values by adding the responseType parameter. The optional values for `responseType` include `'text' | 'arraybuffer' | 'blob' | 'json'`. The return value corresponds to the parameter and can be either `string | ArrayBuffer | Blob | T`, where T is the generic type. All return values are of the [Result](https://github.com/JiangJie/happy-rusty) type, which facilitates error handling.
51
+ // GET JSON data
52
+ const result = await fetchT<{ id: number; title: string }>('https://api.example.com/data', {
53
+ responseType: 'json',
54
+ });
49
55
 
50
- If you don't have these requirements, it is recommended to use the vanilla [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
56
+ result.inspect(data => {
57
+ console.log(data.title);
58
+ }).inspectErr(err => {
59
+ console.error('Request failed:', err.message);
60
+ });
61
+ ```
51
62
 
52
- ## Examples
63
+ ### Abortable Request
53
64
 
54
65
  ```ts
55
- import { fetchT } from '@happy-ts/fetch-t';
56
-
57
- const fetchTask = fetchT('https://example.com', {
66
+ const task = fetchT('https://api.example.com/large-file', {
58
67
  abortable: true,
68
+ responseType: 'arraybuffer',
69
+ });
70
+
71
+ // Abort after 5 seconds
72
+ setTimeout(() => {
73
+ task.abort('User cancelled');
74
+ }, 5000);
75
+
76
+ const result = await task.response;
77
+ ```
78
+
79
+ ### With Timeout
80
+
81
+ ```ts
82
+ const result = await fetchT('https://api.example.com/data', {
59
83
  responseType: 'json',
60
- timeout: 3000,
61
- onChunk(chunk): void {
62
- console.assert(chunk instanceof Uint8Array);
63
- },
64
- onProgress(progressResult): void {
84
+ timeout: 3000, // Auto-abort after 3 seconds
85
+ });
86
+ ```
87
+
88
+ ### Progress Tracking
89
+
90
+ ```ts
91
+ const result = await fetchT('https://api.example.com/large-file', {
92
+ responseType: 'blob',
93
+ onProgress(progressResult) {
65
94
  progressResult.inspect(progress => {
66
- console.log(`${ progress.completedByteLength }/${ progress.totalByteLength }`);
67
- }).inspectErr(err => {
68
- console.error(err);
95
+ const percent = (progress.completedByteLength / progress.totalByteLength * 100).toFixed(1);
96
+ console.log(`Download: ${percent}%`);
69
97
  });
70
98
  },
71
99
  });
100
+ ```
72
101
 
73
- somethingHappenAsync(() => {
74
- fetchTask.abort('cancel');
75
- });
102
+ ### Error Handling
76
103
 
77
- const res = await fetchTask.response;
78
- res.inspect(data => {
79
- console.log(data);
80
- }).inspectErr(err => {
81
- console.assert(err === 'cancel');
104
+ ```ts
105
+ import { fetchT, ABORT_ERROR, TIMEOUT_ERROR } from '@happy-ts/fetch-t';
106
+
107
+ const result = await fetchT('https://api.example.com/data', {
108
+ responseType: 'json',
109
+ timeout: 3000,
82
110
  });
111
+
112
+ if (result.isErr()) {
113
+ const err = result.unwrapErr();
114
+ if (err.name === TIMEOUT_ERROR) {
115
+ console.log('Request timed out');
116
+ } else if (err.name === ABORT_ERROR) {
117
+ console.log('Request was aborted');
118
+ } else {
119
+ console.log('Request failed:', err.message);
120
+ }
121
+ } else {
122
+ console.log('Data:', result.unwrap());
123
+ }
83
124
  ```
84
125
 
85
- For more examples, please refer to test case <a href="tests/fetch.test.ts">fetch.test.ts</a>.
126
+ ## API
127
+
128
+ ### `fetchT(url, options?)`
129
+
130
+ | Parameter | Type | Description |
131
+ |-----------|------|-------------|
132
+ | `url` | `string \| URL` | Request URL |
133
+ | `options` | `FetchInit` | Extended fetch options |
134
+
135
+ ### `FetchInit` Options
136
+
137
+ Extends standard `RequestInit` with:
138
+
139
+ | Option | Type | Description |
140
+ |--------|------|-------------|
141
+ | `abortable` | `boolean` | If `true`, returns `FetchTask` instead of `FetchResponse` |
142
+ | `responseType` | `'text' \| 'arraybuffer' \| 'blob' \| 'json'` | Specifies return data type |
143
+ | `timeout` | `number` | Auto-abort after milliseconds |
144
+ | `onProgress` | `(result: IOResult<FetchProgress>) => void` | Download progress callback |
145
+ | `onChunk` | `(chunk: Uint8Array) => void` | Raw data chunk callback |
146
+
147
+ ### `FetchTask<T>`
148
+
149
+ Returned when `abortable: true`:
150
+
151
+ | Property/Method | Type | Description |
152
+ |-----------------|------|-------------|
153
+ | `response` | `FetchResponse<T>` | The response promise |
154
+ | `abort(reason?)` | `void` | Abort the request |
155
+ | `aborted` | `boolean` | Whether request was aborted |
156
+
157
+ ### Constants
158
+
159
+ | Constant | Description |
160
+ |----------|-------------|
161
+ | `ABORT_ERROR` | Error name for aborted requests |
162
+ | `TIMEOUT_ERROR` | Error name for timed out requests |
163
+
164
+ ## Examples
165
+
166
+ For more examples, see the [examples](examples/) directory.
167
+
168
+ ## License
86
169
 
87
- ## [Docs](docs/README.md)
170
+ [MIT](LICENSE)
package/dist/main.cjs CHANGED
@@ -1,20 +1,28 @@
1
1
  'use strict';
2
2
 
3
- var happyRusty = require('happy-rusty');
4
- var invariant = require('tiny-invariant');
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const happyRusty = require('happy-rusty');
6
+ const invariant = require('tiny-invariant');
5
7
 
6
8
  const ABORT_ERROR = "AbortError";
7
9
  const TIMEOUT_ERROR = "TimeoutError";
8
10
 
9
11
  class FetchError extends Error {
10
12
  /**
11
- * The name of the error.
13
+ * The error name, always `'FetchError'`.
12
14
  */
13
15
  name = "FetchError";
14
16
  /**
15
- * The status code of the response.
17
+ * The HTTP status code of the response (e.g., 404, 500).
16
18
  */
17
19
  status = 0;
20
+ /**
21
+ * Creates a new FetchError instance.
22
+ *
23
+ * @param message - The status text from the HTTP response (e.g., "Not Found").
24
+ * @param status - The HTTP status code (e.g., 404).
25
+ */
18
26
  constructor(message, status) {
19
27
  super(message);
20
28
  this.status = status;
@@ -26,7 +34,7 @@ function fetchT(url, init) {
26
34
  invariant(url instanceof URL, () => `Url must be a string or URL object but received ${url}.`);
27
35
  }
28
36
  const {
29
- // default not abort able
37
+ // default not abortable
30
38
  abortable = false,
31
39
  responseType,
32
40
  timeout,
@@ -59,9 +67,12 @@ function fetchT(url, init) {
59
67
  let totalByteLength = null;
60
68
  let completedByteLength = 0;
61
69
  if (shouldNotifyProgress) {
62
- const contentLength = res.headers.get("content-length") ?? res.headers.get("Content-Length");
70
+ const contentLength = res.headers.get("content-length");
63
71
  if (contentLength == null) {
64
- onProgress(happyRusty.Err(new Error("No content-length in response headers.")));
72
+ try {
73
+ onProgress(happyRusty.Err(new Error("No content-length in response headers.")));
74
+ } catch {
75
+ }
65
76
  } else {
66
77
  totalByteLength = parseInt(contentLength, 10);
67
78
  }
@@ -71,16 +82,24 @@ function fetchT(url, init) {
71
82
  return;
72
83
  }
73
84
  if (shouldNotifyChunk) {
74
- onChunk(value);
85
+ try {
86
+ onChunk(value);
87
+ } catch {
88
+ }
75
89
  }
76
90
  if (shouldNotifyProgress && totalByteLength != null) {
77
91
  completedByteLength += value.byteLength;
78
- onProgress(happyRusty.Ok({
79
- totalByteLength,
80
- completedByteLength
81
- }));
92
+ try {
93
+ onProgress(happyRusty.Ok({
94
+ totalByteLength,
95
+ completedByteLength
96
+ }));
97
+ } catch {
98
+ }
82
99
  }
83
- reader.read().then(notify);
100
+ reader.read().then(notify).catch(() => {
101
+ });
102
+ }).catch(() => {
84
103
  });
85
104
  res = new Response(stream2, {
86
105
  headers: res.headers,
@@ -116,16 +135,12 @@ function fetchT(url, init) {
116
135
  });
117
136
  if (shouldWaitTimeout) {
118
137
  const timer = setTimeout(() => {
119
- if (!controller.signal.aborted) {
120
- const error = new Error();
121
- error.name = TIMEOUT_ERROR;
122
- controller.abort(error);
123
- }
138
+ const error = new Error();
139
+ error.name = TIMEOUT_ERROR;
140
+ controller.abort(error);
124
141
  }, timeout);
125
142
  cancelTimer = () => {
126
- if (timer) {
127
- clearTimeout(timer);
128
- }
143
+ clearTimeout(timer);
129
144
  cancelTimer = null;
130
145
  };
131
146
  }
@@ -143,9 +158,8 @@ function fetchT(url, init) {
143
158
  return response;
144
159
  }
145
160
  };
146
- } else {
147
- return response;
148
161
  }
162
+ return response;
149
163
  }
150
164
 
151
165
  exports.ABORT_ERROR = ABORT_ERROR;