@tanstack/pacer 0.4.0 → 0.6.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 (70) hide show
  1. package/dist/cjs/async-debouncer.cjs +39 -15
  2. package/dist/cjs/async-debouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer.d.cts +63 -11
  4. package/dist/cjs/async-queuer.cjs +102 -119
  5. package/dist/cjs/async-queuer.cjs.map +1 -1
  6. package/dist/cjs/async-queuer.d.cts +94 -52
  7. package/dist/cjs/async-rate-limiter.cjs +48 -16
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +74 -9
  10. package/dist/cjs/async-throttler.cjs +42 -17
  11. package/dist/cjs/async-throttler.cjs.map +1 -1
  12. package/dist/cjs/async-throttler.d.cts +62 -10
  13. package/dist/cjs/debouncer.cjs +16 -3
  14. package/dist/cjs/debouncer.cjs.map +1 -1
  15. package/dist/cjs/debouncer.d.cts +14 -4
  16. package/dist/cjs/index.cjs +2 -0
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/queuer.cjs +13 -5
  19. package/dist/cjs/queuer.cjs.map +1 -1
  20. package/dist/cjs/queuer.d.cts +9 -3
  21. package/dist/cjs/rate-limiter.cjs +26 -7
  22. package/dist/cjs/rate-limiter.cjs.map +1 -1
  23. package/dist/cjs/rate-limiter.d.cts +20 -6
  24. package/dist/cjs/throttler.cjs +19 -5
  25. package/dist/cjs/throttler.cjs.map +1 -1
  26. package/dist/cjs/throttler.d.cts +15 -4
  27. package/dist/cjs/types.d.cts +1 -0
  28. package/dist/cjs/utils.cjs +18 -7
  29. package/dist/cjs/utils.cjs.map +1 -1
  30. package/dist/cjs/utils.d.cts +3 -0
  31. package/dist/esm/async-debouncer.d.ts +63 -11
  32. package/dist/esm/async-debouncer.js +39 -15
  33. package/dist/esm/async-debouncer.js.map +1 -1
  34. package/dist/esm/async-queuer.d.ts +94 -52
  35. package/dist/esm/async-queuer.js +102 -119
  36. package/dist/esm/async-queuer.js.map +1 -1
  37. package/dist/esm/async-rate-limiter.d.ts +74 -9
  38. package/dist/esm/async-rate-limiter.js +48 -16
  39. package/dist/esm/async-rate-limiter.js.map +1 -1
  40. package/dist/esm/async-throttler.d.ts +62 -10
  41. package/dist/esm/async-throttler.js +42 -17
  42. package/dist/esm/async-throttler.js.map +1 -1
  43. package/dist/esm/debouncer.d.ts +14 -4
  44. package/dist/esm/debouncer.js +16 -3
  45. package/dist/esm/debouncer.js.map +1 -1
  46. package/dist/esm/index.js +3 -1
  47. package/dist/esm/queuer.d.ts +9 -3
  48. package/dist/esm/queuer.js +13 -5
  49. package/dist/esm/queuer.js.map +1 -1
  50. package/dist/esm/rate-limiter.d.ts +20 -6
  51. package/dist/esm/rate-limiter.js +26 -7
  52. package/dist/esm/rate-limiter.js.map +1 -1
  53. package/dist/esm/throttler.d.ts +15 -4
  54. package/dist/esm/throttler.js +19 -5
  55. package/dist/esm/throttler.js.map +1 -1
  56. package/dist/esm/types.d.ts +1 -0
  57. package/dist/esm/utils.d.ts +3 -0
  58. package/dist/esm/utils.js +19 -8
  59. package/dist/esm/utils.js.map +1 -1
  60. package/package.json +9 -3
  61. package/src/async-debouncer.ts +89 -22
  62. package/src/async-queuer.ts +205 -175
  63. package/src/async-rate-limiter.ts +111 -25
  64. package/src/async-throttler.ts +92 -24
  65. package/src/debouncer.ts +24 -7
  66. package/src/queuer.ts +19 -7
  67. package/src/rate-limiter.ts +37 -13
  68. package/src/throttler.ts +28 -9
  69. package/src/types.ts +3 -0
  70. package/src/utils.ts +19 -5
@@ -1,14 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const utils = require("./utils.cjs");
3
4
  const defaultOptions = {
4
5
  enabled: true,
5
6
  leading: false,
6
- onError: () => {
7
- },
8
- onSettled: () => {
9
- },
10
- onSuccess: () => {
11
- },
12
7
  trailing: true,
13
8
  wait: 0
14
9
  };
