keq 2.5.0 → 2.5.2

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,26 @@
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.5.2](https://github.com/keq-request/keq/compare/v2.5.1...v2.5.2) (2024-05-22)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * composeMiddleware should not change parameters ([131c1d4](https://github.com/keq-request/keq/commit/131c1d42227eeda2d42436c5285426bf3f787df0))
11
+
12
+
13
+ ### Performance Improvements
14
+
15
+ * avoid duplication of compose middleware ([c4e5cc1](https://github.com/keq-request/keq/commit/c4e5cc161d94b2440768c778489ee5ac1bb07484))
16
+ * warn for incorrect invoke next() ([f4b418f](https://github.com/keq-request/keq/commit/f4b418f28e3df07bad0c03e2aca811ed6254e57c)), closes [#74](https://github.com/keq-request/keq/issues/74)
17
+
18
+ ## [2.5.1](https://github.com/keq-request/keq/compare/v2.5.0...v2.5.1) (2024-05-21)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * throw error when retryOn is not set ([cbf4594](https://github.com/keq-request/keq/commit/cbf45942e113a4f57c4351abd03a82cbab5a0a6a))
24
+
5
25
  ## [2.5.0](https://github.com/keq-request/keq/compare/v2.4.1...v2.5.0) (2024-05-21)
6
26
 
7
27
 
@@ -1 +1,2 @@
1
1
  export declare const OUTPUT_PROPERTY: unique symbol;
2
+ export declare const NEXT_INVOKED_PROPERTY: unique symbol;
@@ -1 +1,2 @@
1
1
  export const OUTPUT_PROPERTY = Symbol('outputProperty');
2
+ export const NEXT_INVOKED_PROPERTY = Symbol('nextInvokedProperty');
@@ -1,7 +1,7 @@
1
1
  import { URL } from 'whatwg-url';
2
2
  import { Exception } from "./exception/exception.js";
3
3
  import { clone } from "./util/clone.js";
4
- import { OUTPUT_PROPERTY } from './constant.js';
4
+ import { NEXT_INVOKED_PROPERTY, OUTPUT_PROPERTY } from './constant.js';
5
5
  import { composeMiddleware } from './util/compose-middleware.js';
6
6
  import { shadowClone } from './util/shadow-clone.js';
7
7
  /**
@@ -59,6 +59,11 @@ export class Core {
59
59
  };
60
60
  const options = shadowClone(this.__options__);
61
61
  const ctx = {
62
+ [NEXT_INVOKED_PROPERTY]: {
63
+ finished: false,
64
+ entryNextTimes: 0,
65
+ outNextTimes: 0,
66
+ },
62
67
  request: requestContext,
63
68
  options,
64
69
  global: this.__global__,
@@ -171,7 +171,9 @@ export class Keq extends Core {
171
171
  retry(retryTimes, retryDelay, retryOn) {
172
172
  this.option('retryTimes', retryTimes);
173
173
  this.option('retryDelay', retryDelay);
174
- this.option('retryOn', retryOn);
174
+ if (retryOn !== undefined) {
175
+ this.option('retryOn', retryOn);
176
+ }
175
177
  return this;
176
178
  }
177
179
  redirect(mod) {
@@ -1,11 +1,20 @@
1
+ import { NEXT_INVOKED_PROPERTY } from "../constant.js";
1
2
  function sleep(ms) {
2
3
  return new Promise((resolve) => setTimeout(resolve, ms));
3
4
  }
4
5
  export function retryMiddleware() {
5
6
  return async function retryMiddleware(ctx, next) {
6
7
  const retryTimes = (Number(ctx.options.retryTimes) || 0) + 1;
7
- const retryDelay = ctx.options.retryDelay || 10;
8
- const retryOn = ctx.options.retryOn;
8
+ const retryDelay = (attempt, error, ctx) => {
9
+ if (typeof ctx.options.retryDelay === 'function') {
10
+ return ctx.options.retryDelay(attempt, error, ctx);
11
+ }
12
+ else if (typeof ctx.options.retryDelay === 'number') {
13
+ return ctx.options.retryDelay;
14
+ }
15
+ return 10;
16
+ };
17
+ const retryOn = typeof ctx.options.retryOn === 'function' ? ctx.options.retryOn : undefined;
9
18
  // Avoid multiple middleware from being added repeatedly
10
19
  ctx.options = {
11
20
  ...ctx.options,
@@ -16,6 +25,8 @@ export function retryMiddleware() {
16
25
  for (let i = 0; i < retryTimes; i++) {
17
26
  let err = null;
18
27
  try {
28
+ ctx[NEXT_INVOKED_PROPERTY].entryNextTimes = 0;
29
+ ctx[NEXT_INVOKED_PROPERTY].outNextTimes = 0;
19
30
  await next();
20
31
  }
21
32
  catch (e) {
@@ -34,7 +45,7 @@ export function retryMiddleware() {
34
45
  else if (!retryOn && !err) {
35
46
  break;
36
47
  }
37
- const delay = typeof retryDelay === 'function' ? retryDelay(i, err, ctx) : retryDelay;
48
+ const delay = retryDelay(i, err, ctx);
38
49
  if (delay > 0)
39
50
  await sleep(delay);
40
51
  }
@@ -10,15 +10,15 @@ export class KeqRouter {
10
10
  this.prependMiddlewares = prependMiddlewares;
11
11
  }
12
12
  route(route, ...middlewares) {
13
- const middleware = async (ctx, next) => {
14
- if (route(ctx)) {
15
- await composeMiddleware(middlewares)(ctx, next);
16
- }
17
- else {
13
+ if (middlewares.length === 0)
14
+ return this;
15
+ const m = middlewares.length > 1 ? composeMiddleware(middlewares) : middlewares[0];
16
+ this.prependMiddlewares.push(async (ctx, next) => {
17
+ if (route(ctx))
18
+ await m(ctx, next);
19
+ else
18
20
  await next();
19
- }
20
- };
21
- this.prependMiddlewares.push(middleware);
21
+ });
22
22
  return this;
23
23
  }
24
24
  host(host, ...middlewares) {
@@ -1,5 +1,5 @@
1
1
  import { URL } from 'whatwg-url';
2
- import { OUTPUT_PROPERTY } from "../constant";
2
+ import { NEXT_INVOKED_PROPERTY, OUTPUT_PROPERTY } from "../constant";
3
3
  import { KeqRequestBody } from './keq-request-body';
4
4
  import { KeqRequestMethod } from './keq-request-method';
5
5
  import { KeqOptionsParameter } from './keq-options.js';
@@ -23,6 +23,17 @@ export interface KeqRequestContext {
23
23
  signal?: AbortSignal | null;
24
24
  }
25
25
  export interface KeqContext {
26
+ /**
27
+ * Middleware invoker counter
28
+ *
29
+ * to prevent someone from calling next
30
+ * multiple times or forgetting to write await
31
+ */
32
+ [NEXT_INVOKED_PROPERTY]: {
33
+ finished: boolean;
34
+ entryNextTimes: number;
35
+ outNextTimes: number;
36
+ };
26
37
  options: KeqContextOptions;
27
38
  /**
28
39
  * Fetch API Arguments
@@ -1 +1 @@
1
- import { OUTPUT_PROPERTY } from "../constant";
1
+ import { NEXT_INVOKED_PROPERTY, OUTPUT_PROPERTY } from "../constant";
@@ -1,12 +1,51 @@
1
1
  import { Exception } from "../exception/exception.js";
2
+ import { NEXT_INVOKED_PROPERTY } from "../constant.js";
2
3
  export function composeMiddleware(middlewares) {
3
4
  if (!middlewares.length) {
4
5
  throw new Exception('At least one middleware');
5
6
  }
6
- const middleware = middlewares
7
+ const middleware = [...middlewares]
7
8
  .reverse()
8
9
  .reduce(function (prev, curr) {
9
- return (ctx, next) => curr(ctx, () => prev(ctx, next));
10
+ return async (ctx, next) => {
11
+ const invoked = {
12
+ finished: false,
13
+ entryNextTimes: 0,
14
+ outNextTimes: 0,
15
+ };
16
+ const context = new Proxy(ctx, {
17
+ get(target, property) {
18
+ if (property === NEXT_INVOKED_PROPERTY) {
19
+ return invoked;
20
+ }
21
+ // @ts-ignore
22
+ return target[property];
23
+ },
24
+ });
25
+ await curr(context, async () => {
26
+ if (invoked.finished) {
27
+ throw new Exception([
28
+ `next() should not invoke after ${curr.toString()} middleware finished.`,
29
+ ].join(''));
30
+ }
31
+ if (invoked.entryNextTimes > 1) {
32
+ console.warn(`next() had be invoke multiple times at ${curr.toString()} middleware`);
33
+ }
34
+ invoked.entryNextTimes += 1;
35
+ await prev(ctx, next);
36
+ invoked.outNextTimes += 1;
37
+ });
38
+ invoked.finished = true;
39
+ if (invoked.entryNextTimes === 0) {
40
+ console.warn(`next() is not invoked at ${curr.toString()}.`);
41
+ }
42
+ if (invoked.entryNextTimes !== invoked.outNextTimes) {
43
+ throw new Exception([
44
+ `next() should be invoke before ${curr.toString()} middleware finish.`,
45
+ 'Maybe you forgot to await when calling next().',
46
+ ].join(''));
47
+ }
48
+ };
10
49
  });
11
50
  return middleware;
12
51
  }
@@ -1 +1,2 @@
1
1
  export declare const OUTPUT_PROPERTY: unique symbol;
2
+ export declare const NEXT_INVOKED_PROPERTY: unique symbol;
@@ -9,6 +9,7 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.OUTPUT_PROPERTY = void 0;
12
+ exports.NEXT_INVOKED_PROPERTY = exports.OUTPUT_PROPERTY = void 0;
13
13
  exports.OUTPUT_PROPERTY = Symbol('outputProperty');
14
+ exports.NEXT_INVOKED_PROPERTY = Symbol('nextInvokedProperty');
14
15
  });
@@ -71,6 +71,11 @@
71
71
  };
72
72
  const options = (0, shadow_clone_js_1.shadowClone)(this.__options__);
73
73
  const ctx = {
74
+ [constant_js_1.NEXT_INVOKED_PROPERTY]: {
75
+ finished: false,
76
+ entryNextTimes: 0,
77
+ outNextTimes: 0,
78
+ },
74
79
  request: requestContext,
75
80
  options,
76
81
  global: this.__global__,
@@ -183,7 +183,9 @@
183
183
  retry(retryTimes, retryDelay, retryOn) {
184
184
  this.option('retryTimes', retryTimes);
185
185
  this.option('retryDelay', retryDelay);
186
- this.option('retryOn', retryOn);
186
+ if (retryOn !== undefined) {
187
+ this.option('retryOn', retryOn);
188
+ }
187
189
  return this;
188
190
  }
189
191
  redirect(mod) {
@@ -4,20 +4,29 @@
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", "../constant.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.retryMiddleware = void 0;
13
+ const constant_js_1 = require("../constant.js");
13
14
  function sleep(ms) {
14
15
  return new Promise((resolve) => setTimeout(resolve, ms));
15
16
  }
16
17
  function retryMiddleware() {
17
18
  return async function retryMiddleware(ctx, next) {
18
19
  const retryTimes = (Number(ctx.options.retryTimes) || 0) + 1;
19
- const retryDelay = ctx.options.retryDelay || 10;
20
- const retryOn = ctx.options.retryOn;
20
+ const retryDelay = (attempt, error, ctx) => {
21
+ if (typeof ctx.options.retryDelay === 'function') {
22
+ return ctx.options.retryDelay(attempt, error, ctx);
23
+ }
24
+ else if (typeof ctx.options.retryDelay === 'number') {
25
+ return ctx.options.retryDelay;
26
+ }
27
+ return 10;
28
+ };
29
+ const retryOn = typeof ctx.options.retryOn === 'function' ? ctx.options.retryOn : undefined;
21
30
  // Avoid multiple middleware from being added repeatedly
22
31
  ctx.options = {
23
32
  ...ctx.options,
@@ -28,6 +37,8 @@
28
37
  for (let i = 0; i < retryTimes; i++) {
29
38
  let err = null;
30
39
  try {
40
+ ctx[constant_js_1.NEXT_INVOKED_PROPERTY].entryNextTimes = 0;
41
+ ctx[constant_js_1.NEXT_INVOKED_PROPERTY].outNextTimes = 0;
31
42
  await next();
32
43
  }
33
44
  catch (e) {
@@ -46,7 +57,7 @@
46
57
  else if (!retryOn && !err) {
47
58
  break;
48
59
  }
49
- const delay = typeof retryDelay === 'function' ? retryDelay(i, err, ctx) : retryDelay;
60
+ const delay = retryDelay(i, err, ctx);
50
61
  if (delay > 0)
51
62
  await sleep(delay);
52
63
  }
@@ -22,15 +22,15 @@
22
22
  this.prependMiddlewares = prependMiddlewares;
23
23
  }
24
24
  route(route, ...middlewares) {
25
- const middleware = async (ctx, next) => {
26
- if (route(ctx)) {
27
- await (0, compose_middleware_js_1.composeMiddleware)(middlewares)(ctx, next);
28
- }
29
- else {
25
+ if (middlewares.length === 0)
26
+ return this;
27
+ const m = middlewares.length > 1 ? (0, compose_middleware_js_1.composeMiddleware)(middlewares) : middlewares[0];
28
+ this.prependMiddlewares.push(async (ctx, next) => {
29
+ if (route(ctx))
30
+ await m(ctx, next);
31
+ else
30
32
  await next();
31
- }
32
- };
33
- this.prependMiddlewares.push(middleware);
33
+ });
34
34
  return this;
35
35
  }
36
36
  host(host, ...middlewares) {
@@ -1,5 +1,5 @@
1
1
  import { URL } from 'whatwg-url';
2
- import { OUTPUT_PROPERTY } from "../constant";
2
+ import { NEXT_INVOKED_PROPERTY, OUTPUT_PROPERTY } from "../constant";
3
3
  import { KeqRequestBody } from './keq-request-body';
4
4
  import { KeqRequestMethod } from './keq-request-method';
5
5
  import { KeqOptionsParameter } from './keq-options.js';
@@ -23,6 +23,17 @@ export interface KeqRequestContext {
23
23
  signal?: AbortSignal | null;
24
24
  }
25
25
  export interface KeqContext {
26
+ /**
27
+ * Middleware invoker counter
28
+ *
29
+ * to prevent someone from calling next
30
+ * multiple times or forgetting to write await
31
+ */
32
+ [NEXT_INVOKED_PROPERTY]: {
33
+ finished: boolean;
34
+ entryNextTimes: number;
35
+ outNextTimes: number;
36
+ };
26
37
  options: KeqContextOptions;
27
38
  /**
28
39
  * Fetch API Arguments
@@ -4,21 +4,60 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "../exception/exception.js"], factory);
7
+ define(["require", "exports", "../exception/exception.js", "../constant.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.composeMiddleware = void 0;
13
13
  const exception_js_1 = require("../exception/exception.js");
14
+ const constant_js_1 = require("../constant.js");
14
15
  function composeMiddleware(middlewares) {
15
16
  if (!middlewares.length) {
16
17
  throw new exception_js_1.Exception('At least one middleware');
17
18
  }
18
- const middleware = middlewares
19
+ const middleware = [...middlewares]
19
20
  .reverse()
20
21
  .reduce(function (prev, curr) {
21
- return (ctx, next) => curr(ctx, () => prev(ctx, next));
22
+ return async (ctx, next) => {
23
+ const invoked = {
24
+ finished: false,
25
+ entryNextTimes: 0,
26
+ outNextTimes: 0,
27
+ };
28
+ const context = new Proxy(ctx, {
29
+ get(target, property) {
30
+ if (property === constant_js_1.NEXT_INVOKED_PROPERTY) {
31
+ return invoked;
32
+ }
33
+ // @ts-ignore
34
+ return target[property];
35
+ },
36
+ });
37
+ await curr(context, async () => {
38
+ if (invoked.finished) {
39
+ throw new exception_js_1.Exception([
40
+ `next() should not invoke after ${curr.toString()} middleware finished.`,
41
+ ].join(''));
42
+ }
43
+ if (invoked.entryNextTimes > 1) {
44
+ console.warn(`next() had be invoke multiple times at ${curr.toString()} middleware`);
45
+ }
46
+ invoked.entryNextTimes += 1;
47
+ await prev(ctx, next);
48
+ invoked.outNextTimes += 1;
49
+ });
50
+ invoked.finished = true;
51
+ if (invoked.entryNextTimes === 0) {
52
+ console.warn(`next() is not invoked at ${curr.toString()}.`);
53
+ }
54
+ if (invoked.entryNextTimes !== invoked.outNextTimes) {
55
+ throw new exception_js_1.Exception([
56
+ `next() should be invoke before ${curr.toString()} middleware finish.`,
57
+ 'Maybe you forgot to await when calling next().',
58
+ ].join(''));
59
+ }
60
+ };
22
61
  });
23
62
  return middleware;
24
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keq",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "Request API write by Typescript for flexibility, readability, and a low learning curve.",
5
5
  "keywords": [
6
6
  "request",
@@ -71,7 +71,7 @@
71
71
  "typescript": "^5.4.5",
72
72
  "typescript-transform-paths": "^3.4.7"
73
73
  },
74
- "packageManager": "pnpm@8.6.4",
74
+ "packageManager": "pnpm@9.1.2",
75
75
  "engines": {
76
76
  "node": ">=18.0.0"
77
77
  }