@stdex/request 1.0.1 → 1.2.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.md CHANGED
@@ -1 +1 @@
1
- # 可靠、简洁的网络请求模块
1
+ # 可靠、可定制的 HTTP 请求库
package/dist/client.d.ts CHANGED
@@ -2,17 +2,13 @@
2
2
  * 实现一个功能完善、异常处理逻辑全面的请求发起器。
3
3
  * 可通过继承子类来扩展其功能。
4
4
  */
5
- import { type Logger } from '@stdex/logging';
6
5
  import { type Result, type Success, type Failure } from '@stdex/utils/result.js';
7
6
  import { type RequestFailure } from './error.js';
8
7
  import type { Options, PredefinedOptions, FormattedOptions } from './options.js';
9
8
  /** 此基类不可直接使用,因其未对错误格式进行具体约定 */
10
9
  export declare abstract class BaseRequestClient<FailureT extends Failure> {
11
- logger: Logger;
12
10
  prefefinedOptions: PredefinedOptions;
13
- constructor(options?: PredefinedOptions & {
14
- logger?: string | Logger;
15
- });
11
+ constructor(options?: PredefinedOptions);
16
12
  /** 生成一个快捷方式函数,调用它相当于调用 client.request() */
17
13
  asFunction(): <T>(inputUrl: string, inputOptions?: Options<T>) => Promise<Result<T, FailureT>>;
18
14
  request<T>(inputUrl: string, inputOptions?: Options<T>): Promise<Result<T, FailureT>>;
@@ -30,7 +26,7 @@ export declare abstract class BaseRequestClient<FailureT extends Failure> {
30
26
  * - 其他情况,返回纯文本结果
31
27
  */
32
28
  protected parseResponse(options: FormattedOptions, response: Response): Promise<Result<unknown, FailureT>>;
33
- protected handleError(result: RequestFailure): FailureT;
29
+ protected handleError(result: RequestFailure, options: FormattedOptions): FailureT;
34
30
  /** 生成符合 FailureT 约定的失败结果 */
35
31
  protected abstract makeFailureResult(result: RequestFailure): FailureT;
36
32
  /**
package/dist/client.js CHANGED
@@ -2,21 +2,14 @@
2
2
  * 实现一个功能完善、异常处理逻辑全面的请求发起器。
3
3
  * 可通过继承子类来扩展其功能。
4
4
  */
5
- import { getLogger } from '@stdex/logging';
6
5
  import { success, failure, formatFailure, exceptionToFailure, } from '@stdex/utils/result.js';
7
6
  import { combineUrl } from '@stdex/utils/url.js';
8
7
  import isPlainObject from 'lodash/isPlainObject.js';
9
8
  import pick from 'lodash/pick.js';
10
9
  /** 此基类不可直接使用,因其未对错误格式进行具体约定 */
11
10
  export class BaseRequestClient {
12
- logger;
13
11
  prefefinedOptions;
14
12
  constructor(options = {}) {
15
- this.logger = !options.logger
16
- ? getLogger('request')
17
- : typeof options.logger === 'string'
18
- ? getLogger(options.logger)
19
- : options.logger;
20
13
  this.prefefinedOptions = options;
21
14
  }
22
15
  /** 生成一个快捷方式函数,调用它相当于调用 client.request() */
@@ -36,7 +29,7 @@ export class BaseRequestClient {
36
29
  do {
37
30
  tryCount += 1;
38
31
  if (tryCount > 1) {
39
- this.logger.warn('Retrying Request', { ...options, tryCount });
32
+ options.logger?.warn('Retrying Request', { ...options, tryCount });
40
33
  }
41
34
  result = await this.realRequest(options);
42
35
  } while (!result.success && tryCount < options.retry + 1);
@@ -57,14 +50,14 @@ export class BaseRequestClient {
57
50
  // 失败情形“手动取消”
58
51
  if (manualSignal?.aborted && manualSignal.reason === error) {
59
52
  const reason = error instanceof DOMException && error.name === 'AbortError' ? null : error;
60
- return this.handleError(failure('Request Was Canceled', 'RequestAborted', { options, reason }));
53
+ return this.handleError(failure('Request Was Canceled', 'RequestAborted', { options, reason }), options);
61
54
  }
62
55
  // 失败情形“请求超时”
63
56
  if (error instanceof DOMException && error.name === 'TimeoutError') {
64
- return this.handleError(failure('Request Timed Out', 'RequestTimeout', { options }));
57
+ return this.handleError(failure('Request Timed Out', 'RequestTimeout', { options }), options);
65
58
  }
66
59
  // 失败情形“请求发起失败”
67
- return this.handleError(failure("Couldn't Send Request", 'RequestSendFailed', { options, originalError: error }));
60
+ return this.handleError(failure("Couldn't Send Request", 'RequestSendFailed', { options, originalError: error }), options);
68
61
  }
69
62
  // 失败情形“失败状态码”
70
63
  if (!response.status.toString().startsWith('2')) {
@@ -77,7 +70,7 @@ export class BaseRequestClient {
77
70
  status: response.status,
78
71
  response,
79
72
  responseData,
80
- }));
73
+ }), options);
81
74
  }
