@wildix/xbees-conversations-utils 1.1.57 → 1.1.59

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.
Files changed (50) hide show
  1. package/dist-cjs/index.js +2 -2
  2. package/dist-cjs/rateLimits/RedisCooldown.js +45 -35
  3. package/dist-cjs/rateLimits/constants.js +6 -13
  4. package/dist-cjs/rateLimits/createRateLimitedStreamProxy.js +30 -73
  5. package/dist-cjs/rateLimits/exception/createRateLimitExceededException.js +17 -14
  6. package/dist-cjs/rateLimits/exception/processStreamRateLimitException.js +39 -23
  7. package/dist-cjs/rateLimits/executeStreamRequestWithRateLimitHandling.js +86 -67
  8. package/dist-cjs/rateLimits/helpers/RedisCooldown.js +56 -0
  9. package/dist-cjs/rateLimits/helpers/createRateLimitExceededException.js +18 -0
  10. package/dist-cjs/rateLimits/helpers/executeWithRateLimitHandling.js +73 -0
  11. package/dist-cjs/rateLimits/helpers/isStreamChannelLike.js +15 -0
  12. package/dist-cjs/rateLimits/helpers/processStreamRateLimitException.js +31 -0
  13. package/dist-cjs/rateLimits/helpers/splitCallArgsAndOptions.js +21 -0
  14. package/dist-cjs/rateLimits/index.js +1 -0
  15. package/dist-cjs/rateLimits/types.js +1 -0
  16. package/dist-cjs/rateLimits/withStreamRateLimitOptions.js +10 -0
  17. package/dist-es/index.js +2 -2
  18. package/dist-es/rateLimits/RedisCooldown.js +43 -33
  19. package/dist-es/rateLimits/constants.js +5 -12
  20. package/dist-es/rateLimits/createRateLimitedStreamProxy.js +30 -72
  21. package/dist-es/rateLimits/exception/createRateLimitExceededException.js +15 -12
  22. package/dist-es/rateLimits/exception/processStreamRateLimitException.js +32 -22
  23. package/dist-es/rateLimits/executeStreamRequestWithRateLimitHandling.js +87 -65
  24. package/dist-es/rateLimits/helpers/RedisCooldown.js +52 -0
  25. package/dist-es/rateLimits/helpers/createRateLimitExceededException.js +14 -0
  26. package/dist-es/rateLimits/helpers/executeWithRateLimitHandling.js +69 -0
  27. package/dist-es/rateLimits/helpers/isStreamChannelLike.js +11 -0
  28. package/dist-es/rateLimits/helpers/processStreamRateLimitException.js +27 -0
  29. package/dist-es/rateLimits/helpers/splitCallArgsAndOptions.js +17 -0
  30. package/dist-es/rateLimits/index.js +1 -0
  31. package/dist-es/rateLimits/types.js +1 -1
  32. package/dist-es/rateLimits/withStreamRateLimitOptions.js +6 -0
  33. package/dist-types/index.d.ts +2 -2
  34. package/dist-types/rateLimits/RedisCooldown.d.ts +5 -3
  35. package/dist-types/rateLimits/constants.d.ts +3 -5
  36. package/dist-types/rateLimits/createRateLimitedStreamProxy.d.ts +6 -13
  37. package/dist-types/rateLimits/exception/createRateLimitExceededException.d.ts +8 -6
  38. package/dist-types/rateLimits/exception/processStreamRateLimitException.d.ts +8 -3
  39. package/dist-types/rateLimits/executeStreamRequestWithRateLimitHandling.d.ts +8 -2
  40. package/dist-types/rateLimits/helpers/RedisCooldown.d.ts +5 -0
  41. package/dist-types/rateLimits/helpers/createRateLimitExceededException.d.ts +10 -0
  42. package/dist-types/rateLimits/helpers/executeWithRateLimitHandling.d.ts +2 -0
  43. package/dist-types/rateLimits/helpers/isStreamChannelLike.d.ts +6 -0
  44. package/dist-types/rateLimits/helpers/processStreamRateLimitException.d.ts +2 -0
  45. package/dist-types/rateLimits/helpers/splitCallArgsAndOptions.d.ts +6 -0
  46. package/dist-types/rateLimits/index.d.ts +1 -0
  47. package/dist-types/rateLimits/types.d.ts +15 -3
  48. package/dist-types/rateLimits/withStreamRateLimitOptions.d.ts +6 -0
  49. package/dist-types/types.d.ts +1 -1
  50. package/package.json +6 -1
