tiny-essentials 1.27.1 → 1.28.1
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/README.md +1 -0
- package/changelog/1/28/0.md +16 -0
- package/changelog/1/28/1.md +23 -0
- package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
- package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
- package/dist/v1/TinyAnalogClock.min.js +1 -1
- package/dist/v1/TinyBasicsEs.min.js +1 -2
- package/dist/v1/TinyDayNightCycle.min.js +1 -1
- package/dist/v1/TinyDragDropDetector.min.js +1 -1
- package/dist/v1/TinyDragger.min.js +1 -1
- package/dist/v1/TinyElementObserver.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -2
- package/dist/v1/TinyGamepad.min.js +1 -1
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinyI18.min.js +1 -1
- package/dist/v1/TinyIframeEvents.min.js +1 -1
- package/dist/v1/TinyLocalStorage.min.js +1 -1
- package/dist/v1/TinyMediaPlayer.min.js +1 -1
- package/dist/v1/TinyNewWinEvents.min.js +1 -1
- package/dist/v1/TinyNotifyCenter.min.js +1 -1
- package/dist/v1/TinyRadioFm.min.js +1 -1
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextDiffer.min.js +1 -1
- package/dist/v1/TinyTextarea.min.js +1 -1
- package/dist/v1/TinyTimeout.min.js +1 -1
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -2
- package/dist/v1/UltraRandomMsgGen.min.js +1 -1
- package/dist/v1/basics/array.cjs +57 -9
- package/dist/v1/basics/array.d.mts +27 -11
- package/dist/v1/basics/array.mjs +51 -9
- package/dist/v1/basics/clock.cjs +94 -42
- package/dist/v1/basics/clock.d.mts +10 -10
- package/dist/v1/basics/clock.mjs +91 -54
- package/dist/v1/basics/deprecated/html.cjs +246 -0
- package/dist/v1/basics/deprecated/html.d.mts +63 -0
- package/dist/v1/basics/deprecated/html.mjs +190 -0
- package/dist/v1/basics/extendObjType/Buffer.cjs +13 -0
- package/dist/v1/basics/extendObjType/Buffer.d.mts +4 -0
- package/dist/v1/basics/extendObjType/Buffer.mjs +8 -0
- package/dist/v1/basics/html.cjs +149 -130
- package/dist/v1/basics/html.d.mts +26 -61
- package/dist/v1/basics/html.mjs +113 -105
- package/dist/v1/basics/index.cjs +2 -0
- package/dist/v1/basics/index.d.mts +3 -1
- package/dist/v1/basics/index.mjs +3 -3
- package/dist/v1/basics/mediaContent.cjs +51 -9
- package/dist/v1/basics/mediaContent.d.mts +0 -87
- package/dist/v1/basics/mediaContent.mjs +40 -10
- package/dist/v1/basics/objChecker.cjs +7 -5
- package/dist/v1/basics/objChecker.d.mts +3 -2
- package/dist/v1/basics/objChecker.mjs +6 -6
- package/dist/v1/basics/objFilter.cjs +40 -18
- package/dist/v1/basics/objFilter.d.mts +6 -6
- package/dist/v1/basics/objFilter.mjs +32 -17
- package/dist/v1/basics/simpleMath.cjs +81 -25
- package/dist/v1/basics/simpleMath.d.mts +2 -2
- package/dist/v1/basics/simpleMath.mjs +70 -27
- package/dist/v1/basics/text.cjs +76 -31
- package/dist/v1/basics/text.d.mts +69 -7
- package/dist/v1/basics/text.mjs +62 -31
- package/dist/v1/index.cjs +4 -2
- package/dist/v1/index.d.mts +4 -2
- package/dist/v1/index.mjs +4 -4
- package/dist/v1/libs/TinyAdvancedRaffle.cjs +71 -255
- package/dist/v1/libs/TinyAdvancedRaffle.d.mts +6 -134
- package/dist/v1/libs/TinyAdvancedRaffle.mjs +69 -228
- package/dist/v1/libs/TinyAfterScrollWatcher.cjs +17 -1
- package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +4 -1
- package/dist/v1/libs/TinyAfterScrollWatcher.mjs +15 -1
- package/dist/v1/libs/TinyAnalogClock.cjs +60 -0
- package/dist/v1/libs/TinyAnalogClock.d.mts +6 -0
- package/dist/v1/libs/TinyAnalogClock.mjs +57 -0
- package/dist/v1/libs/TinyClipboard.cjs +2 -0
- package/dist/v1/libs/TinyClipboard.d.mts +4 -2
- package/dist/v1/libs/TinyClipboard.mjs +2 -0
- package/dist/v1/libs/TinyColorConverter.cjs +1 -0
- package/dist/v1/libs/TinyColorConverter.d.mts +2 -1
- package/dist/v1/libs/TinyColorConverter.mjs +1 -0
- package/dist/v1/libs/TinyDayNightCycle.cjs +67 -42
- package/dist/v1/libs/TinyDayNightCycle.d.mts +2 -7
- package/dist/v1/libs/TinyDayNightCycle.mjs +65 -41
- package/dist/v1/libs/TinyDragDropDetector.cjs +25 -0
- package/dist/v1/libs/TinyDragDropDetector.d.mts +6 -0
- package/dist/v1/libs/TinyDragDropDetector.mjs +22 -0
- package/dist/v1/libs/TinyDragger.cjs +46 -19
- package/dist/v1/libs/TinyDragger.mjs +45 -18
- package/dist/v1/libs/TinyElementObserver.cjs +39 -0
- package/dist/v1/libs/TinyElementObserver.d.mts +6 -0
- package/dist/v1/libs/TinyElementObserver.mjs +36 -0
- package/dist/v1/libs/TinyGamepad.cjs +131 -1
- package/dist/v1/libs/TinyGamepad.mjs +129 -1
- package/dist/v1/libs/TinyHtml.cjs +1 -1
- package/dist/v1/libs/TinyHtml.mjs +2 -2
- package/dist/v1/libs/TinyI18.cjs +61 -0
- package/dist/v1/libs/TinyI18.d.mts +14 -0
- package/dist/v1/libs/TinyI18.mjs +55 -0
- package/dist/v1/libs/TinyIframeEvents.cjs +26 -196
- package/dist/v1/libs/TinyIframeEvents.d.mts +3 -122
- package/dist/v1/libs/TinyIframeEvents.mjs +26 -171
- package/dist/v1/libs/TinyLocalStorage.cjs +53 -205
- package/dist/v1/libs/TinyLocalStorage.d.mts +6 -127
- package/dist/v1/libs/TinyLocalStorage.mjs +50 -178
- package/dist/v1/libs/TinyMediaPlayer.cjs +74 -2
- package/dist/v1/libs/TinyMediaPlayer.d.mts +12 -2
- package/dist/v1/libs/TinyMediaPlayer.mjs +68 -2
- package/dist/v1/libs/TinyNewWinEvents.cjs +39 -197
- package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -119
- package/dist/v1/libs/TinyNewWinEvents.mjs +36 -172
- package/dist/v1/libs/TinyNotifyCenter.cjs +36 -0
- package/dist/v1/libs/TinyNotifyCenter.d.mts +6 -0
- package/dist/v1/libs/TinyNotifyCenter.mjs +32 -0
- package/dist/v1/libs/TinyRadioFm.cjs +40 -3
- package/dist/v1/libs/TinyRadioFm.d.mts +3 -3
- package/dist/v1/libs/TinyRadioFm.mjs +37 -3
- package/dist/v1/libs/TinyRateLimiter.cjs +52 -0
- package/dist/v1/libs/TinyRateLimiter.d.mts +6 -0
- package/dist/v1/libs/TinyRateLimiter.mjs +48 -0
- package/dist/v1/libs/TinySmartScroller.cjs +56 -205
- package/dist/v1/libs/TinySmartScroller.d.mts +19 -152
- package/dist/v1/libs/TinySmartScroller.mjs +55 -181
- package/dist/v1/libs/TinyTextDiffer.cjs +19 -18
- package/dist/v1/libs/TinyTextDiffer.d.mts +1 -0
- package/dist/v1/libs/TinyTextDiffer.mjs +16 -17
- package/dist/v1/libs/TinyTextarea.cjs +19 -0
- package/dist/v1/libs/TinyTextarea.d.mts +6 -0
- package/dist/v1/libs/TinyTextarea.mjs +17 -0
- package/dist/v1/libs/TinyTimeout.cjs +11 -1
- package/dist/v1/libs/TinyTimeout.mjs +9 -1
- package/dist/v1/libs/TinyToastNotify.cjs +35 -0
- package/dist/v1/libs/TinyToastNotify.d.mts +6 -0
- package/dist/v1/libs/TinyToastNotify.mjs +31 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +20 -0
- package/dist/v1/libs/TinyUploadClicker.d.mts +6 -0
- package/dist/v1/libs/TinyUploadClicker.mjs +17 -0
- package/dist/v1/libs/UltraRandomMsgGen.cjs +148 -35
- package/dist/v1/libs/UltraRandomMsgGen.d.mts +17 -3
- package/dist/v1/libs/UltraRandomMsgGen.mjs +133 -34
- package/dist/v1/libs/utils.cjs +21 -0
- package/dist/v1/libs/utils.d.mts +2 -0
- package/dist/v1/libs/utils.mjs +16 -0
- package/docs/v1/Exports.md +0 -1
- package/docs/v1/README.md +1 -1
- package/docs/v1/basics/array.md +2 -2
- package/docs/v1/basics/deprecated/html.md +17 -0
- package/docs/v1/basics/fullScreen.md +1 -1
- package/docs/v1/basics/html.md +12 -26
- package/docs/v1/basics/mediaContent.md +1 -1
- package/docs/v1/basics/objChecker.md +1 -1
- package/docs/v1/basics/objFilter.md +1 -1
- package/docs/v1/basics/text.md +2 -2
- package/docs/v1/fileManager/main.md +1 -1
- package/docs/v1/libs/ColorSafeStringify.md +1 -1
- package/docs/v1/libs/TinyAfterScrollWatcher.md +1 -1
- package/docs/v1/libs/TinyAnalogClock.md +1 -1
- package/docs/v1/libs/TinyArrayComparator.md +1 -1
- package/docs/v1/libs/TinyArrayPaginator.md +1 -1
- package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +2 -2
- package/docs/v1/libs/TinyClassManager.md +1 -1
- package/docs/v1/libs/TinyClipboard.md +1 -1
- package/docs/v1/libs/TinyCookieConsent.md +1 -1
- package/docs/v1/libs/TinyDomReadyManager.md +1 -1
- package/docs/v1/libs/TinyDragDropDetector.md +1 -1
- package/docs/v1/libs/TinyDragger.md +1 -1
- package/docs/v1/libs/TinyElementObserver.md +1 -1
- package/docs/v1/libs/TinyHtml.md +1 -1
- package/docs/v1/libs/TinyIframeEvents.md +10 -10
- package/docs/v1/libs/TinyLevelUp.md +1 -1
- package/docs/v1/libs/TinyLoadingScreen.md +1 -1
- package/docs/v1/libs/TinyLocalStorage.md +3 -3
- package/docs/v1/libs/TinyNewWinEvents.md +10 -11
- package/docs/v1/libs/TinyNotifications.md +1 -1
- package/docs/v1/libs/TinyPromiseQueue.md +2 -2
- package/docs/v1/libs/TinyTextDiffer.md +1 -1
- package/docs/v1/libs/TinyTextarea.md +1 -1
- package/package.json +15 -5
- package/dist/v1/TinyBasicsEs.min.js.LICENSE.txt +0 -8
- package/dist/v1/TinyEssentials.min.js.LICENSE.txt +0 -8
- package/dist/v1/TinyEvents.min.js +0 -1
- package/dist/v1/TinyUploadClicker.min.js.LICENSE.txt +0 -8
- package/dist/v1/build/TinyEvents.cjs +0 -7
- package/dist/v1/build/TinyEvents.d.mts +0 -3
- package/dist/v1/build/TinyEvents.mjs +0 -2
- package/dist/v1/libs/TinyEvents.cjs +0 -395
- package/dist/v1/libs/TinyEvents.d.mts +0 -164
- package/dist/v1/libs/TinyEvents.mjs +0 -360
- package/docs/v1/libs/TinyEvents.md +0 -187
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var utils = require('./utils.cjs');
|
|
4
|
+
|
|
5
|
+
const checkDestroy = utils.createCheckDestroyed('TinyRateLimiter');
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Callback triggered when a group's stored hit history exceeds
|
|
5
9
|
* the configured memory limit.
|
|
@@ -70,6 +74,21 @@
|
|
|
70
74
|
* and safe to use in long-running Node.js processes.
|
|
71
75
|
*/
|
|
72
76
|
class TinyRateLimiter {
|
|
77
|
+
/**
|
|
78
|
+
* Tracks whether the instance has been destroyed.
|
|
79
|
+
* @type {boolean}
|
|
80
|
+
*/
|
|
81
|
+
#destroyed = false;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Gets the current destruction status of the instance.
|
|
85
|
+
*
|
|
86
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
87
|
+
*/
|
|
88
|
+
get destroyed() {
|
|
89
|
+
return this.#destroyed;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
/**
|
|
74
93
|
* Maximum number of timestamps stored per group.
|
|
75
94
|
* When exceeded, older entries are discarded.
|
|
@@ -186,6 +205,7 @@ class TinyRateLimiter {
|
|
|
186
205
|
* @param {OnMemoryExceeded} callback
|
|
187
206
|
*/
|
|
188
207
|
setOnMemoryExceeded(callback) {
|
|
208
|
+
checkDestroy(this.#destroyed);
|
|
189
209
|
if (typeof callback !== 'function') throw new Error('onMemoryExceeded must be a function');
|
|
190
210
|
this.#onMemoryExceeded = callback;
|
|
191
211
|
}
|
|
@@ -194,6 +214,7 @@ class TinyRateLimiter {
|
|
|
194
214
|
* Removes the memory-exceeded callback.
|
|
195
215
|
*/
|
|
196
216
|
clearOnMemoryExceeded() {
|
|
217
|
+
checkDestroy(this.#destroyed);
|
|
197
218
|
this.#onMemoryExceeded = null;
|
|
198
219
|
}
|
|
199
220
|
|
|
@@ -213,6 +234,7 @@ class TinyRateLimiter {
|
|
|
213
234
|
* @param {OnGroupExpired} callback - A function that receives the expired groupId.
|
|
214
235
|
*/
|
|
215
236
|
setOnGroupExpired(callback) {
|
|
237
|
+
checkDestroy(this.#destroyed);
|
|
216
238
|
if (typeof callback !== 'function') throw new Error('onGroupExpired must be a function');
|
|
217
239
|
this.#onGroupExpired = callback;
|
|
218
240
|
}
|
|
@@ -221,6 +243,7 @@ class TinyRateLimiter {
|
|
|
221
243
|
* Removes the group-expiration callback.
|
|
222
244
|
*/
|
|
223
245
|
clearOnGroupExpired() {
|
|
246
|
+
checkDestroy(this.#destroyed);
|
|
224
247
|
this.#onGroupExpired = null;
|
|
225
248
|
}
|
|
226
249
|
|
|
@@ -285,6 +308,7 @@ class TinyRateLimiter {
|
|
|
285
308
|
* @returns {boolean}
|
|
286
309
|
*/
|
|
287
310
|
isGroupId(id) {
|
|
311
|
+
checkDestroy(this.#destroyed);
|
|
288
312
|
const result = this.groupFlags.get(id);
|
|
289
313
|
return typeof result === 'boolean' ? result : false;
|
|
290
314
|
}
|
|
@@ -295,6 +319,7 @@ class TinyRateLimiter {
|
|
|
295
319
|
* @returns {string[]}
|
|
296
320
|
*/
|
|
297
321
|
getUsersInGroup(groupId) {
|
|
322
|
+
checkDestroy(this.#destroyed);
|
|
298
323
|
const users = [];
|
|
299
324
|
for (const [userId, assignedGroup] of this.userToGroup.entries()) {
|
|
300
325
|
if (assignedGroup === groupId) {
|
|
@@ -311,6 +336,7 @@ class TinyRateLimiter {
|
|
|
311
336
|
* @param {number} ttl
|
|
312
337
|
*/
|
|
313
338
|
setGroupTTL(groupId, ttl) {
|
|
339
|
+
checkDestroy(this.#destroyed);
|
|
314
340
|
if (typeof ttl !== 'number' || !Number.isFinite(ttl) || ttl <= 0)
|
|
315
341
|
throw new Error('TTL must be a positive number in milliseconds');
|
|
316
342
|
this.groupTTL.set(groupId, ttl);
|
|
@@ -322,6 +348,7 @@ class TinyRateLimiter {
|
|
|
322
348
|
* @returns {number|null}
|
|
323
349
|
*/
|
|
324
350
|
getGroupTTL(groupId) {
|
|
351
|
+
checkDestroy(this.#destroyed);
|
|
325
352
|
return this.groupTTL.get(groupId) ?? null;
|
|
326
353
|
}
|
|
327
354
|
|
|
@@ -330,6 +357,7 @@ class TinyRateLimiter {
|
|
|
330
357
|
* @param {string} groupId
|
|
331
358
|
*/
|
|
332
359
|
deleteGroupTTL(groupId) {
|
|
360
|
+
checkDestroy(this.#destroyed);
|
|
333
361
|
this.groupTTL.delete(groupId);
|
|
334
362
|
}
|
|
335
363
|
|
|
@@ -344,6 +372,7 @@ class TinyRateLimiter {
|
|
|
344
372
|
* @throws {Error} If the user belongs to another group
|
|
345
373
|
*/
|
|
346
374
|
assignToGroup(userId, groupId) {
|
|
375
|
+
checkDestroy(this.#destroyed);
|
|
347
376
|
const existingGroup = this.userToGroup.get(userId);
|
|
348
377
|
if (existingGroup && existingGroup !== groupId)
|
|
349
378
|
throw new Error(`User ${userId} is already assigned to group ${existingGroup}`);
|
|
@@ -391,6 +420,7 @@ class TinyRateLimiter {
|
|
|
391
420
|
* @returns {string}
|
|
392
421
|
*/
|
|
393
422
|
getGroupId(userId) {
|
|
423
|
+
checkDestroy(this.#destroyed);
|
|
394
424
|
return this.userToGroup.get(userId) || userId; // fallback: use userId as own group
|
|
395
425
|
}
|
|
396
426
|
|
|
@@ -417,6 +447,7 @@ class TinyRateLimiter {
|
|
|
417
447
|
* @param {string} userId
|
|
418
448
|
*/
|
|
419
449
|
hit(userId) {
|
|
450
|
+
checkDestroy(this.#destroyed);
|
|
420
451
|
const groupId = this.getGroupId(userId);
|
|
421
452
|
const now = Date.now();
|
|
422
453
|
|
|
@@ -470,6 +501,7 @@ class TinyRateLimiter {
|
|
|
470
501
|
* @returns {boolean}
|
|
471
502
|
*/
|
|
472
503
|
isRateLimited(userId) {
|
|
504
|
+
checkDestroy(this.#destroyed);
|
|
473
505
|
const groupId = this.getGroupId(userId);
|
|
474
506
|
if (!this.groupData.has(groupId)) return false;
|
|
475
507
|
|
|
@@ -501,6 +533,7 @@ class TinyRateLimiter {
|
|
|
501
533
|
* @param {string} groupId
|
|
502
534
|
*/
|
|
503
535
|
resetGroup(groupId) {
|
|
536
|
+
checkDestroy(this.#destroyed);
|
|
504
537
|
this.groupFlags.delete(groupId);
|
|
505
538
|
this.groupData.delete(groupId);
|
|
506
539
|
this.lastSeen.delete(groupId);
|
|
@@ -512,6 +545,7 @@ class TinyRateLimiter {
|
|
|
512
545
|
* @param {string} userId
|
|
513
546
|
*/
|
|
514
547
|
resetUserGroup(userId) {
|
|
548
|
+
checkDestroy(this.#destroyed);
|
|
515
549
|
this.userToGroup.delete(userId);
|
|
516
550
|
}
|
|
517
551
|
|
|
@@ -521,6 +555,7 @@ class TinyRateLimiter {
|
|
|
521
555
|
* @param {number[]} timestamps
|
|
522
556
|
*/
|
|
523
557
|
setData(groupId, timestamps) {
|
|
558
|
+
checkDestroy(this.#destroyed);
|
|
524
559
|
if (!Array.isArray(timestamps)) throw new Error('timestamps must be an array of numbers.');
|
|
525
560
|
for (const t of timestamps) {
|
|
526
561
|
if (typeof t !== 'number' || !Number.isFinite(t)) {
|
|
@@ -538,6 +573,7 @@ class TinyRateLimiter {
|
|
|
538
573
|
* @returns {boolean}
|
|
539
574
|
*/
|
|
540
575
|
hasData(groupId) {
|
|
576
|
+
checkDestroy(this.#destroyed);
|
|
541
577
|
return this.groupData.has(groupId);
|
|
542
578
|
}
|
|
543
579
|
|
|
@@ -547,6 +583,7 @@ class TinyRateLimiter {
|
|
|
547
583
|
* @returns {number[]}
|
|
548
584
|
*/
|
|
549
585
|
getData(groupId) {
|
|
586
|
+
checkDestroy(this.#destroyed);
|
|
550
587
|
return this.groupData.get(groupId) || [];
|
|
551
588
|
}
|
|
552
589
|
|
|
@@ -555,6 +592,7 @@ class TinyRateLimiter {
|
|
|
555
592
|
* @returns {number}
|
|
556
593
|
*/
|
|
557
594
|
getMaxIdle() {
|
|
595
|
+
checkDestroy(this.#destroyed);
|
|
558
596
|
if (typeof this.#maxIdle !== 'number' || !Number.isFinite(this.#maxIdle) || this.#maxIdle < 0) {
|
|
559
597
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
560
598
|
}
|
|
@@ -566,6 +604,7 @@ class TinyRateLimiter {
|
|
|
566
604
|
* @param {number} ms
|
|
567
605
|
*/
|
|
568
606
|
setMaxIdle(ms) {
|
|
607
|
+
checkDestroy(this.#destroyed);
|
|
569
608
|
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) {
|
|
570
609
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
571
610
|
}
|
|
@@ -599,6 +638,7 @@ class TinyRateLimiter {
|
|
|
599
638
|
* @returns {string[]}
|
|
600
639
|
*/
|
|
601
640
|
getActiveGroups() {
|
|
641
|
+
checkDestroy(this.#destroyed);
|
|
602
642
|
return Array.from(this.groupData.keys());
|
|
603
643
|
}
|
|
604
644
|
|
|
@@ -607,6 +647,7 @@ class TinyRateLimiter {
|
|
|
607
647
|
* @returns {Record<string, string>}
|
|
608
648
|
*/
|
|
609
649
|
getAllUserMappings() {
|
|
650
|
+
checkDestroy(this.#destroyed);
|
|
610
651
|
return Object.fromEntries(this.userToGroup);
|
|
611
652
|
}
|
|
612
653
|
|
|
@@ -619,6 +660,7 @@ class TinyRateLimiter {
|
|
|
619
660
|
* @returns {number}
|
|
620
661
|
*/
|
|
621
662
|
getInterval() {
|
|
663
|
+
checkDestroy(this.#destroyed);
|
|
622
664
|
if (typeof this.#interval !== 'number' || !Number.isFinite(this.#interval)) {
|
|
623
665
|
throw new Error("'interval' is not a valid finite number.");
|
|
624
666
|
}
|
|
@@ -630,6 +672,7 @@ class TinyRateLimiter {
|
|
|
630
672
|
* @returns {number}
|
|
631
673
|
*/
|
|
632
674
|
getMaxHits() {
|
|
675
|
+
checkDestroy(this.#destroyed);
|
|
633
676
|
if (typeof this.#maxHits !== 'number' || !Number.isFinite(this.#maxHits)) {
|
|
634
677
|
throw new Error("'maxHits' is not a valid finite number.");
|
|
635
678
|
}
|
|
@@ -642,6 +685,7 @@ class TinyRateLimiter {
|
|
|
642
685
|
* @returns {number}
|
|
643
686
|
*/
|
|
644
687
|
getTotalHits(groupId) {
|
|
688
|
+
checkDestroy(this.#destroyed);
|
|
645
689
|
const history = this.groupData.get(groupId);
|
|
646
690
|
return Array.isArray(history) ? history.length : 0;
|
|
647
691
|
}
|
|
@@ -652,6 +696,7 @@ class TinyRateLimiter {
|
|
|
652
696
|
* @returns {number|null}
|
|
653
697
|
*/
|
|
654
698
|
getLastHit(groupId) {
|
|
699
|
+
checkDestroy(this.#destroyed);
|
|
655
700
|
const history = this.groupData.get(groupId);
|
|
656
701
|
return history?.length ? history[history.length - 1] : null;
|
|
657
702
|
}
|
|
@@ -662,6 +707,7 @@ class TinyRateLimiter {
|
|
|
662
707
|
* @returns {number|null}
|
|
663
708
|
*/
|
|
664
709
|
getTimeSinceLastHit(groupId) {
|
|
710
|
+
checkDestroy(this.#destroyed);
|
|
665
711
|
const last = this.getLastHit(groupId);
|
|
666
712
|
return last !== null ? Date.now() - last : null;
|
|
667
713
|
}
|
|
@@ -687,6 +733,7 @@ class TinyRateLimiter {
|
|
|
687
733
|
* @returns {number|null}
|
|
688
734
|
*/
|
|
689
735
|
getAverageHitSpacing(groupId) {
|
|
736
|
+
checkDestroy(this.#destroyed);
|
|
690
737
|
return this._calculateAverageSpacing(this.groupData.get(groupId));
|
|
691
738
|
}
|
|
692
739
|
|
|
@@ -701,6 +748,7 @@ class TinyRateLimiter {
|
|
|
701
748
|
* }}
|
|
702
749
|
*/
|
|
703
750
|
getMetrics(groupId) {
|
|
751
|
+
checkDestroy(this.#destroyed);
|
|
704
752
|
const history = this.groupData.get(groupId);
|
|
705
753
|
|
|
706
754
|
if (!Array.isArray(history) || history.length === 0) {
|
|
@@ -729,6 +777,7 @@ class TinyRateLimiter {
|
|
|
729
777
|
* Destroy the rate limiter, stopping cleanup and clearing data
|
|
730
778
|
*/
|
|
731
779
|
destroy() {
|
|
780
|
+
if (this.destroyed) return;
|
|
732
781
|
if (this.#cleanupTimer) clearInterval(this.#cleanupTimer);
|
|
733
782
|
this._cleanup();
|
|
734
783
|
this.groupData.clear();
|
|
@@ -736,6 +785,9 @@ class TinyRateLimiter {
|
|
|
736
785
|
this.userToGroup.clear();
|
|
737
786
|
this.groupTTL.clear();
|
|
738
787
|
this.groupFlags.clear();
|
|
788
|
+
|
|
789
|
+
// Lock the instance
|
|
790
|
+
this.#destroyed = true;
|
|
739
791
|
}
|
|
740
792
|
}
|
|
741
793
|
|
|
@@ -106,6 +106,12 @@ declare class TinyRateLimiter {
|
|
|
106
106
|
cleanupInterval?: number | undefined;
|
|
107
107
|
maxIdle?: number | undefined;
|
|
108
108
|
});
|
|
109
|
+
/**
|
|
110
|
+
* Gets the current destruction status of the instance.
|
|
111
|
+
*
|
|
112
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
113
|
+
*/
|
|
114
|
+
get destroyed(): boolean;
|
|
109
115
|
/**
|
|
110
116
|
* Stores hit timestamps per group.
|
|
111
117
|
*
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createCheckDestroyed } from './utils.mjs';
|
|
2
|
+
const checkDestroy = createCheckDestroyed('TinyRateLimiter');
|
|
1
3
|
/**
|
|
2
4
|
* Callback triggered when a group's stored hit history exceeds
|
|
3
5
|
* the configured memory limit.
|
|
@@ -66,6 +68,19 @@
|
|
|
66
68
|
* and safe to use in long-running Node.js processes.
|
|
67
69
|
*/
|
|
68
70
|
class TinyRateLimiter {
|
|
71
|
+
/**
|
|
72
|
+
* Tracks whether the instance has been destroyed.
|
|
73
|
+
* @type {boolean}
|
|
74
|
+
*/
|
|
75
|
+
#destroyed = false;
|
|
76
|
+
/**
|
|
77
|
+
* Gets the current destruction status of the instance.
|
|
78
|
+
*
|
|
79
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
80
|
+
*/
|
|
81
|
+
get destroyed() {
|
|
82
|
+
return this.#destroyed;
|
|
83
|
+
}
|
|
69
84
|
/**
|
|
70
85
|
* Maximum number of timestamps stored per group.
|
|
71
86
|
* When exceeded, older entries are discarded.
|
|
@@ -170,6 +185,7 @@ class TinyRateLimiter {
|
|
|
170
185
|
* @param {OnMemoryExceeded} callback
|
|
171
186
|
*/
|
|
172
187
|
setOnMemoryExceeded(callback) {
|
|
188
|
+
checkDestroy(this.#destroyed);
|
|
173
189
|
if (typeof callback !== 'function')
|
|
174
190
|
throw new Error('onMemoryExceeded must be a function');
|
|
175
191
|
this.#onMemoryExceeded = callback;
|
|
@@ -178,6 +194,7 @@ class TinyRateLimiter {
|
|
|
178
194
|
* Removes the memory-exceeded callback.
|
|
179
195
|
*/
|
|
180
196
|
clearOnMemoryExceeded() {
|
|
197
|
+
checkDestroy(this.#destroyed);
|
|
181
198
|
this.#onMemoryExceeded = null;
|
|
182
199
|
}
|
|
183
200
|
/**
|
|
@@ -195,6 +212,7 @@ class TinyRateLimiter {
|
|
|
195
212
|
* @param {OnGroupExpired} callback - A function that receives the expired groupId.
|
|
196
213
|
*/
|
|
197
214
|
setOnGroupExpired(callback) {
|
|
215
|
+
checkDestroy(this.#destroyed);
|
|
198
216
|
if (typeof callback !== 'function')
|
|
199
217
|
throw new Error('onGroupExpired must be a function');
|
|
200
218
|
this.#onGroupExpired = callback;
|
|
@@ -203,6 +221,7 @@ class TinyRateLimiter {
|
|
|
203
221
|
* Removes the group-expiration callback.
|
|
204
222
|
*/
|
|
205
223
|
clearOnGroupExpired() {
|
|
224
|
+
checkDestroy(this.#destroyed);
|
|
206
225
|
this.#onGroupExpired = null;
|
|
207
226
|
}
|
|
208
227
|
/**
|
|
@@ -262,6 +281,7 @@ class TinyRateLimiter {
|
|
|
262
281
|
* @returns {boolean}
|
|
263
282
|
*/
|
|
264
283
|
isGroupId(id) {
|
|
284
|
+
checkDestroy(this.#destroyed);
|
|
265
285
|
const result = this.groupFlags.get(id);
|
|
266
286
|
return typeof result === 'boolean' ? result : false;
|
|
267
287
|
}
|
|
@@ -271,6 +291,7 @@ class TinyRateLimiter {
|
|
|
271
291
|
* @returns {string[]}
|
|
272
292
|
*/
|
|
273
293
|
getUsersInGroup(groupId) {
|
|
294
|
+
checkDestroy(this.#destroyed);
|
|
274
295
|
const users = [];
|
|
275
296
|
for (const [userId, assignedGroup] of this.userToGroup.entries()) {
|
|
276
297
|
if (assignedGroup === groupId) {
|
|
@@ -285,6 +306,7 @@ class TinyRateLimiter {
|
|
|
285
306
|
* @param {number} ttl
|
|
286
307
|
*/
|
|
287
308
|
setGroupTTL(groupId, ttl) {
|
|
309
|
+
checkDestroy(this.#destroyed);
|
|
288
310
|
if (typeof ttl !== 'number' || !Number.isFinite(ttl) || ttl <= 0)
|
|
289
311
|
throw new Error('TTL must be a positive number in milliseconds');
|
|
290
312
|
this.groupTTL.set(groupId, ttl);
|
|
@@ -295,6 +317,7 @@ class TinyRateLimiter {
|
|
|
295
317
|
* @returns {number|null}
|
|
296
318
|
*/
|
|
297
319
|
getGroupTTL(groupId) {
|
|
320
|
+
checkDestroy(this.#destroyed);
|
|
298
321
|
return this.groupTTL.get(groupId) ?? null;
|
|
299
322
|
}
|
|
300
323
|
/**
|
|
@@ -302,6 +325,7 @@ class TinyRateLimiter {
|
|
|
302
325
|
* @param {string} groupId
|
|
303
326
|
*/
|
|
304
327
|
deleteGroupTTL(groupId) {
|
|
328
|
+
checkDestroy(this.#destroyed);
|
|
305
329
|
this.groupTTL.delete(groupId);
|
|
306
330
|
}
|
|
307
331
|
/**
|
|
@@ -315,6 +339,7 @@ class TinyRateLimiter {
|
|
|
315
339
|
* @throws {Error} If the user belongs to another group
|
|
316
340
|
*/
|
|
317
341
|
assignToGroup(userId, groupId) {
|
|
342
|
+
checkDestroy(this.#destroyed);
|
|
318
343
|
const existingGroup = this.userToGroup.get(userId);
|
|
319
344
|
if (existingGroup && existingGroup !== groupId)
|
|
320
345
|
throw new Error(`User ${userId} is already assigned to group ${existingGroup}`);
|
|
@@ -363,6 +388,7 @@ class TinyRateLimiter {
|
|
|
363
388
|
* @returns {string}
|
|
364
389
|
*/
|
|
365
390
|
getGroupId(userId) {
|
|
391
|
+
checkDestroy(this.#destroyed);
|
|
366
392
|
return this.userToGroup.get(userId) || userId; // fallback: use userId as own group
|
|
367
393
|
}
|
|
368
394
|
/**
|
|
@@ -388,6 +414,7 @@ class TinyRateLimiter {
|
|
|
388
414
|
* @param {string} userId
|
|
389
415
|
*/
|
|
390
416
|
hit(userId) {
|
|
417
|
+
checkDestroy(this.#destroyed);
|
|
391
418
|
const groupId = this.getGroupId(userId);
|
|
392
419
|
const now = Date.now();
|
|
393
420
|
if (!this.groupData.has(groupId)) {
|
|
@@ -437,6 +464,7 @@ class TinyRateLimiter {
|
|
|
437
464
|
* @returns {boolean}
|
|
438
465
|
*/
|
|
439
466
|
isRateLimited(userId) {
|
|
467
|
+
checkDestroy(this.#destroyed);
|
|
440
468
|
const groupId = this.getGroupId(userId);
|
|
441
469
|
if (!this.groupData.has(groupId))
|
|
442
470
|
return false;
|
|
@@ -468,6 +496,7 @@ class TinyRateLimiter {
|
|
|
468
496
|
* @param {string} groupId
|
|
469
497
|
*/
|
|
470
498
|
resetGroup(groupId) {
|
|
499
|
+
checkDestroy(this.#destroyed);
|
|
471
500
|
this.groupFlags.delete(groupId);
|
|
472
501
|
this.groupData.delete(groupId);
|
|
473
502
|
this.lastSeen.delete(groupId);
|
|
@@ -478,6 +507,7 @@ class TinyRateLimiter {
|
|
|
478
507
|
* @param {string} userId
|
|
479
508
|
*/
|
|
480
509
|
resetUserGroup(userId) {
|
|
510
|
+
checkDestroy(this.#destroyed);
|
|
481
511
|
this.userToGroup.delete(userId);
|
|
482
512
|
}
|
|
483
513
|
/**
|
|
@@ -486,6 +516,7 @@ class TinyRateLimiter {
|
|
|
486
516
|
* @param {number[]} timestamps
|
|
487
517
|
*/
|
|
488
518
|
setData(groupId, timestamps) {
|
|
519
|
+
checkDestroy(this.#destroyed);
|
|
489
520
|
if (!Array.isArray(timestamps))
|
|
490
521
|
throw new Error('timestamps must be an array of numbers.');
|
|
491
522
|
for (const t of timestamps) {
|
|
@@ -504,6 +535,7 @@ class TinyRateLimiter {
|
|
|
504
535
|
* @returns {boolean}
|
|
505
536
|
*/
|
|
506
537
|
hasData(groupId) {
|
|
538
|
+
checkDestroy(this.#destroyed);
|
|
507
539
|
return this.groupData.has(groupId);
|
|
508
540
|
}
|
|
509
541
|
/**
|
|
@@ -512,6 +544,7 @@ class TinyRateLimiter {
|
|
|
512
544
|
* @returns {number[]}
|
|
513
545
|
*/
|
|
514
546
|
getData(groupId) {
|
|
547
|
+
checkDestroy(this.#destroyed);
|
|
515
548
|
return this.groupData.get(groupId) || [];
|
|
516
549
|
}
|
|
517
550
|
/**
|
|
@@ -519,6 +552,7 @@ class TinyRateLimiter {
|
|
|
519
552
|
* @returns {number}
|
|
520
553
|
*/
|
|
521
554
|
getMaxIdle() {
|
|
555
|
+
checkDestroy(this.#destroyed);
|
|
522
556
|
if (typeof this.#maxIdle !== 'number' || !Number.isFinite(this.#maxIdle) || this.#maxIdle < 0) {
|
|
523
557
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
524
558
|
}
|
|
@@ -529,6 +563,7 @@ class TinyRateLimiter {
|
|
|
529
563
|
* @param {number} ms
|
|
530
564
|
*/
|
|
531
565
|
setMaxIdle(ms) {
|
|
566
|
+
checkDestroy(this.#destroyed);
|
|
532
567
|
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) {
|
|
533
568
|
throw new Error("'maxIdle' must be a non-negative finite number.");
|
|
534
569
|
}
|
|
@@ -559,6 +594,7 @@ class TinyRateLimiter {
|
|
|
559
594
|
* @returns {string[]}
|
|
560
595
|
*/
|
|
561
596
|
getActiveGroups() {
|
|
597
|
+
checkDestroy(this.#destroyed);
|
|
562
598
|
return Array.from(this.groupData.keys());
|
|
563
599
|
}
|
|
564
600
|
/**
|
|
@@ -566,6 +602,7 @@ class TinyRateLimiter {
|
|
|
566
602
|
* @returns {Record<string, string>}
|
|
567
603
|
*/
|
|
568
604
|
getAllUserMappings() {
|
|
605
|
+
checkDestroy(this.#destroyed);
|
|
569
606
|
return Object.fromEntries(this.userToGroup);
|
|
570
607
|
}
|
|
571
608
|
/**
|
|
@@ -577,6 +614,7 @@ class TinyRateLimiter {
|
|
|
577
614
|
* @returns {number}
|
|
578
615
|
*/
|
|
579
616
|
getInterval() {
|
|
617
|
+
checkDestroy(this.#destroyed);
|
|
580
618
|
if (typeof this.#interval !== 'number' || !Number.isFinite(this.#interval)) {
|
|
581
619
|
throw new Error("'interval' is not a valid finite number.");
|
|
582
620
|
}
|
|
@@ -587,6 +625,7 @@ class TinyRateLimiter {
|
|
|
587
625
|
* @returns {number}
|
|
588
626
|
*/
|
|
589
627
|
getMaxHits() {
|
|
628
|
+
checkDestroy(this.#destroyed);
|
|
590
629
|
if (typeof this.#maxHits !== 'number' || !Number.isFinite(this.#maxHits)) {
|
|
591
630
|
throw new Error("'maxHits' is not a valid finite number.");
|
|
592
631
|
}
|
|
@@ -598,6 +637,7 @@ class TinyRateLimiter {
|
|
|
598
637
|
* @returns {number}
|
|
599
638
|
*/
|
|
600
639
|
getTotalHits(groupId) {
|
|
640
|
+
checkDestroy(this.#destroyed);
|
|
601
641
|
const history = this.groupData.get(groupId);
|
|
602
642
|
return Array.isArray(history) ? history.length : 0;
|
|
603
643
|
}
|
|
@@ -607,6 +647,7 @@ class TinyRateLimiter {
|
|
|
607
647
|
* @returns {number|null}
|
|
608
648
|
*/
|
|
609
649
|
getLastHit(groupId) {
|
|
650
|
+
checkDestroy(this.#destroyed);
|
|
610
651
|
const history = this.groupData.get(groupId);
|
|
611
652
|
return history?.length ? history[history.length - 1] : null;
|
|
612
653
|
}
|
|
@@ -616,6 +657,7 @@ class TinyRateLimiter {
|
|
|
616
657
|
* @returns {number|null}
|
|
617
658
|
*/
|
|
618
659
|
getTimeSinceLastHit(groupId) {
|
|
660
|
+
checkDestroy(this.#destroyed);
|
|
619
661
|
const last = this.getLastHit(groupId);
|
|
620
662
|
return last !== null ? Date.now() - last : null;
|
|
621
663
|
}
|
|
@@ -640,6 +682,7 @@ class TinyRateLimiter {
|
|
|
640
682
|
* @returns {number|null}
|
|
641
683
|
*/
|
|
642
684
|
getAverageHitSpacing(groupId) {
|
|
685
|
+
checkDestroy(this.#destroyed);
|
|
643
686
|
return this._calculateAverageSpacing(this.groupData.get(groupId));
|
|
644
687
|
}
|
|
645
688
|
/**
|
|
@@ -653,6 +696,7 @@ class TinyRateLimiter {
|
|
|
653
696
|
* }}
|
|
654
697
|
*/
|
|
655
698
|
getMetrics(groupId) {
|
|
699
|
+
checkDestroy(this.#destroyed);
|
|
656
700
|
const history = this.groupData.get(groupId);
|
|
657
701
|
if (!Array.isArray(history) || history.length === 0) {
|
|
658
702
|
return {
|
|
@@ -677,6 +721,8 @@ class TinyRateLimiter {
|
|
|
677
721
|
* Destroy the rate limiter, stopping cleanup and clearing data
|
|
678
722
|
*/
|
|
679
723
|
destroy() {
|
|
724
|
+
if (this.destroyed)
|
|
725
|
+
return;
|
|
680
726
|
if (this.#cleanupTimer)
|
|
681
727
|
clearInterval(this.#cleanupTimer);
|
|
682
728
|
this._cleanup();
|
|
@@ -685,6 +731,8 @@ class TinyRateLimiter {
|
|
|
685
731
|
this.userToGroup.clear();
|
|
686
732
|
this.groupTTL.clear();
|
|
687
733
|
this.groupFlags.clear();
|
|
734
|
+
// Lock the instance
|
|
735
|
+
this.#destroyed = true;
|
|
688
736
|
}
|
|
689
737
|
}
|
|
690
738
|
export default TinyRateLimiter;
|