@tanstack/pacer 0.2.0 → 0.4.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-debouncer.cjs +78 -45
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +60 -24
- package/dist/cjs/async-queuer.cjs +53 -3
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +28 -3
- package/dist/cjs/async-rate-limiter.cjs +75 -38
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +76 -24
- package/dist/cjs/async-throttler.cjs +85 -57
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +66 -25
- package/dist/cjs/debouncer.cjs +12 -14
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +10 -9
- package/dist/cjs/queuer.cjs +51 -1
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +30 -1
- package/dist/cjs/rate-limiter.cjs +19 -9
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +31 -11
- package/dist/cjs/throttler.cjs +29 -36
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +16 -17
- package/dist/cjs/types.d.cts +2 -6
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/async-debouncer.d.ts +60 -24
- package/dist/esm/async-debouncer.js +78 -45
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +28 -3
- package/dist/esm/async-queuer.js +53 -3
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +76 -24
- package/dist/esm/async-rate-limiter.js +75 -38
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +66 -25
- package/dist/esm/async-throttler.js +85 -57
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/debouncer.d.ts +10 -9
- package/dist/esm/debouncer.js +12 -14
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/queuer.d.ts +30 -1
- package/dist/esm/queuer.js +51 -1
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +31 -11
- package/dist/esm/rate-limiter.js +19 -9
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +16 -17
- package/dist/esm/throttler.js +29 -36
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/types.d.ts +2 -6
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/async-debouncer.ts +130 -81
- package/src/async-queuer.ts +93 -8
- package/src/async-rate-limiter.ts +141 -67
- package/src/async-throttler.ts +152 -94
- package/src/debouncer.ts +26 -33
- package/src/queuer.ts +92 -4
- package/src/rate-limiter.ts +56 -32
- package/src/throttler.ts +45 -53
- package/src/types.ts +2 -10
- package/src/utils.ts +1 -1
package/dist/cjs/queuer.cjs
CHANGED
|
@@ -4,6 +4,8 @@ const defaultOptions = {
|
|
|
4
4
|
addItemsTo: "back",
|
|
5
5
|
getItemsFrom: "front",
|
|
6
6
|
getPriority: (item) => (item == null ? void 0 : item.priority) ?? 0,
|
|
7
|
+
getIsExpired: () => false,
|
|
8
|
+
expirationDuration: Infinity,
|
|
7
9
|
initialItems: [],
|
|
8
10
|
maxSize: Infinity,
|
|
9
11
|
onGetNextItem: () => {
|
|
@@ -14,14 +16,18 @@ const defaultOptions = {
|
|
|
14
16
|
},
|
|
15
17
|
onReject: () => {
|
|
16
18
|
},
|
|
19
|
+
onExpire: () => {
|
|
20
|
+
},
|
|
17
21
|
started: false,
|
|
18
22
|
wait: 0
|
|
19
23
|
};
|
|
20
24
|
class Queuer {
|
|
21
25
|
constructor(initialOptions = defaultOptions) {
|
|
22
26
|
this._items = [];
|
|
27
|
+
this._itemTimestamps = [];
|
|
23
28
|
this._executionCount = 0;
|
|
24
29
|
this._rejectionCount = 0;
|
|
30
|
+
this._expirationCount = 0;
|
|
25
31
|
this._onItemsChanges = [];
|
|
26
32
|
this._pendingTick = false;
|
|
27
33
|
this._options = { ...defaultOptions, ...initialOptions };
|
|
@@ -38,7 +44,6 @@ class Queuer {
|
|
|
38
44
|
*/
|
|
39
45
|
setOptions(newOptions) {
|
|
40
46
|
this._options = { ...this._options, ...newOptions };
|
|
41
|
-
return this._options;
|
|
42
47
|
}
|
|
43
48
|
/**
|
|
44
49
|
* Returns the current queuer options
|
|
@@ -54,6 +59,7 @@ class Queuer {
|
|
|
54
59
|
this._pendingTick = false;
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
62
|
+
this.checkExpiredItems();
|
|
57
63
|
while (!this.getIsEmpty()) {
|
|
58
64
|
const nextItem = this.getNextItem(this._options.getItemsFrom);
|
|
59
65
|
if (nextItem === void 0) {
|
|
@@ -68,6 +74,38 @@ class Queuer {
|
|
|
68
74
|
}
|
|
69
75
|
this._pendingTick = false;
|
|
70
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks for and removes expired items from the queuer
|
|
79
|
+
*/
|
|
80
|
+
checkExpiredItems() {
|
|
81
|
+
if (this._options.expirationDuration === Infinity && this._options.getIsExpired === defaultOptions.getIsExpired)
|
|
82
|
+
return;
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const expiredIndices = [];
|
|
85
|
+
for (let i = 0; i < this._items.length; i++) {
|
|
86
|
+
const timestamp = this._itemTimestamps[i];
|
|
87
|
+
if (timestamp === void 0) continue;
|
|
88
|
+
const item = this._items[i];
|
|
89
|
+
if (item === void 0) continue;
|
|
90
|
+
const isExpired = this._options.getIsExpired !== defaultOptions.getIsExpired ? this._options.getIsExpired(item, timestamp) : now - timestamp > this._options.expirationDuration;
|
|
91
|
+
if (isExpired) {
|
|
92
|
+
expiredIndices.push(i);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (let i = expiredIndices.length - 1; i >= 0; i--) {
|
|
96
|
+
const index = expiredIndices[i];
|
|
97
|
+
if (index === void 0) continue;
|
|
98
|
+
const expiredItem = this._items[index];
|
|
99
|
+
if (expiredItem === void 0) continue;
|
|
100
|
+
this._items.splice(index, 1);
|
|
101
|
+
this._itemTimestamps.splice(index, 1);
|
|
102
|
+
this._expirationCount++;
|
|
103
|
+
this._options.onExpire(expiredItem, this);
|
|
104
|
+
}
|
|
105
|
+
if (expiredIndices.length > 0) {
|
|
106
|
+
this._options.onItemsChange(this);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
71
109
|
/**
|
|
72
110
|
* Stops the queuer from processing items
|
|
73
111
|
*/
|
|
@@ -122,14 +160,18 @@ class Queuer {
|
|
|
122
160
|
);
|
|
123
161
|
if (insertIndex === -1) {
|
|
124
162
|
this._items.push(item);
|
|
163
|
+
this._itemTimestamps.push(Date.now());
|
|
125
164
|
} else {
|
|
126
165
|
this._items.splice(insertIndex, 0, item);
|
|
166
|
+
this._itemTimestamps.splice(insertIndex, 0, Date.now());
|
|
127
167
|
}
|
|
128
168
|
} else {
|
|
129
169
|
if (position === "front") {
|
|
130
170
|
this._items.unshift(item);
|
|
171
|
+
this._itemTimestamps.unshift(Date.now());
|
|
131
172
|
} else {
|
|
132
173
|
this._items.push(item);
|
|
174
|
+
this._itemTimestamps.push(Date.now());
|
|
133
175
|
}
|
|
134
176
|
}
|
|
135
177
|
if (this._running && !this._pendingTick) {
|
|
@@ -156,8 +198,10 @@ class Queuer {
|
|
|
156
198
|
let item;
|
|
157
199
|
if (position === "front") {
|
|
158
200
|
item = this._items.shift();
|
|
201
|
+
this._itemTimestamps.shift();
|
|
159
202
|
} else {
|
|
160
203
|
item = this._items.pop();
|
|
204
|
+
this._itemTimestamps.pop();
|
|
161
205
|
}
|
|
162
206
|
if (item !== void 0) {
|
|
163
207
|
this._executionCount++;
|
|
@@ -219,6 +263,12 @@ class Queuer {
|
|
|
219
263
|
getRejectionCount() {
|
|
220
264
|
return this._rejectionCount;
|
|
221
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Returns the number of items that have expired from the queuer
|
|
268
|
+
*/
|
|
269
|
+
getExpirationCount() {
|
|
270
|
+
return this._expirationCount;
|
|
271
|
+
}
|
|
222
272
|
/**
|
|
223
273
|
* Returns true if the queuer is running
|
|
224
274
|
*/
|
package/dist/cjs/queuer.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queuer.cjs","sources":["../../src/queuer.ts"],"sourcesContent":["/**\n * Options for configuring a Queuer instance\n */\nexport interface QueuerOptions<TValue> {\n /**\n * Default position to add items to the queuer\n * @default 'back'\n */\n addItemsTo?: QueuePosition\n /**\n * Default position to get items from during processing\n * @default 'front'\n */\n getItemsFrom?: QueuePosition\n /**\n * Function to determine priority of items in the queuer\n * Higher priority items will be processed first\n */\n getPriority?: (item: TValue) => number\n /**\n * Initial items to populate the queuer with\n */\n initialItems?: Array<TValue>\n /**\n * Maximum number of items allowed in the queuer\n */\n maxSize?: number\n /**\n * Callback fired whenever an item is removed from the queuer\n */\n onGetNextItem?: (item: TValue, queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever the queuer's running state changes\n */\n onIsRunningChange?: (queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever an item is added or removed from the queuer\n */\n onItemsChange?: (queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever an item is rejected from being added to the queuer\n */\n onReject?: (item: TValue, queuer: Queuer<TValue>) => void\n /**\n * Whether the queuer should start processing tasks immediately\n */\n started?: boolean\n /**\n * Time in milliseconds to wait between processing items\n */\n wait?: number\n}\n\nconst defaultOptions: Required<QueuerOptions<any>> = {\n addItemsTo: 'back',\n getItemsFrom: 'front',\n getPriority: (item) => item?.priority ?? 0,\n initialItems: [],\n maxSize: Infinity,\n onGetNextItem: () => {},\n onIsRunningChange: () => {},\n onItemsChange: () => {},\n onReject: () => {},\n started: false,\n wait: 0,\n}\n\n/**\n * Position type for addItem and getNextItem operations\n */\nexport type QueuePosition = 'front' | 'back'\n\n/**\n * A flexible queue data structure that defaults to FIFO (First In First Out) behavior\n * with optional position overrides for stack-like or double-ended operations.\n *\n * The queuer can automatically process items as they are added, with configurable\n * wait times between processing each item. Processing can be started/stopped\n * and the queuer will maintain its state.\n *\n * Supports priority-based ordering when a getPriority function is provided.\n * Items with higher priority values will be processed first.\n *\n * Default queue behavior:\n * - addItem(item): adds to back of queuer\n * - getNextItem(): removes and returns from front of queuer\n *\n * Stack (LIFO) behavior:\n * - addItem(item, 'back'): adds to back\n * - getNextItem('back'): removes and returns from back\n *\n * Double-ended queuer behavior:\n * - addItem(item, position): adds to specified position ('front' or 'back')\n * - getNextItem(position): removes and returns from specified position\n *\n * Processing behavior:\n * - start(): begins processing items in the queuer\n * - stop(): pauses processing\n * - wait: configurable delay between processing items\n * - onItemsChange/onGetNextItem: callbacks for monitoring queuer state\n *\n * @example\n * ```ts\n * // FIFO queuer\n * const queuer = new Queuer<number>();\n * queuer.addItem(1); // [1]\n * queuer.addItem(2); // [1, 2]\n * queuer.getNextItem(); // returns 1, queuer is [2]\n *\n * // Priority queuer with processing\n * const priorityQueue = new Queuer<number>({\n * getPriority: (n) => n, // Higher numbers have priority\n * started: true, // Begin processing immediately\n * wait: 1000, // Wait 1s between items\n * onGetNextItem: (item, queuer) => console.log(item)\n * });\n * priorityQueue.addItem(1); // [1]\n * priorityQueue.addItem(3); // [3, 1] - 3 processed first\n * priorityQueue.addItem(2); // [3, 2, 1]\n * ```\n */\nexport class Queuer<TValue> {\n private _options: Required<QueuerOptions<TValue>>\n private _items: Array<TValue> = []\n private _executionCount = 0\n private _rejectionCount = 0\n private _onItemsChanges: Array<(item: TValue) => void> = []\n private _running: boolean\n private _pendingTick = false\n\n constructor(initialOptions: QueuerOptions<TValue> = defaultOptions) {\n this._options = { ...defaultOptions, ...initialOptions }\n this._running = this._options.started\n\n for (let i = 0; i < this._options.initialItems.length; i++) {\n const item = this._options.initialItems[i]!\n const isLast = i === this._options.initialItems.length - 1\n this.addItem(item, this._options.addItemsTo, isLast)\n }\n }\n\n /**\n * Updates the queuer options\n * Returns the new options state\n */\n setOptions(\n newOptions: Partial<QueuerOptions<TValue>>,\n ): QueuerOptions<TValue> {\n this._options = { ...this._options, ...newOptions }\n return this._options\n }\n\n /**\n * Returns the current queuer options\n */\n getOptions(): Required<QueuerOptions<TValue>> {\n return this._options\n }\n\n /**\n * Processes items in the queuer\n */\n private tick() {\n if (!this._running) {\n this._pendingTick = false\n return\n }\n while (!this.getIsEmpty()) {\n const nextItem = this.getNextItem(this._options.getItemsFrom)\n if (nextItem === undefined) {\n break\n }\n this._onItemsChanges.forEach((cb) => cb(nextItem))\n\n if (this._options.wait > 0) {\n // Use setTimeout to wait before processing next item\n setTimeout(() => this.tick(), this._options.wait)\n return\n }\n\n this.tick()\n }\n this._pendingTick = false\n }\n\n /**\n * Stops the queuer from processing items\n */\n stop() {\n this._running = false\n this._pendingTick = false\n this._options.onIsRunningChange(this)\n }\n\n /**\n * Starts the queuer and processes items\n */\n start() {\n this._running = true\n if (!this._pendingTick && !this.getIsEmpty()) {\n this._pendingTick = true\n this.tick()\n }\n this._options.onIsRunningChange(this)\n }\n\n /**\n * Removes all items from the queuer\n */\n clear(): void {\n this._items = []\n this._options.onItemsChange(this)\n }\n\n /**\n * Resets the queuer to its initial state\n */\n reset(withInitialItems?: boolean): void {\n this.clear()\n this._executionCount = 0\n if (withInitialItems) {\n this._items = [...this._options.initialItems]\n }\n this._running = this._options.started\n }\n\n /**\n * Adds an item to the queuer and starts processing if not already running\n * @returns true if item was added, false if queuer is full\n */\n addItem(\n item: TValue,\n position: QueuePosition = this._options.addItemsTo,\n runOnUpdate: boolean = true,\n ): boolean {\n if (this.getIsFull()) {\n this._rejectionCount++\n this._options.onReject(item, this)\n return false\n }\n\n if (this._options.getPriority !== defaultOptions.getPriority) {\n // If custom priority function is provided, insert based on priority\n const priority = this._options.getPriority(item)\n const insertIndex = this._items.findIndex(\n (existing) => this._options.getPriority(existing) > priority,\n )\n\n if (insertIndex === -1) {\n this._items.push(item)\n } else {\n this._items.splice(insertIndex, 0, item)\n }\n } else {\n // Default FIFO/LIFO behavior\n if (position === 'front') {\n this._items.unshift(item)\n } else {\n this._items.push(item)\n }\n }\n\n if (this._running && !this._pendingTick) {\n this._pendingTick = true\n this.tick()\n }\n if (runOnUpdate) {\n this._options.onItemsChange(this)\n }\n return true\n }\n\n /**\n * Removes and returns an item from the queuer using shift (default) or pop\n *\n * @example\n * ```ts\n * // Standard FIFO queuer\n * queuer.getNextItem()\n * // Stack-like behavior (LIFO)\n * queuer.getNextItem('back')\n * ```\n */\n getNextItem(\n position: QueuePosition = this._options.getItemsFrom,\n ): TValue | undefined {\n let item: TValue | undefined\n\n if (position === 'front') {\n item = this._items.shift()\n } else {\n item = this._items.pop()\n }\n\n if (item !== undefined) {\n this._executionCount++\n this._options.onItemsChange(this)\n this._options.onGetNextItem(item, this)\n }\n return item\n }\n\n /**\n * Returns an item without removing it\n *\n * @example\n * ```ts\n * // Look at next item to getNextItem\n * queuer.getPeek()\n * // Look at last item (like stack top)\n * queuer.getPeek('back')\n * ```\n */\n getPeek(\n position: QueuePosition = this._options.getItemsFrom,\n ): TValue | undefined {\n if (position === 'front') {\n return this._items[0]\n }\n return this._items[this._items.length - 1]\n }\n\n /**\n * Returns true if the queuer is empty\n */\n getIsEmpty(): boolean {\n return this._items.length === 0\n }\n\n /**\n * Returns true if the queuer is full\n */\n getIsFull(): boolean {\n return this._items.length >= this._options.maxSize\n }\n\n /**\n * Returns the current size of the queuer\n */\n getSize(): number {\n return this._items.length\n }\n\n /**\n * Returns a copy of all items in the queuer\n */\n getAllItems(): Array<TValue> {\n return [...this._items]\n }\n\n /**\n * Returns the number of items that have been removed from the queuer\n */\n getExecutionCount(): number {\n return this._executionCount\n }\n\n /**\n * Returns the number of items that have been rejected from the queuer\n */\n getRejectionCount(): number {\n return this._rejectionCount\n }\n\n /**\n * Returns true if the queuer is running\n */\n getIsRunning() {\n return this._running\n }\n\n /**\n * Returns true if the queuer is running but has no items to process\n */\n getIsIdle() {\n return this._running && this.getIsEmpty()\n }\n}\n\n/**\n * Creates a queue that processes items in a queuer immediately upon addition.\n * Items are processed sequentially in FIFO order by default.\n *\n * This is a simplified wrapper around the Queuer class that only exposes the\n * `addItem` method. This queue is always running and will process items as they are added.\n * For more control over queuer processing, use the Queuer class\n * directly which provides methods like `start`, `stop`, `reset`, and more.\n *\n * @example\n * ```ts\n * // Basic sequential processing\n * const processItems = queuer<number>({\n * wait: 1000,\n * onItemsChange: (queuer) => console.log(queuer.getAllItems())\n * })\n * processItems(1) // Logs: 1\n * processItems(2) // Logs: 2 after 1 completes\n *\n * // Priority queuer\n * const processPriority = queuer<number>({\n * process: async (n) => console.log(n),\n * getPriority: n => n // Higher numbers processed first\n * })\n * processPriority(1)\n * processPriority(3) // Processed before 1\n * ```\n */\nexport function queue<TValue>(options: QueuerOptions<TValue> = {}) {\n const queuer = new Queuer<TValue>({ ...options, started: true })\n return queuer.addItem.bind(queuer)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"queuer.cjs","sources":["../../src/queuer.ts"],"sourcesContent":["/**\n * Options for configuring a Queuer instance\n */\nexport interface QueuerOptions<TValue> {\n /**\n * Default position to add items to the queuer\n * @default 'back'\n */\n addItemsTo?: QueuePosition\n /**\n * Maximum time in milliseconds that an item can stay in the queue\n * If not provided, items will never expire\n */\n expirationDuration?: number\n /**\n * Function to determine if an item has expired\n * If provided, this overrides the expirationDuration behavior\n */\n getIsExpired?: (item: TValue, addedAt: number) => boolean\n /**\n * Default position to get items from during processing\n * @default 'front'\n */\n getItemsFrom?: QueuePosition\n /**\n * Function to determine priority of items in the queuer\n * Higher priority items will be processed first\n */\n getPriority?: (item: TValue) => number\n /**\n * Initial items to populate the queuer with\n */\n initialItems?: Array<TValue>\n /**\n * Maximum number of items allowed in the queuer\n */\n maxSize?: number\n /**\n * Callback fired whenever an item expires in the queuer\n */\n onExpire?: (item: TValue, queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever an item is removed from the queuer\n */\n onGetNextItem?: (item: TValue, queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever the queuer's running state changes\n */\n onIsRunningChange?: (queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever an item is added or removed from the queuer\n */\n onItemsChange?: (queuer: Queuer<TValue>) => void\n /**\n * Callback fired whenever an item is rejected from being added to the queuer\n */\n onReject?: (item: TValue, queuer: Queuer<TValue>) => void\n /**\n * Whether the queuer should start processing tasks immediately\n */\n started?: boolean\n /**\n * Time in milliseconds to wait between processing items\n */\n wait?: number\n}\n\nconst defaultOptions: Required<QueuerOptions<any>> = {\n addItemsTo: 'back',\n getItemsFrom: 'front',\n getPriority: (item) => item?.priority ?? 0,\n getIsExpired: () => false,\n expirationDuration: Infinity,\n initialItems: [],\n maxSize: Infinity,\n onGetNextItem: () => {},\n onIsRunningChange: () => {},\n onItemsChange: () => {},\n onReject: () => {},\n onExpire: () => {},\n started: false,\n wait: 0,\n}\n\n/**\n * Position type for addItem and getNextItem operations\n */\nexport type QueuePosition = 'front' | 'back'\n\n/**\n * A flexible queue data structure that defaults to FIFO (First In First Out) behavior\n * with optional position overrides for stack-like or double-ended operations.\n *\n * The queuer can automatically process items as they are added, with configurable\n * wait times between processing each item. Processing can be started/stopped\n * and the queuer will maintain its state.\n *\n * Supports priority-based ordering when a getPriority function is provided.\n * Items with higher priority values will be processed first.\n *\n * Default queue behavior:\n * - addItem(item): adds to back of queuer\n * - getNextItem(): removes and returns from front of queuer\n *\n * Stack (LIFO) behavior:\n * - addItem(item, 'back'): adds to back\n * - getNextItem('back'): removes and returns from back\n *\n * Double-ended queuer behavior:\n * - addItem(item, position): adds to specified position ('front' or 'back')\n * - getNextItem(position): removes and returns from specified position\n *\n * Processing behavior:\n * - start(): begins processing items in the queuer\n * - stop(): pauses processing\n * - wait: configurable delay between processing items\n * - onItemsChange/onGetNextItem: callbacks for monitoring queuer state\n *\n * Supports item expiration to clear stale items from the queuer\n * - expirationDuration: maximum time in milliseconds that an item can stay in the queue\n * - getIsExpired: function to override default expiration behavior\n * - onExpire: callback for when an item expires\n *\n * @example\n * ```ts\n * // FIFO queuer\n * const queuer = new Queuer<number>();\n * queuer.addItem(1); // [1]\n * queuer.addItem(2); // [1, 2]\n * queuer.getNextItem(); // returns 1, queuer is [2]\n *\n * // Priority queuer with processing\n * const priorityQueue = new Queuer<number>({\n * getPriority: (n) => n, // Higher numbers have priority\n * started: true, // Begin processing immediately\n * wait: 1000, // Wait 1s between items\n * onGetNextItem: (item, queuer) => console.log(item)\n * });\n * priorityQueue.addItem(1); // [1]\n * priorityQueue.addItem(3); // [3, 1] - 3 processed first\n * priorityQueue.addItem(2); // [3, 2, 1]\n * ```\n */\nexport class Queuer<TValue> {\n private _options: Required<QueuerOptions<TValue>>\n private _items: Array<TValue> = []\n private _itemTimestamps: Array<number> = []\n private _executionCount = 0\n private _rejectionCount = 0\n private _expirationCount = 0\n private _onItemsChanges: Array<(item: TValue) => void> = []\n private _running: boolean\n private _pendingTick = false\n\n constructor(initialOptions: QueuerOptions<TValue> = defaultOptions) {\n this._options = { ...defaultOptions, ...initialOptions }\n this._running = this._options.started\n\n for (let i = 0; i < this._options.initialItems.length; i++) {\n const item = this._options.initialItems[i]!\n const isLast = i === this._options.initialItems.length - 1\n this.addItem(item, this._options.addItemsTo, isLast)\n }\n }\n\n /**\n * Updates the queuer options\n * Returns the new options state\n */\n setOptions(newOptions: Partial<QueuerOptions<TValue>>): void {\n this._options = { ...this._options, ...newOptions }\n }\n\n /**\n * Returns the current queuer options\n */\n getOptions(): Required<QueuerOptions<TValue>> {\n return this._options\n }\n\n /**\n * Processes items in the queuer\n */\n private tick() {\n if (!this._running) {\n this._pendingTick = false\n return\n }\n\n // Check for expired items\n this.checkExpiredItems()\n\n while (!this.getIsEmpty()) {\n const nextItem = this.getNextItem(this._options.getItemsFrom)\n if (nextItem === undefined) {\n break\n }\n this._onItemsChanges.forEach((cb) => cb(nextItem))\n\n if (this._options.wait > 0) {\n // Use setTimeout to wait before processing next item\n setTimeout(() => this.tick(), this._options.wait)\n return\n }\n\n this.tick()\n }\n this._pendingTick = false\n }\n\n /**\n * Checks for and removes expired items from the queuer\n */\n private checkExpiredItems() {\n if (\n this._options.expirationDuration === Infinity &&\n this._options.getIsExpired === defaultOptions.getIsExpired\n )\n return\n\n const now = Date.now()\n const expiredIndices: Array<number> = []\n\n // Find indices of expired items\n for (let i = 0; i < this._items.length; i++) {\n const timestamp = this._itemTimestamps[i]\n if (timestamp === undefined) continue\n\n const item = this._items[i]\n if (item === undefined) continue\n\n const isExpired =\n this._options.getIsExpired !== defaultOptions.getIsExpired\n ? this._options.getIsExpired(item, timestamp)\n : now - timestamp > this._options.expirationDuration\n\n if (isExpired) {\n expiredIndices.push(i)\n }\n }\n\n // Remove expired items from back to front to maintain indices\n for (let i = expiredIndices.length - 1; i >= 0; i--) {\n const index = expiredIndices[i]\n if (index === undefined) continue\n\n const expiredItem = this._items[index]\n if (expiredItem === undefined) continue\n\n this._items.splice(index, 1)\n this._itemTimestamps.splice(index, 1)\n this._expirationCount++\n this._options.onExpire(expiredItem, this)\n }\n\n if (expiredIndices.length > 0) {\n this._options.onItemsChange(this)\n }\n }\n\n /**\n * Stops the queuer from processing items\n */\n stop() {\n this._running = false\n this._pendingTick = false\n this._options.onIsRunningChange(this)\n }\n\n /**\n * Starts the queuer and processes items\n */\n start() {\n this._running = true\n if (!this._pendingTick && !this.getIsEmpty()) {\n this._pendingTick = true\n this.tick()\n }\n this._options.onIsRunningChange(this)\n }\n\n /**\n * Removes all items from the queuer\n */\n clear(): void {\n this._items = []\n this._options.onItemsChange(this)\n }\n\n /**\n * Resets the queuer to its initial state\n */\n reset(withInitialItems?: boolean): void {\n this.clear()\n this._executionCount = 0\n if (withInitialItems) {\n this._items = [...this._options.initialItems]\n }\n this._running = this._options.started\n }\n\n /**\n * Adds an item to the queuer and starts processing if not already running\n * @returns true if item was added, false if queuer is full\n */\n addItem(\n item: TValue,\n position: QueuePosition = this._options.addItemsTo,\n runOnUpdate: boolean = true,\n ): boolean {\n if (this.getIsFull()) {\n this._rejectionCount++\n this._options.onReject(item, this)\n return false\n }\n\n if (this._options.getPriority !== defaultOptions.getPriority) {\n // If custom priority function is provided, insert based on priority\n const priority = this._options.getPriority(item)\n const insertIndex = this._items.findIndex(\n (existing) => this._options.getPriority(existing) > priority,\n )\n\n if (insertIndex === -1) {\n this._items.push(item)\n this._itemTimestamps.push(Date.now())\n } else {\n this._items.splice(insertIndex, 0, item)\n this._itemTimestamps.splice(insertIndex, 0, Date.now())\n }\n } else {\n // Default FIFO/LIFO behavior\n if (position === 'front') {\n this._items.unshift(item)\n this._itemTimestamps.unshift(Date.now())\n } else {\n this._items.push(item)\n this._itemTimestamps.push(Date.now())\n }\n }\n\n if (this._running && !this._pendingTick) {\n this._pendingTick = true\n this.tick()\n }\n if (runOnUpdate) {\n this._options.onItemsChange(this)\n }\n return true\n }\n\n /**\n * Removes and returns an item from the queuer using shift (default) or pop\n *\n * @example\n * ```ts\n * // Standard FIFO queuer\n * queuer.getNextItem()\n * // Stack-like behavior (LIFO)\n * queuer.getNextItem('back')\n * ```\n */\n getNextItem(\n position: QueuePosition = this._options.getItemsFrom,\n ): TValue | undefined {\n let item: TValue | undefined\n\n if (position === 'front') {\n item = this._items.shift()\n this._itemTimestamps.shift()\n } else {\n item = this._items.pop()\n this._itemTimestamps.pop()\n }\n\n if (item !== undefined) {\n this._executionCount++\n this._options.onItemsChange(this)\n this._options.onGetNextItem(item, this)\n }\n return item\n }\n\n /**\n * Returns an item without removing it\n *\n * @example\n * ```ts\n * // Look at next item to getNextItem\n * queuer.getPeek()\n * // Look at last item (like stack top)\n * queuer.getPeek('back')\n * ```\n */\n getPeek(\n position: QueuePosition = this._options.getItemsFrom,\n ): TValue | undefined {\n if (position === 'front') {\n return this._items[0]\n }\n return this._items[this._items.length - 1]\n }\n\n /**\n * Returns true if the queuer is empty\n */\n getIsEmpty(): boolean {\n return this._items.length === 0\n }\n\n /**\n * Returns true if the queuer is full\n */\n getIsFull(): boolean {\n return this._items.length >= this._options.maxSize\n }\n\n /**\n * Returns the current size of the queuer\n */\n getSize(): number {\n return this._items.length\n }\n\n /**\n * Returns a copy of all items in the queuer\n */\n getAllItems(): Array<TValue> {\n return [...this._items]\n }\n\n /**\n * Returns the number of items that have been removed from the queuer\n */\n getExecutionCount(): number {\n return this._executionCount\n }\n\n /**\n * Returns the number of items that have been rejected from the queuer\n */\n getRejectionCount(): number {\n return this._rejectionCount\n }\n\n /**\n * Returns the number of items that have expired from the queuer\n */\n getExpirationCount(): number {\n return this._expirationCount\n }\n\n /**\n * Returns true if the queuer is running\n */\n getIsRunning() {\n return this._running\n }\n\n /**\n * Returns true if the queuer is running but has no items to process\n */\n getIsIdle() {\n return this._running && this.getIsEmpty()\n }\n}\n\n/**\n * Creates a queue that processes items in a queuer immediately upon addition.\n * Items are processed sequentially in FIFO order by default.\n *\n * This is a simplified wrapper around the Queuer class that only exposes the\n * `addItem` method. This queue is always running and will process items as they are added.\n * For more control over queuer processing, use the Queuer class\n * directly which provides methods like `start`, `stop`, `reset`, and more.\n *\n * @example\n * ```ts\n * // Basic sequential processing\n * const processItems = queuer<number>({\n * wait: 1000,\n * onItemsChange: (queuer) => console.log(queuer.getAllItems())\n * })\n * processItems(1) // Logs: 1\n * processItems(2) // Logs: 2 after 1 completes\n *\n * // Priority queuer\n * const processPriority = queuer<number>({\n * process: async (n) => console.log(n),\n * getPriority: n => n // Higher numbers processed first\n * })\n * processPriority(1)\n * processPriority(3) // Processed before 1\n * ```\n */\nexport function queue<TValue>(options: QueuerOptions<TValue> = {}) {\n const queuer = new Queuer<TValue>({ ...options, started: true })\n return queuer.addItem.bind(queuer)\n}\n"],"names":[],"mappings":";;AAmEA,MAAM,iBAA+C;AAAA,EACnD,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,aAAa,CAAC,UAAS,6BAAM,aAAY;AAAA,EACzC,cAAc,MAAM;AAAA,EACpB,oBAAoB;AAAA,EACpB,cAAc,CAAC;AAAA,EACf,SAAS;AAAA,EACT,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,mBAAmB,MAAM;AAAA,EAAC;AAAA,EAC1B,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,SAAS;AAAA,EACT,MAAM;AACR;AA6DO,MAAM,OAAe;AAAA,EAW1B,YAAY,iBAAwC,gBAAgB;AATpE,SAAQ,SAAwB,CAAC;AACjC,SAAQ,kBAAiC,CAAC;AAC1C,SAAQ,kBAAkB;AAC1B,SAAQ,kBAAkB;AAC1B,SAAQ,mBAAmB;AAC3B,SAAQ,kBAAiD,CAAC;AAE1D,SAAQ,eAAe;AAGrB,SAAK,WAAW,EAAE,GAAG,gBAAgB,GAAG,eAAe;AAClD,SAAA,WAAW,KAAK,SAAS;AAE9B,aAAS,IAAI,GAAG,IAAI,KAAK,SAAS,aAAa,QAAQ,KAAK;AAC1D,YAAM,OAAO,KAAK,SAAS,aAAa,CAAC;AACzC,YAAM,SAAS,MAAM,KAAK,SAAS,aAAa,SAAS;AACzD,WAAK,QAAQ,MAAM,KAAK,SAAS,YAAY,MAAM;AAAA,IAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF,WAAW,YAAkD;AAC3D,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,GAAG,WAAW;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMpD,aAA8C;AAC5C,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMN,OAAO;AACT,QAAA,CAAC,KAAK,UAAU;AAClB,WAAK,eAAe;AACpB;AAAA,IAAA;AAIF,SAAK,kBAAkB;AAEhB,WAAA,CAAC,KAAK,cAAc;AACzB,YAAM,WAAW,KAAK,YAAY,KAAK,SAAS,YAAY;AAC5D,UAAI,aAAa,QAAW;AAC1B;AAAA,MAAA;AAEF,WAAK,gBAAgB,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;AAE7C,UAAA,KAAK,SAAS,OAAO,GAAG;AAE1B,mBAAW,MAAM,KAAK,KAAQ,GAAA,KAAK,SAAS,IAAI;AAChD;AAAA,MAAA;AAGF,WAAK,KAAK;AAAA,IAAA;AAEZ,SAAK,eAAe;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,oBAAoB;AAC1B,QACE,KAAK,SAAS,uBAAuB,YACrC,KAAK,SAAS,iBAAiB,eAAe;AAE9C;AAEI,UAAA,MAAM,KAAK,IAAI;AACrB,UAAM,iBAAgC,CAAC;AAGvC,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AACrC,YAAA,YAAY,KAAK,gBAAgB,CAAC;AACxC,UAAI,cAAc,OAAW;AAEvB,YAAA,OAAO,KAAK,OAAO,CAAC;AAC1B,UAAI,SAAS,OAAW;AAExB,YAAM,YACJ,KAAK,SAAS,iBAAiB,eAAe,eAC1C,KAAK,SAAS,aAAa,MAAM,SAAS,IAC1C,MAAM,YAAY,KAAK,SAAS;AAEtC,UAAI,WAAW;AACb,uBAAe,KAAK,CAAC;AAAA,MAAA;AAAA,IACvB;AAIF,aAAS,IAAI,eAAe,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,YAAA,QAAQ,eAAe,CAAC;AAC9B,UAAI,UAAU,OAAW;AAEnB,YAAA,cAAc,KAAK,OAAO,KAAK;AACrC,UAAI,gBAAgB,OAAW;AAE1B,WAAA,OAAO,OAAO,OAAO,CAAC;AACtB,WAAA,gBAAgB,OAAO,OAAO,CAAC;AAC/B,WAAA;AACA,WAAA,SAAS,SAAS,aAAa,IAAI;AAAA,IAAA;AAGtC,QAAA,eAAe,SAAS,GAAG;AACxB,WAAA,SAAS,cAAc,IAAI;AAAA,IAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAMF,OAAO;AACL,SAAK,WAAW;AAChB,SAAK,eAAe;AACf,SAAA,SAAS,kBAAkB,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,QAAQ;AACN,SAAK,WAAW;AAChB,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,cAAc;AAC5C,WAAK,eAAe;AACpB,WAAK,KAAK;AAAA,IAAA;AAEP,SAAA,SAAS,kBAAkB,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,QAAc;AACZ,SAAK,SAAS,CAAC;AACV,SAAA,SAAS,cAAc,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMlC,MAAM,kBAAkC;AACtC,SAAK,MAAM;AACX,SAAK,kBAAkB;AACvB,QAAI,kBAAkB;AACpB,WAAK,SAAS,CAAC,GAAG,KAAK,SAAS,YAAY;AAAA,IAAA;AAEzC,SAAA,WAAW,KAAK,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhC,QACE,MACA,WAA0B,KAAK,SAAS,YACxC,cAAuB,MACd;AACL,QAAA,KAAK,aAAa;AACf,WAAA;AACA,WAAA,SAAS,SAAS,MAAM,IAAI;AAC1B,aAAA;AAAA,IAAA;AAGT,QAAI,KAAK,SAAS,gBAAgB,eAAe,aAAa;AAE5D,YAAM,WAAW,KAAK,SAAS,YAAY,IAAI;AACzC,YAAA,cAAc,KAAK,OAAO;AAAA,QAC9B,CAAC,aAAa,KAAK,SAAS,YAAY,QAAQ,IAAI;AAAA,MACtD;AAEA,UAAI,gBAAgB,IAAI;AACjB,aAAA,OAAO,KAAK,IAAI;AACrB,aAAK,gBAAgB,KAAK,KAAK,IAAA,CAAK;AAAA,MAAA,OAC/B;AACL,aAAK,OAAO,OAAO,aAAa,GAAG,IAAI;AACvC,aAAK,gBAAgB,OAAO,aAAa,GAAG,KAAK,KAAK;AAAA,MAAA;AAAA,IACxD,OACK;AAEL,UAAI,aAAa,SAAS;AACnB,aAAA,OAAO,QAAQ,IAAI;AACxB,aAAK,gBAAgB,QAAQ,KAAK,IAAA,CAAK;AAAA,MAAA,OAClC;AACA,aAAA,OAAO,KAAK,IAAI;AACrB,aAAK,gBAAgB,KAAK,KAAK,IAAA,CAAK;AAAA,MAAA;AAAA,IACtC;AAGF,QAAI,KAAK,YAAY,CAAC,KAAK,cAAc;AACvC,WAAK,eAAe;AACpB,WAAK,KAAK;AAAA,IAAA;AAEZ,QAAI,aAAa;AACV,WAAA,SAAS,cAAc,IAAI;AAAA,IAAA;AAE3B,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcT,YACE,WAA0B,KAAK,SAAS,cACpB;AAChB,QAAA;AAEJ,QAAI,aAAa,SAAS;AACjB,aAAA,KAAK,OAAO,MAAM;AACzB,WAAK,gBAAgB,MAAM;AAAA,IAAA,OACtB;AACE,aAAA,KAAK,OAAO,IAAI;AACvB,WAAK,gBAAgB,IAAI;AAAA,IAAA;AAG3B,QAAI,SAAS,QAAW;AACjB,WAAA;AACA,WAAA,SAAS,cAAc,IAAI;AAC3B,WAAA,SAAS,cAAc,MAAM,IAAI;AAAA,IAAA;AAEjC,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcT,QACE,WAA0B,KAAK,SAAS,cACpB;AACpB,QAAI,aAAa,SAAS;AACjB,aAAA,KAAK,OAAO,CAAC;AAAA,IAAA;AAEtB,WAAO,KAAK,OAAO,KAAK,OAAO,SAAS,CAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,aAAsB;AACb,WAAA,KAAK,OAAO,WAAW;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMhC,YAAqB;AACnB,WAAO,KAAK,OAAO,UAAU,KAAK,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM7C,UAAkB;AAChB,WAAO,KAAK,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMrB,cAA6B;AACpB,WAAA,CAAC,GAAG,KAAK,MAAM;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,oBAA4B;AAC1B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,oBAA4B;AAC1B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,qBAA6B;AAC3B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,eAAe;AACb,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,YAAY;AACH,WAAA,KAAK,YAAY,KAAK,WAAW;AAAA,EAAA;AAE5C;AA8BgB,SAAA,MAAc,UAAiC,IAAI;AAC3D,QAAA,SAAS,IAAI,OAAe,EAAE,GAAG,SAAS,SAAS,MAAM;AACxD,SAAA,OAAO,QAAQ,KAAK,MAAM;AACnC;;;"}
|
package/dist/cjs/queuer.d.cts
CHANGED
|
@@ -7,6 +7,16 @@ export interface QueuerOptions<TValue> {
|
|
|
7
7
|
* @default 'back'
|
|
8
8
|
*/
|
|
9
9
|
addItemsTo?: QueuePosition;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum time in milliseconds that an item can stay in the queue
|
|
12
|
+
* If not provided, items will never expire
|
|
13
|
+
*/
|
|
14
|
+
expirationDuration?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Function to determine if an item has expired
|
|
17
|
+
* If provided, this overrides the expirationDuration behavior
|
|
18
|
+
*/
|
|
19
|
+
getIsExpired?: (item: TValue, addedAt: number) => boolean;
|
|
10
20
|
/**
|
|
11
21
|
* Default position to get items from during processing
|
|
12
22
|
* @default 'front'
|
|
@@ -25,6 +35,10 @@ export interface QueuerOptions<TValue> {
|
|
|
25
35
|
* Maximum number of items allowed in the queuer
|
|
26
36
|
*/
|
|
27
37
|
maxSize?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Callback fired whenever an item expires in the queuer
|
|
40
|
+
*/
|
|
41
|
+
onExpire?: (item: TValue, queuer: Queuer<TValue>) => void;
|
|
28
42
|
/**
|
|
29
43
|
* Callback fired whenever an item is removed from the queuer
|
|
30
44
|
*/
|
|
@@ -83,6 +97,11 @@ export type QueuePosition = 'front' | 'back';
|
|
|
83
97
|
* - wait: configurable delay between processing items
|
|
84
98
|
* - onItemsChange/onGetNextItem: callbacks for monitoring queuer state
|
|
85
99
|
*
|
|
100
|
+
* Supports item expiration to clear stale items from the queuer
|
|
101
|
+
* - expirationDuration: maximum time in milliseconds that an item can stay in the queue
|
|
102
|
+
* - getIsExpired: function to override default expiration behavior
|
|
103
|
+
* - onExpire: callback for when an item expires
|
|
104
|
+
*
|
|
86
105
|
* @example
|
|
87
106
|
* ```ts
|
|
88
107
|
* // FIFO queuer
|
|
@@ -106,8 +125,10 @@ export type QueuePosition = 'front' | 'back';
|
|
|
106
125
|
export declare class Queuer<TValue> {
|
|
107
126
|
private _options;
|
|
108
127
|
private _items;
|
|
128
|
+
private _itemTimestamps;
|
|
109
129
|
private _executionCount;
|
|
110
130
|
private _rejectionCount;
|
|
131
|
+
private _expirationCount;
|
|
111
132
|
private _onItemsChanges;
|
|
112
133
|
private _running;
|
|
113
134
|
private _pendingTick;
|
|
@@ -116,7 +137,7 @@ export declare class Queuer<TValue> {
|
|
|
116
137
|
* Updates the queuer options
|
|
117
138
|
* Returns the new options state
|
|
118
139
|
*/
|
|
119
|
-
setOptions(newOptions: Partial<QueuerOptions<TValue>>):
|
|
140
|
+
setOptions(newOptions: Partial<QueuerOptions<TValue>>): void;
|
|
120
141
|
/**
|
|
121
142
|
* Returns the current queuer options
|
|
122
143
|
*/
|
|
@@ -125,6 +146,10 @@ export declare class Queuer<TValue> {
|
|
|
125
146
|
* Processes items in the queuer
|
|
126
147
|
*/
|
|
127
148
|
private tick;
|
|
149
|
+
/**
|
|
150
|
+
* Checks for and removes expired items from the queuer
|
|
151
|
+
*/
|
|
152
|
+
private checkExpiredItems;
|
|
128
153
|
/**
|
|
129
154
|
* Stops the queuer from processing items
|
|
130
155
|
*/
|
|
@@ -194,6 +219,10 @@ export declare class Queuer<TValue> {
|
|
|
194
219
|
* Returns the number of items that have been rejected from the queuer
|
|
195
220
|
*/
|
|
196
221
|
getRejectionCount(): number;
|
|
222
|
+
/**
|
|
223
|
+
* Returns the number of items that have expired from the queuer
|
|
224
|
+
*/
|
|
225
|
+
getExpirationCount(): number;
|
|
197
226
|
/**
|
|
198
227
|
* Returns true if the queuer is running
|
|
199
228
|
*/
|
|
@@ -7,7 +7,8 @@ const defaultOptions = {
|
|
|
7
7
|
},
|
|
8
8
|
onReject: () => {
|
|
9
9
|
},
|
|
10
|
-
window: 0
|
|
10
|
+
window: 0,
|
|
11
|
+
windowType: "fixed"
|
|
11
12
|
};
|
|
12
13
|
class RateLimiter {
|
|
13
14
|
constructor(fn, initialOptions) {
|
|
@@ -25,11 +26,7 @@ class RateLimiter {
|
|
|
25
26
|
* Returns the new options state
|
|
26
27
|
*/
|
|
27
28
|
setOptions(newOptions) {
|
|
28
|
-
this._options = {
|
|
29
|
-
...this._options,
|
|
30
|
-
...newOptions
|
|
31
|
-
};
|
|
32
|
-
return this._options;
|
|
29
|
+
this._options = { ...this._options, ...newOptions };
|
|
33
30
|
}
|
|
34
31
|
/**
|
|
35
32
|
* Returns the current rate limiter options
|
|
@@ -54,9 +51,19 @@ class RateLimiter {
|
|
|
54
51
|
*/
|
|
55
52
|
maybeExecute(...args) {
|
|
56
53
|
this.cleanupOldExecutions();
|
|
57
|
-
if (this.
|
|
58
|
-
this.
|
|
59
|
-
|
|
54
|
+
if (this._options.windowType === "sliding") {
|
|
55
|
+
if (this._executionTimes.length < this._options.limit) {
|
|
56
|
+
this.executeFunction(...args);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
const oldestExecution = Math.min(...this._executionTimes);
|
|
62
|
+
const isNewWindow = oldestExecution + this._options.window <= now;
|
|
63
|
+
if (isNewWindow || this._executionTimes.length < this._options.limit) {
|
|
64
|
+
this.executeFunction(...args);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
68
|
this.rejectFunction();
|
|
62
69
|
return false;
|
|
@@ -106,6 +113,9 @@ class RateLimiter {
|
|
|
106
113
|
* Returns the number of milliseconds until the next execution will be possible
|
|
107
114
|
*/
|
|
108
115
|
getMsUntilNextWindow() {
|
|
116
|
+
if (this.getRemainingInWindow() > 0) {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
109
119
|
const oldestExecution = Math.min(...this._executionTimes);
|
|
110
120
|
return oldestExecution + this._options.window - Date.now();
|
|
111
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.cjs","sources":["../../src/rate-limiter.ts"],"sourcesContent":["import type { AnyFunction } from './types'\n\n/**\n * Options for configuring a rate-limited function\n */\nexport interface RateLimiterOptions<\n TFn extends AnyFunction,\n TArgs extends Parameters<TFn>,\n> {\n /**\n * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * Maximum number of executions allowed within the time window\n */\n limit: number\n /**\n * Callback function that is called after the function is executed\n */\n onExecute?: (rateLimiter: RateLimiter<TFn, TArgs>) => void\n /**\n * Optional callback function that is called when an execution is rejected due to rate limiting\n */\n onReject?: (rateLimiter: RateLimiter<TFn, TArgs>) => void\n /**\n * Time window in milliseconds within which the limit applies\n */\n window: number\n}\n\nconst defaultOptions: Required<RateLimiterOptions<any, any>> = {\n enabled: true,\n limit: 1,\n onExecute: () => {},\n onReject: () => {},\n window: 0,\n}\n\n/**\n * A class that creates a rate-limited function.\n *\n * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window,\n * then blocks all subsequent calls until the window passes. This can lead to \"bursty\" behavior where\n * all executions happen immediately, followed by a complete block.\n *\n * For smoother execution patterns, consider using:\n * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)\n * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)\n *\n * Rate limiting is best used for hard API limits or resource constraints. For UI updates or\n * smoothing out frequent events, throttling or debouncing usually provide better user experience.\n *\n * @example\n * ```ts\n * const rateLimiter = new RateLimiter(\n * (id: string) => api.getData(id),\n * { limit: 5, window: 1000 } // 5 calls per second\n * );\n *\n * // Will execute immediately until limit reached, then block\n * rateLimiter.maybeExecute('123');\n * ```\n */\nexport class RateLimiter<\n TFn extends AnyFunction,\n TArgs extends Parameters<TFn>,\n> {\n private _executionCount = 0\n private _rejectionCount = 0\n private _executionTimes: Array<number> = []\n private _options: RateLimiterOptions<TFn, TArgs>\n\n constructor(\n private fn: TFn,\n initialOptions: RateLimiterOptions<TFn, TArgs>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n }\n }\n\n /**\n * Updates the rate limiter options\n * Returns the new options state\n */\n setOptions(\n newOptions: Partial<RateLimiterOptions<TFn, TArgs>>,\n ): RateLimiterOptions<TFn, TArgs> {\n this._options = {\n ...this._options,\n ...newOptions,\n }\n return this._options\n }\n\n /**\n * Returns the current rate limiter options\n */\n getOptions(): Required<RateLimiterOptions<TFn, TArgs>> {\n return this._options as Required<RateLimiterOptions<TFn, TArgs>>\n }\n\n /**\n * Attempts to execute the rate-limited function if within the configured limits.\n * Will reject execution if the number of calls in the current window exceeds the limit.\n *\n * @example\n * ```ts\n * const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 });\n *\n * // First 5 calls will return true\n * rateLimiter.maybeExecute('arg1', 'arg2'); // true\n *\n * // Additional calls within the window will return false\n * rateLimiter.maybeExecute('arg1', 'arg2'); // false\n * ```\n */\n maybeExecute(...args: TArgs): boolean {\n this.cleanupOldExecutions()\n\n if (this._executionTimes.length < this._options.limit) {\n this.executeFunction(...args)\n return true\n }\n\n this.rejectFunction()\n\n return false\n }\n\n private executeFunction(...args: TArgs): void {\n if (!this._options.enabled) return\n const now = Date.now()\n this._executionCount++\n this._executionTimes.push(now)\n this.fn(...args) // execute the function\n this._options.onExecute?.(this)\n }\n\n private rejectFunction(): void {\n this._rejectionCount++\n if (this._options.onReject) {\n this._options.onReject(this)\n }\n }\n\n private cleanupOldExecutions(): void {\n const now = Date.now()\n const windowStart = now - this._options.window\n this._executionTimes = this._executionTimes.filter(\n (time) => time > windowStart,\n )\n }\n\n /**\n * Returns the number of times the function has been executed\n */\n getExecutionCount(): number {\n return this._executionCount\n }\n\n /**\n * Returns the number of times the function has been rejected\n */\n getRejectionCount(): number {\n return this._rejectionCount\n }\n\n /**\n * Returns the number of remaining executions allowed in the current window\n */\n getRemainingInWindow(): number {\n this.cleanupOldExecutions()\n return Math.max(0, this._options.limit - this._executionTimes.length)\n }\n\n /**\n * Returns the number of milliseconds until the next execution will be possible\n */\n getMsUntilNextWindow(): number {\n const oldestExecution = Math.min(...this._executionTimes)\n return oldestExecution + this._options.window - Date.now()\n }\n\n /**\n * Resets the rate limiter state\n */\n reset(): void {\n this._executionTimes = []\n this._executionCount = 0\n this._rejectionCount = 0\n }\n}\n\n/**\n * Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window.\n *\n * Note that rate limiting is a simpler form of execution control compared to throttling or debouncing:\n * - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets\n * - A throttler ensures even spacing between executions, which can be better for consistent performance\n * - A debouncer collapses multiple calls into one, which is better for handling bursts of events\n *\n * Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically\n * need to enforce a hard limit on the number of executions within a time period.\n *\n * @example\n * ```ts\n * // Rate limit to 5 calls per minute\n * const rateLimited = rateLimit(makeApiCall, {\n * limit: 5,\n * window: 60000,\n * onReject: (rateLimiter) => {\n * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);\n * }\n * });\n *\n * // First 5 calls will execute immediately\n * // Additional calls will be rejected until the minute window resets\n * rateLimited();\n *\n * // For more even execution, consider using throttle instead:\n * const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds\n * ```\n */\nexport function rateLimit<TFn extends AnyFunction>(\n fn: TFn,\n initialOptions: Omit<RateLimiterOptions<TFn, Parameters<TFn>>, 'enabled'>,\n) {\n const rateLimiter = new RateLimiter(fn, initialOptions)\n return rateLimiter.maybeExecute.bind(rateLimiter)\n}\n"],"names":[],"mappings":";;AAgCA,MAAM,iBAAyD;AAAA,EAC7D,SAAS;AAAA,EACT,OAAO;AAAA,EACP,WAAW,MAAM;AAAA,EAAC;AAAA,EAClB,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,QAAQ;AACV;AA2BO,MAAM,YAGX;AAAA,EAMA,YACU,IACR,gBACA;AAFQ,SAAA,KAAA;AANV,SAAQ,kBAAkB;AAC1B,SAAQ,kBAAkB;AAC1B,SAAQ,kBAAiC,CAAC;AAOxC,SAAK,WAAW;AAAA,MACd,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF,WACE,YACgC;AAChC,SAAK,WAAW;AAAA,MACd,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AACA,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,aAAuD;AACrD,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBd,gBAAgB,MAAsB;AACpC,SAAK,qBAAqB;AAE1B,QAAI,KAAK,gBAAgB,SAAS,KAAK,SAAS,OAAO;AAChD,WAAA,gBAAgB,GAAG,IAAI;AACrB,aAAA;AAAA,IAAA;AAGT,SAAK,eAAe;AAEb,WAAA;AAAA,EAAA;AAAA,EAGD,mBAAmB,MAAmB;;AACxC,QAAA,CAAC,KAAK,SAAS,QAAS;AACtB,UAAA,MAAM,KAAK,IAAI;AAChB,SAAA;AACA,SAAA,gBAAgB,KAAK,GAAG;AACxB,SAAA,GAAG,GAAG,IAAI;AACV,qBAAA,UAAS,cAAT,4BAAqB;AAAA,EAAI;AAAA,EAGxB,iBAAuB;AACxB,SAAA;AACD,QAAA,KAAK,SAAS,UAAU;AACrB,WAAA,SAAS,SAAS,IAAI;AAAA,IAAA;AAAA,EAC7B;AAAA,EAGM,uBAA6B;AAC7B,UAAA,MAAM,KAAK,IAAI;AACf,UAAA,cAAc,MAAM,KAAK,SAAS;AACnC,SAAA,kBAAkB,KAAK,gBAAgB;AAAA,MAC1C,CAAC,SAAS,OAAO;AAAA,IACnB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMF,oBAA4B;AAC1B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,oBAA4B;AAC1B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,uBAA+B;AAC7B,SAAK,qBAAqB;AACnB,WAAA,KAAK,IAAI,GAAG,KAAK,SAAS,QAAQ,KAAK,gBAAgB,MAAM;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMtE,uBAA+B;AAC7B,UAAM,kBAAkB,KAAK,IAAI,GAAG,KAAK,eAAe;AACxD,WAAO,kBAAkB,KAAK,SAAS,SAAS,KAAK,IAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM3D,QAAc;AACZ,SAAK,kBAAkB,CAAC;AACxB,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AAAA,EAAA;AAE3B;AAgCgB,SAAA,UACd,IACA,gBACA;AACA,QAAM,cAAc,IAAI,YAAY,IAAI,cAAc;AAC/C,SAAA,YAAY,aAAa,KAAK,WAAW;AAClD;;;"}
|
|
1
|
+
{"version":3,"file":"rate-limiter.cjs","sources":["../../src/rate-limiter.ts"],"sourcesContent":["import type { AnyFunction } from './types'\n\n/**\n * Options for configuring a rate-limited function\n */\nexport interface RateLimiterOptions<TFn extends AnyFunction> {\n /**\n * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * Maximum number of executions allowed within the time window\n */\n limit: number\n /**\n * Callback function that is called after the function is executed\n */\n onExecute?: (rateLimiter: RateLimiter<TFn>) => void\n /**\n * Optional callback function that is called when an execution is rejected due to rate limiting\n */\n onReject?: (rateLimiter: RateLimiter<TFn>) => void\n /**\n * Time window in milliseconds within which the limit applies\n */\n window: number\n /**\n * Type of window to use for rate limiting\n * - 'fixed': Uses a fixed window that resets after the window period\n * - 'sliding': Uses a sliding window that allows executions as old ones expire\n * Defaults to 'fixed'\n */\n windowType?: 'fixed' | 'sliding'\n}\n\nconst defaultOptions: Required<RateLimiterOptions<any>> = {\n enabled: true,\n limit: 1,\n onExecute: () => {},\n onReject: () => {},\n window: 0,\n windowType: 'fixed',\n}\n\n/**\n * A class that creates a rate-limited function.\n *\n * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window,\n * then blocks all subsequent calls until the window passes. This can lead to \"bursty\" behavior where\n * all executions happen immediately, followed by a complete block.\n *\n * The rate limiter supports two types of windows:\n * - 'fixed': A strict window that resets after the window period. All executions within the window count\n * towards the limit, and the window resets completely after the period.\n * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more\n * consistent rate of execution over time.\n *\n * For smoother execution patterns, consider using:\n * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)\n * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)\n *\n * Rate limiting is best used for hard API limits or resource constraints. For UI updates or\n * smoothing out frequent events, throttling or debouncing usually provide better user experience.\n *\n * @example\n * ```ts\n * const rateLimiter = new RateLimiter(\n * (id: string) => api.getData(id),\n * { limit: 5, window: 1000, windowType: 'sliding' } // 5 calls per second with sliding window\n * );\n *\n * // Will execute immediately until limit reached, then block\n * rateLimiter.maybeExecute('123');\n * ```\n */\nexport class RateLimiter<TFn extends AnyFunction> {\n private _executionCount = 0\n private _rejectionCount = 0\n private _executionTimes: Array<number> = []\n private _options: RateLimiterOptions<TFn>\n\n constructor(\n private fn: TFn,\n initialOptions: RateLimiterOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n }\n }\n\n /**\n * Updates the rate limiter options\n * Returns the new options state\n */\n setOptions(newOptions: Partial<RateLimiterOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n }\n\n /**\n * Returns the current rate limiter options\n */\n getOptions(): Required<RateLimiterOptions<TFn>> {\n return this._options as Required<RateLimiterOptions<TFn>>\n }\n\n /**\n * Attempts to execute the rate-limited function if within the configured limits.\n * Will reject execution if the number of calls in the current window exceeds the limit.\n *\n * @example\n * ```ts\n * const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 });\n *\n * // First 5 calls will return true\n * rateLimiter.maybeExecute('arg1', 'arg2'); // true\n *\n * // Additional calls within the window will return false\n * rateLimiter.maybeExecute('arg1', 'arg2'); // false\n * ```\n */\n maybeExecute(...args: Parameters<TFn>): boolean {\n this.cleanupOldExecutions()\n\n if (this._options.windowType === 'sliding') {\n // For sliding window, we can execute if we have capacity in the current window\n if (this._executionTimes.length < this._options.limit) {\n this.executeFunction(...args)\n return true\n }\n } else {\n // For fixed window, we need to check if we're in a new window\n const now = Date.now()\n const oldestExecution = Math.min(...this._executionTimes)\n const isNewWindow = oldestExecution + this._options.window <= now\n\n if (isNewWindow || this._executionTimes.length < this._options.limit) {\n this.executeFunction(...args)\n return true\n }\n }\n\n this.rejectFunction()\n return false\n }\n\n private executeFunction(...args: Parameters<TFn>): void {\n if (!this._options.enabled) return\n const now = Date.now()\n this._executionCount++\n this._executionTimes.push(now)\n this.fn(...args) // execute the function\n this._options.onExecute?.(this)\n }\n\n private rejectFunction(): void {\n this._rejectionCount++\n if (this._options.onReject) {\n this._options.onReject(this)\n }\n }\n\n private cleanupOldExecutions(): void {\n const now = Date.now()\n const windowStart = now - this._options.window\n this._executionTimes = this._executionTimes.filter(\n (time) => time > windowStart,\n )\n }\n\n /**\n * Returns the number of times the function has been executed\n */\n getExecutionCount(): number {\n return this._executionCount\n }\n\n /**\n * Returns the number of times the function has been rejected\n */\n getRejectionCount(): number {\n return this._rejectionCount\n }\n\n /**\n * Returns the number of remaining executions allowed in the current window\n */\n getRemainingInWindow(): number {\n this.cleanupOldExecutions()\n return Math.max(0, this._options.limit - this._executionTimes.length)\n }\n\n /**\n * Returns the number of milliseconds until the next execution will be possible\n */\n getMsUntilNextWindow(): number {\n if (this.getRemainingInWindow() > 0) {\n return 0\n }\n const oldestExecution = Math.min(...this._executionTimes)\n return oldestExecution + this._options.window - Date.now()\n }\n\n /**\n * Resets the rate limiter state\n */\n reset(): void {\n this._executionTimes = []\n this._executionCount = 0\n this._rejectionCount = 0\n }\n}\n\n/**\n * Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window.\n *\n * Note that rate limiting is a simpler form of execution control compared to throttling or debouncing:\n * - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets\n * - A throttler ensures even spacing between executions, which can be better for consistent performance\n * - A debouncer collapses multiple calls into one, which is better for handling bursts of events\n *\n * The rate limiter supports two types of windows:\n * - 'fixed': A strict window that resets after the window period. All executions within the window count\n * towards the limit, and the window resets completely after the period.\n * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more\n * consistent rate of execution over time.\n *\n * Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically\n * need to enforce a hard limit on the number of executions within a time period.\n *\n * @example\n * ```ts\n * // Rate limit to 5 calls per minute with a sliding window\n * const rateLimited = rateLimit(makeApiCall, {\n * limit: 5,\n * window: 60000,\n * windowType: 'sliding',\n * onReject: (rateLimiter) => {\n * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);\n * }\n * });\n *\n * // First 5 calls will execute immediately\n * // Additional calls will be rejected until the minute window resets\n * rateLimited();\n *\n * // For more even execution, consider using throttle instead:\n * const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds\n * ```\n */\nexport function rateLimit<TFn extends AnyFunction>(\n fn: TFn,\n initialOptions: Omit<RateLimiterOptions<TFn>, 'enabled'>,\n) {\n const rateLimiter = new RateLimiter(fn, initialOptions)\n return rateLimiter.maybeExecute.bind(rateLimiter)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { AnyFunction } from './types.cjs';
|
|
|
2
2
|
/**
|
|
3
3
|
* Options for configuring a rate-limited function
|
|
4
4
|
*/
|
|
5
|
-
export interface RateLimiterOptions<TFn extends AnyFunction
|
|
5
|
+
export interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
6
6
|
/**
|
|
7
7
|
* Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.
|
|
8
8
|
* Defaults to true.
|
|
@@ -15,15 +15,22 @@ export interface RateLimiterOptions<TFn extends AnyFunction, TArgs extends Param
|
|
|
15
15
|
/**
|
|
16
16
|
* Callback function that is called after the function is executed
|
|
17
17
|
*/
|
|
18
|
-
onExecute?: (rateLimiter: RateLimiter<TFn
|
|
18
|
+
onExecute?: (rateLimiter: RateLimiter<TFn>) => void;
|
|
19
19
|
/**
|
|
20
20
|
* Optional callback function that is called when an execution is rejected due to rate limiting
|
|
21
21
|
*/
|
|
22
|
-
onReject?: (rateLimiter: RateLimiter<TFn
|
|
22
|
+
onReject?: (rateLimiter: RateLimiter<TFn>) => void;
|
|
23
23
|
/**
|
|
24
24
|
* Time window in milliseconds within which the limit applies
|
|
25
25
|
*/
|
|
26
26
|
window: number;
|
|
27
|
+
/**
|
|
28
|
+
* Type of window to use for rate limiting
|
|
29
|
+
* - 'fixed': Uses a fixed window that resets after the window period
|
|
30
|
+
* - 'sliding': Uses a sliding window that allows executions as old ones expire
|
|
31
|
+
* Defaults to 'fixed'
|
|
32
|
+
*/
|
|
33
|
+
windowType?: 'fixed' | 'sliding';
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* A class that creates a rate-limited function.
|
|
@@ -32,6 +39,12 @@ export interface RateLimiterOptions<TFn extends AnyFunction, TArgs extends Param
|
|
|
32
39
|
* then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where
|
|
33
40
|
* all executions happen immediately, followed by a complete block.
|
|
34
41
|
*
|
|
42
|
+
* The rate limiter supports two types of windows:
|
|
43
|
+
* - 'fixed': A strict window that resets after the window period. All executions within the window count
|
|
44
|
+
* towards the limit, and the window resets completely after the period.
|
|
45
|
+
* - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
|
|
46
|
+
* consistent rate of execution over time.
|
|
47
|
+
*
|
|
35
48
|
* For smoother execution patterns, consider using:
|
|
36
49
|
* - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)
|
|
37
50
|
* - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)
|
|
@@ -43,29 +56,29 @@ export interface RateLimiterOptions<TFn extends AnyFunction, TArgs extends Param
|
|
|
43
56
|
* ```ts
|
|
44
57
|
* const rateLimiter = new RateLimiter(
|
|
45
58
|
* (id: string) => api.getData(id),
|
|
46
|
-
* { limit: 5, window: 1000 } // 5 calls per second
|
|
59
|
+
* { limit: 5, window: 1000, windowType: 'sliding' } // 5 calls per second with sliding window
|
|
47
60
|
* );
|
|
48
61
|
*
|
|
49
62
|
* // Will execute immediately until limit reached, then block
|
|
50
63
|
* rateLimiter.maybeExecute('123');
|
|
51
64
|
* ```
|
|
52
65
|
*/
|
|
53
|
-
export declare class RateLimiter<TFn extends AnyFunction
|
|
66
|
+
export declare class RateLimiter<TFn extends AnyFunction> {
|
|
54
67
|
private fn;
|
|
55
68
|
private _executionCount;
|
|
56
69
|
private _rejectionCount;
|
|
57
70
|
private _executionTimes;
|
|
58
71
|
private _options;
|
|
59
|
-
constructor(fn: TFn, initialOptions: RateLimiterOptions<TFn
|
|
72
|
+
constructor(fn: TFn, initialOptions: RateLimiterOptions<TFn>);
|
|
60
73
|
/**
|
|
61
74
|
* Updates the rate limiter options
|
|
62
75
|
* Returns the new options state
|
|
63
76
|
*/
|
|
64
|
-
setOptions(newOptions: Partial<RateLimiterOptions<TFn
|
|
77
|
+
setOptions(newOptions: Partial<RateLimiterOptions<TFn>>): void;
|
|
65
78
|
/**
|
|
66
79
|
* Returns the current rate limiter options
|
|
67
80
|
*/
|
|
68
|
-
getOptions(): Required<RateLimiterOptions<TFn
|
|
81
|
+
getOptions(): Required<RateLimiterOptions<TFn>>;
|
|
69
82
|
/**
|
|
70
83
|
* Attempts to execute the rate-limited function if within the configured limits.
|
|
71
84
|
* Will reject execution if the number of calls in the current window exceeds the limit.
|
|
@@ -81,7 +94,7 @@ export declare class RateLimiter<TFn extends AnyFunction, TArgs extends Paramete
|
|
|
81
94
|
* rateLimiter.maybeExecute('arg1', 'arg2'); // false
|
|
82
95
|
* ```
|
|
83
96
|
*/
|
|
84
|
-
maybeExecute(...args:
|
|
97
|
+
maybeExecute(...args: Parameters<TFn>): boolean;
|
|
85
98
|
private executeFunction;
|
|
86
99
|
private rejectFunction;
|
|
87
100
|
private cleanupOldExecutions;
|
|
@@ -114,15 +127,22 @@ export declare class RateLimiter<TFn extends AnyFunction, TArgs extends Paramete
|
|
|
114
127
|
* - A throttler ensures even spacing between executions, which can be better for consistent performance
|
|
115
128
|
* - A debouncer collapses multiple calls into one, which is better for handling bursts of events
|
|
116
129
|
*
|
|
130
|
+
* The rate limiter supports two types of windows:
|
|
131
|
+
* - 'fixed': A strict window that resets after the window period. All executions within the window count
|
|
132
|
+
* towards the limit, and the window resets completely after the period.
|
|
133
|
+
* - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
|
|
134
|
+
* consistent rate of execution over time.
|
|
135
|
+
*
|
|
117
136
|
* Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically
|
|
118
137
|
* need to enforce a hard limit on the number of executions within a time period.
|
|
119
138
|
*
|
|
120
139
|
* @example
|
|
121
140
|
* ```ts
|
|
122
|
-
* // Rate limit to 5 calls per minute
|
|
141
|
+
* // Rate limit to 5 calls per minute with a sliding window
|
|
123
142
|
* const rateLimited = rateLimit(makeApiCall, {
|
|
124
143
|
* limit: 5,
|
|
125
144
|
* window: 60000,
|
|
145
|
+
* windowType: 'sliding',
|
|
126
146
|
* onReject: (rateLimiter) => {
|
|
127
147
|
* console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
|
|
128
148
|
* }
|
|
@@ -136,4 +156,4 @@ export declare class RateLimiter<TFn extends AnyFunction, TArgs extends Paramete
|
|
|
136
156
|
* const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds
|
|
137
157
|
* ```
|
|
138
158
|
*/
|
|
139
|
-
export declare function rateLimit<TFn extends AnyFunction>(fn: TFn, initialOptions: Omit<RateLimiterOptions<TFn
|
|
159
|
+
export declare function rateLimit<TFn extends AnyFunction>(fn: TFn, initialOptions: Omit<RateLimiterOptions<TFn>, 'enabled'>): (...args: Parameters<TFn>) => boolean;
|
package/dist/cjs/throttler.cjs
CHANGED
|
@@ -3,17 +3,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const defaultOptions = {
|
|
4
4
|
enabled: true,
|
|
5
5
|
leading: true,
|
|
6
|
-
trailing: true,
|
|
7
|
-
wait: 0,
|
|
8
6
|
onExecute: () => {
|
|
9
|
-
}
|
|
7
|
+
},
|
|
8
|
+
trailing: true,
|
|
9
|
+
wait: 0
|
|
10
10
|
};
|
|
11
11
|
class Throttler {
|
|
12
12
|
constructor(fn, initialOptions) {
|
|
13
13
|
this.fn = fn;
|
|
14
14
|
this._executionCount = 0;
|
|
15
15
|
this._lastExecutionTime = 0;
|
|
16
|
-
this._isPending = false;
|
|
17
16
|
this._options = {
|
|
18
17
|
...defaultOptions,
|
|
19
18
|
...initialOptions
|
|
@@ -24,11 +23,10 @@ class Throttler {
|
|
|
24
23
|
* Returns the new options state
|
|
25
24
|
*/
|
|
26
25
|
setOptions(newOptions) {
|
|
27
|
-
this._options = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
return this._options;
|
|
26
|
+
this._options = { ...this._options, ...newOptions };
|
|
27
|
+
if (!this._options.enabled) {
|
|
28
|
+
this.cancel();
|
|
29
|
+
}
|
|
32
30
|
}
|
|
33
31
|
/**
|
|
34
32
|
* Returns the current throttler options
|
|
@@ -61,26 +59,18 @@ class Throttler {
|
|
|
61
59
|
maybeExecute(...args) {
|
|
62
60
|
const now = Date.now();
|
|
63
61
|
const timeSinceLastExecution = now - this._lastExecutionTime;
|
|
64
|
-
if (timeSinceLastExecution >= this._options.wait) {
|
|
65
|
-
|
|
66
|
-
this.executeFunction(...args);
|
|
67
|
-
}
|
|
68
|
-
this._lastExecutionTime = now;
|
|
69
|
-
this._isPending = false;
|
|
62
|
+
if (this._options.leading && timeSinceLastExecution >= this._options.wait) {
|
|
63
|
+
this.executeFunction(...args);
|
|
70
64
|
} else {
|
|
71
65
|
this._lastArgs = args;
|
|
72
66
|
if (!this._timeoutId && this._options.trailing) {
|
|
73
|
-
this.
|
|
67
|
+
const _timeSinceLastExecution = this._lastExecutionTime ? now - this._lastExecutionTime : 0;
|
|
68
|
+
const timeoutDuration = this._options.wait - _timeSinceLastExecution;
|
|
74
69
|
this._timeoutId = setTimeout(() => {
|
|
75
|
-
if (this._lastArgs) {
|
|
70
|
+
if (this._lastArgs !== void 0) {
|
|
76
71
|
this.executeFunction(...this._lastArgs);
|
|
77
|
-
this._lastArgs = void 0;
|
|
78
72
|
}
|
|
79
|
-
|
|
80
|
-
this._timeoutId = void 0;
|
|
81
|
-
this._isPending = false;
|
|
82
|
-
this._options.onExecute(this);
|
|
83
|
-
}, this._options.wait - timeSinceLastExecution);
|
|
73
|
+
}, timeoutDuration);
|
|
84
74
|
}
|
|
85
75
|
}
|
|
86
76
|
}
|
|
@@ -88,6 +78,10 @@ class Throttler {
|
|
|
88
78
|
if (!this._options.enabled) return;
|
|
89
79
|
this.fn(...args);
|
|
90
80
|
this._executionCount++;
|
|
81
|
+
this._lastExecutionTime = Date.now();
|
|
82
|
+
this._timeoutId = void 0;
|
|
83
|
+
this._lastArgs = void 0;
|
|
84
|
+
this._options.onExecute(this);
|
|
91
85
|
}
|
|
92
86
|
/**
|
|
93
87
|
* Cancels any pending trailing execution and clears internal state.
|
|
@@ -103,21 +97,8 @@ class Throttler {
|
|
|
103
97
|
clearTimeout(this._timeoutId);
|
|
104
98
|
this._timeoutId = void 0;
|
|
105
99
|
this._lastArgs = void 0;
|
|
106
|
-
this._isPending = false;
|
|
107
100
|
}
|
|
108
101
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Returns the number of times the function has been executed
|
|
111
|
-
*/
|
|
112
|
-
getExecutionCount() {
|
|
113
|
-
return this._executionCount;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Returns `true` if there is a pending execution
|
|
117
|
-
*/
|
|
118
|
-
getIsPending() {
|
|
119
|
-
return this._options.enabled && this._isPending;
|
|
120
|
-
}
|
|
121
102
|
/**
|
|
122
103
|
* Returns the last execution time
|
|
123
104
|
*/
|
|
@@ -130,6 +111,18 @@ class Throttler {
|
|
|
130
111
|
getNextExecutionTime() {
|
|
131
112
|
return this._lastExecutionTime + this._options.wait;
|
|
132
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns the number of times the function has been executed
|
|
116
|
+
*/
|
|
117
|
+
getExecutionCount() {
|
|
118
|
+
return this._executionCount;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns `true` if there is a pending execution
|
|
122
|
+
*/
|
|
123
|
+
getIsPending() {
|
|
124
|
+
return this._options.enabled && !!this._timeoutId;
|
|
125
|
+
}
|
|
133
126
|
}
|
|
134
127
|
function throttle(fn, initialOptions) {
|
|
135
128
|
const throttler = new Throttler(fn, initialOptions);
|