keq 5.2.2 → 5.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/dist/index.js CHANGED
@@ -32,8 +32,6 @@ let qs = require("qs");
32
32
  qs = __toESM(qs);
33
33
  let picomatch = require("picomatch");
34
34
  picomatch = __toESM(picomatch);
35
- let fastq = require("fastq");
36
- fastq = __toESM(fastq);
37
35
  //#region src/exception/exception.ts
38
36
  var Exception = class extends ts_custom_error.CustomError {
39
37
  constructor(message) {
@@ -349,10 +347,6 @@ var GatewayTimeoutException = class extends RequestException {
349
347
  }
350
348
  };
351
349
  //#endregion
352
- //#region src/utils/base64.ts
353
- const base64Encode = globalThis.btoa || ((str) => Buffer.from(str).toString("base64"));
354
- globalThis.atob;
355
- //#endregion
356
350
  //#region src/validator/validator.ts
357
351
  /**
358
352
  * Validator 类用于类型检查
@@ -437,6 +431,13 @@ var Validator = class Validator {
437
431
  return Validator.isObject(obj) && Validator.isFunction(obj["append"]) && Validator.isFunction(obj["delete"]) && Validator.isFunction(obj["entries"]) && Validator.isFunction(obj["forEach"]) && Validator.isFunction(obj["get"]) && Validator.isFunction(obj["getAll"]) && Validator.isFunction(obj["has"]) && Validator.isFunction(obj["keys"]) && Validator.isFunction(obj["set"]) && Validator.isFunction(obj["values"]) && Validator.isFunction(obj["sort"]) && Validator.isFunction(obj["toString"]);
438
432
  }
439
433
  /**
434
+ * 检查是否为 AbortSignal
435
+ */
436
+ static isAbortSignal(obj) {
437
+ if (typeof AbortSignal !== "undefined" && obj instanceof AbortSignal) return true;
438
+ return Validator.isObject(obj) && typeof obj["aborted"] === "boolean" && Validator.isFunction(obj["addEventListener"]) && Validator.isFunction(obj["removeEventListener"]);
439
+ }
440
+ /**
440
441
  * 检查是否为合法的 Header 值
441
442
  * 允许范围: HTAB(\t), 0x20-0x7E, 0x80-0xFF
442
443
  * 禁止换行等控制字符
@@ -464,11 +465,6 @@ function shallowClone(obj) {
464
465
  return obj;
465
466
  }
466
467
  //#endregion
467
- //#region src/utils/sleep.ts
468
- function sleep(ms) {
469
- return new Promise((resolve) => setTimeout(resolve, ms));
470
- }
471
- //#endregion
472
468
  //#region src/request-init/utils/clone-body.ts
473
469
  function cloneBody(obj) {
474
470
  if (Validator.isFormData(obj)) {
@@ -1234,6 +1230,15 @@ var Core = class extends Promise {
1234
1230
  _defineProperty(this, "__reject__", void 0);
1235
1231
  _defineProperty(this, "__triggered__", false);
1236
1232
  _defineProperty(this, "__requestPromise__", void 0);
1233
+ _defineProperty(this, "__aborted__", false);
1234
+ _defineProperty(this, "__abortReason__", void 0);
1235
+ _defineProperty(this, "__activeSharedContext__", void 0);
1236
+ _defineProperty(this, "__resolveAbort__", void 0);
1237
+ _defineProperty(this, "__abortPromise__", new Promise((resolve) => {
1238
+ this.__resolveAbort__ = resolve;
1239
+ }));
1240
+ _defineProperty(this, "__abortWhenCleanups__", []);
1241
+ _defineProperty(this, "__settled__", false);
1237
1242
  _defineProperty(this, "requestInit", void 0);
1238
1243
  _defineProperty(this, "__listeners__", {});
1239
1244
  _defineProperty(this, "__global__", void 0);
@@ -1276,6 +1281,63 @@ var Core = class extends Promise {
1276
1281
  for (const [key, value] of Object.entries(opts)) this.__options__[key] = value;
1277
1282
  return this;
1278
1283
  }
1284
+ /**
1285
+ * Abort the request manually.
1286
+ *
1287
+ * The request rejects with the given reason (an {@link AbortException} by default),
1288
+ * the `abort` event is emitted, and no further retry is performed.
1289
+ * It is safe to call `abort()` before the request is sent: the request will be
1290
+ * aborted as soon as it starts.
1291
+ *
1292
+ * @description 手动中止请求
1293
+ * @param reason Abort reason. Defaults to `new AbortException('Request aborted')`.
1294
+ */
1295
+ abort(reason = new AbortException("Request aborted")) {
1296
+ if (this.__aborted__) return;
1297
+ this.__aborted__ = true;
1298
+ this.__abortReason__ = reason;
1299
+ this.__resolveAbort__(reason);
1300
+ this.__activeSharedContext__?.request.abort(reason);
1301
+ this.__clearAbortWhenCleanups__();
1302
+ }
1303
+ /**
1304
+ * Abort the request when the given `AbortSignal` aborts,
1305
+ * or when the given `Promise` settles (resolves or rejects).
1306
+ *
1307
+ * It is designed for the normal `await` flow, so business code
1308
+ * does not need to call `.fire()` first:
1309
+ * `await request.get('/api/cats').abortWhen(signal)`.
1310
+ *
1311
+ * @description 当 signal 中止或 promise 完成时终止请求
1312
+ */
1313
+ abortWhen(source) {
1314
+ if (this.__aborted__ || this.__settled__) return this;
1315
+ if (Validator.isAbortSignal(source)) {
1316
+ const reason = () => source.reason ?? new AbortException("Request aborted by AbortSignal passed to abortWhen()");
1317
+ if (source.aborted) {
1318
+ this.abort(reason());
1319
+ return this;
1320
+ }
1321
+ const onAbort = () => this.abort(reason());
1322
+ source.addEventListener("abort", onAbort, { once: true });
1323
+ this.__abortWhenCleanups__.push(() => source.removeEventListener("abort", onAbort));
1324
+ return this;
1325
+ }
1326
+ if (Validator.isObject(source) && typeof source.then === "function") {
1327
+ const reason = new AbortException("Request aborted by Promise passed to abortWhen()");
1328
+ let cancelled = false;
1329
+ this.__abortWhenCleanups__.push(() => {
1330
+ cancelled = true;
1331
+ });
1332
+ Promise.resolve(source).then(() => {
1333
+ if (!cancelled) this.abort(reason);
1334
+ }, () => {
1335
+ if (!cancelled) this.abort(reason);
1336
+ });
1337
+ return this;
1338
+ }
1339
+ throw new TypeException("abortWhen() expects an AbortSignal or a Promise");
1340
+ }
1279
1341
  derive() {
1280
1342
  const derived = new this.constructor(this.requestInit.url, {
1281
1343
  method: this.requestInit.method,
@@ -1312,68 +1374,103 @@ var Core = class extends Promise {
1312
1374
  const listeners = this.__listeners__[eventName];
1313
1375
  for (const listener of listeners) coreContext.emitter.on(eventName, listener);
1314
1376
  }
1377
+ this.__activeSharedContext__ = coreContext;
1378
+ if (this.__aborted__) coreContext.request.abort(this.__abortReason__);
1315
1379
  return coreContext;
1316
1380
  }
1317
1381
  async run() {
1318
1382
  let attempt;
1319
- while (true) {
1320
- const attemptWithDefault = attempt || 0;
1321
- const sharedContext = this.buildSharedContext();
1322
- const orchestrator = new KeqMiddlewareOrchestrator(sharedContext, this.__middlewares__);
1323
- if (attempt !== void 0) sharedContext.data.retry = { attempt };
1324
- if (attempt && attempt >= 1) sharedContext.emitter.emit("retry", { context: sharedContext });
1325
- let error = null;
1326
- try {
1327
- await orchestrator.execute();
1328
- } catch (err) {
1329
- error = err;
1330
- }
1331
- const retryOn = typeof sharedContext.options.retry?.on === "function" ? sharedContext.options.retry.on : (attempt, error) => {
1332
- if (error instanceof RequestException && error.fatal) return false;
1333
- return !!error;
1334
- };
1335
- let retryTimes = sharedContext.options.retry?.times || 0;
1336
- if (retryTimes) {
1337
- if (retryTimes < 0) retryTimes = 0;
1338
- else if (!Number.isInteger(retryTimes)) retryTimes = Math.floor(retryTimes);
1339
- }
1340
- if (attemptWithDefault >= retryTimes || await retryOn(attemptWithDefault, error, sharedContext) === false) {
1341
- if (error) {
1383
+ try {
1384
+ while (true) {
1385
+ if (this.__aborted__ && attempt !== void 0) throw this.__abortReason__;
1386
+ const attemptWithDefault = attempt || 0;
1387
+ const sharedContext = this.buildSharedContext();
1388
+ const orchestrator = new KeqMiddlewareOrchestrator(sharedContext, this.__middlewares__);
1389
+ if (attempt !== void 0) sharedContext.data.retry = { attempt };
1390
+ if (attempt && attempt >= 1) sharedContext.emitter.emit("retry", { context: sharedContext });
1391
+ let error = null;
1392
+ try {
1393
+ await orchestrator.execute();
1394
+ } catch (err) {
1395
+ error = err;
1396
+ }
1397
+ if (this.__aborted__) {
1398
+ const reason = error ?? this.__abortReason__;
1342
1399
  sharedContext.emitter.emit("error", { context: sharedContext });
1343
- throw error;
1400
+ throw reason;
1401
+ }
1402
+ const retryOn = typeof sharedContext.options.retry?.on === "function" ? sharedContext.options.retry.on : (attempt, error) => {
1403
+ if (error instanceof RequestException && error.fatal) return false;
1404
+ return !!error;
1405
+ };
1406
+ let retryTimes = sharedContext.options.retry?.times || 0;
1407
+ if (retryTimes) {
1408
+ if (retryTimes < 0) retryTimes = 0;
1409
+ else if (!Number.isInteger(retryTimes)) retryTimes = Math.floor(retryTimes);
1410
+ }
1411
+ if (attemptWithDefault >= retryTimes || await retryOn(attemptWithDefault, error, sharedContext) === false) {
1412
+ if (error) {
1413
+ sharedContext.emitter.emit("error", { context: sharedContext });
1414
+ throw error;
1415
+ }
1416
+ return sharedContext;
1344
1417
  }
1345
- return sharedContext;
1418
+ const retryDelay = typeof sharedContext.options.retry?.delay === "function" ? await sharedContext.options.retry.delay(attemptWithDefault, error, sharedContext) : sharedContext.options.retry?.delay || 0;
1419
+ if (retryDelay) await new Promise((resolve) => {
1420
+ const timer = setTimeout(resolve, retryDelay);
1421
+ this.__abortPromise__.then(() => {
1422
+ clearTimeout(timer);
1423
+ resolve();
1424
+ });
1425
+ });
1426
+ attempt = attemptWithDefault + 1;
1346
1427
  }
1347
- const retryDelay = typeof sharedContext.options.retry?.delay === "function" ? await sharedContext.options.retry.delay(attemptWithDefault, error, sharedContext) : sharedContext.options.retry?.delay || 0;
1348
- if (retryDelay) await sleep(retryDelay);
1349
- attempt = attemptWithDefault + 1;
1428
+ } finally {
1429
+ this.__activeSharedContext__ = void 0;
1350
1430
  }
1351
1431
  }
1352
1432
  __execute__() {
1353
1433
  if (!this.__requestPromise__) this.__requestPromise__ = (async () => {
1354
- const coreContext = await this.run();
1355
- const resolveWithMode = coreContext.options.resolveWith;
1356
- if (resolveWithMode === "response") return coreContext.response?.clone();
1357
- const response = coreContext.response;
1358
- if (!resolveWithMode || resolveWithMode === "intelligent") {
1359
- const output = coreContext.output;
1360
- if (output !== void 0) return output;
1361
- return await intelligentParseResponse(response);
1434
+ try {
1435
+ const coreContext = await this.run();
1436
+ const resolveWithMode = coreContext.options.resolveWith;
1437
+ if (resolveWithMode === "response") return coreContext.response?.clone();
1438
+ const response = coreContext.response;
1439
+ if (!resolveWithMode || resolveWithMode === "intelligent") {
1440
+ const output = coreContext.output;
1441
+ if (output !== void 0) return output;
1442
+ return await intelligentParseResponse(response);
1443
+ }
1444
+ if (!response) throw new Exception([
1445
+ `Unable to process the response with '${resolveWithMode}'. Possible causes:`,
1446
+ "1. The request was never initiated or sent",
1447
+ "2. The request failed before a response was received."
1448
+ ].join("\n"));
1449
+ return await resolveWith(response, resolveWithMode);
1450
+ } finally {
1451
+ this.__settled__ = true;
1452
+ this.__clearAbortWhenCleanups__();
1362
1453
  }
1363
- if (!response) throw new Exception([
1364
- `Unable to process the response with '${resolveWithMode}'. Possible causes:`,
1365
- "1. The request was never initiated or sent",
1366
- "2. The request failed before a response was received."
1367
- ].join("\n"));
1368
- return await resolveWith(response, resolveWithMode);
1369
1454
  })();
1370
1455
  return this.__requestPromise__;
1371
1456
  }
1457
+ __clearAbortWhenCleanups__() {
1458
+ const cleanups = this.__abortWhenCleanups__;
1459
+ this.__abortWhenCleanups__ = [];
1460
+ for (const cleanup of cleanups) cleanup();
1461
+ }
1462
+ /**
1463
+ * Trigger the request without waiting for the result.
1464
+ *
1465
+ * Returns the request instance itself, so the handler can still be used later,
1466
+ * for example `request.get('/api/cats').fire().abort()`.
1467
+ */
1372
1468
  fire() {
1373
1469
  if (!this.__triggered__) {
1374
1470
  this.__triggered__ = true;
1375
1471
  this.__execute__().then(this.__resolve__, this.__reject__);
1376
1472
  }
1473
+ return this;
1377
1474
  }
1378
1475
  /** @deprecated Await the Keq instance directly or use `.fire()` for fire-and-forget. */
1379
1476
  end() {
@@ -1394,6 +1491,53 @@ var Core = class extends Promise {
1394
1491
  }
1395
1492
  };
1396
1493
  //#endregion
1494
+ //#region src/utils/base64.ts
1495
+ const base64Encode = globalThis.btoa || ((str) => Buffer.from(str).toString("base64"));
1496
+ globalThis.atob;
1497
+ //#endregion
1498
+ //#region src/utils/semaphore.ts
1499
+ /**
1500
+ * Concurrency limiter that preserves the caller's async context.
1501
+ *
1502
+ * @description
1503
+ * 并发限流器。等待者 await 属于自己的 Promise,`release()` 仅负责 resolve,
1504
+ * 因此等待者在自己的 async context 中恢复 —— Node 的 AsyncLocalStorage
1505
+ * 上下文得以保留。这是它与 fastq 等「由前一个任务的完成回调驱动下一个任务」
1506
+ * 的队列实现的关键区别。
1507
+ */
1508
+ var Semaphore = class {
1509
+ constructor(concurrency) {
1510
+ this.concurrency = concurrency;
1511
+ _defineProperty(this, "running", 0);
1512
+ _defineProperty(this, "waiters", []);
1513
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new TypeError("Semaphore concurrency must be a positive integer");
1514
+ }
1515
+ /**
1516
+ * Acquire a slot, waiting until one is released if the limit is reached.
1517
+ *
1518
+ * @description 获取一个名额。若已达并发上限,则等待直到有名额释放。
1519
+ */
1520
+ async acquire() {
1521
+ if (this.running < this.concurrency) {
1522
+ this.running++;
1523
+ return;
1524
+ }
1525
+ await new Promise((resolve) => {
1526
+ this.waiters.push(resolve);
1527
+ });
1528
+ }
1529
+ /**
1530
+ * Release a slot, handing it directly to the next waiter when one exists.
1531
+ *
1532
+ * @description 释放名额。存在等待者时直接移交名额(不减计数),否则减少计数。
1533
+ */
1534
+ release() {
1535
+ const waiter = this.waiters.shift();
1536
+ if (waiter) waiter();
1537
+ else if (this.running > 0) this.running--;
1538
+ }
1539
+ };
1540
+ //#endregion
1397
1541
  //#region src/request/keq.ts
1398
1542
  /**
1399
1543
  * extends Core to provide a fluent API for building HTTP requests
@@ -1775,10 +1919,14 @@ function keqSerialFlowControlMiddleware() {
1775
1919
  const key = typeof signal === "string" ? signal : signal(ctx);
1776
1920
  if (!ctx.global.core) ctx.global.core = {};
1777
1921
  if (!ctx.global.core.serialFlowControl) ctx.global.core.serialFlowControl = {};
1778
- if (!ctx.global.core.serialFlowControl[key]) ctx.global.core.serialFlowControl[key] = fastq.promise(async ({ next }) => {
1922
+ if (!ctx.global.core.serialFlowControl[key]) ctx.global.core.serialFlowControl[key] = new Semaphore(concurrent);
1923
+ const semaphore = ctx.global.core.serialFlowControl[key];
1924
+ await semaphore.acquire();
1925
+ try {
1779
1926
  await next();
1780
- }, concurrent);
1781
- await ctx.global.core.serialFlowControl[key].push({ next });
1927
+ } finally {
1928
+ semaphore.release();
1929
+ }
1782
1930
  };
1783
1931
  }
1784
1932
  //#endregion
@@ -1994,6 +2142,7 @@ exports.PreconditionFailedException = PreconditionFailedException;
1994
2142
  exports.ProxyAuthenticationRequiredException = ProxyAuthenticationRequiredException;
1995
2143
  exports.RequestException = RequestException;
1996
2144
  exports.RequestTimeoutException = RequestTimeoutException;
2145
+ exports.Semaphore = Semaphore;
1997
2146
  exports.ServiceUnavailableException = ServiceUnavailableException;
1998
2147
  exports.TimeoutException = TimeoutException;
1999
2148
  exports.TooManyRequestsException = TooManyRequestsException;