@@ -25,7 +20,8 @@ class AsyncDebouncer {
25
20
  this._timeoutId = null;
26
21
  this._options = {
27
22
  ...defaultOptions,
28
- ...initialOptions
23
+ ...initialOptions,
24
+ throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
29
25
  };
30
26
  }
31
27
  /**
@@ -45,8 +41,30 @@ class AsyncDebouncer {
45
41
  return this._options;
46
42
  }
47
43
  /**
48
- * Attempts to execute the debounced function
49
- * If a call is already in progress, it will be queued
44
+ * Returns the current debouncer enabled state
45
+ */
46
+ getEnabled() {
47
+ return !!utils.parseFunctionOrValue(this._options.enabled, this);
48
+ }
49
+ /**
50
+ * Returns the current debouncer wait state
51
+ */
52
+ getWait() {
53
+ return utils.parseFunctionOrValue(this._options.wait, this);
54
+ }
55
+ /**
56
+ * Attempts to execute the debounced function.
57
+ * If a call is already in progress, it will be queued.
58
+ *
59
+ * Error Handling:
60
+ * - If the debounced function throws and no `onError` handler is configured,
61
+ * the error will be thrown from this method.
62
+ * - If an `onError` handler is configured, errors will be caught and passed to the handler,
63
+ * and this method will return undefined.
64
+ * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
65
+ *
66
+ * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
67
+ * @throws The error from the debounced function if no onError handler is configured
50
68
  */
51
69
  async maybeExecute(...args) {
52
70
  this._cancel();
@@ -66,26 +84,32 @@ class AsyncDebouncer {
66
84
  }
67
85
  this._canLeadingExecute = true;
68
86
  resolve(this._lastResult);
69
- }, this._options.wait);
87
+ }, this.getWait());
70
88
  });
71
89
  }
72
90
  async executeFunction(...args) {
73
- if (!this._options.enabled) return void 0;
91
+ var _a, _b, _c, _d, _e, _f;
92
+ if (!this.getEnabled()) return void 0;
74
93
  this._abortController = new AbortController();
75
94
  try {
76
95
  this._isExecuting = true;
77
96
  this._lastResult = await this.fn(...args);
78
97
  this._successCount++;
79
- this._options.onSuccess(this._lastResult, this);
98
+ (_b = (_a = this._options).onSuccess) == null ? void 0 : _b.call(_a, this._lastResult, this);
80
99
  } catch (error) {
81
100
  this._errorCount++;
82
- this._options.onError(error, this);
101
+ (_d = (_c = this._options).onError) == null ? void 0 : _d.call(_c, error, this);
102
+ if (this._options.throwOnError) {
103
+ throw error;
104
+ } else {
105
+ console.error(error);
106
+ }
83
107
  } finally {
84
108
  this._isExecuting = false;
85
109
  this._isPending = false;
86
110
  this._settleCount++;
87
111
  this._abortController = null;
88
- this._options.onSettled(this);
112
+ (_f = (_e = this._options).onSettled) == null ? void 0 : _f.call(_e, this);
89
113
  }
90
114
  return this._lastResult;
91
115
  }
