@tanstack/pacer 0.12.0 → 0.14.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 (69) hide show
  1. package/dist/cjs/async-batcher.cjs +2 -3
  2. package/dist/cjs/async-batcher.cjs.map +1 -1
  3. package/dist/cjs/async-batcher.d.cts +4 -8
  4. package/dist/cjs/async-debouncer.cjs +4 -4
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +3 -3
  7. package/dist/cjs/async-queuer.cjs +4 -4
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +3 -3
  10. package/dist/cjs/async-rate-limiter.cjs +2 -2
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +3 -3
  13. package/dist/cjs/async-throttler.cjs +4 -4
  14. package/dist/cjs/async-throttler.cjs.map +1 -1
  15. package/dist/cjs/async-throttler.d.cts +3 -3
  16. package/dist/cjs/batcher.cjs +1 -1
  17. package/dist/cjs/batcher.cjs.map +1 -1
  18. package/dist/cjs/batcher.d.cts +4 -4
  19. package/dist/cjs/debouncer.cjs +3 -3
  20. package/dist/cjs/debouncer.cjs.map +1 -1
  21. package/dist/cjs/debouncer.d.cts +2 -2
  22. package/dist/cjs/queuer.cjs.map +1 -1
  23. package/dist/cjs/queuer.d.cts +2 -2
  24. package/dist/cjs/rate-limiter.cjs +3 -3
  25. package/dist/cjs/rate-limiter.cjs.map +1 -1
  26. package/dist/cjs/rate-limiter.d.cts +2 -2
  27. package/dist/cjs/throttler.cjs +3 -3
  28. package/dist/cjs/throttler.cjs.map +1 -1
  29. package/dist/cjs/throttler.d.cts +2 -2
  30. package/dist/esm/async-batcher.d.ts +4 -8
  31. package/dist/esm/async-batcher.js +2 -3
  32. package/dist/esm/async-batcher.js.map +1 -1
  33. package/dist/esm/async-debouncer.d.ts +3 -3
  34. package/dist/esm/async-debouncer.js +4 -4
  35. package/dist/esm/async-debouncer.js.map +1 -1
  36. package/dist/esm/async-queuer.d.ts +3 -3
  37. package/dist/esm/async-queuer.js +4 -4
  38. package/dist/esm/async-queuer.js.map +1 -1
  39. package/dist/esm/async-rate-limiter.d.ts +3 -3
  40. package/dist/esm/async-rate-limiter.js +2 -2
  41. package/dist/esm/async-rate-limiter.js.map +1 -1
  42. package/dist/esm/async-throttler.d.ts +3 -3
  43. package/dist/esm/async-throttler.js +4 -4
  44. package/dist/esm/async-throttler.js.map +1 -1
  45. package/dist/esm/batcher.d.ts +4 -4
  46. package/dist/esm/batcher.js +1 -1
  47. package/dist/esm/batcher.js.map +1 -1
  48. package/dist/esm/debouncer.d.ts +2 -2
  49. package/dist/esm/debouncer.js +3 -3
  50. package/dist/esm/debouncer.js.map +1 -1
  51. package/dist/esm/queuer.d.ts +2 -2
  52. package/dist/esm/queuer.js.map +1 -1
  53. package/dist/esm/rate-limiter.d.ts +2 -2
  54. package/dist/esm/rate-limiter.js +3 -3
  55. package/dist/esm/rate-limiter.js.map +1 -1
  56. package/dist/esm/throttler.d.ts +2 -2
  57. package/dist/esm/throttler.js +3 -3
  58. package/dist/esm/throttler.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/async-batcher.ts +10 -16
  61. package/src/async-debouncer.ts +11 -7
  62. package/src/async-queuer.ts +7 -7
  63. package/src/async-rate-limiter.ts +8 -4
  64. package/src/async-throttler.ts +10 -6
  65. package/src/batcher.ts +5 -5
  66. package/src/debouncer.ts +5 -5
  67. package/src/queuer.ts +2 -2
  68. package/src/rate-limiter.ts +5 -5
  69. package/src/throttler.ts +5 -5
@@ -48,7 +48,7 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
48
48
  /**
49
49
  * Callback function that is called after the function is executed
50
50
  */
51
- onExecute?: (throttler: Throttler<TFn>) => void;
51
+ onExecute?: (args: Parameters<TFn>, throttler: Throttler<TFn>) => void;
52
52
  /**
53
53
  * Whether to execute on the trailing edge of the timeout.
54
54
  * Defaults to true.
@@ -98,7 +98,7 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
98
98
  */
