keq 2.8.11 → 2.8.12

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,8 @@
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.8.12](https://github.com/keq-request/keq/compare/v2.8.11...v2.8.12) (2025-12-16)
6
+
5
7
  ## [2.8.11](https://github.com/keq-request/keq/compare/v2.8.10...v2.8.11) (2025-06-05)
6
8
 
7
9
 
@@ -6,6 +6,7 @@ import { ABORT_PROPERTY, OUTPUT_PROPERTY } from './constant.js';
6
6
  import { composeMiddleware } from './util/compose-middleware.js';
7
7
  import { shallowClone } from './util/shallow-clone.js';
8
8
  import { compileUrl } from './util/compile-url.js';
9
+ import { unwrap } from './util/fork.js';
9
10
  /**
10
11
  * @description Keq 核心 API,发送请求必要的原子化的API
11
12
  */
@@ -109,7 +110,7 @@ export class Core {
109
110
  await middleware(ctx, async function emptyNext() { });
110
111
  const output = ctx[OUTPUT_PROPERTY];
111
112
  if (ctx.options.resolveWithFullResponse || ctx.options.resolveWith === 'response') {
112
- return ctx.response;
113
+ return ctx.response?.clone();
113
114
  }
114
115
  const response = ctx.response;
115
116
  if (!response) {
@@ -119,7 +120,7 @@ export class Core {
119
120
  return await response.text();
120
121
  }
121
122
  else if (ctx.options.resolveWith === 'json') {
122
- return await response.json();
123
+ return unwrap(await response.json());
123
124
  }
124
125
  else if (ctx.options.resolveWith === 'form-data') {
125
126
  return await response.formData();
@@ -140,7 +141,7 @@ export class Core {
140
141
  const contentType = response.headers.get('content-type') || '';
141
142
  try {
142
143
  if (contentType.includes('application/json')) {
143
- return await response.json();
144
+ return unwrap(await response.json());
144
145
  }
145
146
  else if (contentType.includes('multipart/form-data')) {
146
147
  return await response.formData();
@@ -1 +1 @@
1
- export declare function createResponseProxy(res: Response): Response;
1
+ export declare function createResponseProxy(response: Response): Response;
@@ -1,20 +1,50 @@
1
- export function createResponseProxy(res) {
2
- return new Proxy(res, {
1
+ import { fork } from './fork.js';
2
+ const JsonCachePropertyKey = Symbol('KeqResponseProxyJsonCachePropertyKey');
3
+ const TextCachePropertyKey = Symbol('KeqResponseProxyTextCachePropertyKey');
4
+ export function createResponseProxy(response) {
5
+ return new Proxy(response, {
3
6
  get(res, prop) {
4
7
  if (typeof prop === 'string') {
5
- if (['json', 'text', 'arrayBuffer', 'blob', 'buffer', 'formData'].includes(prop)) {
8
+ if (prop === 'json') {
9
+ return new Proxy(res[prop], {
10
+ apply() {
11
+ if (res[JsonCachePropertyKey]) {
12
+ return fork(res[JsonCachePropertyKey]);
13
+ }
14
+ return res.clone().json()
15
+ .then((body) => {
16
+ res[JsonCachePropertyKey] = body;
17
+ return fork(body);
18
+ });
19
+ },
20
+ });
21
+ }
22
+ else if (prop === 'text') {
23
+ return new Proxy(res[prop], {
24
+ apply() {
25
+ if (res[TextCachePropertyKey]) {
26
+ return fork(res[TextCachePropertyKey]);
27
+ }
28
+ return res.clone().text()
29
+ .then((body) => {
30
+ res[TextCachePropertyKey] = body;
31
+ return body;
32
+ });
33
+ },
34
+ });
35
+ }
36
+ else if (['arrayBuffer', 'blob', 'buffer', 'formData'].includes(prop)) {
6
37
  /**
7
38
  * clone when invoking body, json, text, arrayBuffer, blob, buffer, formData
8
39
  * to avoid time-consuming cloning
9
40
  */
10
41
  return new Proxy(res[prop], {
11
42
  apply(target, thisArg, argArray) {
12
- const mirror = res.clone();
13
- return mirror[prop](...argArray);
43
+ return res.clone()[prop](...argArray);
14
44
  },
15
45
  });
16
46
  }
17
- if (prop === 'body') {
47
+ else if (prop === 'body') {
18
48
  const mirror = res.clone();
19
49
  return mirror.body;
20
50
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Fork an json object, using copy-on-write strategy to avoid unnecessary deep cloning.
3
+ */
4
+ export declare function fork<T>(original: T): T;
5
+ export declare function unwrap<T>(proxy: T): T;
@@ -0,0 +1,76 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
2
+ import { klona } from 'klona/json';
3
+ const UnWrapPropertyKey = Symbol('UnWrapPropertyKey');
4
+ const ARRAY_MUTATORS = new Set([
5
+ 'push',
6
+ 'pop',
7
+ 'shift',
8
+ 'unshift',
9
+ 'splice',
10
+ 'sort',
11
+ 'reverse',
12
+ 'fill',
13
+ 'copyWithin',
14
+ ]);
15
+ function objectPath(obj, path) {
16
+ return path.reduce((o, k) => o[k], obj);
17
+ }
18
+ /**
19
+ * Fork an json object, using copy-on-write strategy to avoid unnecessary deep cloning.
20
+ */
21
+ export function fork(original) {
22
+ if (original === null || typeof original !== 'object') {
23
+ // primitive value, return directly
24
+ return original;
25
+ }
26
+ let current = original;
27
+ const ensureCopy = () => {
28
+ if (current === original) {
29
+ current = klona(original);
30
+ }
31
+ return current;
32
+ };
33
+ const createProxy = (path = []) => {
34
+ return new Proxy({}, {
35
+ get(_, prop) {
36
+ const target = objectPath(current, path);
37
+ if (prop === UnWrapPropertyKey)
38
+ return target;
39
+ const value = target[prop];
40
+ // return value directly if already copied
41
+ if (current !== original) {
42
+ return value;
43
+ }
44
+ // handle array mutator methods
45
+ if (Array.isArray(target) && ARRAY_MUTATORS.has(prop)) {
46
+ return new Proxy(value, {
47
+ apply(fn, thisArg, args) {
48
+ ensureCopy();
49
+ const t = objectPath(current, path);
50
+ return Reflect.apply(t[prop], t, args);
51
+ },
52
+ });
53
+ }
54
+ // 未复制,嵌套对象需要继续代理
55
+ if (typeof value === 'object' && value !== null) {
56
+ return createProxy([...path, prop]);
57
+ }
58
+ return value;
59
+ },
60
+ set(_, prop, value) {
61
+ ensureCopy();
62
+ objectPath(current, path)[prop] = value;
63
+ return true;
64
+ },
65
+ deleteProperty(_, prop) {
66
+ ensureCopy();
67
+ delete objectPath(current, path)[prop];
68
+ return true;
69
+ },
70
+ });
71
+ };
72
+ return createProxy();
73
+ }
74
+ export function unwrap(proxy) {
75
+ return proxy && proxy[UnWrapPropertyKey] ? proxy[UnWrapPropertyKey] : proxy;
76
+ }
@@ -7,7 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  if (v !== undefined) module.exports = v;
8
8
  }
9
9
  else if (typeof define === "function" && define.amd) {
10
- define(["require", "exports", "mitt", "./exception/exception.js", "./util/clone-body.js", "./constant.js", "./util/compose-middleware.js", "./util/shallow-clone.js", "./util/compile-url.js"], factory);
10
+ define(["require", "exports", "mitt", "./exception/exception.js", "./util/clone-body.js", "./constant.js", "./util/compose-middleware.js", "./util/shallow-clone.js", "./util/compile-url.js", "./util/fork.js"], factory);
11
11
  }
12
12
  })(function (require, exports) {
13
13
  "use strict";
@@ -21,6 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  const compose_middleware_js_1 = require("./util/compose-middleware.js");
22
22
  const shallow_clone_js_1 = require("./util/shallow-clone.js");
23
23
  const compile_url_js_1 = require("./util/compile-url.js");
24
+ const fork_js_1 = require("./util/fork.js");
24
25
  /**
25
26
  * @description Keq 核心 API,发送请求必要的原子化的API
26
27
  */
@@ -124,7 +125,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
124
125
  await middleware(ctx, async function emptyNext() { });
125
126
  const output = ctx[constant_js_1.OUTPUT_PROPERTY];
126
127
  if (ctx.options.resolveWithFullResponse || ctx.options.resolveWith === 'response') {
127
- return ctx.response;
128
+ return ctx.response?.clone();
128
129
  }
129
130
  const response = ctx.response;
130
131
  if (!response) {
@@ -134,7 +135,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
134
135
  return await response.text();
135
136
  }
136
137
  else if (ctx.options.resolveWith === 'json') {
137
- return await response.json();
138
+ return (0, fork_js_1.unwrap)(await response.json());
138
139
  }
139
140
  else if (ctx.options.resolveWith === 'form-data') {
140
141
  return await response.formData();
@@ -155,7 +156,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
155
156
  const contentType = response.headers.get('content-type') || '';
156
157
  try {
157
158
  if (contentType.includes('application/json')) {
158
- return await response.json();
159
+ return (0, fork_js_1.unwrap)(await response.json());
159
160
  }
160
161
  else if (contentType.includes('multipart/form-data')) {
161
162
  return await response.formData();
@@ -1 +1 @@
1
- export declare function createResponseProxy(res: Response): Response;
1
+ export declare function createResponseProxy(response: Response): Response;
@@ -4,29 +4,59 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports"], factory);
7
+ define(["require", "exports", "./fork.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.createResponseProxy = void 0;
13
- function createResponseProxy(res) {
14
- return new Proxy(res, {
13
+ const fork_js_1 = require("./fork.js");
14
+ const JsonCachePropertyKey = Symbol('KeqResponseProxyJsonCachePropertyKey');
15
+ const TextCachePropertyKey = Symbol('KeqResponseProxyTextCachePropertyKey');
16
+ function createResponseProxy(response) {
17
+ return new Proxy(response, {
15
18
  get(res, prop) {
16
19
  if (typeof prop === 'string') {
17
- if (['json', 'text', 'arrayBuffer', 'blob', 'buffer', 'formData'].includes(prop)) {
20
+ if (prop === 'json') {
21
+ return new Proxy(res[prop], {
22
+ apply() {
23
+ if (res[JsonCachePropertyKey]) {
24
+ return (0, fork_js_1.fork)(res[JsonCachePropertyKey]);
25
+ }
26
+ return res.clone().json()
27
+ .then((body) => {
28
+ res[JsonCachePropertyKey] = body;
29
+ return (0, fork_js_1.fork)(body);
30
+ });
31
+ },
32
+ });
33
+ }
34
+ else if (prop === 'text') {
35
+ return new Proxy(res[prop], {
36
+ apply() {
37
+ if (res[TextCachePropertyKey]) {
38
+ return (0, fork_js_1.fork)(res[TextCachePropertyKey]);
39
+ }
40
+ return res.clone().text()
41
+ .then((body) => {
42
+ res[TextCachePropertyKey] = body;
43
+ return body;
44
+ });
45
+ },
46
+ });
47
+ }
48
+ else if (['arrayBuffer', 'blob', 'buffer', 'formData'].includes(prop)) {
18
49
  /**
19
50
  * clone when invoking body, json, text, arrayBuffer, blob, buffer, formData
20
51
  * to avoid time-consuming cloning
21
52
  */
22
53
  return new Proxy(res[prop], {
23
54
  apply(target, thisArg, argArray) {
24
- const mirror = res.clone();
25
- return mirror[prop](...argArray);
55
+ return res.clone()[prop](...argArray);
26
56
  },
27
57
  });
28
58
  }
29
- if (prop === 'body') {
59
+ else if (prop === 'body') {
30
60
  const mirror = res.clone();
31
61
  return mirror.body;
32
62
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Fork an json object, using copy-on-write strategy to avoid unnecessary deep cloning.
3
+ */
4
+ export declare function fork<T>(original: T): T;
5
+ export declare function unwrap<T>(proxy: T): T;
@@ -0,0 +1,91 @@
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", "klona/json"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.unwrap = exports.fork = void 0;
13
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
14
+ const json_1 = require("klona/json");
15
+ const UnWrapPropertyKey = Symbol('UnWrapPropertyKey');
16
+ const ARRAY_MUTATORS = new Set([
17
+ 'push',
18
+ 'pop',
19
+ 'shift',
20
+ 'unshift',
21
+ 'splice',
22
+ 'sort',
23
+ 'reverse',
24
+ 'fill',
25
+ 'copyWithin',
26
+ ]);
27
+ function objectPath(obj, path) {
28
+ return path.reduce((o, k) => o[k], obj);
29
+ }
30
+ /**
31
+ * Fork an json object, using copy-on-write strategy to avoid unnecessary deep cloning.
32
+ */
33
+ function fork(original) {
34
+ if (original === null || typeof original !== 'object') {
35
+ // primitive value, return directly
36
+ return original;
37
+ }
38
+ let current = original;
39
+ const ensureCopy = () => {
40
+ if (current === original) {
41
+ current = (0, json_1.klona)(original);
42
+ }
43
+ return current;
44
+ };
45
+ const createProxy = (path = []) => {
46
+ return new Proxy({}, {
47
+ get(_, prop) {
48
+ const target = objectPath(current, path);
49
+ if (prop === UnWrapPropertyKey)
50
+ return target;
51
+ const value = target[prop];
52
+ // return value directly if already copied
53
+ if (current !== original) {
54
+ return value;
55
+ }
56
+ // handle array mutator methods
57
+ if (Array.isArray(target) && ARRAY_MUTATORS.has(prop)) {
58
+ return new Proxy(value, {
59
+ apply(fn, thisArg, args) {
60
+ ensureCopy();
61
+ const t = objectPath(current, path);
62
+ return Reflect.apply(t[prop], t, args);
63
+ },
64
+ });
65
+ }
66
+ // 未复制,嵌套对象需要继续代理
67
+ if (typeof value === 'object' && value !== null) {
68
+ return createProxy([...path, prop]);
69
+ }
70
+ return value;
71
+ },
72
+ set(_, prop, value) {
73
+ ensureCopy();
74
+ objectPath(current, path)[prop] = value;
75
+ return true;
76
+ },
77
+ deleteProperty(_, prop) {
78
+ ensureCopy();
79
+ delete objectPath(current, path)[prop];
80
+ return true;
81
+ },
82
+ });
83
+ };
84
+ return createProxy();
85
+ }
86
+ exports.fork = fork;
87
+ function unwrap(proxy) {
88
+ return proxy && proxy[UnWrapPropertyKey] ? proxy[UnWrapPropertyKey] : proxy;
89
+ }
90
+ exports.unwrap = unwrap;
91
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keq",
3
- "version": "2.8.11",
3
+ "version": "2.8.12",
4
4
  "description": "Request API write by Typescript for flexibility, readability, and a low learning curve.",
5
5
  "keywords": [
6
6
  "request",
@@ -31,18 +31,9 @@
31
31
  "type": "git",
32
32
  "url": "git+https://github.com/keq-request/keq.git"
33
33
  },
34
- "scripts": {
35
- "build": "npm run clean && ./build/build.sh",
36
- "clean": "rm -rf ./dist/*",
37
- "dev": "npm run clean && ./build/watch.sh",
38
- "prepare": "ts-patch install -s && is-ci || husky",
39
- "prepublishOnly": "npm run build",
40
- "release": "standard-version",
41
- "release:alpha": "standard-version --prerelease alpha",
42
- "test": "jest"
43
- },
44
34
  "dependencies": {
45
35
  "fastq": "^1.17.1",
36
+ "klona": "^2.0.6",
46
37
  "minimatch": "^9.0.4",
47
38
  "mitt": "^3.0.1",
48
39
  "ts-custom-error": "^3.3.1"
@@ -68,8 +59,15 @@
68
59
  "typescript": "5.4.5",
69
60
  "typescript-transform-paths": "^3.5.1"
70
61
  },
71
- "packageManager": "pnpm@9.15.1",
72
62
  "engines": {
73
63
  "node": ">=18.0.0"
64
+ },
65
+ "scripts": {
66
+ "build": "npm run clean && ./build/build.sh",
67
+ "clean": "rm -rf ./dist/*",
68
+ "dev": "npm run clean && ./build/watch.sh",
69
+ "release": "standard-version",
70
+ "release:alpha": "standard-version --prerelease alpha",
71
+ "test": "jest"
74
72
  }
75
- }
73
+ }