@@ -140,7 +164,7 @@ class AsyncDebouncer {
140
164
  * Returns `true` if there is a pending execution queued up for trailing execution
141
165
  */
142
166
  getIsPending() {
143
- return this._options.enabled && this._isPending;
167
+ return this.getEnabled() && this._isPending;
144
168
  }
145
169
  /**
146
170
  * Returns `true` if there is currently an execution in progress
@@ -1 +1 @@
1
- {"version":3,"file":"async-debouncer.cjs","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import type { AnyAsyncFunction } from './types'\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws\n */\n onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing\n * Defaults to 0ms\n */\n wait: number\n}\n\nconst defaultOptions: Required<AsyncDebouncerOptions<any>> = {\n enabled: true,\n leading: false,\n onError: () => {},\n onSettled: () => {},\n onSuccess: () => {},\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, { wait: 500 });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n private _abortController: AbortController | null = null\n private _canLeadingExecute = true\n private _errorCount = 0\n private _isExecuting = false\n private _isPending = false\n private _lastArgs: Parameters<TFn> | undefined\n private _lastResult: ReturnType<TFn> | undefined\n private _options: Required<AsyncDebouncerOptions<TFn>>\n private _settleCount = 0\n private _successCount = 0\n private _timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n }\n }\n\n /**\n * Updates the debouncer options\n * Returns the new options state\n */\n setOptions(newOptions: Partial<AsyncDebouncerOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n\n // End the pending state if the debouncer is disabled\n if (!this._options.enabled) {\n this._isPending = false\n }\n }\n\n /**\n * Returns the current debouncer options\n */\n getOptions(): Required<AsyncDebouncerOptions<TFn>> {\n return this._options\n }\n\n /**\n * Attempts to execute the debounced function\n * If a call is already in progress, it will be queued\n */\n async maybeExecute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n this._cancel()\n this._lastArgs = args\n\n // Handle leading execution\n if (this._options.leading && this._canLeadingExecute) {\n this._canLeadingExecute = false\n await this.executeFunction(...args)\n return this._lastResult\n }\n\n // Handle trailing execution\n if (this._options.trailing) {\n this._isPending = true\n }\n\n return new Promise((resolve) => {\n this._timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this._options.trailing && this._lastArgs) {\n await this.executeFunction(...this._lastArgs)\n }\n\n // Reset state and resolve\n this._canLeadingExecute = true\n resolve(this._lastResult)\n }, this._options.wait)\n })\n }\n\n private async executeFunction(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n if (!this._options.enabled) return undefined\n this._abortController = new AbortController()\n try {\n this._isExecuting = true\n this._lastResult = await this.fn(...args) // EXECUTE!\n this._successCount++\n this._options.onSuccess(this._lastResult!, this)\n } catch (error) {\n this._errorCount++\n this._options.onError(error, this)\n } finally {\n this._isExecuting = false\n this._isPending = false\n this._settleCount++\n this._abortController = null\n this._options.onSettled(this)\n }\n return this._lastResult\n }\n\n /**\n * Cancel without resetting _canLeadingExecute\n */\n private _cancel(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n if (this._abortController) {\n this._abortController.abort()\n this._abortController = null\n }\n this._lastArgs = undefined\n this._isPending = false\n this._isExecuting = false\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel(): void {\n this._canLeadingExecute = true\n this._cancel()\n }\n\n /**\n * Returns the last result of the debounced function\n */\n getLastResult(): ReturnType<TFn> | undefined {\n return this._lastResult\n }\n\n /**\n * Returns the number of times the function has been executed successfully\n */\n getSuccessCount(): number {\n return this._successCount\n }\n\n /**\n * Returns the number of times the function has settled (completed or errored)\n */\n getSettleCount(): number {\n return this._settleCount\n }\n\n /**\n * Returns the number of times the function has errored\n */\n getErrorCount(): number {\n return this._errorCount\n }\n\n /**\n * Returns `true` if there is a pending execution queued up for trailing execution\n */\n getIsPending(): boolean {\n return this._options.enabled && this._isPending\n }\n\n /**\n * Returns `true` if there is currently an execution in progress\n */\n getIsExecuting(): boolean {\n return this._isExecuting\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, { wait: 1000 });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: Omit<AsyncDebouncerOptions<TFn>, 'enabled'>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute.bind(asyncDebouncer)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"async-debouncer.cjs","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import { parseFunctionOrValue } from './utils'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws.\n * If provided, the handler will be called with the error and debouncer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)\n}\n\ntype AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncDebouncerOptions<any>,\n 'onError' | 'onSettled' | 'onSuccess'\n>\n\nconst defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {\n enabled: true,\n leading: false,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller.\n * - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown.\n * - The error count can be tracked using `getErrorCount()`.\n * - The debouncer maintains its state and can continue to be used after an error occurs.\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, {\n * wait: 500,\n * onError: (error) => {\n * console.error('Search failed:', error);\n * }\n * });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n private _options: AsyncDebouncerOptionsWithOptionalCallbacks\n private _abortController: AbortController | null = null\n private _canLeadingExecute = true\n private _errorCount = 0\n private _isExecuting = false\n private _isPending = false\n private _lastArgs: Parameters<TFn> | undefined\n private _lastResult: ReturnType<TFn> | undefined\n private _settleCount = 0\n private _successCount = 0\n private _timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n }\n\n /**\n * Updates the debouncer options\n * Returns the new options state\n */\n setOptions(newOptions: Partial<AsyncDebouncerOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n\n // End the pending state if the debouncer is disabled\n if (!this._options.enabled) {\n this._isPending = false\n }\n }\n\n /**\n * Returns the current debouncer options\n */\n getOptions(): AsyncDebouncerOptions<TFn> {\n return this._options\n }\n\n /**\n * Returns the current debouncer enabled state\n */\n getEnabled(): boolean {\n return !!parseFunctionOrValue(this._options.enabled, this)\n }\n\n /**\n * Returns the current debouncer wait state\n */\n getWait(): number {\n return parseFunctionOrValue(this._options.wait, this)\n }\n\n /**\n * Attempts to execute the debounced function.\n * If a call is already in progress, it will be queued.\n *\n * Error Handling:\n * - If the debounced function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the debounced function if no onError handler is configured\n */\n async maybeExecute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n this._cancel()\n this._lastArgs = args\n\n // Handle leading execution\n if (this._options.leading && this._canLeadingExecute) {\n this._canLeadingExecute = false\n await this.executeFunction(...args)\n return this._lastResult\n }\n\n // Handle trailing execution\n if (this._options.trailing) {\n this._isPending = true\n }\n\n return new Promise((resolve) => {\n this._timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this._options.trailing && this._lastArgs) {\n await this.executeFunction(...this._lastArgs)\n }\n\n // Reset state and resolve\n this._canLeadingExecute = true\n resolve(this._lastResult)\n }, this.getWait())\n })\n }\n\n private async executeFunction(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n if (!this.getEnabled()) return undefined\n this._abortController = new AbortController()\n try {\n this._isExecuting = true\n this._lastResult = await this.fn(...args) // EXECUTE!\n this._successCount++\n this._options.onSuccess?.(this._lastResult!, this)\n } catch (error) {\n this._errorCount++\n this._options.onError?.(error, this)\n if (this._options.throwOnError) {\n throw error\n } else {\n console.error(error)\n }\n } finally {\n this._isExecuting = false\n this._isPending = false\n this._settleCount++\n this._abortController = null\n this._options.onSettled?.(this)\n }\n return this._lastResult\n }\n\n /**\n * Cancel without resetting _canLeadingExecute\n */\n private _cancel(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n if (this._abortController) {\n this._abortController.abort()\n this._abortController = null\n }\n this._lastArgs = undefined\n this._isPending = false\n this._isExecuting = false\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel(): void {\n this._canLeadingExecute = true\n this._cancel()\n }\n\n /**\n * Returns the last result of the debounced function\n */\n getLastResult(): ReturnType<TFn> | undefined {\n return this._lastResult\n }\n\n /**\n * Returns the number of times the function has been executed successfully\n */\n getSuccessCount(): number {\n return this._successCount\n }\n\n /**\n * Returns the number of times the function has settled (completed or errored)\n */\n getSettleCount(): number {\n return this._settleCount\n }\n\n /**\n * Returns the number of times the function has errored\n */\n getErrorCount(): number {\n return this._errorCount\n }\n\n /**\n * Returns `true` if there is a pending execution queued up for trailing execution\n */\n getIsPending(): boolean {\n return this.getEnabled() && this._isPending\n }\n\n /**\n * Returns `true` if there is currently an execution in progress\n */\n getIsExecuting(): boolean {\n return this._isExecuting\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - The error state can be checked using the underlying AsyncDebouncer instance\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * },\n * throwOnError: true // Will both log the error and throw it\n * });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute.bind(asyncDebouncer)\n}\n"],"names":["parseFunctionOrValue"],"mappings":";;;AAwDA,MAAM,iBAA6D;AAAA,EACjE,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AACR;AAuCO,MAAM,eAA6C;AAAA,EAaxD,YACU,IACR,gBACA;AAFQ,SAAA,KAAA;AAZV,SAAQ,mBAA2C;AACnD,SAAQ,qBAAqB;AAC7B,SAAQ,cAAc;AACtB,SAAQ,eAAe;AACvB,SAAQ,aAAa;AAGrB,SAAQ,eAAe;AACvB,SAAQ,gBAAgB;AACxB,SAAQ,aAAoC;AAM1C,SAAK,WAAW;AAAA,MACd,GAAG;AAAA,MACH,GAAG;AAAA,MACH,cAAc,eAAe,gBAAgB,CAAC,eAAe;AAAA,IAC/D;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF,WAAW,YAAuD;AAChE,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,GAAG,WAAW;AAG9C,QAAA,CAAC,KAAK,SAAS,SAAS;AAC1B,WAAK,aAAa;AAAA,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAMF,aAAyC;AACvC,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,aAAsB;AACpB,WAAO,CAAC,CAACA,MAAAA,qBAAqB,KAAK,SAAS,SAAS,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM3D,UAAkB;AAChB,WAAOA,MAAqB,qBAAA,KAAK,SAAS,MAAM,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBtD,MAAM,gBACD,MACmC;AACtC,SAAK,QAAQ;AACb,SAAK,YAAY;AAGjB,QAAI,KAAK,SAAS,WAAW,KAAK,oBAAoB;AACpD,WAAK,qBAAqB;AACpB,YAAA,KAAK,gBAAgB,GAAG,IAAI;AAClC,aAAO,KAAK;AAAA,IAAA;AAIV,QAAA,KAAK,SAAS,UAAU;AAC1B,WAAK,aAAa;AAAA,IAAA;AAGb,WAAA,IAAI,QAAQ,CAAC,YAAY;AACzB,WAAA,aAAa,WAAW,YAAY;AAEvC,YAAI,KAAK,SAAS,YAAY,KAAK,WAAW;AAC5C,gBAAM,KAAK,gBAAgB,GAAG,KAAK,SAAS;AAAA,QAAA;AAI9C,aAAK,qBAAqB;AAC1B,gBAAQ,KAAK,WAAW;AAAA,MAAA,GACvB,KAAK,SAAS;AAAA,IAAA,CAClB;AAAA,EAAA;AAAA,EAGH,MAAc,mBACT,MACmC;;AACtC,QAAI,CAAC,KAAK,WAAW,EAAU,QAAA;AAC1B,SAAA,mBAAmB,IAAI,gBAAgB;AACxC,QAAA;AACF,WAAK,eAAe;AACpB,WAAK,cAAc,MAAM,KAAK,GAAG,GAAG,IAAI;AACnC,WAAA;AACL,uBAAK,UAAS,cAAd,4BAA0B,KAAK,aAAc;AAAA,aACtC,OAAO;AACT,WAAA;AACA,uBAAA,UAAS,YAAT,4BAAmB,OAAO;AAC3B,UAAA,KAAK,SAAS,cAAc;AACxB,cAAA;AAAA,MAAA,OACD;AACL,gBAAQ,MAAM,KAAK;AAAA,MAAA;AAAA,IACrB,UACA;AACA,WAAK,eAAe;AACpB,WAAK,aAAa;AACb,WAAA;AACL,WAAK,mBAAmB;AACnB,uBAAA,UAAS,cAAT,4BAAqB;AAAA,IAAI;AAEhC,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMN,UAAgB;AACtB,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IAAA;AAEpB,QAAI,KAAK,kBAAkB;AACzB,WAAK,iBAAiB,MAAM;AAC5B,WAAK,mBAAmB;AAAA,IAAA;AAE1B,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,eAAe;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMtB,SAAe;AACb,SAAK,qBAAqB;AAC1B,SAAK,QAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMf,gBAA6C;AAC3C,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,kBAA0B;AACxB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,iBAAyB;AACvB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,gBAAwB;AACtB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,eAAwB;AACf,WAAA,KAAK,gBAAgB,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMnC,iBAA0B;AACxB,WAAO,KAAK;AAAA,EAAA;AAEhB;AAoCgB,SAAA,cACd,IACA,gBACA;AACA,QAAM,iBAAiB,IAAI,eAAe,IAAI,cAAc;AACrD,SAAA,eAAe,aAAa,KAAK,cAAc;AACxD;;;"}
@@ -5,16 +5,19 @@ import { AnyAsyncFunction } from './types.cjs';
5
5
  export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
6
6
  /**
7
7
  * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
8
+ * Can be a boolean or a function that returns a boolean.
8
9
  * Defaults to true.
9
10
  */
10
- enabled?: boolean;
11
+ enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean);
11
12
  /**
12
13
  * Whether to execute on the leading edge of the timeout.
13
14
  * Defaults to false.
14
15
  */
