keq 2.2.0 → 2.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.3.0](https://github.com/keq-request/keq/compare/v2.2.0...v2.3.0) (2024-02-24)
6
+
7
+
8
+ ### Features
9
+
10
+ * add .resolveWith method add deprecated resolveWithFullResponse ([1e01e7f](https://github.com/keq-request/keq/commit/1e01e7fe07c146d9f122f0f52778b45258797c68))
11
+
5
12
  ## [2.2.0](https://github.com/keq-request/keq/compare/v2.1.2...v2.2.0) (2024-02-04)
6
13
 
7
14
 
package/README.md CHANGED
@@ -82,20 +82,22 @@ await request.del("https://example.com/search");
82
82
  > `.del()` is the alias of `.delete()`.
83
83
 
84
84
  `Keq` will parse `body` according to the `Content-Type` of [`Response`][Response MDN]
85
- and return `body` of [`Response`][Response MDN] by defaulted.
86
- Add option `resolveWithFullResponse` to get the origin [`Response`][Response MDN] Object.
85
+ and return `undefined` if `Content-Type` not found.
86
+ Add invoke `.resolveWith('response')` to get the origin [`Response`][Response MDN] Object.
87
87
 
88
88
  ```javascript
89
89
  import { request } from "keq";
90
90
 
91
91
  const response = await request
92
92
  .get("http://test.com")
93
- .option("resolveWithFullResponse");
93
+ .resolve('response')
94
94
 
95
95
  const body = await response.json();
96
96
  ```
97
97
 
98
- ###### `Keq` won't auto parse body, if response.status is 204. The HTTP 204 No Content success status response code indicates that server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation
98
+ We will introduce `resolveWith` in more detail later.
99
+
100
+ ###### `Keq` won't auto parse body, if response.status is 204. The HTTP 204 No Content success status response code indicates that server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta information
99
101
 
100
102
  ### Setting header fields
101
103
 
@@ -269,6 +271,22 @@ await request
269
271
  | jpeg, bmp, apng, gif, x-icon, png, webp, tiff | image/jpeg, image/bmp, image/apng, image/gif, image/x-icon, image/png, image/webp, image/tiff |
270
272
  | svg | image/svg+xml |
271
273
 
274
+ ### resolve responseBody
275
+
276
+ It was mentioned before that `Keq` will automatically parses the response body.
277
+ And we can control the parsing behavior by calling `.resolveWith(method)`.
278
+ There are multiple parsing methods for us to choose from
279
+
280
+ | method | description |
281
+ | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
282
+ | `.resolveWith('intelligent')` | It is the default method of `Keq`. This will returned `context.output` first if it exists. Otherwise return undefined when the response status is 204. Or return parsed response body according to the `Content-Type` of [`Response`][Response MDN]. |
283
+ | `.resolveWith('response')` | Return [`Response`][Response MDN]. |
284
+ | `.resolveWith('text')` | Return `response.text()`. |
285
+ | `.resolveWith('json')` | Return `response.json()`. |
286
+ | `.resolveWith('form-data')` | Return `response.formData()`. |
287
+ | `.resolveWith('blob')` | Return `response.blob()`. |
288
+ | `.resolveWith('array-buffer')` | Return `response.arrayBuffer()` |
289
+
272
290
  ### Request Retry
273
291
 
274
292
  No retry by default, invoke `.retry(retryTimes[, retryDelay[, retryOn]])` to set retry parameters
@@ -328,11 +346,6 @@ import { request } from "keq";
328
346
 
329
347
  const response = await request
330
348
  .get("http://test.com")
331
- /**
332
- * keq will return Response rather than parsed body
333
- * when set resolveWithFullResponse
334
- */
335
- .option("resolveWithFullResponse")
336
349
  .option("middlewareOption", "value");
337
350
  ```
338
351
 
@@ -344,14 +357,12 @@ import { request } from "keq";
344
357
  await request
345
358
  .get("http://test.com")
346
359
  .options({
347
- resolveWithFullResponse: true,
348
360
  middlewareOption: "value",
349
361
  });
350
362
  ```
351
363
 
352
364
  | **Option** | **Description** |
353
365
  | :------------------------ | :------------------------------------------------------------------------------------------------------ |
354
- | `resolveWithFullResponse` | Get the [`Response`][Response MDN] Class. This is the `.clone()` of original [`Response`][Response MDN] |
355
366
  | `fetchAPI` | Replace the defaulted `fetch` function used by `Keq`. |
356
367
 
357
368
  <!-- ###### The options with **DEPRECATED** will be removed in next major version -->
@@ -431,7 +442,7 @@ const middleware = async (context, next) => {
431
442
  const body = await response.json()
432
443
 
433
444
  // custom keq return type
434
- response.output = JSON.stringify(body)
445
+ context.output = JSON.stringify(body)
435
446
  }
436
447
 
437
448
  // Global Middleware
@@ -510,10 +521,10 @@ Keq's context object has many parameters. The following lists all the built-in c
510
521
  | `context.request.referrer` | `referrer` arguments in [Fetch API][Fetch MDN] |
511
522
  | `context.request.referrerPolicy` | `referrerPolicy` arguments in [Fetch API][Fetch MDN] |
512
523
  | `context.request.signal` | `signal` arguments in [Fetch API][Fetch MDN] |
513
- | `context.options` | It is an object includes request options.(example: `context.options.resolveWithFullResponse`). Middleware can get custom options from here. |
524
+ | `context.options` | It is an object includes request options.(example: `context.options.fetchAPI`). Middleware can get custom options from here. |
514
525
  | `context.res` | The origin [`Response`][Response MDN] Class. It will be undefined before run `await next()` or error throwed. |
515
526
  | `context.response` | Cloned from `ctx.res`. |
516
- | `context.output` | The return value of `await request()`. By defaulted, `context.output` is the parsed body of response. `context.output` will be the `ctx.response` When `options.resolveWithFullResponse` is true. **This property is writeonly.** |
527
+ | `context.output` | Custom return value of `await request()`。 It only take effect when `resolveWith` is not set or set to 'intelligent'. **This property is writeonly.** |
517
528
 
518
529
  #### .useRouter()
519
530
 
@@ -13,7 +13,10 @@ export class Core {
13
13
  __global__;
14
14
  __prepend_middlewares__ = [];
15
15
  __append_middlewares__ = [];
16
- __options__ = { resolveWithFullResponse: false };
16
+ __options__ = {
17
+ resolveWithFullResponse: false,
18
+ resolveWith: 'intelligent',
19
+ };
17
20
  constructor(url, init, global = {}) {
18
21
  this.__global__ = global;
19
22
  this.requestContext = {
@@ -69,39 +72,58 @@ export class Core {
69
72
  const middleware = composeMiddleware([...this.__prepend_middlewares__, ...this.__append_middlewares__]);
70
73
  // eslint-disable-next-line @typescript-eslint/no-empty-function
71
74
  await middleware(ctx, async () => { });
72
- let output = ctx[OUTPUT_PROPERTY];
73
- if (ctx.options.resolveWithFullResponse) {
75
+ const output = ctx[OUTPUT_PROPERTY];
76
+ if (ctx.options.resolveWithFullResponse || ctx.options.resolveWith === 'response') {
74
77
  return ctx.response;
75
78
  }
76
- if (!(OUTPUT_PROPERTY in ctx)) {
77
- const response = ctx.response;
78
- if (response?.status === 204) {
79
- // 204: NO CONTENT
80
- output = response && response.body;
79
+ const response = ctx.response;
80
+ if (!response) {
81
+ return (OUTPUT_PROPERTY in ctx) ? output : undefined;
82
+ }
83
+ if (ctx.options.resolveWith === 'text') {
84
+ return await response.text();
85
+ }
86
+ else if (ctx.options.resolveWith === 'json') {
87
+ return await response.json();
88
+ }
89
+ else if (ctx.options.resolveWith === 'form-data') {
90
+ return await response.formData();
91
+ }
92
+ else if (ctx.options.resolveWith === 'blob') {
93
+ return await response.blob();
94
+ }
95
+ else if (ctx.options.resolveWith === 'array-buffer') {
96
+ return await response.arrayBuffer();
97
+ }
98
+ if (OUTPUT_PROPERTY in ctx) {
99
+ return output;
100
+ }
101
+ if (response.status === 204) {
102
+ // 204: NO CONTENT
103
+ return undefined;
104
+ }
105
+ const contentType = response.headers.get('content-type') || '';
106
+ try {
107
+ if (contentType.includes('application/json')) {
108
+ return await response.json();
81
109
  }
82
- else {
83
- const headers = response?.headers;
84
- const contentType = headers?.get('content-type') || '';
85
- try {
86
- if (contentType.includes('application/json')) {
87
- output = ctx.response && await ctx.response.json();
88
- }
89
- else if (contentType.includes('multipart/form-data')) {
90
- output = ctx.response && await ctx.response.formData();
91
- }
92
- else if (contentType.includes('plain/text')) {
93
- output = ctx.response && await ctx.response.text();
94
- }
95
- else {
96
- output = ctx.response && ctx.response.body;
97
- }
98
- }
99
- catch (e) {
100
- console.warn('Failed to auto parse response body', e);
101
- }
110
+ else if (contentType.includes('multipart/form-data')) {
111
+ return await response.formData();
102
112
  }
113
+ else if (contentType.includes('plain/text')) {
114
+ return await response.text();
115
+ }
116
+ }
117
+ catch (e) {
118
+ console.warn('Failed to auto parse response body', e);
103
119
  }
104
- return output;
120
+ /**
121
+ * Unable to parse response body
122
+ * Return undefined
123
+ * Enable users to discover the problem
124
+ * And modify the method of parsing response
125
+ */
126
+ return undefined;
105
127
  }
106
128
  async end() {
107
129
  return this.run();
@@ -71,4 +71,9 @@ export declare class Keq<T> extends Core<T> {
71
71
  mode(mod: RequestMode): this;
72
72
  flowControl(mode: KeqFlowControlMode, signal?: KeqFlowControlSignal): this;
73
73
  timeout(milliseconds: number): this;
74
+ resolveWith(m: 'response'): Keq<Response>;
75
+ resolveWith(m: 'array-buffer'): Keq<ArrayBuffer>;
76
+ resolveWith(m: 'blob'): Keq<Blob>;
77
+ resolveWith(m: 'text'): Keq<string>;
78
+ resolveWith<U = any>(m: 'json' | 'form-data'): Keq<U>;
74
79
  }
@@ -200,4 +200,9 @@ export class Keq extends Core {
200
200
  this.option('timeout', { millisecond: milliseconds });
201
201
  return this;
202
202
  }
203
+ resolveWith(m) {
204
+ this.option('resolveWith', m);
205
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
206
+ return this;
207
+ }
203
208
  }
@@ -1,4 +1,5 @@
1
1
  import { KeqFlowControl } from './keq-flow-control.js';
2
+ import { KeqResolveMethod } from './keq-resolve-with-mode.js';
2
3
  import { KeqRetryDelay } from './keq-retry-delay';
3
4
  import { KeqRetryOn } from './keq-retry-on';
4
5
  import { KeqTimeout } from './keq-timeout.js';
@@ -8,8 +9,17 @@ export interface KeqBuildInOptions {
8
9
  * default use node-fetch@2 in node and window.fetch in browser
9
10
  */
10
11
  fetchAPI?: typeof fetch;
11
- /** get response object, defaulted `false` */
12
+ /**
13
+ * get response object, defaulted `false`
14
+ * @deprecated use `resolveWith` instead
15
+ * */
12
16
  resolveWithFullResponse?: boolean;
17
+ /**
18
+ * how to resolve the response body
19
+ * @description 如何解析响应体
20
+ * @default 'intelligent'
21
+ */
22
+ resolveWith?: KeqResolveMethod;
13
23
  /**
14
24
  * The request retry times
15
25
  * @description 重试次数
@@ -0,0 +1 @@
1
+ export type KeqResolveMethod = 'intelligent' | 'response' | 'text' | 'json' | 'form-data' | 'blob' | 'array-buffer';
@@ -0,0 +1 @@
1
+ export {};
@@ -25,7 +25,10 @@
25
25
  __global__;
26
26
  __prepend_middlewares__ = [];
27
27
  __append_middlewares__ = [];
28
- __options__ = { resolveWithFullResponse: false };
28
+ __options__ = {
29
+ resolveWithFullResponse: false,
30
+ resolveWith: 'intelligent',
31
+ };
29
32
  constructor(url, init, global = {}) {
30
33
  this.__global__ = global;
31
34
  this.requestContext = {
@@ -81,39 +84,58 @@
81
84
  const middleware = (0, compose_middleware_1.composeMiddleware)([...this.__prepend_middlewares__, ...this.__append_middlewares__]);
82
85
  // eslint-disable-next-line @typescript-eslint/no-empty-function
83
86
  await middleware(ctx, async () => { });
84
- let output = ctx[constant_1.OUTPUT_PROPERTY];
85
- if (ctx.options.resolveWithFullResponse) {
87
+ const output = ctx[constant_1.OUTPUT_PROPERTY];
88
+ if (ctx.options.resolveWithFullResponse || ctx.options.resolveWith === 'response') {
86
89
  return ctx.response;
87
90
  }
88
- if (!(constant_1.OUTPUT_PROPERTY in ctx)) {
89
- const response = ctx.response;
90
- if (response?.status === 204) {
91
- // 204: NO CONTENT
92
- output = response && response.body;
91
+ const response = ctx.response;
92
+ if (!response) {
93
+ return (constant_1.OUTPUT_PROPERTY in ctx) ? output : undefined;
94
+ }
95
+ if (ctx.options.resolveWith === 'text') {
96
+ return await response.text();
97
+ }
98
+ else if (ctx.options.resolveWith === 'json') {
99
+ return await response.json();
100
+ }
101
+ else if (ctx.options.resolveWith === 'form-data') {
102
+ return await response.formData();
103
+ }
104
+ else if (ctx.options.resolveWith === 'blob') {
105
+ return await response.blob();
106
+ }
107
+ else if (ctx.options.resolveWith === 'array-buffer') {
108
+ return await response.arrayBuffer();
109
+ }
110
+ if (constant_1.OUTPUT_PROPERTY in ctx) {
111
+ return output;
112
+ }
113
+ if (response.status === 204) {
114
+ // 204: NO CONTENT
115
+ return undefined;
116
+ }
117
+ const contentType = response.headers.get('content-type') || '';
118
+ try {
119
+ if (contentType.includes('application/json')) {
120
+ return await response.json();
93
121
  }
94
- else {
95
- const headers = response?.headers;
96
- const contentType = headers?.get('content-type') || '';
97
- try {
98
- if (contentType.includes('application/json')) {
99
- output = ctx.response && await ctx.response.json();
100
- }
101
- else if (contentType.includes('multipart/form-data')) {
102
- output = ctx.response && await ctx.response.formData();
103
- }
104
- else if (contentType.includes('plain/text')) {
105
- output = ctx.response && await ctx.response.text();
106
- }
107
- else {
108
- output = ctx.response && ctx.response.body;
109
- }
110
- }
111
- catch (e) {
112
- console.warn('Failed to auto parse response body', e);
113
- }
122
+ else if (contentType.includes('multipart/form-data')) {
123
+ return await response.formData();
114
124
  }
125
+ else if (contentType.includes('plain/text')) {
126
+ return await response.text();
127
+ }
128
+ }
129
+ catch (e) {
130
+ console.warn('Failed to auto parse response body', e);
115
131
  }
116
- return output;
132
+ /**
133
+ * Unable to parse response body
134
+ * Return undefined
135
+ * Enable users to discover the problem
136
+ * And modify the method of parsing response
137
+ */
138
+ return undefined;
117
139
  }
118
140
  async end() {
119
141
  return this.run();
@@ -71,4 +71,9 @@ export declare class Keq<T> extends Core<T> {
71
71
  mode(mod: RequestMode): this;
72
72
  flowControl(mode: KeqFlowControlMode, signal?: KeqFlowControlSignal): this;
73
73
  timeout(milliseconds: number): this;
74
+ resolveWith(m: 'response'): Keq<Response>;
75
+ resolveWith(m: 'array-buffer'): Keq<ArrayBuffer>;
76
+ resolveWith(m: 'blob'): Keq<Blob>;
77
+ resolveWith(m: 'text'): Keq<string>;
78
+ resolveWith<U = any>(m: 'json' | 'form-data'): Keq<U>;
74
79
  }
@@ -212,6 +212,11 @@
212
212
  this.option('timeout', { millisecond: milliseconds });
213
213
  return this;
214
214
  }
215
+ resolveWith(m) {
216
+ this.option('resolveWith', m);
217
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
218
+ return this;
219
+ }
215
220
  }
216
221
  exports.Keq = Keq;
217
222
  });
@@ -1,4 +1,5 @@
1
1
  import { KeqFlowControl } from './keq-flow-control.js';
2
+ import { KeqResolveMethod } from './keq-resolve-with-mode.js';
2
3
  import { KeqRetryDelay } from './keq-retry-delay';
3
4
  import { KeqRetryOn } from './keq-retry-on';
4
5
  import { KeqTimeout } from './keq-timeout.js';
@@ -8,8 +9,17 @@ export interface KeqBuildInOptions {
8
9
  * default use node-fetch@2 in node and window.fetch in browser
9
10
  */
10
11
  fetchAPI?: typeof fetch;
11
- /** get response object, defaulted `false` */
12
+ /**
13
+ * get response object, defaulted `false`
14
+ * @deprecated use `resolveWith` instead
15
+ * */
12
16
  resolveWithFullResponse?: boolean;
17
+ /**
18
+ * how to resolve the response body
19
+ * @description 如何解析响应体
20
+ * @default 'intelligent'
21
+ */
22
+ resolveWith?: KeqResolveMethod;
13
23
  /**
14
24
  * The request retry times
15
25
  * @description 重试次数
@@ -0,0 +1 @@
1
+ export type KeqResolveMethod = 'intelligent' | 'response' | 'text' | 'json' | 'form-data' | 'blob' | 'array-buffer';
@@ -0,0 +1,12 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keq",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Request API write by Typescript for flexibility, readability, and a low learning curve.",
5
5
  "keywords": [
6
6
  "request",
@@ -33,7 +33,7 @@
33
33
  "build": "npm run clean && ./build/build.sh",
34
34
  "clean": "rm -rf ./dist/*",
35
35
  "dev": "npm run clean && ./build/watch.sh",
36
- "prepare": "ts-patch install -s && is-ci || husky install",
36
+ "prepare": "ts-patch install -s && is-ci || husky",
37
37
  "prepublishOnly": "npm run build",
38
38
  "release": "standard-version",
39
39
  "release:alpha": "standard-version --prerelease alpha",
@@ -41,35 +41,35 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "clone": "^2.1.2",
44
- "fastq": "^1.16.0",
44
+ "fastq": "^1.17.1",
45
45
  "minimatch": "^9.0.3",
46
46
  "object.fromentries": "^2.0.7",
47
47
  "ts-custom-error": "^3.3.1",
48
48
  "whatwg-url": "^14.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@buka/eslint-config": "^1.5.0",
52
- "@commitlint/cli": "^18.2.0",
53
- "@commitlint/config-conventional": "^18.1.0",
51
+ "@buka/eslint-config": "^1.6.0",
52
+ "@commitlint/cli": "^18.6.1",
53
+ "@commitlint/config-conventional": "^18.6.2",
54
54
  "@jest/globals": "^29.7.0",
55
- "@rushstack/eslint-patch": "^1.5.1",
55
+ "@rushstack/eslint-patch": "^1.7.2",
56
56
  "@types/clone": "^2.1.4",
57
57
  "@types/minimatch": "^5.1.2",
58
- "@types/node": "^20.9.0",
59
- "@types/whatwg-url": "^11.0.3",
60
- "@typescript-eslint/eslint-plugin": "^6.10.0",
61
- "@typescript-eslint/parser": "^6.10.0",
62
- "eslint": "^8.53.0",
63
- "husky": "^8.0.3",
58
+ "@types/node": "^20.11.20",
59
+ "@types/whatwg-url": "^11.0.4",
60
+ "@typescript-eslint/eslint-plugin": "^7.0.2",
61
+ "@typescript-eslint/parser": "^7.0.2",
62
+ "eslint": "^8.57.0",
63
+ "husky": "^9.0.11",
64
64
  "is-ci": "^3.0.1",
65
65
  "jest": "^29.7.0",
66
66
  "jest-mock": "^29.7.0",
67
67
  "standard-version": "^9.5.0",
68
- "ts-jest": "^29.1.1",
69
- "ts-node": "^10.9.1",
70
- "ts-patch": "^3.0.2",
71
- "typescript": "^5.2.2",
72
- "typescript-transform-paths": "^3.4.6"
68
+ "ts-jest": "^29.1.2",
69
+ "ts-node": "^10.9.2",
70
+ "ts-patch": "^3.1.2",
71
+ "typescript": "^5.3.3",
72
+ "typescript-transform-paths": "^3.4.7"
73
73
  },
74
74
  "packageManager": "pnpm@8.6.4",
75
75
  "engines": {