82
75
  // 解析响应内容
83
76
  let result = await this.parseResponse(options, response);
@@ -92,7 +85,7 @@ export class BaseRequestClient {
92
85
  originalError: error,
93
86
  response,
94
87
  responseData: result.data,
95
- }));
88
+ }), options);
96
89
  }
97
90
  }
98
91
  if (result.success && format) {
@@ -106,7 +99,7 @@ export class BaseRequestClient {
106
99
  originalError: error,
107
100
  response,
108
101
  responseData: result.data,
109
- }));
102
+ }), options);
110
103
  }
111
104
  }
112
105
  return result;
@@ -117,7 +110,7 @@ export class BaseRequestClient {
117
110
  async formatOptions(input) {
118
111
  const predefined = this.prefefinedOptions;
119
112
  const { urlPrefix = predefined.urlPrefix ?? '', url: rawUrl, query = {}, method = predefined.method ?? 'GET', headers: rawHeaders = {}, body: rawBody = null, data, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
120
- binary = false, timeout = predefined.timeout ?? 0, signal, retry = predefined.retry ?? 0, format, } = input;
113
+ binary = false, timeout = predefined.timeout ?? 0, signal, retry = predefined.retry ?? 0, format, logger = predefined.logger, } = input;
121
114
  const headers = {
122
115
  ...(predefined.headers ?? {}),
123
116
  ...rawHeaders,
@@ -145,6 +138,7 @@ export class BaseRequestClient {
145
138
  signal,
146
139
  format,
147
140
  retry,
141
+ logger,
148
142
  };
149
143
  Object.assign(options.headers, await this.getHeaders(options, input));
150
144
  return options;
@@ -178,7 +172,7 @@ export class BaseRequestClient {
178
172
  status: response.status,
179
173
  originalError: result.data,
180
174
  response,
181
- })));
175
+ }), options));
182
176
  }
183
177
  const textResult = await exceptionToFailure(response.text());
184
178
  if (contentType.startsWith('text/') || !textResult.success) {
@@ -187,7 +181,7 @@ export class BaseRequestClient {
187
181
  status: response.status,
188
182
  originalError: result.data,
189
183
  response,
190
- })));
184
+ }), options));
191
185
  }
192
186
  let jsonParseError;
193
187
  try {
@@ -202,17 +196,17 @@ export class BaseRequestClient {
202
196
  status: response.status,
203
197
  originalError: jsonParseError,
204
198
  response,
205
- }));
199
+ }), options);
206
200
  }
207
201
  return textResult;
208
202
  }
209
- handleError(result) {
203
+ handleError(result, options) {
210
204
  const info = pick(result.data.options, ['url', 'method']);
211
205
  for (const key of ['originalError', 'status', 'responseData']) {
212
206
  if (key in result.data)
213
207
  Object.assign(info, pick(result.data, [key]));
214
208
  }
215
- this.logger.error(result.code, info);
209
+ options.logger?.error(result.code, info);
216
210
  return this.makeFailureResult(result);
217
211
  }
218
212
  /**
@@ -1,4 +1,4 @@
1
1
  import { RequestClient } from './client.js';
2
2
  /** 提供一个即取即用的 RequestClient 实例及其函数版本 */
3
3
  export declare const client: RequestClient;
4
- export declare const request: <T>(inputUrl: string, inputOptions?: import("./options.js").Options<T> | undefined) => Promise<import("@stdex/utils/result.js").Result<T, import("./error.js").RequestFailure>>;
4
+ export declare const request: <T>(inputUrl: string, inputOptions?: import("./options.js").Options<T> | undefined) => Promise<import("@stdex/utils").Result<T, import("./error.js").RequestFailure>>;
package/dist/options.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type GeneralLogger } from '@stdex/utils';
1
2
  /**
2
3
  * 请求参数
3
4
  */