15
16
  leading?: boolean;
16
17
  /**
17
- * Optional error handler for when the debounced function throws
18
+ * Optional error handler for when the debounced function throws.
19
+ * If provided, the handler will be called with the error and debouncer instance.
20
+ * This can be used alongside throwOnError - the handler will be called before any error is thrown.
18
21
  */
19
22
  onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void;
20
23
  /**
@@ -25,16 +28,23 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
25
28
  * Optional callback to call when the debounced function is executed
26
29
  */
27
30
  onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
31
+ /**
32
+ * Whether to throw errors when they occur.
33
+ * Defaults to true if no onError handler is provided, false if an onError handler is provided.
34
+ * Can be explicitly set to override these defaults.
35
+ */
36
+ throwOnError?: boolean;
28
37
  /**
29
38
  * Whether to execute on the trailing edge of the timeout.
30
39
  * Defaults to true.
31
40
  */
32
41
  trailing?: boolean;
33
42
  /**
34
- * Delay in milliseconds to wait after the last call before executing
43
+ * Delay in milliseconds to wait after the last call before executing.
44
+ * Can be a number or a function that returns a number.
35
45
  * Defaults to 0ms
36
46
  */
37
- wait: number;
47
+ wait: number | ((debouncer: AsyncDebouncer<TFn>) => number);
38
48
  }
