flowx-control 1.0.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.
Files changed (112) hide show
  1. package/README.md +443 -0
  2. package/dist/batch.d.mts +37 -0
  3. package/dist/batch.d.ts +37 -0
  4. package/dist/batch.js +75 -0
  5. package/dist/batch.js.map +1 -0
  6. package/dist/batch.mjs +73 -0
  7. package/dist/batch.mjs.map +1 -0
  8. package/dist/bulkhead.d.mts +30 -0
  9. package/dist/bulkhead.d.ts +30 -0
  10. package/dist/bulkhead.js +85 -0
  11. package/dist/bulkhead.js.map +1 -0
  12. package/dist/bulkhead.mjs +83 -0
  13. package/dist/bulkhead.mjs.map +1 -0
  14. package/dist/circuit-breaker.d.mts +44 -0
  15. package/dist/circuit-breaker.d.ts +44 -0
  16. package/dist/circuit-breaker.js +132 -0
  17. package/dist/circuit-breaker.js.map +1 -0
  18. package/dist/circuit-breaker.mjs +130 -0
  19. package/dist/circuit-breaker.mjs.map +1 -0
  20. package/dist/debounce.d.mts +30 -0
  21. package/dist/debounce.d.ts +30 -0
  22. package/dist/debounce.js +96 -0
  23. package/dist/debounce.js.map +1 -0
  24. package/dist/debounce.mjs +94 -0
  25. package/dist/debounce.mjs.map +1 -0
  26. package/dist/deferred.d.mts +27 -0
  27. package/dist/deferred.d.ts +27 -0
  28. package/dist/deferred.js +42 -0
  29. package/dist/deferred.js.map +1 -0
  30. package/dist/deferred.mjs +40 -0
  31. package/dist/deferred.mjs.map +1 -0
  32. package/dist/fallback.d.mts +33 -0
  33. package/dist/fallback.d.ts +33 -0
  34. package/dist/fallback.js +43 -0
  35. package/dist/fallback.js.map +1 -0
  36. package/dist/fallback.mjs +40 -0
  37. package/dist/fallback.mjs.map +1 -0
  38. package/dist/hedge.d.mts +18 -0
  39. package/dist/hedge.d.ts +18 -0
  40. package/dist/hedge.js +47 -0
  41. package/dist/hedge.js.map +1 -0
  42. package/dist/hedge.mjs +45 -0
  43. package/dist/hedge.mjs.map +1 -0
  44. package/dist/index.d.mts +18 -0
  45. package/dist/index.d.ts +18 -0
  46. package/dist/index.js +1151 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/index.mjs +1122 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/memo.d.mts +35 -0
  51. package/dist/memo.d.ts +35 -0
  52. package/dist/memo.js +74 -0
  53. package/dist/memo.js.map +1 -0
  54. package/dist/memo.mjs +72 -0
  55. package/dist/memo.mjs.map +1 -0
  56. package/dist/mutex.d.mts +24 -0
  57. package/dist/mutex.d.ts +24 -0
  58. package/dist/mutex.js +46 -0
  59. package/dist/mutex.js.map +1 -0
  60. package/dist/mutex.mjs +44 -0
  61. package/dist/mutex.mjs.map +1 -0
  62. package/dist/pipeline.d.mts +42 -0
  63. package/dist/pipeline.d.ts +42 -0
  64. package/dist/pipeline.js +30 -0
  65. package/dist/pipeline.js.map +1 -0
  66. package/dist/pipeline.mjs +27 -0
  67. package/dist/pipeline.mjs.map +1 -0
  68. package/dist/poll.d.mts +35 -0
  69. package/dist/poll.d.ts +35 -0
  70. package/dist/poll.js +111 -0
  71. package/dist/poll.js.map +1 -0
  72. package/dist/poll.mjs +109 -0
  73. package/dist/poll.mjs.map +1 -0
  74. package/dist/queue.d.mts +47 -0
  75. package/dist/queue.d.ts +47 -0
  76. package/dist/queue.js +121 -0
  77. package/dist/queue.js.map +1 -0
  78. package/dist/queue.mjs +119 -0
  79. package/dist/queue.mjs.map +1 -0
  80. package/dist/rate-limit.d.mts +28 -0
  81. package/dist/rate-limit.d.ts +28 -0
  82. package/dist/rate-limit.js +94 -0
  83. package/dist/rate-limit.js.map +1 -0
  84. package/dist/rate-limit.mjs +92 -0
  85. package/dist/rate-limit.mjs.map +1 -0
  86. package/dist/retry.d.mts +45 -0
  87. package/dist/retry.d.ts +45 -0
  88. package/dist/retry.js +111 -0
  89. package/dist/retry.js.map +1 -0
  90. package/dist/retry.mjs +108 -0
  91. package/dist/retry.mjs.map +1 -0
  92. package/dist/semaphore.d.mts +27 -0
  93. package/dist/semaphore.d.ts +27 -0
  94. package/dist/semaphore.js +47 -0
  95. package/dist/semaphore.js.map +1 -0
  96. package/dist/semaphore.mjs +45 -0
  97. package/dist/semaphore.mjs.map +1 -0
  98. package/dist/throttle.d.mts +25 -0
  99. package/dist/throttle.d.ts +25 -0
  100. package/dist/throttle.js +97 -0
  101. package/dist/throttle.js.map +1 -0
  102. package/dist/throttle.mjs +95 -0
  103. package/dist/throttle.mjs.map +1 -0
  104. package/dist/timeout.d.mts +19 -0
  105. package/dist/timeout.d.ts +19 -0
  106. package/dist/timeout.js +79 -0
  107. package/dist/timeout.js.map +1 -0
  108. package/dist/timeout.mjs +77 -0
  109. package/dist/timeout.mjs.map +1 -0
  110. package/dist/types-BsCO2J40.d.mts +35 -0
  111. package/dist/types-BsCO2J40.d.ts +35 -0
  112. package/package.json +167 -0