@@ -26,8 +27,10 @@ export interface Options<T = unknown> {
26
27
  retry?: number;
27
28
  /** 对请求成功时得到的数据进行最终格式化 */
28
29
  format?: (responseData: any) => T;
30
+ /** 记录请求相关日志 */
31
+ logger?: GeneralLogger;
29
32
  }
30
33
  /** 可预指定的请求参数 */
31
- export type PredefinedOptions = Pick<Options, 'urlPrefix' | 'method' | 'headers' | 'timeout' | 'retry'>;
34
+ export type PredefinedOptions = Pick<Options, 'urlPrefix' | 'method' | 'headers' | 'timeout' | 'retry' | 'logger'>;
32
35
  /** 经过整理的请求参数(client 内部使用) */
33
- export type FormattedOptions<T = unknown> = Required<Pick<Options, 'url' | 'method' | 'headers' | 'body' | 'binary' | 'timeout' | 'retry'>> & Pick<Options<T>, 'signal' | 'format'>;
36
+ export type FormattedOptions<T = unknown> = Required<Pick<Options, 'url' | 'method' | 'headers' | 'body' | 'binary' | 'timeout' | 'retry'>> & Pick<Options<T>, 'signal' | 'format' | 'logger'>;
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@stdex/request",
3
- "version": "1.0.1",
4
- "description": "可靠、简洁的网络请求模块",
5
- "homepage": "https://github.com/anjianshi/stdex-request",
3
+ "version": "1.2.0",
4
+ "description": "可靠、可定制的 HTTP 请求库",
5
+ "homepage": "https://github.com/anjianshi/stdex-packages/request",
6
6
  "bugs": {
7
- "url": "https://github.com/anjianshi/stdex-request/issues",
7
+ "url": "https://github.com/anjianshi/stdex-packages/issues",
8
8
  "email": "anjianshi@gmail.com"
9
9
  },
10
10
  "license": "MIT",
11
11
  "author": "anjianshi <anjianshi@gmail.com>",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+https://github.com/anjianshi/stdex-request.git"
14
+ "url": "git+https://github.com/anjianshi/stdex-packages.git"
15
15
  },
16
16
  "publishConfig": {
17
17
  "registry": "https://registry.npmjs.org/",
@@ -25,14 +25,13 @@
25
25
  "dist/"
26
26
  ],
27
27
  "dependencies": {
28
- "@stdex/logging": "^1.1.0",
29
- "@stdex/utils": "^1.0.2",
28
+ "@stdex/utils": "1.2.0",
30
29
  "lodash": "^4.18.1"
31
30
  },
32
31
  "devDependencies": {
33
- "@stdex/presets-eslint-typescript": "^1.0.2",
34
- "@stdex/presets-prettier": "^1.0.2",
35
- "@stdex/presets-typescript": "^1.0.2",
32
+ "@stdex/presets-eslint-typescript": "1.2.0",
33
+ "@stdex/presets-prettier": "1.2.0",
34
+ "@stdex/presets-typescript": "1.2.0",
36
35
  "@types/lodash": "^4.17.27",
37
36
  "eslint": "^10.6.0",
38
37
  "typescript": "^6.0.3"
package/src/client.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  * 实现一个功能完善、异常处理逻辑全面的请求发起器。
3
3
  * 可通过继承子类来扩展其功能。
4
4
  */
5
- import { getLogger, type Logger } from '@stdex/logging'
6
5
  import {
7
6
  success,
8
7
  failure,
@@ -20,15 +19,9 @@ import type { Options, PredefinedOptions, FormattedOptions } from './options.js'
20
19
 
21
20
  /** 此基类不可直接使用,因其未对错误格式进行具体约定 */
22
21
  export abstract class BaseRequestClient<FailureT extends Failure> {
23
- logger: Logger
24
22
  prefefinedOptions: PredefinedOptions
25
23
 
26
- constructor(options: PredefinedOptions & { logger?: string | Logger } = {}) {
27
- this.logger = !options.logger
28
- ? getLogger('request')
29
- : typeof options.logger === 'string'
30
- ? getLogger(options.logger)
31
- : options.logger
24
+ constructor(options: PredefinedOptions = {}) {
32
25
  this.prefefinedOptions = options
33
26
  }
34
27
 
@@ -53,7 +46,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
53
46
  do {
54
47
  tryCount += 1
55
48
  if (tryCount > 1) {
56
- this.logger.warn('Retrying Request', { ...options, tryCount })
49
+ options.logger?.warn('Retrying Request', { ...options, tryCount })
57
50
  }
58
51
  result = await this.realRequest<T>(options)
59
52
  } while (!result.success && tryCount < options.retry + 1)
@@ -79,15 +72,17 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
79
72
  const reason = error instanceof DOMException && error.name === 'AbortError' ? null : error
80
73
  return this.handleError(
81
74
  failure('Request Was Canceled', 'RequestAborted', { options, reason }),
75
+ options
82
76
  )
83
77
  }
84
78
  // 失败情形“请求超时”
85
79
  if (error instanceof DOMException && error.name === 'TimeoutError') {
86
- return this.handleError(failure('Request Timed Out', 'RequestTimeout', { options }))
80
+ return this.handleError(failure('Request Timed Out', 'RequestTimeout', { options }), options)
87
81
  }
88
82
  // 失败情形“请求发起失败”
89
83
  return this.handleError(
90
84
  failure("Couldn't Send Request", 'RequestSendFailed', { options, originalError: error }),
85
+ options
91
86
  )
92
87
  }
93
88
 
@@ -104,6 +99,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
104
99
  response,
105
100
  responseData,
106
101
  }),
