@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.
- package/dist/cjs/async-batcher.cjs +2 -3
- package/dist/cjs/async-batcher.cjs.map +1 -1
- package/dist/cjs/async-batcher.d.cts +4 -8
- package/dist/cjs/async-debouncer.cjs +4 -4
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +3 -3
- package/dist/cjs/async-queuer.cjs +4 -4
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +3 -3
- package/dist/cjs/async-rate-limiter.cjs +2 -2
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +3 -3
- package/dist/cjs/async-throttler.cjs +4 -4
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +3 -3
- package/dist/cjs/batcher.cjs +1 -1
- package/dist/cjs/batcher.cjs.map +1 -1
- package/dist/cjs/batcher.d.cts +4 -4
- package/dist/cjs/debouncer.cjs +3 -3
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +2 -2
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +2 -2
- package/dist/cjs/rate-limiter.cjs +3 -3
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +2 -2
- package/dist/cjs/throttler.cjs +3 -3
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +2 -2
- package/dist/esm/async-batcher.d.ts +4 -8
- package/dist/esm/async-batcher.js +2 -3
- package/dist/esm/async-batcher.js.map +1 -1
- package/dist/esm/async-debouncer.d.ts +3 -3
- package/dist/esm/async-debouncer.js +4 -4
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +3 -3
- package/dist/esm/async-queuer.js +4 -4
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +3 -3
- package/dist/esm/async-rate-limiter.js +2 -2
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +3 -3
- package/dist/esm/async-throttler.js +4 -4
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +4 -4
- package/dist/esm/batcher.js +1 -1
- package/dist/esm/batcher.js.map +1 -1
- package/dist/esm/debouncer.d.ts +2 -2
- package/dist/esm/debouncer.js +3 -3
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/queuer.d.ts +2 -2
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +2 -2
- package/dist/esm/rate-limiter.js +3 -3
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +2 -2
- package/dist/esm/throttler.js +3 -3
- package/dist/esm/throttler.js.map +1 -1
- package/package.json +1 -1
- package/src/async-batcher.ts +10 -16
- package/src/async-debouncer.ts +11 -7
- package/src/async-queuer.ts +7 -7
- package/src/async-rate-limiter.ts +8 -4
- package/src/async-throttler.ts +10 -6
- package/src/batcher.ts +5 -5
- package/src/debouncer.ts +5 -5
- package/src/queuer.ts +2 -2
- package/src/rate-limiter.ts +5 -5
- package/src/throttler.ts +5 -5
package/dist/esm/throttler.d.ts
CHANGED
|
@@ -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
|
-
|
|
101
|
+
fn: TFn;
|
|
102
102
|
readonly store: Store<Readonly<ThrottlerState<TFn>>>;
|
|
103
103
|
options: ThrottlerOptions<TFn>;
|
|
104
104
|
constructor(fn: TFn, initialOptions: ThrottlerOptions<TFn>);
|
package/dist/esm/throttler.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Store } from "@tanstack/store";
|
|
2
2
|
import { parseFunctionOrValue } from "./utils.js";
|
|
3
3
|
function getDefaultThrottlerState() {
|
|
4
|
-
return
|
|
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
package/src/async-batcher.ts
CHANGED
|
@@ -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?: (
|
|
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
|
-
| '
|
|
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
|
-
|
|
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
|
|
package/src/async-debouncer.ts
CHANGED
|
@@ -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
|
|
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?: (
|
|
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
|
-
|
|
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
|
}
|
package/src/async-queuer.ts
CHANGED
|
@@ -71,7 +71,7 @@ export interface AsyncQueuerState<TValue> {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {
|
|
74
|
-
return
|
|
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
|
-
|
|
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?: (
|
|
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
|
-
|
|
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
|
package/src/async-throttler.ts
CHANGED
|
@@ -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
|
|
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?: (
|
|
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
|
-
|
|
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:',
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
251
|
+
public fn: (item: TValue) => void,
|
|
252
252
|
initialOptions: QueuerOptions<TValue> = {},
|
|
253
253
|
) {
|
|
254
254
|
this.options = {
|
package/src/rate-limiter.ts
CHANGED
|
@@ -26,13 +26,13 @@ export interface RateLimiterState {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function getDefaultRateLimiterState(): RateLimiterState {
|
|
29
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 })
|