package/dist/retry.js ADDED
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var FlowXError = class extends Error {
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = "FlowXError";
8
+ this.code = code;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ };
12
+ var AbortError = class extends FlowXError {
13
+ constructor(message = "Operation aborted") {
14
+ super(message, "ERR_ABORTED");
15
+ this.name = "AbortError";
16
+ }
17
+ };
18
+ function sleep(ms, signal) {
19
+ return new Promise((resolve, reject) => {
20
+ if (signal?.aborted) {
21
+ reject(new AbortError());
22
+ return;
23
+ }
24
+ let onAbort;
25
+ const timer = setTimeout(() => {
26
+ if (signal && onAbort) {
27
+ signal.removeEventListener("abort", onAbort);
28
+ }
29
+ resolve();
30
+ }, ms);
31
+ if (signal) {
32
+ onAbort = () => {
33
+ clearTimeout(timer);
34
+ reject(new AbortError());
35
+ };
36
+ signal.addEventListener("abort", onAbort, { once: true });
37
+ }
38
+ });
39
+ }
40
+ function calculateDelay(attempt, baseDelay, strategy, jitter = false) {
41
+ let delay;
42
+ if (typeof strategy === "function") {
43
+ delay = strategy(attempt, baseDelay);
44
+ } else {
45
+ switch (strategy) {
46
+ case "fixed":
47
+ delay = baseDelay;
48
+ break;
49
+ case "linear":
50
+ delay = baseDelay * attempt;
51
+ break;
52
+ case "exponential":
53
+ delay = baseDelay * Math.pow(2, attempt - 1);
54
+ break;
55
+ }
56
+ }
57
+ if (jitter) {
58
+ const factor = typeof jitter === "number" ? jitter : 1;
59
+ delay = delay * (1 - factor * 0.5 + Math.random() * factor);
60
+ }
61
+ return Math.max(0, Math.floor(delay));
62
+ }
63
+
64
+ // src/retry.ts
65
+ async function retry(fn, options) {
66
+ const {
67
+ retries = 3,
68
+ delay = 1e3,
69
+ backoff = "exponential",
70
+ jitter = false,
71
+ maxDelay = 3e4,
72
+ shouldRetry,
73
+ onRetry,
74
+ signal
75
+ } = options ?? {};
76
+ if (retries < 0) {
77
+ throw new RangeError("retries must be >= 0");
78
+ }
79
+ let lastError;
80
+ for (let attempt = 0; attempt <= retries; attempt++) {
81
+ if (signal?.aborted) {
82
+ throw new AbortError("Retry aborted");
83
+ }
84
+ try {
85
+ const result = await fn();
86
+ return result;
87
+ } catch (error) {
88
+ lastError = error instanceof Error ? error : new Error(String(error));
89
+ if (attempt === retries) break;
90
+ if (shouldRetry) {
91
+ const shouldContinue = await shouldRetry(lastError, attempt + 1);
92
+ if (!shouldContinue) break;
93
+ }
94
+ if (onRetry) {
95
+ await onRetry(lastError, attempt + 1);
96
+ }
97
+ let waitTime = calculateDelay(attempt + 1, delay, backoff, jitter);
98
+ waitTime = Math.min(waitTime, maxDelay);
99
+ await sleep(waitTime, signal);
100
+ }
101
+ }
102
+ throw lastError;
103
+ }
104
+ function retryable(fn, options) {
105
+ return (...args) => retry(() => fn(...args), options);
106
+ }
107
+
108
+ exports.retry = retry;
109
+ exports.retryable = retryable;
110
+ //# sourceMappingURL=retry.js.map
111
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/retry.ts"],"names":[],"mappings":"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","file":"retry.js","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Retry with Advanced Backoff Strategies\n// ============================================================================\nimport { AbortError, BackoffStrategy, calculateDelay, sleep } from './types';\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n retries?: number;\n /** Base delay in ms between retries (default: 1000) */\n delay?: number;\n /** Backoff strategy (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Add randomized jitter to delays (default: false) */\n jitter?: boolean | number;\n /** Maximum delay cap in ms (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to decide if retry should happen */\n shouldRetry?: (error: Error, attempt: number) => boolean | Promise<boolean>;\n /** Callback fired before each retry */\n onRetry?: (error: Error, attempt: number) => void | Promise<void>;\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Execute an async function with automatic retry and configurable backoff.\n *\n * @example\n * ```ts\n * const data = await retry(() => fetch('/api/data'), {\n * retries: 5,\n * backoff: 'exponential',\n * jitter: true,\n * });\n * ```\n */\nexport async function retry<T>(fn: () => T | Promise<T>, options?: RetryOptions): Promise<T> {\n const {\n retries = 3,\n delay = 1000,\n backoff = 'exponential',\n jitter = false,\n maxDelay = 30000,\n shouldRetry,\n onRetry,\n signal,\n } = options ?? {};\n\n if (retries < 0) {\n throw new RangeError('retries must be >= 0');\n }\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (signal?.aborted) {\n throw new AbortError('Retry aborted');\n }\n\n try {\n const result = await fn();\n return result;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt === retries) break;\n\n if (shouldRetry) {\n const shouldContinue = await shouldRetry(lastError, attempt + 1);\n if (!shouldContinue) break;\n }\n\n if (onRetry) {\n await onRetry(lastError, attempt + 1);\n }\n\n let waitTime = calculateDelay(attempt + 1, delay, backoff, jitter);\n waitTime = Math.min(waitTime, maxDelay);\n\n await sleep(waitTime, signal);\n }\n }\n\n throw lastError!;\n}\n\n/**\n * Wrap a function to automatically retry on failure.\n *\n * @example\n * ```ts\n * const safeFetch = retryable(fetch, { retries: 3 });\n * const data = await safeFetch('/api/data');\n * ```\n */\nexport function retryable<TArgs extends any[], TReturn>(\n fn: (...args: TArgs) => Promise<TReturn>,\n options?: RetryOptions,\n): (...args: TArgs) => Promise<TReturn> {\n return (...args: TArgs) => retry(() => fn(...args), options);\n}\n"]}
package/dist/retry.mjs ADDED
@@ -0,0 +1,108 @@
1
+ // src/types.ts
2
+ var FlowXError = class extends Error {
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.name = "FlowXError";
6
+ this.code = code;
7
+ Object.setPrototypeOf(this, new.target.prototype);
8
+ }
9
+ };
10
+ var AbortError = class extends FlowXError {
11
+ constructor(message = "Operation aborted") {
12
+ super(message, "ERR_ABORTED");
13
+ this.name = "AbortError";
14
+ }
15
+ };
16
+ function sleep(ms, signal) {
17
+ return new Promise((resolve, reject) => {
18
+ if (signal?.aborted) {
19
+ reject(new AbortError());
20
+ return;
21
+ }
22
+ let onAbort;
23
+ const timer = setTimeout(() => {
24
+ if (signal && onAbort) {
25
+ signal.removeEventListener("abort", onAbort);
26
+ }
27
+ resolve();
28
+ }, ms);
29
+ if (signal) {
30
+ onAbort = () => {
31
+ clearTimeout(timer);
32
+ reject(new AbortError());
33
+ };
34
+ signal.addEventListener("abort", onAbort, { once: true });
35
+ }
36
+ });
37
+ }
38
+ function calculateDelay(attempt, baseDelay, strategy, jitter = false) {
39
+ let delay;
40
+ if (typeof strategy === "function") {
41
+ delay = strategy(attempt, baseDelay);
42
+ } else {
43
+ switch (strategy) {
44
+ case "fixed":
45
+ delay = baseDelay;
46
+ break;
47
+ case "linear":
48
+ delay = baseDelay * attempt;
49
+ break;
50
+ case "exponential":
51
+ delay = baseDelay * Math.pow(2, attempt - 1);
52
+ break;
53
+ }
54
+ }
55
+ if (jitter) {
56
+ const factor = typeof jitter === "number" ? jitter : 1;
57
+ delay = delay * (1 - factor * 0.5 + Math.random() * factor);
58
+ }
59
+ return Math.max(0, Math.floor(delay));
60
+ }
61
+
62
+ // src/retry.ts
63
+ async function retry(fn, options) {
64
+ const {
65
+ retries = 3,
66
+ delay = 1e3,
67
+ backoff = "exponential",
68
+ jitter = false,
69
+ maxDelay = 3e4,
70
+ shouldRetry,
71
+ onRetry,
72
+ signal
73
+ } = options ?? {};
74
+ if (retries < 0) {
75
+ throw new RangeError("retries must be >= 0");
76
+ }
77
+ let lastError;
78
+ for (let attempt = 0; attempt <= retries; attempt++) {
79
+ if (signal?.aborted) {
80
+ throw new AbortError("Retry aborted");
81
+ }
82
+ try {
83
+ const result = await fn();
84
+ return result;
85
+ } catch (error) {
86
+ lastError = error instanceof Error ? error : new Error(String(error));
87
+ if (attempt === retries) break;
88
+ if (shouldRetry) {
89
+ const shouldContinue = await shouldRetry(lastError, attempt + 1);
90
+ if (!shouldContinue) break;
91
+ }
92
+ if (onRetry) {
93
+ await onRetry(lastError, attempt + 1);
94
+ }
95
+ let waitTime = calculateDelay(attempt + 1, delay, backoff, jitter);
96
+ waitTime = Math.min(waitTime, maxDelay);
97
+ await sleep(waitTime, signal);
98
+ }
99
+ }
100
+ throw lastError;
101
+ }
102
+ function retryable(fn, options) {
103
+ return (...args) => retry(() => fn(...args), options);
104
+ }
105
+
106
+ export { retry, retryable };
107
+ //# sourceMappingURL=retry.mjs.map
108
+ //# sourceMappingURL=retry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/retry.ts"],"names":[],"mappings":"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","file":"retry.mjs","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Retry with Advanced Backoff Strategies\n// ============================================================================\nimport { AbortError, BackoffStrategy, calculateDelay, sleep } from './types';\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n retries?: number;\n /** Base delay in ms between retries (default: 1000) */\n delay?: number;\n /** Backoff strategy (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Add randomized jitter to delays (default: false) */\n jitter?: boolean | number;\n /** Maximum delay cap in ms (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to decide if retry should happen */\n shouldRetry?: (error: Error, attempt: number) => boolean | Promise<boolean>;\n /** Callback fired before each retry */\n onRetry?: (error: Error, attempt: number) => void | Promise<void>;\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Execute an async function with automatic retry and configurable backoff.\n *\n * @example\n * ```ts\n * const data = await retry(() => fetch('/api/data'), {\n * retries: 5,\n * backoff: 'exponential',\n * jitter: true,\n * });\n * ```\n */\nexport async function retry<T>(fn: () => T | Promise<T>, options?: RetryOptions): Promise<T> {\n const {\n retries = 3,\n delay = 1000,\n backoff = 'exponential',\n jitter = false,\n maxDelay = 30000,\n shouldRetry,\n onRetry,\n signal,\n } = options ?? {};\n\n if (retries < 0) {\n throw new RangeError('retries must be >= 0');\n }\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (signal?.aborted) {\n throw new AbortError('Retry aborted');\n }\n\n try {\n const result = await fn();\n return result;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt === retries) break;\n\n if (shouldRetry) {\n const shouldContinue = await shouldRetry(lastError, attempt + 1);\n if (!shouldContinue) break;\n }\n\n if (onRetry) {\n await onRetry(lastError, attempt + 1);\n }\n\n let waitTime = calculateDelay(attempt + 1, delay, backoff, jitter);\n waitTime = Math.min(waitTime, maxDelay);\n\n await sleep(waitTime, signal);\n }\n }\n\n throw lastError!;\n}\n\n/**\n * Wrap a function to automatically retry on failure.\n *\n * @example\n * ```ts\n * const safeFetch = retryable(fetch, { retries: 3 });\n * const data = await safeFetch('/api/data');\n * ```\n */\nexport function retryable<TArgs extends any[], TReturn>(\n fn: (...args: TArgs) => Promise<TReturn>,\n options?: RetryOptions,\n): (...args: TArgs) => Promise<TReturn> {\n return (...args: TArgs) => retry(() => fn(...args), options);\n}\n"]}
@@ -0,0 +1,27 @@
1
+ interface Semaphore {
2
+ /** Acquire a permit. Returns a release function. */
3
+ acquire: () => Promise<() => void>;
4
+ /** Execute a function exclusively within the semaphore */
5
+ runExclusive: <T>(fn: () => T | Promise<T>) => Promise<T>;
6
+ /** Number of available permits */
7
+ readonly available: number;
8
+ /** Number of tasks waiting for a permit */
9
+ readonly waiting: number;
10
+ }
11
+ /**
12
+ * Create a counting semaphore for concurrency control.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const sem = createSemaphore(3);
17
+ * const release = await sem.acquire();
18
+ * try {
19
+ * await doWork();
20
+ * } finally {
21
+ * release();
22
+ * }
23
+ * ```
24
+ */
25
+ declare function createSemaphore(permits: number): Semaphore;
26
+
27
+ export { type Semaphore, createSemaphore };
@@ -0,0 +1,27 @@
1
+ interface Semaphore {
2
+ /** Acquire a permit. Returns a release function. */
3
+ acquire: () => Promise<() => void>;
4
+ /** Execute a function exclusively within the semaphore */
5
+ runExclusive: <T>(fn: () => T | Promise<T>) => Promise<T>;
6
+ /** Number of available permits */
7
+ readonly available: number;
8
+ /** Number of tasks waiting for a permit */
9
+ readonly waiting: number;
10
+ }
11
+ /**
12
+ * Create a counting semaphore for concurrency control.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const sem = createSemaphore(3);
17
+ * const release = await sem.acquire();
18
+ * try {
19
+ * await doWork();
20
+ * } finally {
21
+ * release();
22
+ * }
23
+ * ```
24
+ */
25
+ declare function createSemaphore(permits: number): Semaphore;
26
+
27
+ export { type Semaphore, createSemaphore };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ // src/semaphore.ts
4
+ function createSemaphore(permits) {
5
+ if (permits < 1) throw new RangeError("permits must be >= 1");
6
+ let available = permits;
7
+ const waiters = [];
8
+ function release() {
9
+ available++;
10
+ if (waiters.length > 0) {
11
+ available--;
12
+ const next = waiters.shift();
13
+ next.resolve(release);
14
+ }
15
+ }
16
+ function acquire() {
17
+ if (available > 0) {
18
+ available--;
19
+ return Promise.resolve(release);
20
+ }
21
+ return new Promise((resolve) => {
22
+ waiters.push({ resolve });
23
+ });
24
+ }
25
+ async function runExclusive(fn) {
26
+ const releaseFn = await acquire();
27
+ try {
28
+ return await fn();
29
+ } finally {
30
+ releaseFn();
31
+ }
32
+ }
33
+ return {
34
+ acquire,
35
+ runExclusive,
36
+ get available() {
37
+ return available;
38
+ },
39
+ get waiting() {
40
+ return waiters.length;
41
+ }
42
+ };
43
+ }
44
+
45
+ exports.createSemaphore = createSemaphore;
46
+ //# sourceMappingURL=semaphore.js.map
47
+ //# sourceMappingURL=semaphore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/semaphore.ts"],"names":[],"mappings":";;;AAiCO,SAAS,gBAAgB,OAAA,EAA4B;AAC1D,EAAA,IAAI,OAAA,GAAU,CAAA,EAAG,MAAM,IAAI,WAAW,sBAAsB,CAAA;AAE5D,EAAA,IAAI,SAAA,GAAY,OAAA;AAChB,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,SAAS,OAAA,GAAgB;AACvB,IAAA,SAAA,EAAA;AACA,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,SAAA,EAAA;AACA,MAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,EAAM;AAC3B,MAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,SAAS,OAAA,GAA+B;AACtC,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,SAAA,EAAA;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,OAAO,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,IAAI,OAAA,CAAoB,CAAC,OAAA,KAAY;AAC1C,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,OAAA,EAAS,CAAA;AAAA,IAC1B,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,eAAe,aAAgB,EAAA,EAAsC;AACnE,IAAA,MAAM,SAAA,GAAY,MAAM,OAAA,EAAQ;AAChC,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IAClB,CAAA,SAAE;AACA,MAAA,SAAA,EAAU;AAAA,IACZ;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAI,SAAA,GAAY;AACd,MAAA,OAAO,SAAA;AAAA,IACT,CAAA;AAAA,IACA,IAAI,OAAA,GAAU;AACZ,MAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACjB;AAAA,GACF;AACF","file":"semaphore.js","sourcesContent":["// ============================================================================\n// FlowX — Counting Semaphore\n// ============================================================================\n\nexport interface Semaphore {\n /** Acquire a permit. Returns a release function. */\n acquire: () => Promise<() => void>;\n /** Execute a function exclusively within the semaphore */\n runExclusive: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Number of available permits */\n readonly available: number;\n /** Number of tasks waiting for a permit */\n readonly waiting: number;\n}\n\ninterface Waiter {\n resolve: (release: () => void) => void;\n}\n\n/**\n * Create a counting semaphore for concurrency control.\n *\n * @example\n * ```ts\n * const sem = createSemaphore(3);\n * const release = await sem.acquire();\n * try {\n * await doWork();\n * } finally {\n * release();\n * }\n * ```\n */\nexport function createSemaphore(permits: number): Semaphore {\n if (permits < 1) throw new RangeError('permits must be >= 1');\n\n let available = permits;\n const waiters: Waiter[] = [];\n\n function release(): void {\n available++;\n if (waiters.length > 0) {\n available--;\n const next = waiters.shift()!;\n next.resolve(release);\n }\n }\n\n function acquire(): Promise<() => void> {\n if (available > 0) {\n available--;\n return Promise.resolve(release);\n }\n\n return new Promise<() => void>((resolve) => {\n waiters.push({ resolve });\n });\n }\n\n async function runExclusive<T>(fn: () => T | Promise<T>): Promise<T> {\n const releaseFn = await acquire();\n try {\n return await fn();\n } finally {\n releaseFn();\n }\n }\n\n return {\n acquire,\n runExclusive,\n get available() {\n return available;\n },\n get waiting() {\n return waiters.length;\n },\n };\n}\n"]}
@@ -0,0 +1,45 @@
1
+ // src/semaphore.ts
2
+ function createSemaphore(permits) {
3
+ if (permits < 1) throw new RangeError("permits must be >= 1");
4
+ let available = permits;
5
+ const waiters = [];
6
+ function release() {
7
+ available++;
8
+ if (waiters.length > 0) {
9
+ available--;
10
+ const next = waiters.shift();
11
+ next.resolve(release);
12
+ }
13
+ }
14
+ function acquire() {
15
+ if (available > 0) {
16
+ available--;
17
+ return Promise.resolve(release);
18
+ }
19
+ return new Promise((resolve) => {
20
+ waiters.push({ resolve });
21
+ });
22
+ }
23
+ async function runExclusive(fn) {
24
+ const releaseFn = await acquire();
25
+ try {
26
+ return await fn();
27
+ } finally {
28
+ releaseFn();
29
+ }
30
+ }
31
+ return {
32
+ acquire,
33
+ runExclusive,
34
+ get available() {
35
+ return available;
36
+ },
37
+ get waiting() {
38
+ return waiters.length;
39
+ }
40
+ };
41
+ }
42
+
43
+ export { createSemaphore };
44
+ //# sourceMappingURL=semaphore.mjs.map
45
+ //# sourceMappingURL=semaphore.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/semaphore.ts"],"names":[],"mappings":";AAiCO,SAAS,gBAAgB,OAAA,EAA4B;AAC1D,EAAA,IAAI,OAAA,GAAU,CAAA,EAAG,MAAM,IAAI,WAAW,sBAAsB,CAAA;AAE5D,EAAA,IAAI,SAAA,GAAY,OAAA;AAChB,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,SAAS,OAAA,GAAgB;AACvB,IAAA,SAAA,EAAA;AACA,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,SAAA,EAAA;AACA,MAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,EAAM;AAC3B,MAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,SAAS,OAAA,GAA+B;AACtC,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,SAAA,EAAA;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,OAAO,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,IAAI,OAAA,CAAoB,CAAC,OAAA,KAAY;AAC1C,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,OAAA,EAAS,CAAA;AAAA,IAC1B,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,eAAe,aAAgB,EAAA,EAAsC;AACnE,IAAA,MAAM,SAAA,GAAY,MAAM,OAAA,EAAQ;AAChC,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IAClB,CAAA,SAAE;AACA,MAAA,SAAA,EAAU;AAAA,IACZ;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAI,SAAA,GAAY;AACd,MAAA,OAAO,SAAA;AAAA,IACT,CAAA;AAAA,IACA,IAAI,OAAA,GAAU;AACZ,MAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACjB;AAAA,GACF;AACF","file":"semaphore.mjs","sourcesContent":["// ============================================================================\n// FlowX — Counting Semaphore\n// ============================================================================\n\nexport interface Semaphore {\n /** Acquire a permit. Returns a release function. */\n acquire: () => Promise<() => void>;\n /** Execute a function exclusively within the semaphore */\n runExclusive: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Number of available permits */\n readonly available: number;\n /** Number of tasks waiting for a permit */\n readonly waiting: number;\n}\n\ninterface Waiter {\n resolve: (release: () => void) => void;\n}\n\n/**\n * Create a counting semaphore for concurrency control.\n *\n * @example\n * ```ts\n * const sem = createSemaphore(3);\n * const release = await sem.acquire();\n * try {\n * await doWork();\n * } finally {\n * release();\n * }\n * ```\n */\nexport function createSemaphore(permits: number): Semaphore {\n if (permits < 1) throw new RangeError('permits must be >= 1');\n\n let available = permits;\n const waiters: Waiter[] = [];\n\n function release(): void {\n available++;\n if (waiters.length > 0) {\n available--;\n const next = waiters.shift()!;\n next.resolve(release);\n }\n }\n\n function acquire(): Promise<() => void> {\n if (available > 0) {\n available--;\n return Promise.resolve(release);\n }\n\n return new Promise<() => void>((resolve) => {\n waiters.push({ resolve });\n });\n }\n\n async function runExclusive<T>(fn: () => T | Promise<T>): Promise<T> {\n const releaseFn = await acquire();\n try {\n return await fn();\n } finally {\n releaseFn();\n }\n }\n\n return {\n acquire,\n runExclusive,\n get available() {\n return available;\n },\n get waiting() {\n return waiters.length;\n },\n };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ interface ThrottleOptions {
2
+ /** Invoke on the leading edge (default: true) */
3
+ leading?: boolean;
4
+ /** Invoke on the trailing edge (default: true) */
5
+ trailing?: boolean;
6
+ }
7
+ interface ThrottledFunction<TArgs extends any[], TReturn> {
8
+ (...args: TArgs): Promise<TReturn>;
9
+ /** Cancel any pending trailing invocation */
10
+ cancel: () => void;
11
+ /** Whether there is a pending trailing invocation */
12
+ readonly pending: boolean;
13
+ }
14
+ /**
15
+ * Create a throttled version of an async function.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const throttledSave = throttle(saveData, 1000);
20
+ * await throttledSave(data);
21
+ * ```
22
+ */
23
+ declare function throttle<TArgs extends any[], TReturn>(fn: (...args: TArgs) => TReturn | Promise<TReturn>, wait: number, options?: ThrottleOptions): ThrottledFunction<TArgs, TReturn>;
24
+
25
+ export { type ThrottleOptions, type ThrottledFunction, throttle };
@@ -0,0 +1,25 @@
1
+ interface ThrottleOptions {
2
+ /** Invoke on the leading edge (default: true) */
3
+ leading?: boolean;
4
+ /** Invoke on the trailing edge (default: true) */
5
+ trailing?: boolean;
6
+ }
7
+ interface ThrottledFunction<TArgs extends any[], TReturn> {
8
+ (...args: TArgs): Promise<TReturn>;
9
+ /** Cancel any pending trailing invocation */
10
+ cancel: () => void;
11
+ /** Whether there is a pending trailing invocation */
12
+ readonly pending: boolean;
13
+ }
14
+ /**
15
+ * Create a throttled version of an async function.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const throttledSave = throttle(saveData, 1000);
20
+ * await throttledSave(data);
21
+ * ```
22
+ */
23
+ declare function throttle<TArgs extends any[], TReturn>(fn: (...args: TArgs) => TReturn | Promise<TReturn>, wait: number, options?: ThrottleOptions): ThrottledFunction<TArgs, TReturn>;
24
+
25
+ export { type ThrottleOptions, type ThrottledFunction, throttle };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ // src/throttle.ts
4
+ function throttle(fn, wait, options) {
5
+ const { leading = true, trailing = true } = options ?? {};
6
+ if (wait < 0) throw new RangeError("wait must be >= 0");
7
+ let lastCallTime = null;
8
+ let timer = null;
9
+ let lastArgs = null;
10
+ let isPending = false;
11
+ const trailingResolvers = [];
12
+ async function invokeTrailing() {
13
+ const args = lastArgs;
14
+ const resolvers = trailingResolvers.splice(0);
15
+ lastArgs = null;
16
+ isPending = false;
17
+ timer = null;
18
+ if (!args) {
19
+ for (const r of resolvers) r.resolve(void 0);
20
+ return;
21
+ }
22
+ try {
23
+ const result = await fn(...args);
24
+ for (const r of resolvers) r.resolve(result);
25
+ } catch (error) {
26
+ const err = error instanceof Error ? error : new Error(String(error));
27
+ for (const r of resolvers) r.reject(err);
28
+ }
29
+ }
30
+ function throttled(...args) {
31
+ const now = Date.now();
32
+ lastArgs = args;
33
+ const timeSinceLastCall = lastCallTime === null ? wait : now - lastCallTime;
34
+ const shouldCallLeading = leading && timeSinceLastCall >= wait;
35
+ if (shouldCallLeading) {
36
+ lastCallTime = now;
37
+ lastArgs = null;
38
+ if (timer) {
39
+ clearTimeout(timer);
40
+ timer = null;
41
+ }
42
+ const prevResolvers = trailingResolvers.splice(0);
43
+ const resultPromise = (async () => {
44
+ try {
45
+ const result = await fn(...args);
46
+ for (const r of prevResolvers) r.resolve(result);
47
+ return result;
48
+ } catch (error) {
49
+ const err = error instanceof Error ? error : new Error(String(error));
50
+ for (const r of prevResolvers) r.reject(err);
51
+ throw err;
52
+ }
53
+ })();
54
+ if (trailing) {
55
+ timer = setTimeout(() => {
56
+ lastCallTime = Date.now();
57
+ invokeTrailing();
58
+ }, wait);
59
+ }
60
+ return resultPromise;
61
+ }
62
+ isPending = true;
63
+ return new Promise((resolve, reject) => {
64
+ trailingResolvers.push({ resolve, reject });
65
+ if (!timer && trailing) {
66
+ const remaining = wait - timeSinceLastCall;
67
+ timer = setTimeout(() => {
68
+ lastCallTime = Date.now();
69
+ invokeTrailing();
70
+ }, remaining);
71
+ }
72
+ });
73
+ }
74
+ throttled.cancel = () => {
75
+ if (timer) {
76
+ clearTimeout(timer);
77
+ timer = null;
78
+ }
79
+ lastArgs = null;
80
+ lastCallTime = null;
81
+ isPending = false;
82
+ const resolvers = trailingResolvers.splice(0);
83
+ for (const r of resolvers) {
84
+ r.reject(new Error("Throttled call cancelled"));
85
+ }
86
+ };
87
+ Object.defineProperty(throttled, "pending", {
88
+ get() {
89
+ return isPending;
90
+ }
91
+ });
92
+ return throttled;
93
+ }
94
+
95
+ exports.throttle = throttle;
96
+ //# sourceMappingURL=throttle.js.map
97
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/throttle.ts"],"names":[],"mappings":"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","file":"throttle.js","sourcesContent":["// ============================================================================\n// FlowX — Async-Aware Throttle\n// ============================================================================\n\nexport interface ThrottleOptions {\n /** Invoke on the leading edge (default: true) */\n leading?: boolean;\n /** Invoke on the trailing edge (default: true) */\n trailing?: boolean;\n}\n\nexport interface ThrottledFunction<TArgs extends any[], TReturn> {\n (...args: TArgs): Promise<TReturn>;\n /** Cancel any pending trailing invocation */\n cancel: () => void;\n /** Whether there is a pending trailing invocation */\n readonly pending: boolean;\n}\n\n/**\n * Create a throttled version of an async function.\n *\n * @example\n * ```ts\n * const throttledSave = throttle(saveData, 1000);\n * await throttledSave(data);\n * ```\n */\nexport function throttle<TArgs extends any[], TReturn>(\n fn: (...args: TArgs) => TReturn | Promise<TReturn>,\n wait: number,\n options?: ThrottleOptions,\n): ThrottledFunction<TArgs, TReturn> {\n const { leading = true, trailing = true } = options ?? {};\n\n if (wait < 0) throw new RangeError('wait must be >= 0');\n\n let lastCallTime: number | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: TArgs | null = null;\n let isPending = false;\n const trailingResolvers: Array<{\n resolve: (value: TReturn) => void;\n reject: (error: Error) => void;\n }> = [];\n\n async function invokeTrailing(): Promise<void> {\n const args = lastArgs;\n const resolvers = trailingResolvers.splice(0);\n lastArgs = null;\n isPending = false;\n timer = null;\n\n if (!args) {\n /* istanbul ignore next -- resolvers is always empty here; lastArgs cleared before trailing fires */\n for (const r of resolvers) r.resolve(undefined as TReturn);\n return;\n }\n\n try {\n const result = await fn(...args);\n for (const r of resolvers) r.resolve(result);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const r of resolvers) r.reject(err);\n }\n }\n\n function throttled(...args: TArgs): Promise<TReturn> {\n const now = Date.now();\n lastArgs = args;\n\n const timeSinceLastCall = lastCallTime === null ? wait : now - lastCallTime;\n const shouldCallLeading = leading && timeSinceLastCall >= wait;\n\n if (shouldCallLeading) {\n lastCallTime = now;\n lastArgs = null;\n\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n\n // Resolve any pending trailing resolvers with this call's result\n const prevResolvers = trailingResolvers.splice(0);\n\n const resultPromise = (async () => {\n try {\n const result = await fn(...args);\n for (const r of prevResolvers) r.resolve(result);\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n /* istanbul ignore next -- prevResolvers is always empty on leading calls in normal flow */\n for (const r of prevResolvers) r.reject(err);\n throw err;\n }\n })();\n\n if (trailing) {\n timer = setTimeout(() => {\n lastCallTime = Date.now();\n invokeTrailing();\n }, wait);\n }\n\n return resultPromise;\n }\n\n // Schedule trailing call\n isPending = true;\n\n return new Promise<TReturn>((resolve, reject) => {\n trailingResolvers.push({ resolve, reject });\n\n if (!timer && trailing) {\n const remaining = wait - timeSinceLastCall;\n timer = setTimeout(() => {\n lastCallTime = Date.now();\n invokeTrailing();\n }, remaining);\n }\n });\n }\n\n throttled.cancel = (): void => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n lastArgs = null;\n lastCallTime = null;\n isPending = false;\n const resolvers = trailingResolvers.splice(0);\n for (const r of resolvers) {\n r.reject(new Error('Throttled call cancelled'));\n }\n };\n\n Object.defineProperty(throttled, 'pending', {\n get() {\n return isPending;\n },\n });\n\n return throttled as ThrottledFunction<TArgs, TReturn>;\n}\n"]}