tiny-essentials 1.8.5 → 1.9.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/dist/TinyEssentials.js +351 -28
- package/dist/TinyEssentials.min.js +1 -1
- package/dist/TinyLevelUp.js +125 -15
- package/dist/TinyLevelUp.min.js +1 -1
- package/dist/TinyPromiseQueue.js +27 -13
- package/dist/TinyPromiseQueue.min.js +1 -1
- package/dist/TinyRateLimiter.js +236 -0
- package/dist/TinyRateLimiter.min.js +1 -0
- package/dist/legacy/libs/userLevel.cjs +125 -15
- package/dist/legacy/libs/userLevel.d.mts +86 -59
- package/dist/legacy/libs/userLevel.mjs +123 -15
- package/dist/v1/build/TinyRateLimiter.cjs +7 -0
- package/dist/v1/build/TinyRateLimiter.d.mts +3 -0
- package/dist/v1/build/TinyRateLimiter.mjs +2 -0
- package/dist/v1/index.cjs +2 -0
- package/dist/v1/index.d.mts +2 -1
- package/dist/v1/index.mjs +2 -1
- package/dist/v1/libs/TinyPromiseQueue.cjs +27 -13
- package/dist/v1/libs/TinyPromiseQueue.d.mts +38 -0
- package/dist/v1/libs/TinyPromiseQueue.mjs +26 -13
- package/dist/v1/libs/TinyRateLimit.cjs +196 -0
- package/dist/v1/libs/TinyRateLimit.d.mts +86 -0
- package/dist/v1/libs/TinyRateLimit.mjs +171 -0
- package/dist/v1/libs/TinyRateLimiter.cjs +197 -0
- package/dist/v1/libs/TinyRateLimiter.d.mts +86 -0
- package/dist/v1/libs/TinyRateLimiter.mjs +173 -0
- package/docs/README.md +1 -0
- package/docs/libs/TinyLevelUp.md +208 -68
- package/docs/libs/TinyRateLimiter.md +156 -0
- package/package.json +3 -1
package/dist/TinyEssentials.js
CHANGED
|
@@ -2145,6 +2145,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2145
2145
|
ColorSafeStringify: () => (/* reexport */ libs_ColorSafeStringify),
|
|
2146
2146
|
TinyLevelUp: () => (/* reexport */ userLevel),
|
|
2147
2147
|
TinyPromiseQueue: () => (/* reexport */ libs_TinyPromiseQueue),
|
|
2148
|
+
TinyRateLimiter: () => (/* reexport */ libs_TinyRateLimiter),
|
|
2148
2149
|
addAiMarkerShortcut: () => (/* reexport */ addAiMarkerShortcut),
|
|
2149
2150
|
arraySortPositions: () => (/* reexport */ arraySortPositions),
|
|
2150
2151
|
asyncReplace: () => (/* reexport */ asyncReplace),
|
|
@@ -2201,6 +2202,15 @@ async function asyncReplace(str, regex, asyncFn) {
|
|
|
2201
2202
|
}
|
|
2202
2203
|
|
|
2203
2204
|
;// ./src/legacy/libs/userLevel.mjs
|
|
2205
|
+
/**
|
|
2206
|
+
* Represents a user object used.
|
|
2207
|
+
*
|
|
2208
|
+
* @typedef {Object} UserEditor
|
|
2209
|
+
* @property {number} exp - Current experience points of the user.
|
|
2210
|
+
* @property {number} level - Current level of the user.
|
|
2211
|
+
* @property {number} totalExp - Total accumulated experience.
|
|
2212
|
+
*/
|
|
2213
|
+
|
|
2204
2214
|
/**
|
|
2205
2215
|
* Class to manage user level-up logic based on experience points.
|
|
2206
2216
|
*/
|
|
@@ -2211,26 +2221,95 @@ class TinyLevelUp {
|
|
|
2211
2221
|
* @param {number} expLevel - Base experience needed to level up (per level).
|
|
2212
2222
|
*/
|
|
2213
2223
|
constructor(giveExp, expLevel) {
|
|
2224
|
+
if (typeof giveExp !== 'number' || Number.isNaN(giveExp))
|
|
2225
|
+
throw new Error('giveExp must be a valid number');
|
|
2226
|
+
if (typeof expLevel !== 'number' || Number.isNaN(expLevel))
|
|
2227
|
+
throw new Error('expLevel must be a valid number');
|
|
2214
2228
|
this.giveExp = giveExp;
|
|
2215
2229
|
this.expLevel = expLevel;
|
|
2216
2230
|
}
|
|
2217
2231
|
|
|
2218
2232
|
/**
|
|
2219
|
-
*
|
|
2233
|
+
* Creates a new user object starting at level 0 with 0 experience.
|
|
2234
|
+
* @returns {UserEditor} A fresh user object.
|
|
2235
|
+
*/
|
|
2236
|
+
createUser() {
|
|
2237
|
+
return {
|
|
2238
|
+
exp: 0,
|
|
2239
|
+
level: 1,
|
|
2240
|
+
totalExp: 0,
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* Validates if the given user object has valid numeric properties.
|
|
2246
|
+
* Throws an error if any property is invalid.
|
|
2247
|
+
*
|
|
2248
|
+
* @param {UserEditor} user - The user object to validate.
|
|
2249
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2250
|
+
*/
|
|
2251
|
+
validateUser(user) {
|
|
2252
|
+
if (typeof user.exp !== 'number' || Number.isNaN(user.exp))
|
|
2253
|
+
throw new Error('exp must be a valid number');
|
|
2254
|
+
if (typeof user.level !== 'number' || Number.isNaN(user.level))
|
|
2255
|
+
throw new Error('level must be a valid number');
|
|
2256
|
+
if (user.level < 1) throw new Error('level must be at least 1');
|
|
2257
|
+
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp))
|
|
2258
|
+
throw new Error('totalExp must be a valid number');
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/**
|
|
2262
|
+
* Checks if the given user object is valid by verifying its numeric properties.
|
|
2263
|
+
*
|
|
2264
|
+
* @param {UserEditor} user - The user object to check.
|
|
2265
|
+
* @returns {boolean} `true` if all properties (exp, level, totalExp) are valid numbers; otherwise `false`.
|
|
2220
2266
|
*/
|
|
2267
|
+
isValidUser(user) {
|
|
2268
|
+
if (typeof user.exp !== 'number' || Number.isNaN(user.exp)) return false;
|
|
2269
|
+
if (typeof user.level !== 'number' || Number.isNaN(user.level)) return false;
|
|
2270
|
+
if (user.level < 1) return false;
|
|
2271
|
+
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp)) return false;
|
|
2272
|
+
return true;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
/**
|
|
2276
|
+
* Returns the base experience value used for random experience generation.
|
|
2277
|
+
* Throws an error if the internal giveExp value is not a valid number.
|
|
2278
|
+
*
|
|
2279
|
+
* @returns {number} The base experience value.
|
|
2280
|
+
* @throws {Error} If giveExp is not a valid number.
|
|
2281
|
+
*/
|
|
2282
|
+
getGiveExpBase() {
|
|
2283
|
+
if (typeof this.giveExp !== 'number' || Number.isNaN(this.giveExp))
|
|
2284
|
+
throw new Error('giveExp must be a valid number');
|
|
2285
|
+
return this.giveExp;
|
|
2286
|
+
}
|
|
2221
2287
|
|
|
2222
2288
|
/**
|
|
2223
|
-
*
|
|
2289
|
+
* Returns the base experience required to level up.
|
|
2290
|
+
* Throws an error if the internal expLevel value is not a valid number.
|
|
2291
|
+
*
|
|
2292
|
+
* @returns {number} The base experience needed per level.
|
|
2293
|
+
* @throws {Error} If expLevel is not a valid number.
|
|
2224
2294
|
*/
|
|
2295
|
+
getExpLevelBase() {
|
|
2296
|
+
if (typeof this.expLevel !== 'number' || Number.isNaN(this.expLevel))
|
|
2297
|
+
throw new Error('expLevel must be a valid number');
|
|
2298
|
+
return this.expLevel;
|
|
2299
|
+
}
|
|
2225
2300
|
|
|
2226
2301
|
/**
|
|
2227
2302
|
* Validates and adjusts the user's level based on their current experience.
|
|
2228
2303
|
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
2229
|
-
* @returns {
|
|
2304
|
+
* @returns {UserEditor} The updated user object.
|
|
2305
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2230
2306
|
*/
|
|
2231
2307
|
expValidator(user) {
|
|
2308
|
+
const expLevel = this.getExpLevelBase();
|
|
2309
|
+
this.validateUser(user);
|
|
2310
|
+
|
|
2232
2311
|
let extraValue = 0;
|
|
2233
|
-
const nextLevelExp =
|
|
2312
|
+
const nextLevelExp = expLevel * user.level;
|
|
2234
2313
|
|
|
2235
2314
|
// Level Up
|
|
2236
2315
|
if (user.exp >= nextLevelExp) {
|
|
@@ -2245,7 +2324,7 @@ class TinyLevelUp {
|
|
|
2245
2324
|
if (user.exp < 1 && user.level > 1) {
|
|
2246
2325
|
user.level--;
|
|
2247
2326
|
extraValue = Math.abs(user.exp);
|
|
2248
|
-
user.exp =
|
|
2327
|
+
user.exp = expLevel * user.level;
|
|
2249
2328
|
|
|
2250
2329
|
if (extraValue > 0) return this.remove(user, extraValue, 'extra');
|
|
2251
2330
|
}
|
|
@@ -2255,12 +2334,14 @@ class TinyLevelUp {
|
|
|
2255
2334
|
|
|
2256
2335
|
/**
|
|
2257
2336
|
* Calculates the total experience based on the user's level.
|
|
2258
|
-
* @param {
|
|
2337
|
+
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
2259
2338
|
* @returns {number} The total experience of the user.
|
|
2339
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2260
2340
|
*/
|
|
2261
2341
|
getTotalExp(user) {
|
|
2342
|
+
this.validateUser(user);
|
|
2262
2343
|
let totalExp = 0;
|
|
2263
|
-
for (let p = 1; p <= user.level; p++) totalExp += this.
|
|
2344
|
+
for (let p = 1; p <= user.level; p++) totalExp += this.getExpLevelBase() * p;
|
|
2264
2345
|
totalExp += user.exp;
|
|
2265
2346
|
return totalExp;
|
|
2266
2347
|
}
|
|
@@ -2271,34 +2352,52 @@ class TinyLevelUp {
|
|
|
2271
2352
|
* @returns {number} The generated experience points.
|
|
2272
2353
|
*/
|
|
2273
2354
|
expGenerator(multi = 1) {
|
|
2274
|
-
|
|
2355
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2356
|
+
throw new Error('multi must be a valid number');
|
|
2357
|
+
return Math.floor(Math.random() * this.getGiveExpBase()) * multi;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
/**
|
|
2361
|
+
* Calculates how much experience is missing to next level.
|
|
2362
|
+
* @param {UserEditor} user
|
|
2363
|
+
* @returns {number}
|
|
2364
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2365
|
+
*/
|
|
2366
|
+
getMissingExp(user) {
|
|
2367
|
+
return this.getProgress(user) - user.exp;
|
|
2275
2368
|
}
|
|
2276
2369
|
|
|
2277
2370
|
/**
|
|
2278
2371
|
* Gets the experience points required to reach the next level.
|
|
2279
|
-
* @param {
|
|
2372
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
2280
2373
|
* @returns {number} The experience required for the next level.
|
|
2374
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2281
2375
|
*/
|
|
2282
2376
|
progress(user) {
|
|
2283
|
-
return this.
|
|
2377
|
+
return this.getProgress(user);
|
|
2284
2378
|
}
|
|
2285
2379
|
|
|
2286
2380
|
/**
|
|
2287
2381
|
* Gets the experience points required to reach the next level.
|
|
2288
|
-
* @param {
|
|
2382
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
2289
2383
|
* @returns {number} The experience required for the next level.
|
|
2384
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2290
2385
|
*/
|
|
2291
2386
|
getProgress(user) {
|
|
2292
|
-
|
|
2387
|
+
this.validateUser(user);
|
|
2388
|
+
return this.getExpLevelBase() * user.level;
|
|
2293
2389
|
}
|
|
2294
2390
|
|
|
2295
2391
|
/**
|
|
2296
2392
|
* Sets the experience value for the user, adjusting their level if necessary.
|
|
2297
2393
|
* @param {UserEditor} user - The user object.
|
|
2298
2394
|
* @param {number} value - The new experience value to set.
|
|
2299
|
-
* @returns {
|
|
2395
|
+
* @returns {UserEditor} The updated user object.
|
|
2300
2396
|
*/
|
|
2301
2397
|
set(user, value) {
|
|
2398
|
+
if (typeof value !== 'number' || Number.isNaN(value))
|
|
2399
|
+
throw new Error('value must be a valid number');
|
|
2400
|
+
|
|
2302
2401
|
user.exp = value;
|
|
2303
2402
|
this.expValidator(user);
|
|
2304
2403
|
user.totalExp = this.getTotalExp(user);
|
|
@@ -2311,9 +2410,15 @@ class TinyLevelUp {
|
|
|
2311
2410
|
* @param {number} [extraExp] - Additional experience to be added.
|
|
2312
2411
|
* @param {'add' | 'extra'} [type] - Type of addition ('add' or 'extra').
|
|
2313
2412
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
2314
|
-
* @returns {
|
|
2413
|
+
* @returns {UserEditor} The updated user object.
|
|
2315
2414
|
*/
|
|
2316
2415
|
give(user, extraExp = 0, type = 'add', multi = 1) {
|
|
2416
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2417
|
+
throw new Error('multi must be a valid number');
|
|
2418
|
+
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
2419
|
+
throw new Error('extraExp must be a valid number');
|
|
2420
|
+
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
2421
|
+
|
|
2317
2422
|
if (type === 'add') user.exp += this.expGenerator(multi) + extraExp;
|
|
2318
2423
|
else if (type === 'extra') user.exp += extraExp;
|
|
2319
2424
|
|
|
@@ -2328,9 +2433,15 @@ class TinyLevelUp {
|
|
|
2328
2433
|
* @param {number} [extraExp] - Additional experience to remove.
|
|
2329
2434
|
* @param {'add' | 'extra'} [type] - Type of removal ('add' or 'extra').
|
|
2330
2435
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
2331
|
-
* @returns {
|
|
2436
|
+
* @returns {UserEditor} The updated user object.
|
|
2332
2437
|
*/
|
|
2333
2438
|
remove(user, extraExp = 0, type = 'add', multi = 1) {
|
|
2439
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2440
|
+
throw new Error('multi must be a valid number');
|
|
2441
|
+
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
2442
|
+
throw new Error('extraExp must be a valid number');
|
|
2443
|
+
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
2444
|
+
|
|
2334
2445
|
if (type === 'add') user.exp -= this.expGenerator(multi) + extraExp;
|
|
2335
2446
|
else if (type === 'extra') user.exp -= extraExp;
|
|
2336
2447
|
|
|
@@ -3310,6 +3421,16 @@ class ColorSafeStringify {
|
|
|
3310
3421
|
/* harmony default export */ const libs_ColorSafeStringify = (ColorSafeStringify);
|
|
3311
3422
|
|
|
3312
3423
|
;// ./src/v1/libs/TinyPromiseQueue.mjs
|
|
3424
|
+
/**
|
|
3425
|
+
* @typedef {Object} QueuedTask
|
|
3426
|
+
* @property {(...args: any[]) => Promise<any>|Promise<any>} task - The async task to execute.
|
|
3427
|
+
* @property {(value: any) => any} resolve - The resolve function from the Promise.
|
|
3428
|
+
* @property {(reason?: any) => any} reject - The reject function from the Promise.
|
|
3429
|
+
* @property {string|undefined} [id] - Optional identifier for the task.
|
|
3430
|
+
* @property {string|null|undefined} [marker] - Optional marker for the task.
|
|
3431
|
+
* @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
|
|
3432
|
+
*/
|
|
3433
|
+
|
|
3313
3434
|
/**
|
|
3314
3435
|
* A queue system for managing and executing asynchronous tasks sequentially, one at a time.
|
|
3315
3436
|
*
|
|
@@ -3319,20 +3440,10 @@ class ColorSafeStringify {
|
|
|
3319
3440
|
* @class
|
|
3320
3441
|
*/
|
|
3321
3442
|
class TinyPromiseQueue {
|
|
3322
|
-
/**
|
|
3323
|
-
* @typedef {Object} QueuedTask
|
|
3324
|
-
* @property {(...args: any[]) => Promise<any>|Promise<any>} task - The async task to execute.
|
|
3325
|
-
* @property {(value: any) => any} resolve - The resolve function from the Promise.
|
|
3326
|
-
* @property {(reason?: any) => any} reject - The reject function from the Promise.
|
|
3327
|
-
* @property {string|undefined} [id] - Optional identifier for the task.
|
|
3328
|
-
* @property {string|null|undefined} [marker] - Optional marker for the task.
|
|
3329
|
-
* @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
|
|
3330
|
-
*/
|
|
3331
|
-
|
|
3332
|
-
/** @type {Array<QueuedTask>} */
|
|
3443
|
+
/** @type {QueuedTask[]} */
|
|
3333
3444
|
#queue = [];
|
|
3334
3445
|
#running = false;
|
|
3335
|
-
/** @type {Record<string,
|
|
3446
|
+
/** @type {Record<string, ReturnType<typeof setTimeout>>} */
|
|
3336
3447
|
#timeouts = {};
|
|
3337
3448
|
/** @type {Set<string>} */
|
|
3338
3449
|
#blacklist = new Set();
|
|
@@ -3495,8 +3606,13 @@ class TinyPromiseQueue {
|
|
|
3495
3606
|
* @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
|
|
3496
3607
|
* @param {string} [id] Optional ID to identify the task in the queue.
|
|
3497
3608
|
* @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
|
|
3609
|
+
* @throws {Error} Throws if param is invalid.
|
|
3498
3610
|
*/
|
|
3499
3611
|
async enqueuePoint(task, id) {
|
|
3612
|
+
if (typeof task !== 'function')
|
|
3613
|
+
return Promise.reject(new Error('Task must be a function returning a Promise.'));
|
|
3614
|
+
if (typeof id !== 'undefined' && typeof id !== 'string')
|
|
3615
|
+
throw new Error('The "id" parameter must be a string.');
|
|
3500
3616
|
if (!this.#running) return task();
|
|
3501
3617
|
return new Promise((resolve, reject) => {
|
|
3502
3618
|
this.#queue.push({ marker: 'POINT_MARKER', task, resolve, reject, id });
|
|
@@ -3515,8 +3631,16 @@ class TinyPromiseQueue {
|
|
|
3515
3631
|
* @param {number|null} [delay] Optional delay (in ms) before the task is executed.
|
|
3516
3632
|
* @param {string} [id] Optional ID to identify the task in the queue.
|
|
3517
3633
|
* @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
|
|
3634
|
+
* @throws {Error} Throws if param is invalid.
|
|
3518
3635
|
*/
|
|
3519
3636
|
enqueue(task, delay, id) {
|
|
3637
|
+
if (typeof task !== 'function')
|
|
3638
|
+
return Promise.reject(new Error('Task must be a function returning a Promise.'));
|
|
3639
|
+
if (typeof delay !== 'undefined' && (typeof delay !== 'number' || delay < 0))
|
|
3640
|
+
return Promise.reject(new Error('Delay must be a positive number or undefined.'));
|
|
3641
|
+
if (typeof id !== 'undefined' && typeof id !== 'string')
|
|
3642
|
+
throw new Error('The "id" parameter must be a string.');
|
|
3643
|
+
|
|
3520
3644
|
return new Promise((resolve, reject) => {
|
|
3521
3645
|
this.#queue.push({ task, resolve, reject, id, delay });
|
|
3522
3646
|
this.#processQueue();
|
|
@@ -3529,9 +3653,10 @@ class TinyPromiseQueue {
|
|
|
3529
3653
|
*
|
|
3530
3654
|
* @param {string} id The ID of the task to cancel.
|
|
3531
3655
|
* @returns {boolean} True if a delay was cancelled and the task was removed.
|
|
3656
|
+
* @throws {Error} Throws if `id` is not a string.
|
|
3532
3657
|
*/
|
|
3533
3658
|
cancelTask(id) {
|
|
3534
|
-
if (
|
|
3659
|
+
if (typeof id !== 'string') throw new Error('The "id" parameter must be a string.');
|
|
3535
3660
|
let cancelled = false;
|
|
3536
3661
|
|
|
3537
3662
|
if (id in this.#timeouts) {
|
|
@@ -3555,6 +3680,203 @@ class TinyPromiseQueue {
|
|
|
3555
3680
|
|
|
3556
3681
|
/* harmony default export */ const libs_TinyPromiseQueue = (TinyPromiseQueue);
|
|
3557
3682
|
|
|
3683
|
+
;// ./src/v1/libs/TinyRateLimiter.mjs
|
|
3684
|
+
/**
|
|
3685
|
+
* Class representing a flexible rate limiter per user.
|
|
3686
|
+
*
|
|
3687
|
+
* This rate limiter can be configured by maximum number of hits,
|
|
3688
|
+
* time interval, or a combination of both. It supports automatic
|
|
3689
|
+
* cleanup of inactive users to optimize memory usage.
|
|
3690
|
+
*
|
|
3691
|
+
* @class
|
|
3692
|
+
*/
|
|
3693
|
+
class TinyRateLimiter {
|
|
3694
|
+
/**
|
|
3695
|
+
* @param {Object} options
|
|
3696
|
+
* @param {number} [options.maxHits] - Max interactions allowed
|
|
3697
|
+
* @param {number} [options.interval] - Time window in milliseconds
|
|
3698
|
+
* @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
|
|
3699
|
+
* @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
|
|
3700
|
+
*/
|
|
3701
|
+
constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000 }) {
|
|
3702
|
+
/** @param {number|undefined} val */
|
|
3703
|
+
const isPositiveInteger = (val) =>
|
|
3704
|
+
typeof val === 'number' && Number.isFinite(val) && val >= 1 && Number.isInteger(val);
|
|
3705
|
+
|
|
3706
|
+
const isMaxHitsValid = isPositiveInteger(maxHits);
|
|
3707
|
+
const isIntervalValid = isPositiveInteger(interval);
|
|
3708
|
+
const isCleanupValid = isPositiveInteger(cleanupInterval);
|
|
3709
|
+
const isMaxIdleValid = isPositiveInteger(maxIdle);
|
|
3710
|
+
|
|
3711
|
+
if (!isMaxHitsValid && !isIntervalValid)
|
|
3712
|
+
throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");
|
|
3713
|
+
if (maxHits !== undefined && !isMaxHitsValid)
|
|
3714
|
+
throw new Error("'maxHits' must be a positive integer if defined.");
|
|
3715
|
+
if (interval !== undefined && !isIntervalValid)
|
|
3716
|
+
throw new Error("'interval' must be a positive integer in milliseconds if defined.");
|
|
3717
|
+
if (cleanupInterval !== undefined && !isCleanupValid)
|
|
3718
|
+
throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");
|
|
3719
|
+
if (!isMaxIdleValid) throw new Error("'maxIdle' must be a positive integer in milliseconds.");
|
|
3720
|
+
|
|
3721
|
+
this.maxHits = isMaxHitsValid ? maxHits : null;
|
|
3722
|
+
this.interval = isIntervalValid ? interval : null;
|
|
3723
|
+
this.cleanupInterval = isCleanupValid ? cleanupInterval : null;
|
|
3724
|
+
this.maxIdle = maxIdle;
|
|
3725
|
+
|
|
3726
|
+
/** @type {Map<string, number[]>} */
|
|
3727
|
+
this.userData = new Map();
|
|
3728
|
+
|
|
3729
|
+
/** @type {Map<string, number>} */
|
|
3730
|
+
this.lastSeen = new Map();
|
|
3731
|
+
|
|
3732
|
+
// Start automatic cleanup only if cleanupInterval is valid
|
|
3733
|
+
if (this.cleanupInterval !== null)
|
|
3734
|
+
this._cleanupTimer = setInterval(() => this._cleanup(), this.cleanupInterval);
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
/**
|
|
3738
|
+
* Get the interval window in milliseconds.
|
|
3739
|
+
*
|
|
3740
|
+
* @returns {number} The interval value.
|
|
3741
|
+
* @throws {Error} If interval is not a valid finite number.
|
|
3742
|
+
*/
|
|
3743
|
+
getInterval() {
|
|
3744
|
+
if (typeof this.interval !== 'number' || !Number.isFinite(this.interval))
|
|
3745
|
+
throw new Error("'interval' is not a valid finite number.");
|
|
3746
|
+
return this.interval;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
/**
|
|
3750
|
+
* Get the maximum number of allowed hits.
|
|
3751
|
+
*
|
|
3752
|
+
* @returns {number} The maxHits value.
|
|
3753
|
+
* @throws {Error} If maxHits is not a valid finite number.
|
|
3754
|
+
*/
|
|
3755
|
+
getMaxHits() {
|
|
3756
|
+
if (typeof this.maxHits !== 'number' || !Number.isFinite(this.maxHits)) {
|
|
3757
|
+
throw new Error("'maxHits' is not a valid finite number.");
|
|
3758
|
+
}
|
|
3759
|
+
return this.maxHits;
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
/**
|
|
3763
|
+
* Register a hit for a specific user
|
|
3764
|
+
* @param {string} userId
|
|
3765
|
+
*/
|
|
3766
|
+
hit(userId) {
|
|
3767
|
+
const now = Date.now();
|
|
3768
|
+
|
|
3769
|
+
if (!this.userData.has(userId)) {
|
|
3770
|
+
this.userData.set(userId, []);
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
const history = this.userData.get(userId);
|
|
3774
|
+
if (!history) throw new Error(`No data found for userId: ${userId}`);
|
|
3775
|
+
|
|
3776
|
+
history.push(now);
|
|
3777
|
+
this.lastSeen.set(userId, now);
|
|
3778
|
+
|
|
3779
|
+
// Clean up old entries
|
|
3780
|
+
if (this.interval !== null) {
|
|
3781
|
+
const interval = this.getInterval();
|
|
3782
|
+
const cutoff = now - interval;
|
|
3783
|
+
while (history.length && history[0] < cutoff) {
|
|
3784
|
+
history.shift();
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
// Optional: keep only the last N entries for memory optimization
|
|
3789
|
+
if (this.maxHits !== null) {
|
|
3790
|
+
const maxHits = this.getMaxHits();
|
|
3791
|
+
if (history.length > maxHits) {
|
|
3792
|
+
history.splice(0, history.length - maxHits);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
/**
|
|
3798
|
+
* Check if the user is currently rate limited
|
|
3799
|
+
* @param {string} userId
|
|
3800
|
+
* @returns {boolean}
|
|
3801
|
+
*/
|
|
3802
|
+
isRateLimited(userId) {
|
|
3803
|
+
const now = Date.now();
|
|
3804
|
+
|
|
3805
|
+
if (!this.userData.has(userId)) return false;
|
|
3806
|
+
|
|
3807
|
+
const history = this.userData.get(userId);
|
|
3808
|
+
if (!history) throw new Error(`No data found for userId: ${userId}`);
|
|
3809
|
+
|
|
3810
|
+
if (this.interval !== null) {
|
|
3811
|
+
const interval = this.getInterval();
|
|
3812
|
+
const recent = history.filter((t) => t > now - interval);
|
|
3813
|
+
if (this.maxHits !== null) {
|
|
3814
|
+
return recent.length >= this.getMaxHits();
|
|
3815
|
+
}
|
|
3816
|
+
return recent.length > 0;
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
if (this.maxHits !== null) {
|
|
3820
|
+
return history.length >= this.getMaxHits();
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
return false;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
/**
|
|
3827
|
+
* Manually reset user data
|
|
3828
|
+
* @param {string} userId
|
|
3829
|
+
*/
|
|
3830
|
+
reset(userId) {
|
|
3831
|
+
this.userData.delete(userId);
|
|
3832
|
+
this.lastSeen.delete(userId);
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
/**
|
|
3836
|
+
* Set hit timestamps for a user
|
|
3837
|
+
* @param {string} userId
|
|
3838
|
+
* @param {number[]} timestamps
|
|
3839
|
+
*/
|
|
3840
|
+
setData(userId, timestamps) {
|
|
3841
|
+
this.userData.set(userId, timestamps);
|
|
3842
|
+
this.lastSeen.set(userId, Date.now());
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
/**
|
|
3846
|
+
* Get timestamps from user
|
|
3847
|
+
* @param {string} userId
|
|
3848
|
+
* @returns {number[]}
|
|
3849
|
+
*/
|
|
3850
|
+
getData(userId) {
|
|
3851
|
+
return this.userData.get(userId) || [];
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
/**
|
|
3855
|
+
* Cleanup old/inactive users
|
|
3856
|
+
* @private
|
|
3857
|
+
*/
|
|
3858
|
+
_cleanup() {
|
|
3859
|
+
const now = Date.now();
|
|
3860
|
+
for (const [userId, last] of this.lastSeen.entries()) {
|
|
3861
|
+
if (now - last > this.maxIdle) {
|
|
3862
|
+
this.userData.delete(userId);
|
|
3863
|
+
this.lastSeen.delete(userId);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
/**
|
|
3869
|
+
* Destroy the rate limiter, stopping all intervals
|
|
3870
|
+
*/
|
|
3871
|
+
destroy() {
|
|
3872
|
+
if (this._cleanupTimer) clearInterval(this._cleanupTimer);
|
|
3873
|
+
this.userData.clear();
|
|
3874
|
+
this.lastSeen.clear();
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
/* harmony default export */ const libs_TinyRateLimiter = (TinyRateLimiter);
|
|
3879
|
+
|
|
3558
3880
|
;// ./src/v1/index.mjs
|
|
3559
3881
|
|
|
3560
3882
|
|
|
@@ -3569,6 +3891,7 @@ class TinyPromiseQueue {
|
|
|
3569
3891
|
|
|
3570
3892
|
|
|
3571
3893
|
|
|
3894
|
+
|
|
3572
3895
|
})();
|
|
3573
3896
|
|
|
3574
3897
|
window.TinyEssentials = __webpack_exports__;
|