tiny-essentials 1.28.0 → 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.
Files changed (168) hide show
  1. package/changelog/1/28/1.md +23 -0
  2. package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
  3. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  4. package/dist/v1/TinyAnalogClock.min.js +1 -1
  5. package/dist/v1/TinyBasicsEs.min.js +1 -1
  6. package/dist/v1/TinyDayNightCycle.min.js +1 -1
  7. package/dist/v1/TinyDragDropDetector.min.js +1 -1
  8. package/dist/v1/TinyDragger.min.js +1 -1
  9. package/dist/v1/TinyElementObserver.min.js +1 -1
  10. package/dist/v1/TinyEssentials.min.js +1 -1
  11. package/dist/v1/TinyGamepad.min.js +1 -1
  12. package/dist/v1/TinyHtml.min.js +1 -1
  13. package/dist/v1/TinyI18.min.js +1 -1
  14. package/dist/v1/TinyIframeEvents.min.js +1 -1
  15. package/dist/v1/TinyLocalStorage.min.js +1 -1
  16. package/dist/v1/TinyMediaPlayer.min.js +1 -1
  17. package/dist/v1/TinyNewWinEvents.min.js +1 -1
  18. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  19. package/dist/v1/TinyRadioFm.min.js +1 -1
  20. package/dist/v1/TinyRateLimiter.min.js +1 -1
  21. package/dist/v1/TinySmartScroller.min.js +1 -1
  22. package/dist/v1/TinyTextDiffer.min.js +1 -1
  23. package/dist/v1/TinyTextarea.min.js +1 -1
  24. package/dist/v1/TinyTimeout.min.js +1 -1
  25. package/dist/v1/TinyToastNotify.min.js +1 -1
  26. package/dist/v1/TinyUploadClicker.min.js +1 -1
  27. package/dist/v1/UltraRandomMsgGen.min.js +1 -1
  28. package/dist/v1/basics/array.cjs +57 -9
  29. package/dist/v1/basics/array.d.mts +27 -11
  30. package/dist/v1/basics/array.mjs +51 -9
  31. package/dist/v1/basics/clock.cjs +94 -42
  32. package/dist/v1/basics/clock.d.mts +10 -10
  33. package/dist/v1/basics/clock.mjs +91 -54
  34. package/dist/v1/basics/deprecated/html.cjs +246 -0
  35. package/dist/v1/basics/deprecated/html.d.mts +63 -0
  36. package/dist/v1/basics/deprecated/html.mjs +190 -0
  37. package/dist/v1/basics/html.cjs +149 -130
  38. package/dist/v1/basics/html.d.mts +26 -61
  39. package/dist/v1/basics/html.mjs +113 -105
  40. package/dist/v1/basics/index.cjs +2 -0
  41. package/dist/v1/basics/index.d.mts +3 -1
  42. package/dist/v1/basics/index.mjs +3 -3
  43. package/dist/v1/basics/mediaContent.cjs +51 -9
  44. package/dist/v1/basics/mediaContent.d.mts +0 -87
  45. package/dist/v1/basics/mediaContent.mjs +40 -10
  46. package/dist/v1/basics/objChecker.cjs +7 -5
  47. package/dist/v1/basics/objChecker.d.mts +3 -2
  48. package/dist/v1/basics/objChecker.mjs +6 -6
  49. package/dist/v1/basics/objFilter.cjs +40 -7
  50. package/dist/v1/basics/objFilter.d.mts +6 -6
  51. package/dist/v1/basics/objFilter.mjs +32 -7
  52. package/dist/v1/basics/simpleMath.cjs +81 -25
  53. package/dist/v1/basics/simpleMath.d.mts +2 -2
  54. package/dist/v1/basics/simpleMath.mjs +70 -27
  55. package/dist/v1/basics/text.cjs +76 -31
  56. package/dist/v1/basics/text.d.mts +69 -7
  57. package/dist/v1/basics/text.mjs +62 -31
  58. package/dist/v1/index.cjs +4 -0
  59. package/dist/v1/index.d.mts +4 -1
  60. package/dist/v1/index.mjs +4 -3
  61. package/dist/v1/libs/TinyAdvancedRaffle.cjs +64 -50
  62. package/dist/v1/libs/TinyAdvancedRaffle.d.mts +4 -13
  63. package/dist/v1/libs/TinyAdvancedRaffle.mjs +62 -49
  64. package/dist/v1/libs/TinyAfterScrollWatcher.cjs +17 -1
  65. package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +4 -1
  66. package/dist/v1/libs/TinyAfterScrollWatcher.mjs +15 -1
  67. package/dist/v1/libs/TinyAnalogClock.cjs +60 -0
  68. package/dist/v1/libs/TinyAnalogClock.d.mts +6 -0
  69. package/dist/v1/libs/TinyAnalogClock.mjs +57 -0
  70. package/dist/v1/libs/TinyClipboard.cjs +2 -0
  71. package/dist/v1/libs/TinyClipboard.d.mts +4 -2
  72. package/dist/v1/libs/TinyClipboard.mjs +2 -0
  73. package/dist/v1/libs/TinyColorConverter.cjs +1 -0
  74. package/dist/v1/libs/TinyColorConverter.d.mts +2 -1
  75. package/dist/v1/libs/TinyColorConverter.mjs +1 -0
  76. package/dist/v1/libs/TinyDayNightCycle.cjs +67 -42
  77. package/dist/v1/libs/TinyDayNightCycle.d.mts +2 -7
  78. package/dist/v1/libs/TinyDayNightCycle.mjs +65 -41
  79. package/dist/v1/libs/TinyDragDropDetector.cjs +25 -0
  80. package/dist/v1/libs/TinyDragDropDetector.d.mts +6 -0
  81. package/dist/v1/libs/TinyDragDropDetector.mjs +22 -0
  82. package/dist/v1/libs/TinyDragger.cjs +46 -19
  83. package/dist/v1/libs/TinyDragger.mjs +45 -18
  84. package/dist/v1/libs/TinyElementObserver.cjs +39 -0
  85. package/dist/v1/libs/TinyElementObserver.d.mts +6 -0
  86. package/dist/v1/libs/TinyElementObserver.mjs +36 -0
  87. package/dist/v1/libs/TinyGamepad.cjs +131 -1
  88. package/dist/v1/libs/TinyGamepad.mjs +129 -1
  89. package/dist/v1/libs/TinyHtml.cjs +1 -1
  90. package/dist/v1/libs/TinyHtml.mjs +2 -2
  91. package/dist/v1/libs/TinyI18.cjs +61 -0
  92. package/dist/v1/libs/TinyI18.d.mts +14 -0
  93. package/dist/v1/libs/TinyI18.mjs +55 -0
  94. package/dist/v1/libs/TinyIframeEvents.cjs +18 -1
  95. package/dist/v1/libs/TinyIframeEvents.mjs +18 -1
  96. package/dist/v1/libs/TinyLocalStorage.cjs +48 -0
  97. package/dist/v1/libs/TinyLocalStorage.d.mts +6 -0
  98. package/dist/v1/libs/TinyLocalStorage.mjs +45 -0
  99. package/dist/v1/libs/TinyMediaPlayer.cjs +72 -0
  100. package/dist/v1/libs/TinyMediaPlayer.d.mts +10 -0
  101. package/dist/v1/libs/TinyMediaPlayer.mjs +66 -0
  102. package/dist/v1/libs/TinyNewWinEvents.cjs +29 -0
  103. package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -0
  104. package/dist/v1/libs/TinyNewWinEvents.mjs +26 -0
  105. package/dist/v1/libs/TinyNotifyCenter.cjs +36 -0
  106. package/dist/v1/libs/TinyNotifyCenter.d.mts +6 -0
  107. package/dist/v1/libs/TinyNotifyCenter.mjs +32 -0
  108. package/dist/v1/libs/TinyRadioFm.cjs +38 -0
  109. package/dist/v1/libs/TinyRadioFm.d.mts +1 -0
  110. package/dist/v1/libs/TinyRadioFm.mjs +35 -0
  111. package/dist/v1/libs/TinyRateLimiter.cjs +52 -0
  112. package/dist/v1/libs/TinyRateLimiter.d.mts +6 -0
  113. package/dist/v1/libs/TinyRateLimiter.mjs +48 -0
  114. package/dist/v1/libs/TinySmartScroller.cjs +52 -3
  115. package/dist/v1/libs/TinySmartScroller.d.mts +17 -15
  116. package/dist/v1/libs/TinySmartScroller.mjs +51 -5
  117. package/dist/v1/libs/TinyTextDiffer.cjs +19 -18
  118. package/dist/v1/libs/TinyTextDiffer.d.mts +1 -0
  119. package/dist/v1/libs/TinyTextDiffer.mjs +16 -17
  120. package/dist/v1/libs/TinyTextarea.cjs +19 -0
  121. package/dist/v1/libs/TinyTextarea.d.mts +6 -0
  122. package/dist/v1/libs/TinyTextarea.mjs +17 -0
  123. package/dist/v1/libs/TinyTimeout.cjs +11 -1
  124. package/dist/v1/libs/TinyTimeout.mjs +9 -1
  125. package/dist/v1/libs/TinyToastNotify.cjs +35 -0
  126. package/dist/v1/libs/TinyToastNotify.d.mts +6 -0
  127. package/dist/v1/libs/TinyToastNotify.mjs +31 -0
  128. package/dist/v1/libs/TinyUploadClicker.cjs +19 -0
  129. package/dist/v1/libs/TinyUploadClicker.d.mts +6 -0
  130. package/dist/v1/libs/TinyUploadClicker.mjs +17 -0
  131. package/dist/v1/libs/UltraRandomMsgGen.cjs +148 -35
  132. package/dist/v1/libs/UltraRandomMsgGen.d.mts +17 -3
  133. package/dist/v1/libs/UltraRandomMsgGen.mjs +133 -34
  134. package/dist/v1/libs/utils.cjs +21 -0
  135. package/dist/v1/libs/utils.d.mts +2 -0
  136. package/dist/v1/libs/utils.mjs +16 -0
  137. package/docs/v1/README.md +1 -0
  138. package/docs/v1/basics/array.md +2 -2
  139. package/docs/v1/basics/deprecated/html.md +17 -0
  140. package/docs/v1/basics/fullScreen.md +1 -1
  141. package/docs/v1/basics/html.md +12 -26
  142. package/docs/v1/basics/mediaContent.md +1 -1
  143. package/docs/v1/basics/objChecker.md +1 -1
  144. package/docs/v1/basics/text.md +2 -2
  145. package/docs/v1/fileManager/main.md +1 -1
  146. package/docs/v1/libs/ColorSafeStringify.md +1 -1
  147. package/docs/v1/libs/TinyAfterScrollWatcher.md +1 -1
  148. package/docs/v1/libs/TinyAnalogClock.md +1 -1
  149. package/docs/v1/libs/TinyArrayComparator.md +1 -1
  150. package/docs/v1/libs/TinyArrayPaginator.md +1 -1
  151. package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +2 -2
  152. package/docs/v1/libs/TinyClassManager.md +1 -1
  153. package/docs/v1/libs/TinyClipboard.md +1 -1
  154. package/docs/v1/libs/TinyCookieConsent.md +1 -1
  155. package/docs/v1/libs/TinyDomReadyManager.md +1 -1
  156. package/docs/v1/libs/TinyDragDropDetector.md +1 -1
  157. package/docs/v1/libs/TinyDragger.md +1 -1
  158. package/docs/v1/libs/TinyElementObserver.md +1 -1
  159. package/docs/v1/libs/TinyHtml.md +1 -1
  160. package/docs/v1/libs/TinyIframeEvents.md +2 -2
  161. package/docs/v1/libs/TinyLevelUp.md +1 -1
  162. package/docs/v1/libs/TinyLoadingScreen.md +1 -1
  163. package/docs/v1/libs/TinyNewWinEvents.md +2 -2
  164. package/docs/v1/libs/TinyNotifications.md +1 -1
  165. package/docs/v1/libs/TinyPromiseQueue.md +2 -2
  166. package/docs/v1/libs/TinyTextDiffer.md +1 -1
  167. package/docs/v1/libs/TinyTextarea.md +1 -1
  168. package/package.json +10 -1