package/dist-cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- tslib_1.__exportStar(require("./types"), exports);
5
4
  tslib_1.__exportStar(require("./normalization"), exports);
6
- tslib_1.__exportStar(require("./stream"), exports);
7
5
  tslib_1.__exportStar(require("./rateLimits"), exports);
6
+ tslib_1.__exportStar(require("./stream"), exports);
7
+ tslib_1.__exportStar(require("./types"), exports);
@@ -1,32 +1,39 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', {value: true});
3
3
  exports.RedisCooldown = void 0;
4
- const constants_1 = require("./constants");
4
+ const constants_1 = require('./constants');
5
+
5
6
  class RedisCooldown {
6
- static async getRemainingMs(cooldownKey, context) {
7
- const { redis, logger } = context;
8
- try {
9
- const ttlMs = await redis.pttl(`${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`);
10
- if (ttlMs > 0) {
11
- return ttlMs;
12
- }
13
- }
14
- catch (error) {
15
- logger.warn('Failed to read stream rate-limit cooldown from Redis', {
16
- cooldownKey,
17
- error,
18
- });
19
- }
20
- return 0;
7
+ static async getRemainingMs(cooldownKey, context) {
8
+ const {redis, logger} = context;
9
+
10
+ try {
11
+ const ttlMs = await redis.pttl(`${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`);
12
+
13
+ if (ttlMs > 0) {
14
+ return ttlMs;
15
+ }
16
+ } catch (error) {
17
+ logger.warn('Failed to read stream rate-limit cooldown from Redis', {
18
+ cooldownKey,
19
+ error,
20
+ });
21
+ }
22
+
23
+ return 0;
24
+ }
25
+
26
+ static async setRemainingMs(cooldownKey, cooldownMs, context) {
27
+ if (cooldownMs <= 0) {
28
+ return;
21
29
  }
22
- static async setRemainingMs(cooldownKey, cooldownMs, context) {
23
- if (cooldownMs <= 0) {
24
- return;
25
- }
26
- const { redis, logger } = context;
27
- const redisKey = `${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`;
28
- try {
29
- await redis.eval(`
30
+
31
+ const {redis, logger} = context;
32
+ const redisKey = `${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`;
33
+
34
+ try {
35
+ await redis.eval(
36
+ `
30
37
  local key = KEYS[1]
31
38
  local new_ttl = tonumber(ARGV[1])
32
39
  local current_ttl = redis.call('PTTL', key)
@@ -42,15 +49,18 @@ class RedisCooldown {
42
49
  end
43
50
 
44
51
  return 0
45
- `, 1, redisKey, cooldownMs.toString());
46
- }
47
- catch (error) {
48
- logger.warn('Failed to write stream rate-limit cooldown to Redis', {
49
- cooldownKey,
50
- cooldownMs,
51
- error,
52
- });
53
- }
52
+ `,
53
+ 1,
54
+ redisKey,
55
+ cooldownMs.toString(),
56
+ );
57
+ } catch (error) {
58
+ logger.warn('Failed to write stream rate-limit cooldown to Redis', {
59
+ cooldownKey,
60
+ cooldownMs,
61
+ error,
62
+ });
54
63
  }
64
+ }
55
65
  }
56
66
  exports.RedisCooldown = RedisCooldown;
@@ -1,22 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_OPTS_3_ATTEMPTS_WITH_COOLDOWN = exports.DEFAULT_OPTS_1_ATTEMPT_NO_COOLDOWN = exports.DEFAULT_RETRY_AFTER_MS = exports.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX = exports.RETRY_AFTER_BUFFER_MS = exports.DEFAULT_JITTER_MS = exports.DEFAULT_MAX_RETRYABLE_DELAY_MS = exports.DEFAULT_MAX_DELAY_MS = exports.DEFAULT_FIXED_RATE_LIMIT_DELAY_MS = void 0;
3
+ exports.DEFAULT_OPTIONS = exports.STREAM_RATE_LIMIT_OPTIONS_MARKER = exports.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX = exports.DEFAULT_RETRY_AFTER_MS = exports.RETRY_AFTER_BUFFER_MS = exports.DEFAULT_JITTER_MS = exports.DEFAULT_FIXED_RATE_LIMIT_DELAY_MS = void 0;
4
4
  exports.DEFAULT_FIXED_RATE_LIMIT_DELAY_MS = 15000;
5
- exports.DEFAULT_MAX_DELAY_MS = 15000;
6
- exports.DEFAULT_MAX_RETRYABLE_DELAY_MS = 5000;
7
5
  exports.DEFAULT_JITTER_MS = 150;
8
6
  exports.RETRY_AFTER_BUFFER_MS = 1000;
9
- exports.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX = 'stream-rate-limit-cooldown:';
10
7
  exports.DEFAULT_RETRY_AFTER_MS = 10000;
11
- exports.DEFAULT_OPTS_1_ATTEMPT_NO_COOLDOWN = {
12
- maxAttempts: 1,
13
- enableCooldown: false,
14
- maxDelayMs: exports.DEFAULT_MAX_DELAY_MS,
15
- maxRetryableDelayMs: exports.DEFAULT_MAX_RETRYABLE_DELAY_MS,
16
- };
17
- exports.DEFAULT_OPTS_3_ATTEMPTS_WITH_COOLDOWN = {
8
+ exports.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX = 'stream-rate-limit-cooldown:';
9
+ exports.STREAM_RATE_LIMIT_OPTIONS_MARKER = '__xbsRateLimitOptions';
10
+ exports.DEFAULT_OPTIONS = {
18
11
  maxAttempts: 3,
19
12
  enableCooldown: true,
20
- maxDelayMs: exports.DEFAULT_MAX_DELAY_MS,
21
- maxRetryableDelayMs: exports.DEFAULT_MAX_RETRYABLE_DELAY_MS,
13
+ maxDelayMs: 15000,
14
+ maxRetryableDelayMs: 5000,
22
15
  };
@@ -1,72 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createRateLimitedStreamProxy = exports.withStreamRateLimitOptions = void 0;
3
+ exports.createRateLimitedStreamProxy = void 0;
4
4
  const constants_1 = require("./constants");
5
- const executeStreamRequestWithRateLimitHandling_1 = require("./executeStreamRequestWithRateLimitHandling");
6
- const STREAM_RATE_LIMIT_OPTIONS_MARKER = '__xbsRateLimitOptions';
7
- const STREAM_RATE_LIMIT_PROXY_MARKER = Symbol('xbsStreamRateLimitProxy');
8
- const OPERATION_DEFAULT_OPTIONS = {
9
- queryChannels: constants_1.DEFAULT_OPTS_3_ATTEMPTS_WITH_COOLDOWN,
10
- };
11
- function isObject(value) {
12
- return typeof value === 'object' && value !== null;
13
- }
14
- function hasFunctionProperty(value, key) {
15
- return key in value && typeof value[key] === 'function';
16
- }
17
- function isStreamChannelLike(value) {
18
- if (!isObject(value)) {
19
- return false;
20
- }
21
- return (hasFunctionProperty(value, 'sendMessage') &&
22
- hasFunctionProperty(value, 'queryMembers') &&
23
- hasFunctionProperty(value, 'updatePartial'));
24
- }
25
- function isStreamRateLimitOptionsToken(value) {
26
- return isObject(value) && STREAM_RATE_LIMIT_OPTIONS_MARKER in value;
27
- }
28
- function splitCallArgsAndOptions(args) {
29
- if (args.length === 0) {
30
- return { callArgs: args, callOptions: {} };
5
+ const executeWithRateLimitHandling_1 = require("./helpers/executeWithRateLimitHandling");
6
+ const isStreamChannelLike_1 = require("./helpers/isStreamChannelLike");
7
+ const splitCallArgsAndOptions_1 = require("./helpers/splitCallArgsAndOptions");
8
+ function normalizeStreamCallResult(result, dependencies) {
9
+ if (Array.isArray(result)) {
10
+ return result.map((item) => normalizeStreamCallResult(item, dependencies));
31
11
  }
32
- const lastArg = args[args.length - 1];
33
- if (isStreamRateLimitOptionsToken(lastArg)) {
34
- return {
35
- callArgs: args.slice(0, -1),
36
- callOptions: lastArg[STREAM_RATE_LIMIT_OPTIONS_MARKER],
37
- };
12
+ if (!(0, isStreamChannelLike_1.isStreamChannelLike)(result)) {
13
+ return result;
38
14
  }
39
- return { callArgs: args, callOptions: {} };
40
- }
41
- function withStreamRateLimitOptions(options) {
42
- return {
43
- [STREAM_RATE_LIMIT_OPTIONS_MARKER]: options,
15
+ return wrapTarget(result, dependencies);
16
+ }
17
+ function createRateLimitedMethodWrapper(target, value, operation, dependencies) {
18
+ return async (...args) => {
19
+ const { callArgs, callOptions } = (0, splitCallArgsAndOptions_1.splitCallArgsAndOptions)(args);
20
+ const result = await (0, executeWithRateLimitHandling_1.executeWithRateLimitHandling)(() => Reflect.apply(value, target, callArgs), operation, dependencies.context, {
21
+ ...constants_1.DEFAULT_OPTIONS,
22
+ ...callOptions,
23
+ });
24
+ return normalizeStreamCallResult(result, dependencies);
44
25
  };
45
26
  }
46
- exports.withStreamRateLimitOptions = withStreamRateLimitOptions;
47
- function createRateLimitedProxy(target, context, cache) {
48
- const cached = cache.get(target);
49
- if (cached) {
50
- return cached;
27
+ function wrapTarget(target, dependencies) {
28
+ const cachedObject = dependencies.cache.get(target);
29
+ if (cachedObject) {
30
+ return cachedObject;
51
31
  }
52
- const wrapResult = (result) => {
53
- if (Array.isArray(result)) {
54
- return result.map((item) => wrapResult(item));
55
- }
56
- if (!isStreamChannelLike(result)) {
57
- return result;
58
- }
59
- const cachedResult = cache.get(result);
60
- if (cachedResult) {
61
- return cachedResult;
62
- }
63
- return createRateLimitedProxy(result, context, cache);
64
- };
65
32
  const proxy = new Proxy(target, {
66
33
  get: (originalTarget, property, receiver) => {
67
- if (property === STREAM_RATE_LIMIT_PROXY_MARKER) {
68
- return true;
69
- }
70
34
  const value = Reflect.get(originalTarget, property, receiver);
71
35
  if (typeof property !== 'string' || typeof value !== 'function') {
72
36
  return value;
@@ -74,26 +38,19 @@ function createRateLimitedProxy(target, context, cache) {
74
38
  if (property === 'channel') {
75
39
  return (...args) => {
76
40
  const channel = Reflect.apply(value, originalTarget, args);
77
- return wrapResult(channel);
41
+ return normalizeStreamCallResult(channel, dependencies);
78
42
  };
79
43
  }
80
- return async (...args) => {
81
- const { callArgs, callOptions } = splitCallArgsAndOptions(args);
82
- const operation = property;
83
- const defaultOptions = OPERATION_DEFAULT_OPTIONS[operation] || {};
84
- const result = await (0, executeStreamRequestWithRateLimitHandling_1.executeStreamRequestWithRateLimitHandling)(() => Reflect.apply(value, originalTarget, callArgs), operation, context, {
85
- ...defaultOptions,
86
- ...callOptions,
87
- });
88
- return wrapResult(result);
89
- };
44
+ return createRateLimitedMethodWrapper(originalTarget, value, property, dependencies);
90
45
  },
91
46
  });
92
- cache.set(target, proxy);
47
+ dependencies.cache.set(target, proxy);
93
48
  return proxy;
94
49
  }
95
50
  function createRateLimitedStreamProxy(stream, context) {
96
- const cache = new WeakMap();
97
- return createRateLimitedProxy(stream, context, cache);
51
+ return wrapTarget(stream, {
52
+ context,
53
+ cache: new WeakMap(),
54
+ });
98
55
  }
99
56
  exports.createRateLimitedStreamProxy = createRateLimitedStreamProxy;
@@ -1,18 +1,21 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', {value: true});
3
3
  exports.createRateLimitExceededException = void 0;
4
- const xbees_conversations_client_1 = require("@wildix/xbees-conversations-client");
5
- const constants_1 = require("../constants");
4
+ const xbees_conversations_client_1 = require('@wildix/xbees-conversations-client');
5
+ const constants_1 = require('../constants');
6
+
6
7
  function createRateLimitExceededException(options = {}) {
7
- const { rateLimit, message, operation, retryAfterMs = constants_1.DEFAULT_RETRY_AFTER_MS } = options;
8
- const defaultMessage = operation ? `Rate limit exceeded for ${operation}` : 'Rate limit exceeded';
9
- return new xbees_conversations_client_1.RateLimitExceededException({
10
- $metadata: {},
11
- message: message || defaultMessage,
12
- rateLimitRemaining: rateLimit?.remaining || 0,
13
- retryAfter: rateLimit?.reset ? rateLimit.reset - Date.now() : retryAfterMs,
14
- rateLimit: rateLimit?.limit?.toString() || 'unknown',
15
- rateLimitReset: rateLimit?.reset || Date.now() + retryAfterMs,
16
- });
8
+ const {rateLimit, message, operation, retryAfterMs = constants_1.DEFAULT_RETRY_AFTER_MS} = options;
9
+ const defaultMessage = operation ? `Rate limit exceeded for ${operation}` : 'Rate limit exceeded';
10
+
11
+ return new xbees_conversations_client_1.RateLimitExceededException({
12
+ $metadata: {},
13
+ message: message || defaultMessage,
14
+ rateLimitRemaining: rateLimit?.remaining || 0,
15
+ retryAfter: rateLimit?.reset ? rateLimit.reset - Date.now() : retryAfterMs,
16
+ rateLimit: rateLimit?.limit?.toString() || 'unknown',
17
+ rateLimitReset: rateLimit?.reset || Date.now() + retryAfterMs,
18
+ });
17
19
  }
20
+
18
21
  exports.createRateLimitExceededException = createRateLimitExceededException;
@@ -1,31 +1,47 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', {value: true});
3
3
  exports.processStreamRateLimitException = void 0;
4
- const createRateLimitExceededException_1 = require("./createRateLimitExceededException");
4
+ const createRateLimitExceededException_1 = require('./createRateLimitExceededException');
5
+
5
6
  function isStreamException(error) {
6
- return (typeof error === 'object' &&
7
- error !== null &&
8
- 'metadata' in error &&
9
- typeof error.metadata === 'object' &&
10
- error.metadata !== null &&
11
- 'responseCode' in error.metadata);
7
+ return (
8
+ typeof error === 'object' &&
9
+ error !== null &&
10
+ 'metadata' in error &&
11
+ typeof error.metadata === 'object' &&
12
+ error.metadata !== null &&
13
+ 'responseCode' in error.metadata
14
+ );
12
15
  }
16
+
13
17
  function isStreamAPIException(error) {
14
- return typeof error === 'object' && !!error.code;
18
+ return typeof error === 'object' && !!error.code;
15
19
  }
20
+
16
21
  function processStreamRateLimitException(error, logger) {
17
- if (isStreamException(error) && error.metadata?.responseCode === 429) {
18
- const rateLimit = error.metadata?.rateLimit;
19
- logger.warn('Stream rate limit exceeded [StreamException]', { rateLimit });
20
- return (0, createRateLimitExceededException_1.createRateLimitExceededException)({ rateLimit, message: error.message || 'Rate limit exceeded' });
21
- }
22
- if (isStreamAPIException(error) && (error.code === 429 || error.StatusCode === 429)) {
23
- logger.warn('Stream rate limit exceeded [StreamAPIException]', { code: error.code, StatusCode: error.StatusCode });
24
- return (0, createRateLimitExceededException_1.createRateLimitExceededException)({ message: error.message || 'Rate limit exceeded' });
25
- }
26
- if (typeof error === 'object' && error !== null && 'status' in error && error.status === 429) {
27
- logger.warn('Stream rate limit exceeded [error.status === 429]');
28
- return (0, createRateLimitExceededException_1.createRateLimitExceededException)({ message: 'Rate limit exceeded' });
29
- }
22
+ if (isStreamException(error) && error.metadata?.responseCode === 429) {
23
+ const rateLimit = error.metadata?.rateLimit;
24
+ logger.warn('Stream rate limit exceeded [StreamException]', {rateLimit});
25
+
26
+ return (0, createRateLimitExceededException_1.createRateLimitExceededException)({
27
+ rateLimit,
28
+ message: error.message || 'Rate limit exceeded',
29
+ });
30
+ }
31
+
32
+ if (isStreamAPIException(error) && (error.code === 429 || error.StatusCode === 429)) {
33
+ logger.warn('Stream rate limit exceeded [StreamAPIException]', {code: error.code, StatusCode: error.StatusCode});
34
+
35
+ return (0, createRateLimitExceededException_1.createRateLimitExceededException)({
36
+ message: error.message || 'Rate limit exceeded',
37
+ });
38
+ }
39
+
40
+ if (typeof error === 'object' && error !== null && 'status' in error && error.status === 429) {
41
+ logger.warn('Stream rate limit exceeded [error.status === 429]');
42
+
43
+ return (0, createRateLimitExceededException_1.createRateLimitExceededException)({message: 'Rate limit exceeded'});
44
+ }
30
45
  }
46
+
31
47
  exports.processStreamRateLimitException = processStreamRateLimitException;
@@ -1,75 +1,94 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', {value: true});
3
3
  exports.executeStreamRequestWithRateLimitHandling = void 0;
4
- const promises_1 = require("node:timers/promises");
5
- const constants_1 = require("./constants");
6
- const createRateLimitExceededException_1 = require("./exception/createRateLimitExceededException");
7
- const processStreamRateLimitException_1 = require("./exception/processStreamRateLimitException");
8
- const RedisCooldown_1 = require("./RedisCooldown");
4
+ const promises_1 = require('node:timers/promises');
5
+ const constants_1 = require('./constants');
6
+ const createRateLimitExceededException_1 = require('./exception/createRateLimitExceededException');
7
+ const processStreamRateLimitException_1 = require('./exception/processStreamRateLimitException');
8
+ const RedisCooldown_1 = require('./RedisCooldown');
9
+
9
10
  function calculateRateLimitDelayMs(error, maxDelayMs) {
10
- const retryAfterMs = typeof error.retryAfter === 'number' ? error.retryAfter + constants_1.RETRY_AFTER_BUFFER_MS : constants_1.DEFAULT_RETRY_AFTER_MS;
11
- if (retryAfterMs > 0) {
12
- const jitterMs = Math.floor(Math.random() * constants_1.DEFAULT_JITTER_MS);
13
- return Math.min(maxDelayMs, retryAfterMs + jitterMs);
14
- }
15
- return Math.min(maxDelayMs, constants_1.DEFAULT_FIXED_RATE_LIMIT_DELAY_MS);
11
+ const retryAfterMs =
12
+ typeof error.retryAfter === 'number'
13
+ ? error.retryAfter + constants_1.RETRY_AFTER_BUFFER_MS
14
+ : constants_1.DEFAULT_RETRY_AFTER_MS;
15
+
16
+ if (retryAfterMs > 0) {
17
+ const jitterMs = Math.floor(Math.random() * constants_1.DEFAULT_JITTER_MS);
18
+
19
+ return Math.min(maxDelayMs, retryAfterMs + jitterMs);
20
+ }
21
+
22
+ return Math.min(maxDelayMs, constants_1.DEFAULT_FIXED_RATE_LIMIT_DELAY_MS);
16
23
  }
24
+
17
25
  async function executeStreamRequestWithRateLimitHandling(execute, operation, context, options = {}) {
18
- const { logger, redis } = context;
19
- const { enableCooldown, maxAttempts, maxDelayMs, maxRetryableDelayMs } = {
20
- ...constants_1.DEFAULT_OPTS_1_ATTEMPT_NO_COOLDOWN,
21
- ...options,
22
- };
23
- let attempt = 1;
24
- if (enableCooldown) {
25
- const cooldownMs = await RedisCooldown_1.RedisCooldown.getRemainingMs(operation, { redis, logger });
26
- if (cooldownMs > 0) {
27
- logger.warn('Skipping stream request due to active rate-limit cooldown', {
28
- operation,
29
- cooldown: cooldownMs,
30
- });
31
- throw (0, createRateLimitExceededException_1.createRateLimitExceededException)({ operation, retryAfterMs: cooldownMs });
32
- }
26
+ const {logger, redis} = context;
27
+ const {enableCooldown, maxAttempts, maxDelayMs, maxRetryableDelayMs} = {
28
+ ...constants_1.DEFAULT_OPTS_1_ATTEMPT_NO_COOLDOWN,
29
+ ...options,
30
+ };
31
+ let attempt = 1;
32
+
33
+ if (enableCooldown) {
34
+ const cooldownMs = await RedisCooldown_1.RedisCooldown.getRemainingMs(operation, {redis, logger});
35
+
36
+ if (cooldownMs > 0) {
37
+ logger.warn('Skipping stream request due to active rate-limit cooldown', {
38
+ operation,
39
+ cooldown: cooldownMs,
40
+ });
41
+ throw (0, createRateLimitExceededException_1.createRateLimitExceededException)({
42
+ operation,
43
+ retryAfterMs: cooldownMs,
44
+ });
33
45
  }
34
- for (;;) {
35
- try {
36
- return await execute();
37
- }
38
- catch (error) {
39
- const rateLimitError = (0, processStreamRateLimitException_1.processStreamRateLimitException)(error, logger);
40
- if (!rateLimitError) {
41
- throw error;
42
- }
43
- const retryAfterMs = calculateRateLimitDelayMs(rateLimitError, maxDelayMs);
44
- if (enableCooldown) {
45
- await RedisCooldown_1.RedisCooldown.setRemainingMs(operation, retryAfterMs, { redis, logger });
46
- }
47
- if (attempt < maxAttempts && retryAfterMs <= maxRetryableDelayMs) {
48
- logger.warn('Retrying stream request after rate limit', {
49
- operation,
50
- attempt,
51
- maxAttempts,
52
- retryAfterMs,
53
- rateLimit: rateLimitError.rateLimit,
54
- rateLimitRemaining: rateLimitError.rateLimitRemaining,
55
- rateLimitReset: rateLimitError.rateLimitReset,
56
- });
57
- await (0, promises_1.setTimeout)(retryAfterMs);
58
- attempt++;
59
- continue;
60
- }
61
- logger.warn('Propagating stream rate limit w/o retry', {
62
- operation,
63
- attempt,
64
- maxAttempts,
65
- retryAfterMs,
66
- maxRetryableDelayMs,
67
- rateLimit: rateLimitError.rateLimit,
68
- rateLimitRemaining: rateLimitError.rateLimitRemaining,
69
- rateLimitReset: rateLimitError.rateLimitReset,
70
- });
71
- throw rateLimitError;
72
- }
46
+ }
47
+
48
+ for (;;) {
49
+ try {
50
+ return await execute();
51
+ } catch (error) {
52
+ const rateLimitError = (0, processStreamRateLimitException_1.processStreamRateLimitException)(error, logger);
53
+
54
+ if (!rateLimitError) {
55
+ throw error;
56
+ }
57
+
58
+ const retryAfterMs = calculateRateLimitDelayMs(rateLimitError, maxDelayMs);
59
+
60
+ if (enableCooldown) {
61
+ await RedisCooldown_1.RedisCooldown.setRemainingMs(operation, retryAfterMs, {redis, logger});
62
+ }
63
+
64
+ if (attempt < maxAttempts && retryAfterMs <= maxRetryableDelayMs) {
65
+ logger.warn('Retrying stream request after rate limit', {
66
+ operation,
67
+ attempt,
68
+ maxAttempts,
69
+ retryAfterMs,
70
+ rateLimit: rateLimitError.rateLimit,
71
+ rateLimitRemaining: rateLimitError.rateLimitRemaining,
72
+ rateLimitReset: rateLimitError.rateLimitReset,
73
+ });
74
+ await (0, promises_1.setTimeout)(retryAfterMs);
75
+ attempt++;
76
+ continue;
77
+ }
78
+
79
+ logger.warn('Propagating stream rate limit w/o retry', {
80
+ operation,
81
+ attempt,
82
+ maxAttempts,
83
+ retryAfterMs,
84
+ maxRetryableDelayMs,
85
+ rateLimit: rateLimitError.rateLimit,
86
+ rateLimitRemaining: rateLimitError.rateLimitRemaining,
87
+ rateLimitReset: rateLimitError.rateLimitReset,
88
+ });
89
+ throw rateLimitError;
73
90
  }
91
+ }
74
92
  }
93
+
75
94
  exports.executeStreamRequestWithRateLimitHandling = executeStreamRequestWithRateLimitHandling;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisCooldown = void 0;
4
+ const constants_1 = require("../constants");
5
+ class RedisCooldown {
6
+ static async getRemainingMs(cooldownKey, context) {
7
+ const { redis } = context;
8
+ try {
9
+ const ttlMs = await redis.pttl(`${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`);
10
+ if (ttlMs > 0) {
11
+ return ttlMs;
12
+ }
13
+ }
14
+ catch (error) {
15
+ console.warn('Failed to read stream rate-limit cooldown from Redis', {
16
+ cooldownKey,
17
+ error,
18
+ });
19
+ }
20
+ return 0;
21
+ }
22
+ static async setRemainingMs(cooldownKey, cooldownMs, context) {
23
+ if (cooldownMs <= 0) {
24
+ return;
25
+ }
26
+ const { redis } = context;
27
+ const redisKey = `${constants_1.STREAM_RATE_LIMIT_COOLDOWN_KEY_PREFIX}${cooldownKey}`;
28
+ try {
29
+ await redis.eval(`
30
+ local key = KEYS[1]
31
+ local new_ttl = tonumber(ARGV[1])
32
+ local current_ttl = redis.call('PTTL', key)
33
+
34
+ if current_ttl == -2 then
35
+ redis.call('SET', key, '1', 'PX', new_ttl)
36
+ return 1
37
+ end
38
+
39
+ if current_ttl == -1 or current_ttl < new_ttl then
40
+ redis.call('PEXPIRE', key, new_ttl)
41
+ return 2
42
+ end
43
+
44
+ return 0
45
+ `, 1, redisKey, cooldownMs.toString());
46
+ }
47
+ catch (error) {
48
+ console.warn('Failed to write stream rate-limit cooldown to Redis', {
49
+ cooldownKey,
50
+ cooldownMs,
51
+ error,
52
+ });
53
+ }
54
+ }
55
+ }
56
+ exports.RedisCooldown = RedisCooldown;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRateLimitExceededException = void 0;
4
+ const xbees_conversations_client_1 = require("@wildix/xbees-conversations-client");
5
+ const constants_1 = require("../constants");
6
+ function createRateLimitExceededException(options = {}) {
7
+ const { rateLimit, message, operation, retryAfterMs = constants_1.DEFAULT_RETRY_AFTER_MS } = options;
8
+ const defaultMessage = operation ? `Rate limit exceeded for ${operation}` : 'Rate limit exceeded';
9
+ return new xbees_conversations_client_1.RateLimitExceededException({
10
+ $metadata: {},
11
+ message: message || defaultMessage,
12
+ rateLimitRemaining: rateLimit?.remaining || 0,
13
+ retryAfter: rateLimit?.reset ? rateLimit.reset - Date.now() : retryAfterMs,
14
+ rateLimit: rateLimit?.limit?.toString() || 'unknown',
15
+ rateLimitReset: rateLimit?.reset || Date.now() + retryAfterMs,
16
+ });
17
+ }
18
+ exports.createRateLimitExceededException = createRateLimitExceededException;