102
+ options
107
103
  )
108
104
  }
109
105
 
@@ -122,6 +118,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
122
118
  response,
123
119
  responseData: result.data,
124
120
  }),
121
+ options
125
122
  )
126
123
  }
127
124
  }
@@ -141,6 +138,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
141
138
  responseData: result.data,
142
139
  },
143
140
  ),
141
+ options
144
142
  )
145
143
  }
146
144
  }
@@ -166,6 +164,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
166
164
  signal,
167
165
  retry = predefined.retry ?? 0,
168
166
  format,
167
+ logger = predefined.logger,
169
168
  } = input
170
169
 
171
170
  const headers = {
@@ -197,6 +196,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
197
196
  signal,
198
197
  format,
199
198
  retry,
199
+ logger,
200
200
  }
201
201
  Object.assign(options.headers, await this.getHeaders(options, input))
202
202
  return options
@@ -241,6 +241,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
241
241
  originalError: result.data,
242
242
  response,
243
243
  }),
244
+ options,
244
245
  ),
245
246
  )
246
247
  }
@@ -255,6 +256,7 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
255
256
  originalError: result.data,
256
257
  response,
257
258
  }),
259
+ options,
258
260
  ),
259
261
  )
260
262
  }
@@ -273,17 +275,18 @@ export abstract class BaseRequestClient<FailureT extends Failure> {
273
275
  originalError: jsonParseError,
274
276
  response,
275
277
  }),
278
+ options
276
279
  )
277
280
  }
278
281
  return textResult
279
282
  }
280
283
 
281
- protected handleError(result: RequestFailure) {
284
+ protected handleError(result: RequestFailure, options: FormattedOptions) {
282
285
  const info: Record<string, unknown> = pick(result.data.options, ['url', 'method'])
283
286
  for (const key of ['originalError', 'status', 'responseData'] as const) {
284
287
  if (key in result.data) Object.assign(info, pick(result.data, [key]))
285
288
  }
286
- this.logger.error(result.code, info)
289
+ options.logger?.error(result.code, info)
287
290
  return this.makeFailureResult(result)
288
291
  }
289
292
 
package/src/options.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type GeneralLogger } from '@stdex/utils'
2
+
1
3
  /**
2
4
  * 请求参数
3
5
  */
@@ -34,16 +36,19 @@ export interface Options<T = unknown> {
34
36
  /** 对请求成功时得到的数据进行最终格式化 */
35
37
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
38
  format?: (responseData: any) => T
39
+
40
+ /** 记录请求相关日志 */
41
+ logger?: GeneralLogger
37
42
  }
38
43
 
39
44
  /** 可预指定的请求参数 */
40
45
  export type PredefinedOptions = Pick<
41
46
  Options,
42
- 'urlPrefix' | 'method' | 'headers' | 'timeout' | 'retry'
47
+ 'urlPrefix' | 'method' | 'headers' | 'timeout' | 'retry' | 'logger'
43
48
  >
44
49
 
45
50
  /** 经过整理的请求参数(client 内部使用) */
46
51
  export type FormattedOptions<T = unknown> = Required<
47
52
  Pick<Options, 'url' | 'method' | 'headers' | 'body' | 'binary' | 'timeout' | 'retry'>
48
53
  > &
49
- Pick<Options<T>, 'signal' | 'format'>
54
+ Pick<Options<T>, 'signal' | 'format' | 'logger'>