@@ -1,5 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { blobUrlToBase64, convertToBlobUrl, parseMediaMetadata, revokeContentUrls, } from '../basics/mediaContent.mjs';
3
+ import { createCheckDestroyed } from './utils.mjs';
4
+ const checkDestroy = createCheckDestroyed('TinyRadioFm');
3
5
  /**
4
6
  * @typedef {import('../basics/mediaContent.mjs').MediaContentBase} MediaContentBase
5
7
  * @typedef {import('../basics/mediaContent.mjs').MediaContentMetadata} MediaContentMetadata
@@ -147,11 +149,17 @@ class TinyRadioFm extends EventEmitter {
147
149
  throw new TypeError('unknownArtist must have an string or function.');
148
150
  TinyRadioFm.#unknownArtist = value;
149
151
  }
152
+ /** @type {boolean} */
153
+ #destroyed = false;
154
+ get destroyed() {
155
+ return this.#destroyed;
156
+ }
150
157
  /**
151
158
  * Gets the total count of all content items (music and voice) in the system.
152
159
  * @returns {number}
153
160
  */
154
161
  get size() {
162
+ checkDestroy(this.#destroyed);
155
163
  return this.#musicList.length + this.#voiceList.length;
156
164
  }
157
165
  /**
@@ -159,6 +167,7 @@ class TinyRadioFm extends EventEmitter {
159
167
  * @returns {number}
160
168
  */
161
169
  get musicSize() {
170
+ checkDestroy(this.#destroyed);
162
171
  return this.#musicList.length;
163
172
  }
164
173
  /**
@@ -166,6 +175,7 @@ class TinyRadioFm extends EventEmitter {
166
175
  * @returns {number}
167
176
  */
168
177
  get voiceSize() {
178
+ checkDestroy(this.#destroyed);
169
179
  return this.#voiceList.length;
170
180
  }
171
181
  /**
@@ -173,6 +183,7 @@ class TinyRadioFm extends EventEmitter {
173
183
  * @returns {number}
174
184
  */
175
185
  get customPosSize() {
186
+ checkDestroy(this.#destroyed);
176
187
  return this.#customPositions.length;
177
188
  }
178
189
  /**
@@ -180,6 +191,7 @@ class TinyRadioFm extends EventEmitter {
180
191
  * @returns {number}
181
192
  */
182
193
  get tasksSize() {
194
+ checkDestroy(this.#destroyed);
183
195
  return this.#scheduledTasks.length;
184
196
  }
185
197
  /**
@@ -187,6 +199,7 @@ class TinyRadioFm extends EventEmitter {
187
199
  * @returns {number}
188
200
  */
189
201
  get cycleCacheSize() {
202
+ checkDestroy(this.#destroyed);
190
203
  return this.#cycleCache.size;
191
204
  }
192
205
  /** @type {MediaContent[]} */
@@ -196,6 +209,7 @@ class TinyRadioFm extends EventEmitter {
196
209
  * @returns {MediaContent[]}
197
210
  */
198
211
  get musicList() {
212
+ checkDestroy(this.#destroyed);
199
213
  return structuredClone(this.#musicList);
200
214
  }
201
215
  /** @type {MediaContent[]} */
@@ -205,6 +219,7 @@ class TinyRadioFm extends EventEmitter {
205
219
  * @returns {MediaContent[]}
206
220
  */
207
221
  get voiceList() {
222
+ checkDestroy(this.#destroyed);
208
223
  return structuredClone(this.#voiceList);
209
224
  }
210
225
  /** @type {CustomPosition[]} */
@@ -214,6 +229,7 @@ class TinyRadioFm extends EventEmitter {
214
229
  * @returns {CustomPosition[]}
215
230
  */
216
231
  get customPositions() {
232
+ checkDestroy(this.#destroyed);
217
233
  return structuredClone(this.#customPositions);
218
234
  }
219
235
  /** @type {ScheduledTask[]} */
@@ -223,6 +239,7 @@ class TinyRadioFm extends EventEmitter {
223
239
  * @returns {ScheduledTask[]}
224
240
  */
225
241
  get scheduledTasks() {
242
+ checkDestroy(this.#destroyed);
226
243
  return structuredClone(this.#scheduledTasks);
227
244
  }
228
245
  /**
@@ -230,6 +247,7 @@ class TinyRadioFm extends EventEmitter {
230
247
  * @returns {MediaContent[]} A cloned object of the cache.
231
248
  */
232
249
  get allList() {
250
+ checkDestroy(this.#destroyed);
233
251
  return [...this.musicList, ...this.voiceList];
234
252
  }
235
253
  /** @type {number} */
@@ -239,6 +257,7 @@ class TinyRadioFm extends EventEmitter {
239
257
  * @returns {number}
240
258
  */
241
259
  get seed() {
260
+ checkDestroy(this.#destroyed);
242
261
  return this.#seed;
243
262
  }
244
263
  /**
@@ -246,6 +265,7 @@ class TinyRadioFm extends EventEmitter {
246
265
  * @param {number} seed - The new seed.
247
266
  */
248
267
  set seed(seed) {
268
+ checkDestroy(this.#destroyed);
249
269
  if (typeof seed !== 'number')
250
270
  throw new TypeError('Seed must be a number.');
251
271
  this.#seed = seed;
@@ -259,6 +279,7 @@ class TinyRadioFm extends EventEmitter {
259
279
  * @returns {number}
260
280
  */
261
281
  get anchorDate() {
282
+ checkDestroy(this.#destroyed);
262
283
  return this.#anchorDate;
263
284
  }
264
285
  /** @type {Map<number, CycleBlock>} */
@@ -280,6 +301,7 @@ class TinyRadioFm extends EventEmitter {
280
301
  * @returns {RadioConfig}
281
302
  */
282
303
  get config() {
304
+ checkDestroy(this.#destroyed);
283
305
  return structuredClone(this.#config);
284
306
  }
285
307
  /**
@@ -288,6 +310,7 @@ class TinyRadioFm extends EventEmitter {
288
310
  * @throws {TypeError|RangeError} If the new configuration is invalid.
289
311
  */
290
312
  set config(config) {
313
+ checkDestroy(this.#destroyed);
291
314
  // Validate the entire object before applying it
292
315
  this.#validateConfig(config);
293
316
  this.#config = structuredClone(config);
@@ -377,6 +400,7 @@ class TinyRadioFm extends EventEmitter {
377
400
  * @throws {TypeError} If the type is invalid or the data lacks a valid ID and numerical duration.
378
401
  */
379
402
  add(type, data, smartQueue = true) {
403
+ checkDestroy(this.#destroyed);
380
404
  if (!['music', 'voice', 'custom'].includes(type)) {
381
405
  throw new TypeError('Type must be "music", "voice", or "custom".');
382
406
  }
@@ -410,6 +434,7 @@ class TinyRadioFm extends EventEmitter {
410
434
  * @throws {TypeError} If arguments do not match the required types or action/type constraints.
411
435
  */
412
436
  scheduleTask(timestamp, action, type, payload, smartQueue = true) {
437
+ checkDestroy(this.#destroyed);
413
438
  if (typeof timestamp !== 'number' || isNaN(timestamp))
414
439
  throw new TypeError('timestamp must be a valid number.');
415
440
  if (!['add', 'remove', 'move'].includes(action))
@@ -467,6 +492,7 @@ class TinyRadioFm extends EventEmitter {
467
492
  * @throws {TypeError} If the id is not a string.
468
493
  */
469
494
  remove(id) {
495
+ checkDestroy(this.#destroyed);
470
496
  if (typeof id !== 'string')
471
497
  throw new TypeError('id must be a string.');
472
498
  // Revoke Blob URLs of items being removed to free memory
@@ -505,6 +531,7 @@ class TinyRadioFm extends EventEmitter {
505
531
  * @throws {TypeError|RangeError} If the provided values or the resulting state is invalid.
506
532
  */
507
533
  setConfig(config) {
534
+ checkDestroy(this.#destroyed);
508
535
  // First, validate the incoming partial object for basic type correctness
509
536
  this.#validateConfig(config);
510
537
  // Create the potential new state
@@ -520,6 +547,7 @@ class TinyRadioFm extends EventEmitter {
520
547
  * @returns {RadioEvent|null} The current active event, or null if empty.
521
548
  */
522
549
  getCurrentEvent() {
550
+ checkDestroy(this.#destroyed);
523
551
  const now = Date.now();
524
552
  this.#syncRealTimeState(now);
525
553
  return this.#getEventAtTime(now, now);
@@ -534,6 +562,7 @@ class TinyRadioFm extends EventEmitter {
534
562
  * @throws {RangeError} If the limit exceeds the configured queryLimit or is <= 0.
535
563
  */
536
564
  queryTimeline(targetDate, limit = 10) {
565
+ checkDestroy(this.#destroyed);
537
566
  if (typeof limit !== 'number' || isNaN(limit)) {
538
567
  throw new TypeError(`Invalid query limit value. Ensure it is a number.`);
539
568
  }
@@ -565,6 +594,7 @@ class TinyRadioFm extends EventEmitter {
565
594
  * @returns {CustomPosition[]} Shallow copy of custom positions array.
566
595
  */
567
596
  searchCustomPositions() {
597
+ checkDestroy(this.#destroyed);
568
598
  this.#syncRealTimeState(Date.now());
569
599
  return [...this.#customPositions];
570
600
  }
@@ -607,6 +637,7 @@ class TinyRadioFm extends EventEmitter {
607
637
  * @returns {Promise<string>} Stringified JSON state.
608
638
  */
609
639
  async exportState() {
640
+ checkDestroy(this.#destroyed);
610
641
  const processedMusic = await this._processListForExport(this.#musicList);
611
642
  const processedVoice = await this._processListForExport(this.#voiceList);
612
643
  const processedCustom = await Promise.all(this.#customPositions.map(async (cp) => {
@@ -641,6 +672,7 @@ class TinyRadioFm extends EventEmitter {
641
672
  * @throws {TypeError} If json is not a valid string or object.
642
673
  */
643
674
  importState(json) {
675
+ checkDestroy(this.#destroyed);
644
676
  /** @type {TinyRadioFmImport} */
645
677
  let data;
646
678
  if (typeof json === 'string') {
@@ -1111,6 +1143,8 @@ class TinyRadioFm extends EventEmitter {
1111
1143
  * @param {boolean} [destroyThumbs=true]
1112
1144
  */
1113
1145
  destroy(destroyThumbs = true) {
1146
+ if (this.#destroyed)
1147
+ return;
1114
1148
  if (destroyThumbs) {
1115
1149
  this.#musicList.forEach((item) => revokeContentUrls(item));
1116
1150
  this.#voiceList.forEach((item) => revokeContentUrls(item));
@@ -1131,6 +1165,7 @@ class TinyRadioFm extends EventEmitter {
1131
1165
  this.#cycleCache.clear();
1132
1166
  this.emit('destroyed', { timestamp: Date.now() });
1133
1167
  this.removeAllListeners();
1168
+ this.#destroyed = true;
1134
1169
  }
1135
1170
  }
1136
1171
  export default TinyRadioFm;
@@ -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
  *