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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/******/ (() => { // webpackBootstrap
|
|
2
|
+
/******/ "use strict";
|
|
3
|
+
/******/ // The require scope
|
|
4
|
+
/******/ var __webpack_require__ = {};
|
|
5
|
+
/******/
|
|
6
|
+
/************************************************************************/
|
|
7
|
+
/******/ /* webpack/runtime/define property getters */
|
|
8
|
+
/******/ (() => {
|
|
9
|
+
/******/ // define getter functions for harmony exports
|
|
10
|
+
/******/ __webpack_require__.d = (exports, definition) => {
|
|
11
|
+
/******/ for(var key in definition) {
|
|
12
|
+
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
13
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
14
|
+
/******/ }
|
|
15
|
+
/******/ }
|
|
16
|
+
/******/ };
|
|
17
|
+
/******/ })();
|
|
18
|
+
/******/
|
|
19
|
+
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
20
|
+
/******/ (() => {
|
|
21
|
+
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
22
|
+
/******/ })();
|
|
23
|
+
/******/
|
|
24
|
+
/************************************************************************/
|
|
25
|
+
var __webpack_exports__ = {};
|
|
26
|
+
|
|
27
|
+
// EXPORTS
|
|
28
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
29
|
+
TinyRateLimiter: () => (/* reexport */ libs_TinyRateLimiter)
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
;// ./src/v1/libs/TinyRateLimiter.mjs
|
|
33
|
+
/**
|
|
34
|
+
* Class representing a flexible rate limiter per user.
|
|
35
|
+
*
|
|
36
|
+
* This rate limiter can be configured by maximum number of hits,
|
|
37
|
+
* time interval, or a combination of both. It supports automatic
|
|
38
|
+
* cleanup of inactive users to optimize memory usage.
|
|
39
|
+
*
|
|
40
|
+
* @class
|
|
41
|
+
*/
|
|
42
|
+
class TinyRateLimiter {
|
|
43
|
+
/**
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @param {number} [options.maxHits] - Max interactions allowed
|
|
46
|
+
* @param {number} [options.interval] - Time window in milliseconds
|
|
47
|
+
* @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
|
|
48
|
+
* @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
|
|
49
|
+
*/
|
|
50
|
+
constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000 }) {
|
|
51
|
+
/** @param {number|undefined} val */
|
|
52
|
+
const isPositiveInteger = (val) =>
|
|
53
|
+
typeof val === 'number' && Number.isFinite(val) && val >= 1 && Number.isInteger(val);
|
|
54
|
+
|
|
55
|
+
const isMaxHitsValid = isPositiveInteger(maxHits);
|
|
56
|
+
const isIntervalValid = isPositiveInteger(interval);
|
|
57
|
+
const isCleanupValid = isPositiveInteger(cleanupInterval);
|
|
58
|
+
const isMaxIdleValid = isPositiveInteger(maxIdle);
|
|
59
|
+
|
|
60
|
+
if (!isMaxHitsValid && !isIntervalValid)
|
|
61
|
+
throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");
|
|
62
|
+
if (maxHits !== undefined && !isMaxHitsValid)
|
|
63
|
+
throw new Error("'maxHits' must be a positive integer if defined.");
|
|
64
|
+
if (interval !== undefined && !isIntervalValid)
|
|
65
|
+
throw new Error("'interval' must be a positive integer in milliseconds if defined.");
|
|
66
|
+
if (cleanupInterval !== undefined && !isCleanupValid)
|
|
67
|
+
throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");
|
|
68
|
+
if (!isMaxIdleValid) throw new Error("'maxIdle' must be a positive integer in milliseconds.");
|
|
69
|
+
|
|
70
|
+
this.maxHits = isMaxHitsValid ? maxHits : null;
|
|
71
|
+
this.interval = isIntervalValid ? interval : null;
|
|
72
|
+
this.cleanupInterval = isCleanupValid ? cleanupInterval : null;
|
|
73
|
+
this.maxIdle = maxIdle;
|
|
74
|
+
|
|
75
|
+
/** @type {Map<string, number[]>} */
|
|
76
|
+
this.userData = new Map();
|
|
77
|
+
|
|
78
|
+
/** @type {Map<string, number>} */
|
|
79
|
+
this.lastSeen = new Map();
|
|
80
|
+
|
|
81
|
+
// Start automatic cleanup only if cleanupInterval is valid
|
|
82
|
+
if (this.cleanupInterval !== null)
|
|
83
|
+
this._cleanupTimer = setInterval(() => this._cleanup(), this.cleanupInterval);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the interval window in milliseconds.
|
|
88
|
+
*
|
|
89
|
+
* @returns {number} The interval value.
|
|
90
|
+
* @throws {Error} If interval is not a valid finite number.
|
|
91
|
+
*/
|
|
92
|
+
getInterval() {
|
|
93
|
+
if (typeof this.interval !== 'number' || !Number.isFinite(this.interval))
|
|
94
|
+
throw new Error("'interval' is not a valid finite number.");
|
|
95
|
+
return this.interval;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the maximum number of allowed hits.
|
|
100
|
+
*
|
|
101
|
+
* @returns {number} The maxHits value.
|
|
102
|
+
* @throws {Error} If maxHits is not a valid finite number.
|
|
103
|
+
*/
|
|
104
|
+
getMaxHits() {
|
|
105
|
+
if (typeof this.maxHits !== 'number' || !Number.isFinite(this.maxHits)) {
|
|
106
|
+
throw new Error("'maxHits' is not a valid finite number.");
|
|
107
|
+
}
|
|
108
|
+
return this.maxHits;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Register a hit for a specific user
|
|
113
|
+
* @param {string} userId
|
|
114
|
+
*/
|
|
115
|
+
hit(userId) {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
|
|
118
|
+
if (!this.userData.has(userId)) {
|
|
119
|
+
this.userData.set(userId, []);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const history = this.userData.get(userId);
|
|
123
|
+
if (!history) throw new Error(`No data found for userId: ${userId}`);
|
|
124
|
+
|
|
125
|
+
history.push(now);
|
|
126
|
+
this.lastSeen.set(userId, now);
|
|
127
|
+
|
|
128
|
+
// Clean up old entries
|
|
129
|
+
if (this.interval !== null) {
|
|
130
|
+
const interval = this.getInterval();
|
|
131
|
+
const cutoff = now - interval;
|
|
132
|
+
while (history.length && history[0] < cutoff) {
|
|
133
|
+
history.shift();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Optional: keep only the last N entries for memory optimization
|
|
138
|
+
if (this.maxHits !== null) {
|
|
139
|
+
const maxHits = this.getMaxHits();
|
|
140
|
+
if (history.length > maxHits) {
|
|
141
|
+
history.splice(0, history.length - maxHits);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if the user is currently rate limited
|
|
148
|
+
* @param {string} userId
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
isRateLimited(userId) {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
|
|
154
|
+
if (!this.userData.has(userId)) return false;
|
|
155
|
+
|
|
156
|
+
const history = this.userData.get(userId);
|
|
157
|
+
if (!history) throw new Error(`No data found for userId: ${userId}`);
|
|
158
|
+
|
|
159
|
+
if (this.interval !== null) {
|
|
160
|
+
const interval = this.getInterval();
|
|
161
|
+
const recent = history.filter((t) => t > now - interval);
|
|
162
|
+
if (this.maxHits !== null) {
|
|
163
|
+
return recent.length >= this.getMaxHits();
|
|
164
|
+
}
|
|
165
|
+
return recent.length > 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.maxHits !== null) {
|
|
169
|
+
return history.length >= this.getMaxHits();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Manually reset user data
|
|
177
|
+
* @param {string} userId
|
|
178
|
+
*/
|
|
179
|
+
reset(userId) {
|
|
180
|
+
this.userData.delete(userId);
|
|
181
|
+
this.lastSeen.delete(userId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Set hit timestamps for a user
|
|
186
|
+
* @param {string} userId
|
|
187
|
+
* @param {number[]} timestamps
|
|
188
|
+
*/
|
|
189
|
+
setData(userId, timestamps) {
|
|
190
|
+
this.userData.set(userId, timestamps);
|
|
191
|
+
this.lastSeen.set(userId, Date.now());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get timestamps from user
|
|
196
|
+
* @param {string} userId
|
|
197
|
+
* @returns {number[]}
|
|
198
|
+
*/
|
|
199
|
+
getData(userId) {
|
|
200
|
+
return this.userData.get(userId) || [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Cleanup old/inactive users
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
_cleanup() {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
for (const [userId, last] of this.lastSeen.entries()) {
|
|
210
|
+
if (now - last > this.maxIdle) {
|
|
211
|
+
this.userData.delete(userId);
|
|
212
|
+
this.lastSeen.delete(userId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Destroy the rate limiter, stopping all intervals
|
|
219
|
+
*/
|
|
220
|
+
destroy() {
|
|
221
|
+
if (this._cleanupTimer) clearInterval(this._cleanupTimer);
|
|
222
|
+
this.userData.clear();
|
|
223
|
+
this.lastSeen.clear();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* harmony default export */ const libs_TinyRateLimiter = (TinyRateLimiter);
|
|
228
|
+
|
|
229
|
+
;// ./src/v1/build/TinyRateLimiter.mjs
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
window.TinyRateLimiter = __webpack_exports__.TinyRateLimiter;
|
|
235
|
+
/******/ })()
|
|
236
|
+
;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{TinyRateLimiter:()=>i});const i=class{constructor({maxHits:t,interval:e,cleanupInterval:i,maxIdle:s=3e5}){const r=t=>"number"==typeof t&&Number.isFinite(t)&&t>=1&&Number.isInteger(t),n=r(t),a=r(e),l=r(i),o=r(s);if(!n&&!a)throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");if(void 0!==t&&!n)throw new Error("'maxHits' must be a positive integer if defined.");if(void 0!==e&&!a)throw new Error("'interval' must be a positive integer in milliseconds if defined.");if(void 0!==i&&!l)throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");if(!o)throw new Error("'maxIdle' must be a positive integer in milliseconds.");this.maxHits=n?t:null,this.interval=a?e:null,this.cleanupInterval=l?i:null,this.maxIdle=s,this.userData=new Map,this.lastSeen=new Map,null!==this.cleanupInterval&&(this._cleanupTimer=setInterval((()=>this._cleanup()),this.cleanupInterval))}getInterval(){if("number"!=typeof this.interval||!Number.isFinite(this.interval))throw new Error("'interval' is not a valid finite number.");return this.interval}getMaxHits(){if("number"!=typeof this.maxHits||!Number.isFinite(this.maxHits))throw new Error("'maxHits' is not a valid finite number.");return this.maxHits}hit(t){const e=Date.now();this.userData.has(t)||this.userData.set(t,[]);const i=this.userData.get(t);if(!i)throw new Error(`No data found for userId: ${t}`);if(i.push(e),this.lastSeen.set(t,e),null!==this.interval){const t=e-this.getInterval();for(;i.length&&i[0]<t;)i.shift()}if(null!==this.maxHits){const t=this.getMaxHits();i.length>t&&i.splice(0,i.length-t)}}isRateLimited(t){const e=Date.now();if(!this.userData.has(t))return!1;const i=this.userData.get(t);if(!i)throw new Error(`No data found for userId: ${t}`);if(null!==this.interval){const t=this.getInterval(),s=i.filter((i=>i>e-t));return null!==this.maxHits?s.length>=this.getMaxHits():s.length>0}return null!==this.maxHits&&i.length>=this.getMaxHits()}reset(t){this.userData.delete(t),this.lastSeen.delete(t)}setData(t,e){this.userData.set(t,e),this.lastSeen.set(t,Date.now())}getData(t){return this.userData.get(t)||[]}_cleanup(){const t=Date.now();for(const[e,i]of this.lastSeen.entries())t-i>this.maxIdle&&(this.userData.delete(e),this.lastSeen.delete(e))}destroy(){this._cleanupTimer&&clearInterval(this._cleanupTimer),this.userData.clear(),this.lastSeen.clear()}};window.TinyRateLimiter=e.TinyRateLimiter})();
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Represents a user object used.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {Object} UserEditor
|
|
7
|
+
* @property {number} exp - Current experience points of the user.
|
|
8
|
+
* @property {number} level - Current level of the user.
|
|
9
|
+
* @property {number} totalExp - Total accumulated experience.
|
|
10
|
+
*/
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* Class to manage user level-up logic based on experience points.
|
|
5
14
|
*/
|
|
@@ -10,26 +19,95 @@ class TinyLevelUp {
|
|
|
10
19
|
* @param {number} expLevel - Base experience needed to level up (per level).
|
|
11
20
|
*/
|
|
12
21
|
constructor(giveExp, expLevel) {
|
|
22
|
+
if (typeof giveExp !== 'number' || Number.isNaN(giveExp))
|
|
23
|
+
throw new Error('giveExp must be a valid number');
|
|
24
|
+
if (typeof expLevel !== 'number' || Number.isNaN(expLevel))
|
|
25
|
+
throw new Error('expLevel must be a valid number');
|
|
13
26
|
this.giveExp = giveExp;
|
|
14
27
|
this.expLevel = expLevel;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
/**
|
|
18
|
-
*
|
|
31
|
+
* Creates a new user object starting at level 0 with 0 experience.
|
|
32
|
+
* @returns {UserEditor} A fresh user object.
|
|
33
|
+
*/
|
|
34
|
+
createUser() {
|
|
35
|
+
return {
|
|
36
|
+
exp: 0,
|
|
37
|
+
level: 1,
|
|
38
|
+
totalExp: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validates if the given user object has valid numeric properties.
|
|
44
|
+
* Throws an error if any property is invalid.
|
|
45
|
+
*
|
|
46
|
+
* @param {UserEditor} user - The user object to validate.
|
|
47
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
48
|
+
*/
|
|
49
|
+
validateUser(user) {
|
|
50
|
+
if (typeof user.exp !== 'number' || Number.isNaN(user.exp))
|
|
51
|
+
throw new Error('exp must be a valid number');
|
|
52
|
+
if (typeof user.level !== 'number' || Number.isNaN(user.level))
|
|
53
|
+
throw new Error('level must be a valid number');
|
|
54
|
+
if (user.level < 1) throw new Error('level must be at least 1');
|
|
55
|
+
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp))
|
|
56
|
+
throw new Error('totalExp must be a valid number');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Checks if the given user object is valid by verifying its numeric properties.
|
|
61
|
+
*
|
|
62
|
+
* @param {UserEditor} user - The user object to check.
|
|
63
|
+
* @returns {boolean} `true` if all properties (exp, level, totalExp) are valid numbers; otherwise `false`.
|
|
64
|
+
*/
|
|
65
|
+
isValidUser(user) {
|
|
66
|
+
if (typeof user.exp !== 'number' || Number.isNaN(user.exp)) return false;
|
|
67
|
+
if (typeof user.level !== 'number' || Number.isNaN(user.level)) return false;
|
|
68
|
+
if (user.level < 1) return false;
|
|
69
|
+
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp)) return false;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns the base experience value used for random experience generation.
|
|
75
|
+
* Throws an error if the internal giveExp value is not a valid number.
|
|
76
|
+
*
|
|
77
|
+
* @returns {number} The base experience value.
|
|
78
|
+
* @throws {Error} If giveExp is not a valid number.
|
|
19
79
|
*/
|
|
80
|
+
getGiveExpBase() {
|
|
81
|
+
if (typeof this.giveExp !== 'number' || Number.isNaN(this.giveExp))
|
|
82
|
+
throw new Error('giveExp must be a valid number');
|
|
83
|
+
return this.giveExp;
|
|
84
|
+
}
|
|
20
85
|
|
|
21
86
|
/**
|
|
22
|
-
*
|
|
87
|
+
* Returns the base experience required to level up.
|
|
88
|
+
* Throws an error if the internal expLevel value is not a valid number.
|
|
89
|
+
*
|
|
90
|
+
* @returns {number} The base experience needed per level.
|
|
91
|
+
* @throws {Error} If expLevel is not a valid number.
|
|
23
92
|
*/
|
|
93
|
+
getExpLevelBase() {
|
|
94
|
+
if (typeof this.expLevel !== 'number' || Number.isNaN(this.expLevel))
|
|
95
|
+
throw new Error('expLevel must be a valid number');
|
|
96
|
+
return this.expLevel;
|
|
97
|
+
}
|
|
24
98
|
|
|
25
99
|
/**
|
|
26
100
|
* Validates and adjusts the user's level based on their current experience.
|
|
27
101
|
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
28
|
-
* @returns {
|
|
102
|
+
* @returns {UserEditor} The updated user object.
|
|
103
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
29
104
|
*/
|
|
30
105
|
expValidator(user) {
|
|
106
|
+
const expLevel = this.getExpLevelBase();
|
|
107
|
+
this.validateUser(user);
|
|
108
|
+
|
|
31
109
|
let extraValue = 0;
|
|
32
|
-
const nextLevelExp =
|
|
110
|
+
const nextLevelExp = expLevel * user.level;
|
|
33
111
|
|
|
34
112
|
// Level Up
|
|
35
113
|
if (user.exp >= nextLevelExp) {
|
|
@@ -44,7 +122,7 @@ class TinyLevelUp {
|
|
|
44
122
|
if (user.exp < 1 && user.level > 1) {
|
|
45
123
|
user.level--;
|
|
46
124
|
extraValue = Math.abs(user.exp);
|
|
47
|
-
user.exp =
|
|
125
|
+
user.exp = expLevel * user.level;
|
|
48
126
|
|
|
49
127
|
if (extraValue > 0) return this.remove(user, extraValue, 'extra');
|
|
50
128
|
}
|
|
@@ -54,12 +132,14 @@ class TinyLevelUp {
|
|
|
54
132
|
|
|
55
133
|
/**
|
|
56
134
|
* Calculates the total experience based on the user's level.
|
|
57
|
-
* @param {
|
|
135
|
+
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
58
136
|
* @returns {number} The total experience of the user.
|
|
137
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
59
138
|
*/
|
|
60
139
|
getTotalExp(user) {
|
|
140
|
+
this.validateUser(user);
|
|
61
141
|
let totalExp = 0;
|
|
62
|
-
for (let p = 1; p <= user.level; p++) totalExp += this.
|
|
142
|
+
for (let p = 1; p <= user.level; p++) totalExp += this.getExpLevelBase() * p;
|
|
63
143
|
totalExp += user.exp;
|
|
64
144
|
return totalExp;
|
|
65
145
|
}
|
|
@@ -70,34 +150,52 @@ class TinyLevelUp {
|
|
|
70
150
|
* @returns {number} The generated experience points.
|
|
71
151
|
*/
|
|
72
152
|
expGenerator(multi = 1) {
|
|
73
|
-
|
|
153
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
154
|
+
throw new Error('multi must be a valid number');
|
|
155
|
+
return Math.floor(Math.random() * this.getGiveExpBase()) * multi;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Calculates how much experience is missing to next level.
|
|
160
|
+
* @param {UserEditor} user
|
|
161
|
+
* @returns {number}
|
|
162
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
163
|
+
*/
|
|
164
|
+
getMissingExp(user) {
|
|
165
|
+
return this.getProgress(user) - user.exp;
|
|
74
166
|
}
|
|
75
167
|
|
|
76
168
|
/**
|
|
77
169
|
* Gets the experience points required to reach the next level.
|
|
78
|
-
* @param {
|
|
170
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
79
171
|
* @returns {number} The experience required for the next level.
|
|
172
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
80
173
|
*/
|
|
81
174
|
progress(user) {
|
|
82
|
-
return this.
|
|
175
|
+
return this.getProgress(user);
|
|
83
176
|
}
|
|
84
177
|
|
|
85
178
|
/**
|
|
86
179
|
* Gets the experience points required to reach the next level.
|
|
87
|
-
* @param {
|
|
180
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
88
181
|
* @returns {number} The experience required for the next level.
|
|
182
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
89
183
|
*/
|
|
90
184
|
getProgress(user) {
|
|
91
|
-
|
|
185
|
+
this.validateUser(user);
|
|
186
|
+
return this.getExpLevelBase() * user.level;
|
|
92
187
|
}
|
|
93
188
|
|
|
94
189
|
/**
|
|
95
190
|
* Sets the experience value for the user, adjusting their level if necessary.
|
|
96
191
|
* @param {UserEditor} user - The user object.
|
|
97
192
|
* @param {number} value - The new experience value to set.
|
|
98
|
-
* @returns {
|
|
193
|
+
* @returns {UserEditor} The updated user object.
|
|
99
194
|
*/
|
|
100
195
|
set(user, value) {
|
|
196
|
+
if (typeof value !== 'number' || Number.isNaN(value))
|
|
197
|
+
throw new Error('value must be a valid number');
|
|
198
|
+
|
|
101
199
|
user.exp = value;
|
|
102
200
|
this.expValidator(user);
|
|
103
201
|
user.totalExp = this.getTotalExp(user);
|
|
@@ -110,9 +208,15 @@ class TinyLevelUp {
|
|
|
110
208
|
* @param {number} [extraExp] - Additional experience to be added.
|
|
111
209
|
* @param {'add' | 'extra'} [type] - Type of addition ('add' or 'extra').
|
|
112
210
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
113
|
-
* @returns {
|
|
211
|
+
* @returns {UserEditor} The updated user object.
|
|
114
212
|
*/
|
|
115
213
|
give(user, extraExp = 0, type = 'add', multi = 1) {
|
|
214
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
215
|
+
throw new Error('multi must be a valid number');
|
|
216
|
+
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
217
|
+
throw new Error('extraExp must be a valid number');
|
|
218
|
+
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
219
|
+
|
|
116
220
|
if (type === 'add') user.exp += this.expGenerator(multi) + extraExp;
|
|
117
221
|
else if (type === 'extra') user.exp += extraExp;
|
|
118
222
|
|
|
@@ -127,9 +231,15 @@ class TinyLevelUp {
|
|
|
127
231
|
* @param {number} [extraExp] - Additional experience to remove.
|
|
128
232
|
* @param {'add' | 'extra'} [type] - Type of removal ('add' or 'extra').
|
|
129
233
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
130
|
-
* @returns {
|
|
234
|
+
* @returns {UserEditor} The updated user object.
|
|
131
235
|
*/
|
|
132
236
|
remove(user, extraExp = 0, type = 'add', multi = 1) {
|
|
237
|
+
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
238
|
+
throw new Error('multi must be a valid number');
|
|
239
|
+
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
240
|
+
throw new Error('extraExp must be a valid number');
|
|
241
|
+
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
242
|
+
|
|
133
243
|
if (type === 'add') user.exp -= this.expGenerator(multi) + extraExp;
|
|
134
244
|
else if (type === 'extra') user.exp -= extraExp;
|
|
135
245
|
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
export default TinyLevelUp;
|
|
2
|
+
/**
|
|
3
|
+
* Represents a user object used.
|
|
4
|
+
*/
|
|
5
|
+
export type UserEditor = {
|
|
6
|
+
/**
|
|
7
|
+
* - Current experience points of the user.
|
|
8
|
+
*/
|
|
9
|
+
exp: number;
|
|
10
|
+
/**
|
|
11
|
+
* - Current level of the user.
|
|
12
|
+
*/
|
|
13
|
+
level: number;
|
|
14
|
+
/**
|
|
15
|
+
* - Total accumulated experience.
|
|
16
|
+
*/
|
|
17
|
+
totalExp: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Represents a user object used.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {Object} UserEditor
|
|
23
|
+
* @property {number} exp - Current experience points of the user.
|
|
24
|
+
* @property {number} level - Current level of the user.
|
|
25
|
+
* @property {number} totalExp - Total accumulated experience.
|
|
26
|
+
*/
|
|
2
27
|
/**
|
|
3
28
|
* Class to manage user level-up logic based on experience points.
|
|
4
29
|
*/
|
|
@@ -12,104 +37,106 @@ declare class TinyLevelUp {
|
|
|
12
37
|
giveExp: number;
|
|
13
38
|
expLevel: number;
|
|
14
39
|
/**
|
|
15
|
-
*
|
|
40
|
+
* Creates a new user object starting at level 0 with 0 experience.
|
|
41
|
+
* @returns {UserEditor} A fresh user object.
|
|
42
|
+
*/
|
|
43
|
+
createUser(): UserEditor;
|
|
44
|
+
/**
|
|
45
|
+
* Validates if the given user object has valid numeric properties.
|
|
46
|
+
* Throws an error if any property is invalid.
|
|
47
|
+
*
|
|
48
|
+
* @param {UserEditor} user - The user object to validate.
|
|
49
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
16
50
|
*/
|
|
51
|
+
validateUser(user: UserEditor): void;
|
|
17
52
|
/**
|
|
18
|
-
*
|
|
53
|
+
* Checks if the given user object is valid by verifying its numeric properties.
|
|
54
|
+
*
|
|
55
|
+
* @param {UserEditor} user - The user object to check.
|
|
56
|
+
* @returns {boolean} `true` if all properties (exp, level, totalExp) are valid numbers; otherwise `false`.
|
|
19
57
|
*/
|
|
58
|
+
isValidUser(user: UserEditor): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the base experience value used for random experience generation.
|
|
61
|
+
* Throws an error if the internal giveExp value is not a valid number.
|
|
62
|
+
*
|
|
63
|
+
* @returns {number} The base experience value.
|
|
64
|
+
* @throws {Error} If giveExp is not a valid number.
|
|
65
|
+
*/
|
|
66
|
+
getGiveExpBase(): number;
|
|
67
|
+
/**
|
|
68
|
+
* Returns the base experience required to level up.
|
|
69
|
+
* Throws an error if the internal expLevel value is not a valid number.
|
|
70
|
+
*
|
|
71
|
+
* @returns {number} The base experience needed per level.
|
|
72
|
+
* @throws {Error} If expLevel is not a valid number.
|
|
73
|
+
*/
|
|
74
|
+
getExpLevelBase(): number;
|
|
20
75
|
/**
|
|
21
76
|
* Validates and adjusts the user's level based on their current experience.
|
|
22
77
|
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
23
|
-
* @returns {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
level: number;
|
|
28
|
-
totalExp: any;
|
|
29
|
-
}): {
|
|
30
|
-
exp: number;
|
|
31
|
-
level: number;
|
|
32
|
-
totalExp: number;
|
|
33
|
-
};
|
|
78
|
+
* @returns {UserEditor} The updated user object.
|
|
79
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
80
|
+
*/
|
|
81
|
+
expValidator(user: UserEditor): UserEditor;
|
|
34
82
|
/**
|
|
35
83
|
* Calculates the total experience based on the user's level.
|
|
36
|
-
* @param {
|
|
84
|
+
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
37
85
|
* @returns {number} The total experience of the user.
|
|
86
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
38
87
|
*/
|
|
39
|
-
getTotalExp(user:
|
|
40
|
-
exp: number;
|
|
41
|
-
level: number;
|
|
42
|
-
}): number;
|
|
88
|
+
getTotalExp(user: UserEditor): number;
|
|
43
89
|
/**
|
|
44
90
|
* Generates random experience points based on the configured multiplier.
|
|
45
91
|
* @param {number} [multi] - A multiplier for the generated experience.
|
|
46
92
|
* @returns {number} The generated experience points.
|
|
47
93
|
*/
|
|
48
94
|
expGenerator(multi?: number): number;
|
|
95
|
+
/**
|
|
96
|
+
* Calculates how much experience is missing to next level.
|
|
97
|
+
* @param {UserEditor} user
|
|
98
|
+
* @returns {number}
|
|
99
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
100
|
+
*/
|
|
101
|
+
getMissingExp(user: UserEditor): number;
|
|
49
102
|
/**
|
|
50
103
|
* Gets the experience points required to reach the next level.
|
|
51
|
-
* @param {
|
|
104
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
52
105
|
* @returns {number} The experience required for the next level.
|
|
106
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
53
107
|
*/
|
|
54
|
-
progress(user:
|
|
55
|
-
level: number;
|
|
56
|
-
}): number;
|
|
108
|
+
progress(user: UserEditor): number;
|
|
57
109
|
/**
|
|
58
110
|
* Gets the experience points required to reach the next level.
|
|
59
|
-
* @param {
|
|
111
|
+
* @param {UserEditor} user - The user object containing the level.
|
|
60
112
|
* @returns {number} The experience required for the next level.
|
|
113
|
+
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
61
114
|
*/
|
|
62
|
-
getProgress(user:
|
|
63
|
-
level: number;
|
|
64
|
-
}): number;
|
|
115
|
+
getProgress(user: UserEditor): number;
|
|
65
116
|
/**
|
|
66
117
|
* Sets the experience value for the user, adjusting their level if necessary.
|
|
67
118
|
* @param {UserEditor} user - The user object.
|
|
68
119
|
* @param {number} value - The new experience value to set.
|
|
69
|
-
* @returns {
|
|
70
|
-
*/
|
|
71
|
-
set(user:
|
|
72
|
-
exp: number;
|
|
73
|
-
level: number;
|
|
74
|
-
totalExp: any;
|
|
75
|
-
}, value: number): {
|
|
76
|
-
exp: number;
|
|
77
|
-
level: number;
|
|
78
|
-
totalExp: number;
|
|
79
|
-
};
|
|
120
|
+
* @returns {UserEditor} The updated user object.
|
|
121
|
+
*/
|
|
122
|
+
set(user: UserEditor, value: number): UserEditor;
|
|
80
123
|
/**
|
|
81
124
|
* Adds experience to the user, adjusting their level if necessary.
|
|
82
125
|
* @param {UserEditor} user - The user object.
|
|
83
126
|
* @param {number} [extraExp] - Additional experience to be added.
|
|
84
127
|
* @param {'add' | 'extra'} [type] - Type of addition ('add' or 'extra').
|
|
85
128
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
86
|
-
* @returns {
|
|
87
|
-
*/
|
|
88
|
-
give(user:
|
|
89
|
-
exp: number;
|
|
90
|
-
level: number;
|
|
91
|
-
totalExp: any;
|
|
92
|
-
}, extraExp?: number, type?: "add" | "extra", multi?: number): {
|
|
93
|
-
exp: number;
|
|
94
|
-
level: number;
|
|
95
|
-
totalExp: number;
|
|
96
|
-
};
|
|
129
|
+
* @returns {UserEditor} The updated user object.
|
|
130
|
+
*/
|
|
131
|
+
give(user: UserEditor, extraExp?: number, type?: "add" | "extra", multi?: number): UserEditor;
|
|
97
132
|
/**
|
|
98
133
|
* Removes experience from the user, adjusting their level if necessary.
|
|
99
134
|
* @param {UserEditor} user - The user object.
|
|
100
135
|
* @param {number} [extraExp] - Additional experience to remove.
|
|
101
136
|
* @param {'add' | 'extra'} [type] - Type of removal ('add' or 'extra').
|
|
102
137
|
* @param {number} [multi] - Multiplier for experience generation.
|
|
103
|
-
* @returns {
|
|
104
|
-
*/
|
|
105
|
-
remove(user:
|
|
106
|
-
exp: number;
|
|
107
|
-
level: number;
|
|
108
|
-
totalExp: any;
|
|
109
|
-
}, extraExp?: number, type?: "add" | "extra", multi?: number): {
|
|
110
|
-
exp: number;
|
|
111
|
-
level: number;
|
|
112
|
-
totalExp: number;
|
|
113
|
-
};
|
|
138
|
+
* @returns {UserEditor} The updated user object.
|
|
139
|
+
*/
|
|
140
|
+
remove(user: UserEditor, extraExp?: number, type?: "add" | "extra", multi?: number): UserEditor;
|
|
114
141
|
}
|
|
115
142
|
//# sourceMappingURL=userLevel.d.mts.map
|