99
99
  export declare class Throttler<TFn extends AnyFunction> {
100
100
  #private;
101
- private fn;
101
+ fn: TFn;
102
102
  readonly store: Store<Readonly<ThrottlerState<TFn>>>;
103
103
  options: ThrottlerOptions<TFn>;
104
104
  constructor(fn: TFn, initialOptions: ThrottlerOptions<TFn>);
@@ -1,14 +1,14 @@
1
1
  import { Store } from "@tanstack/store";
2
2
  import { parseFunctionOrValue } from "./utils.js";
3
3
  function getDefaultThrottlerState() {
4
- return structuredClone({
4
+ return {
5
5
  executionCount: 0,
6
6
  isPending: false,
7
7
  lastArgs: void 0,
8
8
  lastExecutionTime: 0,
9
9
  nextExecutionTime: 0,
10
10
  status: "idle"
11
- });
11
+ };
12
12
  }
13
13
  const defaultOptions = {
14
14
  enabled: true,
@@ -83,7 +83,7 @@ class Throttler {
83
83
  isPending: false,
84
84
  lastArgs: void 0
85
85
  });
86
- this.options.onExecute?.(this);
86
+ this.options.onExecute?.(args, this);
87
87
  setTimeout(() => {
88
88
  if (!this.store.state.isPending) {
89
89
  this.#setState({ nextExecutionTime: void 0 });
@@ -1 +1 @@
1
- {"version":3,"file":"throttler.js","sources":["../../src/throttler.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { parseFunctionOrValue } from './utils'\nimport type { AnyFunction } from './types'\n\nexport interface ThrottlerState<TFn extends AnyFunction> {\n /**\n * Number of function executions that have been completed\n */\n executionCount: number\n /**\n * Whether the throttler is waiting for the timeout to trigger execution\n */\n isPending: boolean\n /**\n * The arguments from the most recent call to maybeExecute\n */\n lastArgs: Parameters<TFn> | undefined\n /**\n * Timestamp of the last function execution in milliseconds\n */\n lastExecutionTime: number\n /**\n * Timestamp when the next execution can occur in milliseconds\n */\n nextExecutionTime: number | undefined\n /**\n * Current execution status - 'idle' when not active, 'pending' when waiting for timeout\n */\n status: 'disabled' | 'idle' | 'pending'\n}\n\nfunction getDefaultThrottlerState<\n TFn extends AnyFunction,\n>(): ThrottlerState<TFn> {\n return structuredClone({\n executionCount: 0,\n isPending: false,\n lastArgs: undefined,\n lastExecutionTime: 0,\n nextExecutionTime: 0,\n status: 'idle',\n })\n}\n\n/**\n * Options for configuring a throttled function\n */\nexport interface ThrottlerOptions<TFn extends AnyFunction> {\n /**\n * Whether the throttler 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 | ((throttler: Throttler<TFn>) => boolean)\n /**\n * Initial state for the throttler\n */\n initialState?: Partial<ThrottlerState<TFn>>\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to true.\n */\n leading?: boolean\n /**\n * Callback function that is called after the function is executed\n */\n onExecute?: (throttler: Throttler<TFn>) => void\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Time window in milliseconds during which the function can only be executed once.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((throttler: Throttler<TFn>) => number)\n}\n\nconst defaultOptions: Omit<\n Required<ThrottlerOptions<any>>,\n 'initialState' | 'onExecute'\n> = {\n enabled: true,\n leading: true,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates a throttled function.\n *\n * Throttling ensures a function is called at most once within a specified time window.\n * Unlike debouncing which waits for a pause in calls, throttling guarantees consistent\n * execution timing regardless of call frequency.\n *\n * Supports both leading and trailing edge execution:\n * - Leading: Execute immediately on first call (default: true)\n * - Trailing: Execute after wait period if called during throttle (default: true)\n *\n * For collapsing rapid-fire events where you only care about the last call, consider using Debouncer.\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the throttler\n * - Use `onExecute` callback to react to function execution and implement custom logic\n * - The state includes execution count, last execution time, pending status, and more\n * - State can be accessed via `throttler.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `throttler.state`\n *\n * @example\n * ```ts\n * const throttler = new Throttler(\n * (id: string) => api.getData(id),\n * { wait: 1000 } // Execute at most once per second\n * );\n *\n * // First call executes immediately\n * throttler.maybeExecute('123');\n *\n * // Subsequent calls within 1000ms are throttled\n * throttler.maybeExecute('123'); // Throttled\n * ```\n */\nexport class Throttler<TFn extends AnyFunction> {\n readonly store: Store<Readonly<ThrottlerState<TFn>>> = new Store(\n getDefaultThrottlerState(),\n )\n options: ThrottlerOptions<TFn>\n #timeoutId: NodeJS.Timeout | undefined\n\n constructor(\n private fn: TFn,\n initialOptions: ThrottlerOptions<TFn>,\n ) {\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n }\n this.#setState(this.options.initialState ?? {})\n }\n\n /**\n * Updates the throttler options\n */\n setOptions = (newOptions: Partial<ThrottlerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // Cancel pending execution if the throttler is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<ThrottlerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isPending } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isPending\n ? 'pending'\n : 'idle',\n }\n })\n }\n\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the throttled function. The execution behavior depends on the throttler options:\n *\n * - If enough time has passed since the last execution (>= wait period):\n * - With leading=true: Executes immediately\n * - With leading=false: Waits for the next trailing execution\n *\n * - If within the wait period:\n * - With trailing=true: Schedules execution for end of wait period\n * - With trailing=false: Drops the execution\n *\n * @example\n * ```ts\n * const throttled = new Throttler(fn, { wait: 1000 });\n *\n * // First call executes immediately\n * throttled.maybeExecute('a', 'b');\n *\n * // Call during wait period - gets throttled\n * throttled.maybeExecute('c', 'd');\n * ```\n */\n maybeExecute = (...args: Parameters<TFn>): void => {\n const now = Date.now()\n const timeSinceLastExecution = now - this.store.state.lastExecutionTime\n const wait = this.#getWait()\n\n // Handle leading execution\n if (this.options.leading && timeSinceLastExecution >= wait) {\n this.#execute(...args)\n } else {\n // Store the most recent arguments for potential trailing execution\n this.#setState({\n lastArgs: args,\n })\n // Set up trailing execution if not already scheduled\n if (!this.#timeoutId && this.options.trailing) {\n // prevent large number if lastExecutionTime is undefined\n const _timeSinceLastExecution = this.store.state.lastExecutionTime\n ? now - this.store.state.lastExecutionTime\n : 0\n const timeoutDuration = wait - _timeSinceLastExecution\n this.#setState({ isPending: true })\n this.#timeoutId = setTimeout(() => {\n const { lastArgs } = this.store.state\n if (lastArgs !== undefined) {\n this.#execute(...lastArgs)\n }\n }, timeoutDuration)\n }\n }\n }\n\n #execute = (...args: Parameters<TFn>): void => {\n if (!this.#getEnabled()) return\n this.fn(...args) // EXECUTE!\n const lastExecutionTime = Date.now()\n const nextExecutionTime = lastExecutionTime + this.#getWait()\n this.#clearTimeout()\n this.#setState({\n executionCount: this.store.state.executionCount + 1,\n lastExecutionTime,\n nextExecutionTime,\n isPending: false,\n lastArgs: undefined,\n })\n this.options.onExecute?.(this)\n setTimeout(() => {\n if (!this.store.state.isPending) {\n this.#setState({ nextExecutionTime: undefined })\n }\n }, this.#getWait())\n }\n\n /**\n * Processes the current pending execution immediately\n */\n flush = (): void => {\n if (this.store.state.isPending && this.store.state.lastArgs) {\n this.#execute(...this.store.state.lastArgs)\n }\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = undefined\n }\n }\n\n /**\n * Cancels any pending trailing execution and clears internal state.\n *\n * If a trailing execution is scheduled (due to throttling with trailing=true),\n * this will prevent that execution from occurring. The internal timeout and\n * stored arguments will be cleared.\n *\n * Has no effect if there is no pending execution.\n */\n cancel = (): void => {\n this.#clearTimeout()\n this.#setState({\n lastArgs: undefined,\n isPending: false,\n })\n }\n\n /**\n * Resets the throttler state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultThrottlerState<TFn>())\n }\n}\n\n/**\n * Creates a throttled function that limits how often the provided function can execute.\n *\n * Throttling ensures a function executes at most once within a specified time window,\n * regardless of how many times it is called. This is useful for rate-limiting\n * expensive operations or UI updates.\n *\n * The throttled function can be configured to execute on the leading and/or trailing\n * edge of the throttle window via options.\n *\n * For handling bursts of events, consider using debounce() instead. For hard execution\n * limits, consider using rateLimit().\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the throttler\n * - Use `onExecute` callback to react to function execution and implement custom logic\n * - The state includes execution count, last execution time, pending status, and more\n * - State can be accessed via the underlying Throttler instance's `store.state` property\n * - When using framework adapters (React/Solid), state is accessed from the hook's state property\n *\n * @example\n * ```ts\n * // Basic throttling - max once per second\n * const throttled = throttle(updateUI, { wait: 1000 });\n *\n * // Configure leading/trailing execution\n * const throttled = throttle(saveData, {\n * wait: 2000,\n * leading: true, // Execute immediately on first call\n * trailing: true // Execute again after delay if called during wait\n * });\n * ```\n */\nexport function throttle<TFn extends AnyFunction>(\n fn: TFn,\n initialOptions: ThrottlerOptions<TFn>,\n) {\n const throttler = new Throttler(fn, initialOptions)\n return throttler.maybeExecute\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"throttler.js","sources":["../../src/throttler.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { parseFunctionOrValue } from './utils'\nimport type { AnyFunction } from './types'\n\nexport interface ThrottlerState<TFn extends AnyFunction> {\n /**\n * Number of function executions that have been completed\n */\n executionCount: number\n /**\n * Whether the throttler is waiting for the timeout to trigger execution\n */\n isPending: boolean\n /**\n * The arguments from the most recent call to maybeExecute\n */\n lastArgs: Parameters<TFn> | undefined\n /**\n * Timestamp of the last function execution in milliseconds\n */\n lastExecutionTime: number\n /**\n * Timestamp when the next execution can occur in milliseconds\n */\n nextExecutionTime: number | undefined\n /**\n * Current execution status - 'idle' when not active, 'pending' when waiting for timeout\n */\n status: 'disabled' | 'idle' | 'pending'\n}\n\nfunction getDefaultThrottlerState<\n TFn extends AnyFunction,\n>(): ThrottlerState<TFn> {\n return {\n executionCount: 0,\n isPending: false,\n lastArgs: undefined,\n lastExecutionTime: 0,\n nextExecutionTime: 0,\n status: 'idle',\n }\n}\n\n/**\n * Options for configuring a throttled function\n */\nexport interface ThrottlerOptions<TFn extends AnyFunction> {\n /**\n * Whether the throttler 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 | ((throttler: Throttler<TFn>) => boolean)\n /**\n * Initial state for the throttler\n */\n initialState?: Partial<ThrottlerState<TFn>>\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to true.\n */\n leading?: boolean\n /**\n * Callback function that is called after the function is executed\n */\n onExecute?: (args: Parameters<TFn>, throttler: Throttler<TFn>) => void\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Time window in milliseconds during which the function can only be executed once.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((throttler: Throttler<TFn>) => number)\n}\n\nconst defaultOptions: Omit<\n Required<ThrottlerOptions<any>>,\n 'initialState' | 'onExecute'\n> = {\n enabled: true,\n leading: true,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates a throttled function.\n *\n * Throttling ensures a function is called at most once within a specified time window.\n * Unlike debouncing which waits for a pause in calls, throttling guarantees consistent\n * execution timing regardless of call frequency.\n *\n * Supports both leading and trailing edge execution:\n * - Leading: Execute immediately on first call (default: true)\n * - Trailing: Execute after wait period if called during throttle (default: true)\n *\n * For collapsing rapid-fire events where you only care about the last call, consider using Debouncer.\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the throttler\n * - Use `onExecute` callback to react to function execution and implement custom logic\n * - The state includes execution count, last execution time, pending status, and more\n * - State can be accessed via `throttler.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `throttler.state`\n *\n * @example\n * ```ts\n * const throttler = new Throttler(\n * (id: string) => api.getData(id),\n * { wait: 1000 } // Execute at most once per second\n * );\n *\n * // First call executes immediately\n * throttler.maybeExecute('123');\n *\n * // Subsequent calls within 1000ms are throttled\n * throttler.maybeExecute('123'); // Throttled\n * ```\n */\nexport class Throttler<TFn extends AnyFunction> {\n readonly store: Store<Readonly<ThrottlerState<TFn>>> = new Store(\n getDefaultThrottlerState(),\n )\n options: ThrottlerOptions<TFn>\n #timeoutId: NodeJS.Timeout | undefined\n\n constructor(\n public fn: TFn,\n initialOptions: ThrottlerOptions<TFn>,\n ) {\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n }\n this.#setState(this.options.initialState ?? {})\n }\n\n /**\n * Updates the throttler options\n */\n setOptions = (newOptions: Partial<ThrottlerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // Cancel pending execution if the throttler is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<ThrottlerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isPending } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isPending\n ? 'pending'\n : 'idle',\n }\n })\n }\n\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the throttled function. The execution behavior depends on the throttler options:\n *\n * - If enough time has passed since the last execution (>= wait period):\n * - With leading=true: Executes immediately\n * - With leading=false: Waits for the next trailing execution\n *\n * - If within the wait period:\n * - With trailing=true: Schedules execution for end of wait period\n * - With trailing=false: Drops the execution\n *\n * @example\n * ```ts\n * const throttled = new Throttler(fn, { wait: 1000 });\n *\n * // First call executes immediately\n * throttled.maybeExecute('a', 'b');\n *\n * // Call during wait period - gets throttled\n * throttled.maybeExecute('c', 'd');\n * ```\n */\n maybeExecute = (...args: Parameters<TFn>): void => {\n const now = Date.now()\n const timeSinceLastExecution = now - this.store.state.lastExecutionTime\n const wait = this.#getWait()\n\n // Handle leading execution\n if (this.options.leading && timeSinceLastExecution >= wait) {\n this.#execute(...args)\n } else {\n // Store the most recent arguments for potential trailing execution\n this.#setState({\n lastArgs: args,\n })\n // Set up trailing execution if not already scheduled\n if (!this.#timeoutId && this.options.trailing) {\n // prevent large number if lastExecutionTime is undefined\n const _timeSinceLastExecution = this.store.state.lastExecutionTime\n ? now - this.store.state.lastExecutionTime\n : 0\n const timeoutDuration = wait - _timeSinceLastExecution\n this.#setState({ isPending: true })\n this.#timeoutId = setTimeout(() => {\n const { lastArgs } = this.store.state\n if (lastArgs !== undefined) {\n this.#execute(...lastArgs)\n }\n }, timeoutDuration)\n }\n }\n }\n\n #execute = (...args: Parameters<TFn>): void => {\n if (!this.#getEnabled()) return\n this.fn(...args) // EXECUTE!\n const lastExecutionTime = Date.now()\n const nextExecutionTime = lastExecutionTime + this.#getWait()\n this.#clearTimeout()\n this.#setState({\n executionCount: this.store.state.executionCount + 1,\n lastExecutionTime,\n nextExecutionTime,\n isPending: false,\n lastArgs: undefined,\n })\n this.options.onExecute?.(args, this)\n setTimeout(() => {\n if (!this.store.state.isPending) {\n this.#setState({ nextExecutionTime: undefined })\n }\n }, this.#getWait())\n }\n\n /**\n * Processes the current pending execution immediately\n */\n flush = (): void => {\n if (this.store.state.isPending && this.store.state.lastArgs) {\n this.#execute(...this.store.state.lastArgs)\n }\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = undefined\n }\n }\n\n /**\n * Cancels any pending trailing execution and clears internal state.\n *\n * If a trailing execution is scheduled (due to throttling with trailing=true),\n * this will prevent that execution from occurring. The internal timeout and\n * stored arguments will be cleared.\n *\n * Has no effect if there is no pending execution.\n */\n cancel = (): void => {\n this.#clearTimeout()\n this.#setState({\n lastArgs: undefined,\n isPending: false,\n })\n }\n\n /**\n * Resets the throttler state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultThrottlerState<TFn>())\n }\n}\n\n/**\n * Creates a throttled function that limits how often the provided function can execute.\n *\n * Throttling ensures a function executes at most once within a specified time window,\n * regardless of how many times it is called. This is useful for rate-limiting\n * expensive operations or UI updates.\n *\n * The throttled function can be configured to execute on the leading and/or trailing\n * edge of the throttle window via options.\n *\n * For handling bursts of events, consider using debounce() instead. For hard execution\n * limits, consider using rateLimit().\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the throttler\n * - Use `onExecute` callback to react to function execution and implement custom logic\n * - The state includes execution count, last execution time, pending status, and more\n * - State can be accessed via the underlying Throttler instance's `store.state` property\n * - When using framework adapters (React/Solid), state is accessed from the hook's state property\n *\n * @example\n * ```ts\n * // Basic throttling - max once per second\n * const throttled = throttle(updateUI, { wait: 1000 });\n *\n * // Configure leading/trailing execution\n * const throttled = throttle(saveData, {\n * wait: 2000,\n * leading: true, // Execute immediately on first call\n * trailing: true // Execute again after delay if called during wait\n * });\n * ```\n */\nexport function throttle<TFn extends AnyFunction>(\n fn: TFn,\n initialOptions: ThrottlerOptions<TFn>,\n) {\n const throttler = new Throttler(fn, initialOptions)\n return throttler.maybeExecute\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/pacer",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Utilities for debouncing, throttling, rate-limiting, queuing, and more.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -106,10 +106,6 @@ export interface AsyncBatcherOptions<TValue> {
106
106
  batch: Array<TValue>,
107
107
  batcher: AsyncBatcher<TValue>,
108
108
  ) => void
109
- /**
110
- * Callback fired after a batch is processed
111
- */
112
- onExecute?: (batcher: AsyncBatcher<TValue>) => void
113
109
  /**
114
110
  * Callback fired after items are added to the batcher
115
111
  */
@@ -117,11 +113,15 @@ export interface AsyncBatcherOptions<TValue> {
117
113
  /**
118
114
  * Optional callback to call when a batch is settled (completed or failed)
119
115
  */
120
- onSettled?: (batcher: AsyncBatcher<TValue>) => void
116
+ onSettled?: (batch: Array<TValue>, batcher: AsyncBatcher<TValue>) => void
121
117
  /**
122
118
  * Optional callback to call when a batch succeeds
123
119
  */
124
- onSuccess?: (result: any, batcher: AsyncBatcher<TValue>) => void
120
+ onSuccess?: (
121
+ result: any,
122
+ batch: Array<TValue>,
123
+ batcher: AsyncBatcher<TValue>,
124
+ ) => void
125
125
  /**
126
126
  * Whether the batcher should start processing immediately
127
127
  * @default true
@@ -144,12 +144,7 @@ export interface AsyncBatcherOptions<TValue> {
144
144
 
145
145
  type AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<
146
146
  Required<AsyncBatcherOptions<TValue>>,
147
- | 'initialState'
148
- | 'onError'
149
- | 'onExecute'
150
- | 'onItemsChange'
151
- | 'onSettled'
152
- | 'onSuccess'
147
+ 'initialState' | 'onError' | 'onItemsChange' | 'onSettled' | 'onSuccess'
153
148
  >
154
149
 
155
150
  const defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks<any> = {
@@ -229,7 +224,7 @@ export class AsyncBatcher<TValue> {
229
224
  #timeoutId: NodeJS.Timeout | null = null
230
225
 
231
226
  constructor(
232
- private fn: (items: Array<TValue>) => Promise<any>,
227
+ public fn: (items: Array<TValue>) => Promise<any>,
233
228
  initialOptions: AsyncBatcherOptions<TValue>,
234
229
  ) {
235
230
  this.options = {
@@ -329,7 +324,7 @@ export class AsyncBatcher<TValue> {
329
324
  lastResult: result,
330
325
  successCount: this.store.state.successCount + 1,
331
326
  })
332
- this.options.onSuccess?.(result, this)
327
+ this.options.onSuccess?.(result, batch, this)
333
328
  return result
334
329
  } catch (error) {
335
330
  this.#setState({
@@ -347,8 +342,7 @@ export class AsyncBatcher<TValue> {
347
342
  isExecuting: false,
348
343
  settleCount: this.store.state.settleCount + 1,
349
344
  })
350
- this.options.onSettled?.(this)
351
- this.options.onExecute?.(this)
345
+ this.options.onSettled?.(batch, this)
352
346
  }
353
347
  }
354
348
 
@@ -44,7 +44,7 @@ export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
44
44
  function getDefaultAsyncDebouncerState<
45
45
  TFn extends AnyAsyncFunction,
46
46
  >(): AsyncDebouncerState<TFn> {
47
- return structuredClone({
47
+ return {
48
48
  canLeadingExecute: true,
49
49
  errorCount: 0,
50
50
  isExecuting: false,
@@ -54,7 +54,7 @@ function getDefaultAsyncDebouncerState<
54
54
  settleCount: 0,
55
55
  successCount: 0,
56
56
  status: 'idle',
57
- })
57
+ }
58
58
  }
59
59
 
60
60
  /**
@@ -89,11 +89,15 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
89
89
  /**
90
90
  * Optional callback to call when the debounced function is executed
91
91
  */
92
- onSettled?: (debouncer: AsyncDebouncer<TFn>) => void
92
+ onSettled?: (args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void
93
93
  /**
94
94
  * Optional callback to call when the debounced function is executed
95
95
  */
96
- onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void
96
+ onSuccess?: (
97
+ result: ReturnType<TFn>,
98
+ args: Parameters<TFn>,
99
+ debouncer: AsyncDebouncer<TFn>,
100
+ ) => void
97
101
  /**
98
102
  * Whether to throw errors when they occur.
99
103
  * Defaults to true if no onError handler is provided, false if an onError handler is provided.
@@ -182,7 +186,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
182
186
  | null = null
183
187
 
184
188
  constructor(
185
- private fn: TFn,
189
+ public fn: TFn,
186
190
  initialOptions: AsyncDebouncerOptions<TFn>,
187
191
  ) {
188
192
  this.options = {
@@ -307,7 +311,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
307
311
  lastResult: result,
308
312
  successCount: this.store.state.successCount + 1,
309
313
  })
310
- this.options.onSuccess?.(result, this)
314
+ this.options.onSuccess?.(result, args, this)
311
315
  } catch (error) {
312
316
  this.#setState({
313
317
  errorCount: this.store.state.errorCount + 1,
@@ -324,7 +328,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
324
328
  settleCount: this.store.state.settleCount + 1,
325
329
  })
326
330
  this.#abortController = null
327
- this.options.onSettled?.(this)
331
+ this.options.onSettled?.(args, this)
328
332
  }
329
333
  return this.store.state.lastResult
330
334
  }
@@ -71,7 +71,7 @@ export interface AsyncQueuerState<TValue> {
71
71
  }
72
72
 
73
73
  function getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {
74
- return structuredClone({
74
+ return {
75
75
  activeItems: [],
76
76
  errorCount: 0,
77
77
  expirationCount: 0,
@@ -88,7 +88,7 @@ function getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {
88
88
  size: 0,
89
89
  status: 'idle',
90
90
  successCount: 0,
91
- })
91
+ }
92
92
  }
93
93
 
94
94
  export interface AsyncQueuerOptions<TValue> {
@@ -157,11 +157,11 @@ export interface AsyncQueuerOptions<TValue> {
157
157
  /**
158
158
  * Optional callback to call when a task is settled
159
159
  */
160
- onSettled?: (queuer: AsyncQueuer<TValue>) => void
160
+ onSettled?: (item: TValue, queuer: AsyncQueuer<TValue>) => void
161
161
  /**
162
162
  * Optional callback to call when a task succeeds
163
163
  */
164
- onSuccess?: (result: any, queuer: AsyncQueuer<TValue>) => void
164
+ onSuccess?: (result: any, item: TValue, queuer: AsyncQueuer<TValue>) => void
165
165
  /**
166
166
  * Whether the queuer should start processing tasks immediately or not.
167
167
  */
@@ -264,7 +264,7 @@ export class AsyncQueuer<TValue> {
264
264
  #timeoutIds: Set<NodeJS.Timeout> = new Set()
265
265
 
266
266
  constructor(
267
- private fn: (item: TValue) => Promise<any>,
267
+ public fn: (item: TValue) => Promise<any>,
268
268
  initialOptions: AsyncQueuerOptions<TValue> = {},
269
269
  ) {
270
270
  this.options = {
@@ -532,7 +532,7 @@ export class AsyncQueuer<TValue> {
532
532
  successCount: this.store.state.successCount + 1,
533
533
  lastResult,
534
534
  })
535
- this.options.onSuccess?.(lastResult, this)
535
+ this.options.onSuccess?.(lastResult, item, this)
536
536
  } catch (error) {
537
537
  this.#setState({
538
538
  errorCount: this.store.state.errorCount + 1,
@@ -548,7 +548,7 @@ export class AsyncQueuer<TValue> {
548
548
  ),
549
549
  settledCount: this.store.state.settledCount + 1,
550
550
  })
551
- this.options.onSettled?.(this)
551
+ this.options.onSettled?.(item, this)
552
552
  }
553
553
  }
554
554
  return item
@@ -93,12 +93,16 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
93
93
  /**
94
94
  * Optional function to call when the rate-limited function is executed
95
95
  */
96
- onSettled?: (rateLimiter: AsyncRateLimiter<TFn>) => void
96
+ onSettled?: (
97
+ args: Parameters<TFn>,
98
+ rateLimiter: AsyncRateLimiter<TFn>,
99
+ ) => void
97
100
  /**
98
101
  * Optional function to call when the rate-limited function is executed
99
102
  */
100
103
  onSuccess?: (
101
104
  result: ReturnType<TFn>,
105
+ args: Parameters<TFn>,
102
106
  rateLimiter: AsyncRateLimiter<TFn>,
103
107
  ) => void
104
108
  /**
@@ -206,7 +210,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
206
210
  #timeoutIds: Set<NodeJS.Timeout> = new Set()
207
211
 
208
212
  constructor(
209
- private fn: TFn,
213
+ public fn: TFn,
210
214
  initialOptions: AsyncRateLimiterOptions<TFn>,
211
215
  ) {
212
216
  this.options = {
@@ -333,7 +337,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
333
337
  successCount: this.store.state.successCount + 1,
334
338
  lastResult: result,
335
339
  })
336
- this.options.onSuccess?.(result, this)
340
+ this.options.onSuccess?.(result, args, this)
337
341
  } catch (error) {
338
342
  this.#setState({
339
343
  errorCount: this.store.state.errorCount + 1,
@@ -347,7 +351,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
347
351
  isExecuting: false,
348
352
  settleCount: this.store.state.settleCount + 1,
349
353
  })
350
- this.options.onSettled?.(this)
354
+ this.options.onSettled?.(args, this)
351
355
  }
352
356
 
353
357
  return this.store.state.lastResult
@@ -48,7 +48,7 @@ export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
48
48
  function getDefaultAsyncThrottlerState<
49
49
  TFn extends AnyAsyncFunction,
50
50
  >(): AsyncThrottlerState<TFn> {
51
- return structuredClone({
51
+ return {
52
52
  errorCount: 0,
53
53
  isExecuting: false,
54
54
  isPending: false,
@@ -59,7 +59,7 @@ function getDefaultAsyncThrottlerState<
59
59
  settleCount: 0,
60
60
  status: 'idle',
61
61
  successCount: 0,
62
- })
62
+ }
63
63
  }
64
64
 
65
65
  /**
@@ -94,12 +94,16 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
94
94
  /**
95
95
  * Optional function to call when the throttled function is executed
96
96
  */
97
- onSettled?: (asyncThrottler: AsyncThrottler<TFn>) => void
97
+ onSettled?: (
98
+ args: Parameters<TFn>,
99
+ asyncThrottler: AsyncThrottler<TFn>,
100
+ ) => void
98
101
  /**
99
102
  * Optional function to call when the throttled function is executed
100
103
  */
101
104
  onSuccess?: (
102
105
  result: ReturnType<TFn>,
106
+ args: Parameters<TFn>,
103
107
  asyncThrottler: AsyncThrottler<TFn>,
104
108
  ) => void
105
109
  /**
@@ -193,7 +197,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
193
197
  | null = null
194
198
 
195
199
  constructor(
196
- private fn: TFn,
200
+ public fn: TFn,
197
201
  initialOptions: AsyncThrottlerOptions<TFn>,
198
202
  ) {
199
203
  this.options = {
@@ -331,7 +335,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
331
335
  lastResult: result,
332
336
  successCount: this.store.state.successCount + 1,
333
337
  })
334
- this.options.onSuccess?.(result, this)
338
+ this.options.onSuccess?.(result, args, this)
335
339
  } catch (error) {
336
340
  this.#setState({
337
341
  errorCount: this.store.state.errorCount + 1,
@@ -351,7 +355,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
351
355
  nextExecutionTime,
352
356
  })
353
357
  this.#abortController = null
354
- this.options.onSettled?.(this)
358
+ this.options.onSettled?.(args, this)
355
359
  setTimeout(() => {
356
360
  if (!this.store.state.isPending) {
357
361
  this.#setState({ nextExecutionTime: undefined })
package/src/batcher.ts CHANGED
@@ -66,7 +66,7 @@ export interface BatcherOptions<TValue> {
66
66
  /**
67
67
  * Callback fired after a batch is processed
68
68
  */
69
- onExecute?: (batcher: Batcher<TValue>) => void
69
+ onExecute?: (batch: Array<TValue>, batcher: Batcher<TValue>) => void
70
70
  /**
71
71
  * Callback fired after items are added to the batcher
72
72
  */
@@ -124,7 +124,7 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks<any> = {
124
124
  * {
125
125
  * maxSize: 5,
126
126
  * wait: 2000,
127
- * onExecute: (batcher) => console.log('Batch executed:', batcher.peekAllItems())
127
+ * onExecute: (batch, batcher) => console.log('Batch executed:', batch)
128
128
  * }
129
129
  * );
130
130
  *
@@ -143,7 +143,7 @@ export class Batcher<TValue> {
143
143
  #timeoutId: NodeJS.Timeout | null = null
144
144
 
145
145
  constructor(
146
- private fn: (items: Array<TValue>) => void,
146
+ public fn: (items: Array<TValue>) => void,
147
147
  initialOptions: BatcherOptions<TValue>,
148
148
  ) {
149
149
  this.options = {
@@ -228,7 +228,7 @@ export class Batcher<TValue> {
228
228
  executionCount: this.store.state.executionCount + 1,
229
229
  totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
230
230
  })
231
- this.options.onExecute?.(this)
231
+ this.options.onExecute?.(batch, this)
232
232
  }
233
233
 
234
234
  /**
@@ -278,7 +278,7 @@ export class Batcher<TValue> {
278
278
  * (items) => console.log('Processing:', items),
279
279
  * {
280
280
  * maxSize: 3,
281
- * onExecute: (batcher) => console.log('Batch executed')
281
+ * onExecute: (batch, batcher) => console.log('Batch executed:', batch)
282
282
  * }
283
283
  * );
284
284
  *
package/src/debouncer.ts CHANGED
@@ -28,13 +28,13 @@ export interface DebouncerState<TFn extends AnyFunction> {
28
28
  function getDefaultDebouncerState<
29
29
  TFn extends AnyFunction,
30
30
  >(): DebouncerState<TFn> {
31
- return structuredClone({
31
+ return {
32
32
  canLeadingExecute: true,
33
33
  executionCount: 0,
34
34
  isPending: false,
35
35
  lastArgs: undefined,
36
36
  status: 'idle',
37
- })
37
+ }
38
38
  }
39
39
 
40
40
  /**
@@ -60,7 +60,7 @@ export interface DebouncerOptions<TFn extends AnyFunction> {
60
60
  /**
61
61
  * Callback function that is called after the function is executed
62
62
  */
63
- onExecute?: (debouncer: Debouncer<TFn>) => void
63
+ onExecute?: (args: Parameters<TFn>, debouncer: Debouncer<TFn>) => void
64
64
  /**
65
65
  * Whether to execute on the trailing edge of the timeout.
66
66
  * Defaults to true.
@@ -123,7 +123,7 @@ export class Debouncer<TFn extends AnyFunction> {
123
123
  #timeoutId: NodeJS.Timeout | undefined
124
124
 
125
125
  constructor(
126
- private fn: TFn,
126
+ public fn: TFn,
127
127
  initialOptions: DebouncerOptions<TFn>,
128
128
  ) {
129
129
  this.options = {
@@ -217,7 +217,7 @@ export class Debouncer<TFn extends AnyFunction> {
217
217
  isPending: false,
218
218
  lastArgs: undefined,
219
219
  })
220
- this.options.onExecute?.(this)
220
+ this.options.onExecute?.(args, this)
221
221
  }
222
222
 
223
223
  /**
package/src/queuer.ts CHANGED
@@ -225,7 +225,7 @@ export type QueuePosition = 'front' | 'back'
225
225
  * const autoQueue = new Queuer<number>((n) => console.log(n), {
226
226
  * started: true, // Begin processing immediately
227
227
  * wait: 1000, // Wait 1s between items
228
- * onExecute: (item) => console.log(`Processed ${item}`)
228
+ * onExecute: (item, queuer) => console.log(`Processed ${item}`)
229
229
  * });
230
230
  * autoQueue.addItem(1); // Will process after 1s
231
231
  * autoQueue.addItem(2); // Will process 1s after first item
@@ -248,7 +248,7 @@ export class Queuer<TValue> {
248
248
  #timeoutId: NodeJS.Timeout | null = null
249
249
 
250
250
  constructor(
251
- private fn: (item: TValue) => void,
251
+ public fn: (item: TValue) => void,
252
252
  initialOptions: QueuerOptions<TValue> = {},
253
253
  ) {
254
254
  this.options = {
@@ -26,13 +26,13 @@ export interface RateLimiterState {
26
26
  }
27
27
 
28
28
  function getDefaultRateLimiterState(): RateLimiterState {
29
- return structuredClone({
29
+ return {
30
30
  executionCount: 0,
31
31
  executionTimes: [],
32
32
  isExceeded: false,
33
33
  rejectionCount: 0,
34
34
  status: 'idle',
35
- })
35
+ }
36
36
  }
37
37
 
38
38
  /**
@@ -56,7 +56,7 @@ export interface RateLimiterOptions<TFn extends AnyFunction> {
56
56
  /**
57
57
  * Callback function that is called after the function is executed
58
58
  */
59
- onExecute?: (rateLimiter: RateLimiter<TFn>) => void
59
+ onExecute?: (args: Parameters<TFn>, rateLimiter: RateLimiter<TFn>) => void
60
60
  /**
61
61
  * Optional callback function that is called when an execution is rejected due to rate limiting
62
62
  */
@@ -136,7 +136,7 @@ export class RateLimiter<TFn extends AnyFunction> {
136
136
  #timeoutIds: Set<NodeJS.Timeout> = new Set()
137
137
 
138
138
  constructor(
139
- private fn: TFn,
139
+ public fn: TFn,
140
140
  initialOptions: RateLimiterOptions<TFn>,
141
141
  ) {
142
142
  this.options = {
@@ -240,7 +240,7 @@ export class RateLimiter<TFn extends AnyFunction> {
240
240
  this.#setState({
241
241
  executionCount: this.store.state.executionCount + 1,
242
242
  })
243
- this.options.onExecute?.(this)
243
+ this.options.onExecute?.(args, this)
244
244
  }
245
245
 
246
246
  #getExecutionTimesInWindow = (): Array<number> => {
package/src/throttler.ts CHANGED
@@ -32,14 +32,14 @@ export interface ThrottlerState<TFn extends AnyFunction> {
32
32
  function getDefaultThrottlerState<
33
33
  TFn extends AnyFunction,
34
34
  >(): ThrottlerState<TFn> {
35
- return structuredClone({
35
+ return {
36
36
  executionCount: 0,
37
37
  isPending: false,
38
38
  lastArgs: undefined,
39
39
  lastExecutionTime: 0,
40
40
  nextExecutionTime: 0,
41
41
  status: 'idle',
42
- })
42
+ }
43
43
  }
44
44
 
45
45
  /**
@@ -64,7 +64,7 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
64
64
  /**
65
65
  * Callback function that is called after the function is executed
66
66
  */
67
- onExecute?: (throttler: Throttler<TFn>) => void
67
+ onExecute?: (args: Parameters<TFn>, throttler: Throttler<TFn>) => void
68
68
  /**
69
69
  * Whether to execute on the trailing edge of the timeout.
70
70
  * Defaults to true.
@@ -131,7 +131,7 @@ export class Throttler<TFn extends AnyFunction> {
131
131
  #timeoutId: NodeJS.Timeout | undefined
132
132
 
133
133
  constructor(
134
- private fn: TFn,
134
+ public fn: TFn,
135
135
  initialOptions: ThrottlerOptions<TFn>,
136
136
  ) {
137
137
  this.options = {
@@ -245,7 +245,7 @@ export class Throttler<TFn extends AnyFunction> {
245
245
  isPending: false,
246
246
  lastArgs: undefined,
247
247
  })
248
- this.options.onExecute?.(this)
248
+ this.options.onExecute?.(args, this)
249
249
  setTimeout(() => {
250
250
  if (!this.store.state.isPending) {
251
251
  this.#setState({ nextExecutionTime: undefined })