39
49
  /**
40
50
  * A class that creates an async debounced function.
@@ -50,12 +60,23 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
50
60
  * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
51
61
  * instead of setting the result on a state variable from within the debounced function.
52
62
  *
63
+ * Error Handling:
64
+ * - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller.
65
+ * - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown.
66
+ * - The error count can be tracked using `getErrorCount()`.
67
+ * - The debouncer maintains its state and can continue to be used after an error occurs.
68
+ *
53
69
  * @example
54
70
  * ```ts
55
71
  * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {
56
72
  * const results = await searchAPI(value);
57
73
  * return results; // Return value is preserved
58
- * }, { wait: 500 });
74
+ * }, {
75
+ * wait: 500,
76
+ * onError: (error) => {
77
+ * console.error('Search failed:', error);
78
+ * }
79
+ * });
59
80
  *
60
81
  * // Called on each keystroke but only executes after 500ms of no typing
61
82
  * // Returns the API response directly
@@ -64,6 +85,7 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
64
85
  */
65
86
  export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
66
87
  private fn;
88
+ private _options;
67
89
  private _abortController;
68
90
  private _canLeadingExecute;
69
91
  private _errorCount;
@@ -71,7 +93,6 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
71
93
  private _isPending;
72
94
  private _lastArgs;
73
95
  private _lastResult;
74
- private _options;
75
96
  private _settleCount;
76
97
  private _successCount;
77
98
  private _timeoutId;
@@ -84,10 +105,28 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
84
105
  /**
85
106
  * Returns the current debouncer options
86
107
  */
87
- getOptions(): Required<AsyncDebouncerOptions<TFn>>;
108
+ getOptions(): AsyncDebouncerOptions<TFn>;
88
109
  /**
89
- * Attempts to execute the debounced function
90
- * If a call is already in progress, it will be queued
110
+ * Returns the current debouncer enabled state
111
+ */
112
+ getEnabled(): boolean;
113
+ /**
114
+ * Returns the current debouncer wait state
115
+ */
116
+ getWait(): number;
117
+ /**
118
+ * Attempts to execute the debounced function.
119
+ * If a call is already in progress, it will be queued.
120
+ *
121
+ * Error Handling:
122
+ * - If the debounced function throws and no `onError` handler is configured,
123
+ * the error will be thrown from this method.
124
+ * - If an `onError` handler is configured, errors will be caught and passed to the handler,
125
+ * and this method will return undefined.
126
+ * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
127
+ *
128
+ * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
129
+ * @throws The error from the debounced function if no onError handler is configured
91
130
  */
92
131
  maybeExecute(...args: Parameters<TFn>): Promise<ReturnType<TFn> | undefined>;
93
132
  private executeFunction;
@@ -133,16 +172,29 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
133
172
  * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
134
173
  * instead of setting the result on a state variable from within the debounced function.
135
174
  *
175
+ * Error Handling:
176
+ * - If an `onError` handler is provided, it will be called with the error and debouncer instance
177
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
178
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
179
+ * - The error state can be checked using the underlying AsyncDebouncer instance
180
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
181
+ *
136
182
  * @example
137
183
  * ```ts
138
184
  * const debounced = asyncDebounce(async (value: string) => {
139
185
  * const result = await saveToAPI(value);
140
186
  * return result; // Return value is preserved
141
- * }, { wait: 1000 });
187
+ * }, {
188
+ * wait: 1000,
189
+ * onError: (error) => {
190
+ * console.error('API call failed:', error);
191
+ * },
192
+ * throwOnError: true // Will both log the error and throw it
193
+ * });
142
194
  *
143
195
  * // Will only execute once, 1 second after the last call
144
196
  * // Returns the API response directly
145
197
  * const result = await debounced("third");
146
198
  * ```
147
199
  */
148
- export declare function asyncDebounce<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: Omit<AsyncDebouncerOptions<TFn>, 'enabled'>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
200
+ export declare function asyncDebounce<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;