tiny-essentials 1.9.0 → 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.
@@ -30,6 +30,15 @@ __webpack_require__.d(__webpack_exports__, {
30
30
  });
31
31
 
32
32
  ;// ./src/legacy/libs/userLevel.mjs
33
+ /**
34
+ * Represents a user object used.
35
+ *
36
+ * @typedef {Object} UserEditor
37
+ * @property {number} exp - Current experience points of the user.
38
+ * @property {number} level - Current level of the user.
39
+ * @property {number} totalExp - Total accumulated experience.
40
+ */
41
+
33
42
  /**
34
43
  * Class to manage user level-up logic based on experience points.
35
44
  */
@@ -40,26 +49,95 @@ class TinyLevelUp {
40
49
  * @param {number} expLevel - Base experience needed to level up (per level).
41
50
  */
42
51
  constructor(giveExp, expLevel) {
52
+ if (typeof giveExp !== 'number' || Number.isNaN(giveExp))
53
+ throw new Error('giveExp must be a valid number');
54
+ if (typeof expLevel !== 'number' || Number.isNaN(expLevel))
55
+ throw new Error('expLevel must be a valid number');
43
56
  this.giveExp = giveExp;
44
57
  this.expLevel = expLevel;
45
58
  }
46
59
 
47
60
  /**
48
- * @typedef {{ exp: number, level: number, totalExp: number }} UserResult
61
+ * Creates a new user object starting at level 0 with 0 experience.
62
+ * @returns {UserEditor} A fresh user object.
63
+ */
64
+ createUser() {
65
+ return {
66
+ exp: 0,
67
+ level: 1,
68
+ totalExp: 0,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Validates if the given user object has valid numeric properties.
74
+ * Throws an error if any property is invalid.
75
+ *
76
+ * @param {UserEditor} user - The user object to validate.
77
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
78
+ */
79
+ validateUser(user) {
80
+ if (typeof user.exp !== 'number' || Number.isNaN(user.exp))
81
+ throw new Error('exp must be a valid number');
82
+ if (typeof user.level !== 'number' || Number.isNaN(user.level))
83
+ throw new Error('level must be a valid number');
84
+ if (user.level < 1) throw new Error('level must be at least 1');
85
+ if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp))
86
+ throw new Error('totalExp must be a valid number');
87
+ }
88
+
89
+ /**
90
+ * Checks if the given user object is valid by verifying its numeric properties.
91
+ *
92
+ * @param {UserEditor} user - The user object to check.
93
+ * @returns {boolean} `true` if all properties (exp, level, totalExp) are valid numbers; otherwise `false`.
94
+ */
95
+ isValidUser(user) {
96
+ if (typeof user.exp !== 'number' || Number.isNaN(user.exp)) return false;
97
+ if (typeof user.level !== 'number' || Number.isNaN(user.level)) return false;
98
+ if (user.level < 1) return false;
99
+ if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp)) return false;
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Returns the base experience value used for random experience generation.
105
+ * Throws an error if the internal giveExp value is not a valid number.
106
+ *
107
+ * @returns {number} The base experience value.
108
+ * @throws {Error} If giveExp is not a valid number.
49
109
  */
110
+ getGiveExpBase() {
111
+ if (typeof this.giveExp !== 'number' || Number.isNaN(this.giveExp))
112
+ throw new Error('giveExp must be a valid number');
113
+ return this.giveExp;
114
+ }
50
115
 
51
116
  /**
52
- * @typedef {{ exp: number, level: number, totalExp: any }} UserEditor
117
+ * Returns the base experience required to level up.
118
+ * Throws an error if the internal expLevel value is not a valid number.
119
+ *
120
+ * @returns {number} The base experience needed per level.
121
+ * @throws {Error} If expLevel is not a valid number.
53
122
  */
123
+ getExpLevelBase() {
124
+ if (typeof this.expLevel !== 'number' || Number.isNaN(this.expLevel))
125
+ throw new Error('expLevel must be a valid number');
126
+ return this.expLevel;
127
+ }
54
128
 
55
129
  /**
56
130
  * Validates and adjusts the user's level based on their current experience.
57
131
  * @param {UserEditor} user - The user object containing experience and level properties.
58
- * @returns {UserResult} The updated user object.
132
+ * @returns {UserEditor} The updated user object.
133
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
59
134
  */
60
135
  expValidator(user) {
136
+ const expLevel = this.getExpLevelBase();
137
+ this.validateUser(user);
138
+
61
139
  let extraValue = 0;
62
- const nextLevelExp = this.expLevel * user.level;
140
+ const nextLevelExp = expLevel * user.level;
63
141
 
64
142
  // Level Up
65
143
  if (user.exp >= nextLevelExp) {
@@ -74,7 +152,7 @@ class TinyLevelUp {
74
152
  if (user.exp < 1 && user.level > 1) {
75
153
  user.level--;
76
154
  extraValue = Math.abs(user.exp);
77
- user.exp = this.expLevel * user.level;
155
+ user.exp = expLevel * user.level;
78
156
 
79
157
  if (extraValue > 0) return this.remove(user, extraValue, 'extra');
80
158
  }
@@ -84,12 +162,14 @@ class TinyLevelUp {
84
162
 
85
163
  /**
86
164
  * Calculates the total experience based on the user's level.
87
- * @param {{ exp: number, level: number }} user - The user object containing experience and level properties.
165
+ * @param {UserEditor} user - The user object containing experience and level properties.
88
166
  * @returns {number} The total experience of the user.
167
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
89
168
  */
90
169
  getTotalExp(user) {
170
+ this.validateUser(user);
91
171
  let totalExp = 0;
92
- for (let p = 1; p <= user.level; p++) totalExp += this.expLevel * p;
172
+ for (let p = 1; p <= user.level; p++) totalExp += this.getExpLevelBase() * p;
93
173
  totalExp += user.exp;
94
174
  return totalExp;
95
175
  }
@@ -100,34 +180,52 @@ class TinyLevelUp {
100
180
  * @returns {number} The generated experience points.
101
181
  */
102
182
  expGenerator(multi = 1) {
103
- return Math.floor(Math.random() * this.giveExp) + 1 * multi;
183
+ if (typeof multi !== 'number' || Number.isNaN(multi))
184
+ throw new Error('multi must be a valid number');
185
+ return Math.floor(Math.random() * this.getGiveExpBase()) * multi;
186
+ }
187
+
188
+ /**
189
+ * Calculates how much experience is missing to next level.
190
+ * @param {UserEditor} user
191
+ * @returns {number}
192
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
193
+ */
194
+ getMissingExp(user) {
195
+ return this.getProgress(user) - user.exp;
104
196
  }
105
197
 
106
198
  /**
107
199
  * Gets the experience points required to reach the next level.
108
- * @param {{ level: number }} user - The user object containing the level.
200
+ * @param {UserEditor} user - The user object containing the level.
109
201
  * @returns {number} The experience required for the next level.
202
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
110
203
  */
111
204
  progress(user) {
112
- return this.expLevel * user.level;
205
+ return this.getProgress(user);
113
206
  }
114
207
 
115
208
  /**
116
209
  * Gets the experience points required to reach the next level.
117
- * @param {{ level: number }} user - The user object containing the level.
210
+ * @param {UserEditor} user - The user object containing the level.
118
211
  * @returns {number} The experience required for the next level.
212
+ * @throws {Error} If any property (exp, level, totalExp) is not a valid number.
119
213
  */
120
214
  getProgress(user) {
121
- return this.expLevel * user.level;
215
+ this.validateUser(user);
216
+ return this.getExpLevelBase() * user.level;
122
217
  }
123
218
 
124
219
  /**
125
220
  * Sets the experience value for the user, adjusting their level if necessary.
126
221
  * @param {UserEditor} user - The user object.
127
222
  * @param {number} value - The new experience value to set.
128
- * @returns {UserResult} The updated user object.
223
+ * @returns {UserEditor} The updated user object.
129
224
  */
130
225
  set(user, value) {
226
+ if (typeof value !== 'number' || Number.isNaN(value))
227
+ throw new Error('value must be a valid number');
228
+
131
229
  user.exp = value;
132
230
  this.expValidator(user);
133
231
  user.totalExp = this.getTotalExp(user);
@@ -140,9 +238,15 @@ class TinyLevelUp {
140
238
  * @param {number} [extraExp] - Additional experience to be added.
141
239
  * @param {'add' | 'extra'} [type] - Type of addition ('add' or 'extra').
142
240
  * @param {number} [multi] - Multiplier for experience generation.
143
- * @returns {UserResult} The updated user object.
241
+ * @returns {UserEditor} The updated user object.
144
242
  */
145
243
  give(user, extraExp = 0, type = 'add', multi = 1) {
244
+ if (typeof multi !== 'number' || Number.isNaN(multi))
245
+ throw new Error('multi must be a valid number');
246
+ if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
247
+ throw new Error('extraExp must be a valid number');
248
+ if (typeof type !== 'string') throw new Error('type must be a valid string');
249
+
146
250
  if (type === 'add') user.exp += this.expGenerator(multi) + extraExp;
147
251
  else if (type === 'extra') user.exp += extraExp;
148
252
 
@@ -157,9 +261,15 @@ class TinyLevelUp {
157
261
  * @param {number} [extraExp] - Additional experience to remove.
158
262
  * @param {'add' | 'extra'} [type] - Type of removal ('add' or 'extra').
159
263
  * @param {number} [multi] - Multiplier for experience generation.
160
- * @returns {UserResult} The updated user object.
264
+ * @returns {UserEditor} The updated user object.
161
265
  */
162
266
  remove(user, extraExp = 0, type = 'add', multi = 1) {
267
+ if (typeof multi !== 'number' || Number.isNaN(multi))
268
+ throw new Error('multi must be a valid number');
269
+ if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
270
+ throw new Error('extraExp must be a valid number');
271
+ if (typeof type !== 'string') throw new Error('type must be a valid string');
272
+
163
273
  if (type === 'add') user.exp -= this.expGenerator(multi) + extraExp;
164
274
  else if (type === 'extra') user.exp -= extraExp;
165
275
 
@@ -1 +1 @@
1
- (()=>{"use strict";var e={d:(t,r)=>{for(var l in r)e.o(r,l)&&!e.o(t,l)&&Object.defineProperty(t,l,{enumerable:!0,get:r[l]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyLevelUp:()=>r});const r=class{constructor(e,t){this.giveExp=e,this.expLevel=t}expValidator(e){let t=0;const r=this.expLevel*e.level;return e.exp>=r&&(e.level++,t=e.exp-r,e.exp=0,t>0)?this.give(e,t,"extra"):e.exp<1&&e.level>1&&(e.level--,t=Math.abs(e.exp),e.exp=this.expLevel*e.level,t>0)?this.remove(e,t,"extra"):e}getTotalExp(e){let t=0;for(let r=1;r<=e.level;r++)t+=this.expLevel*r;return t+=e.exp,t}expGenerator(e=1){return Math.floor(Math.random()*this.giveExp)+1*e}progress(e){return this.expLevel*e.level}getProgress(e){return this.expLevel*e.level}set(e,t){return e.exp=t,this.expValidator(e),e.totalExp=this.getTotalExp(e),e}give(e,t=0,r="add",l=1){return"add"===r?e.exp+=this.expGenerator(l)+t:"extra"===r&&(e.exp+=t),this.expValidator(e),e.totalExp=this.getTotalExp(e),e}remove(e,t=0,r="add",l=1){return"add"===r?e.exp-=this.expGenerator(l)+t:"extra"===r&&(e.exp-=t),this.expValidator(e),e.totalExp=this.getTotalExp(e),e}};window.TinyLevelUp=t.TinyLevelUp})();
1
+ (()=>{"use strict";var e={d:(r,t)=>{for(var a in t)e.o(t,a)&&!e.o(r,a)&&Object.defineProperty(r,a,{enumerable:!0,get:t[a]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)},r={};e.d(r,{TinyLevelUp:()=>t});const t=class{constructor(e,r){if("number"!=typeof e||Number.isNaN(e))throw new Error("giveExp must be a valid number");if("number"!=typeof r||Number.isNaN(r))throw new Error("expLevel must be a valid number");this.giveExp=e,this.expLevel=r}createUser(){return{exp:0,level:1,totalExp:0}}validateUser(e){if("number"!=typeof e.exp||Number.isNaN(e.exp))throw new Error("exp must be a valid number");if("number"!=typeof e.level||Number.isNaN(e.level))throw new Error("level must be a valid number");if(e.level<1)throw new Error("level must be at least 1");if("number"!=typeof e.totalExp||Number.isNaN(e.totalExp))throw new Error("totalExp must be a valid number")}isValidUser(e){return!("number"!=typeof e.exp||Number.isNaN(e.exp)||"number"!=typeof e.level||Number.isNaN(e.level)||e.level<1||"number"!=typeof e.totalExp||Number.isNaN(e.totalExp))}getGiveExpBase(){if("number"!=typeof this.giveExp||Number.isNaN(this.giveExp))throw new Error("giveExp must be a valid number");return this.giveExp}getExpLevelBase(){if("number"!=typeof this.expLevel||Number.isNaN(this.expLevel))throw new Error("expLevel must be a valid number");return this.expLevel}expValidator(e){const r=this.getExpLevelBase();this.validateUser(e);let t=0;const a=r*e.level;return e.exp>=a&&(e.level++,t=e.exp-a,e.exp=0,t>0)?this.give(e,t,"extra"):e.exp<1&&e.level>1&&(e.level--,t=Math.abs(e.exp),e.exp=r*e.level,t>0)?this.remove(e,t,"extra"):e}getTotalExp(e){this.validateUser(e);let r=0;for(let t=1;t<=e.level;t++)r+=this.getExpLevelBase()*t;return r+=e.exp,r}expGenerator(e=1){if("number"!=typeof e||Number.isNaN(e))throw new Error("multi must be a valid number");return Math.floor(Math.random()*this.getGiveExpBase())*e}getMissingExp(e){return this.getProgress(e)-e.exp}progress(e){return this.getProgress(e)}getProgress(e){return this.validateUser(e),this.getExpLevelBase()*e.level}set(e,r){if("number"!=typeof r||Number.isNaN(r))throw new Error("value must be a valid number");return e.exp=r,this.expValidator(e),e.totalExp=this.getTotalExp(e),e}give(e,r=0,t="add",a=1){if("number"!=typeof a||Number.isNaN(a))throw new Error("multi must be a valid number");if("number"!=typeof r||Number.isNaN(r))throw new Error("extraExp must be a valid number");if("string"!=typeof t)throw new Error("type must be a valid string");return"add"===t?e.exp+=this.expGenerator(a)+r:"extra"===t&&(e.exp+=r),this.expValidator(e),e.totalExp=this.getTotalExp(e),e}remove(e,r=0,t="add",a=1){if("number"!=typeof a||Number.isNaN(a))throw new Error("multi must be a valid number");if("number"!=typeof r||Number.isNaN(r))throw new Error("extraExp must be a valid number");if("string"!=typeof t)throw new Error("type must be a valid string");return"add"===t?e.exp-=this.expGenerator(a)+r:"extra"===t&&(e.exp-=r),this.expValidator(e),e.totalExp=this.getTotalExp(e),e}};window.TinyLevelUp=r.TinyLevelUp})();
@@ -30,6 +30,16 @@ __webpack_require__.d(__webpack_exports__, {
30
30
  });
31
31
 
32
32
  ;// ./src/v1/libs/TinyPromiseQueue.mjs
33
+ /**
34
+ * @typedef {Object} QueuedTask
35
+ * @property {(...args: any[]) => Promise<any>|Promise<any>} task - The async task to execute.
36
+ * @property {(value: any) => any} resolve - The resolve function from the Promise.
37
+ * @property {(reason?: any) => any} reject - The reject function from the Promise.
38
+ * @property {string|undefined} [id] - Optional identifier for the task.
39
+ * @property {string|null|undefined} [marker] - Optional marker for the task.
40
+ * @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
41
+ */
42
+
33
43
  /**
34
44
  * A queue system for managing and executing asynchronous tasks sequentially, one at a time.
35
45
  *
@@ -39,20 +49,10 @@ __webpack_require__.d(__webpack_exports__, {
39
49
  * @class
40
50
  */
41
51
  class TinyPromiseQueue {
42
- /**
43
- * @typedef {Object} QueuedTask
44
- * @property {(...args: any[]) => Promise<any>|Promise<any>} task - The async task to execute.
45
- * @property {(value: any) => any} resolve - The resolve function from the Promise.
46
- * @property {(reason?: any) => any} reject - The reject function from the Promise.
47
- * @property {string|undefined} [id] - Optional identifier for the task.
48
- * @property {string|null|undefined} [marker] - Optional marker for the task.
49
- * @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
50
- */
51
-
52
- /** @type {Array<QueuedTask>} */
52
+ /** @type {QueuedTask[]} */
53
53
  #queue = [];
54
54
  #running = false;
55
- /** @type {Record<string, *>} */
55
+ /** @type {Record<string, ReturnType<typeof setTimeout>>} */
56
56
  #timeouts = {};
57
57
  /** @type {Set<string>} */
58
58
  #blacklist = new Set();
@@ -215,8 +215,13 @@ class TinyPromiseQueue {
215
215
  * @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
216
216
  * @param {string} [id] Optional ID to identify the task in the queue.
217
217
  * @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
218
+ * @throws {Error} Throws if param is invalid.
218
219
  */
219
220
  async enqueuePoint(task, id) {
221
+ if (typeof task !== 'function')
222
+ return Promise.reject(new Error('Task must be a function returning a Promise.'));
223
+ if (typeof id !== 'undefined' && typeof id !== 'string')
224
+ throw new Error('The "id" parameter must be a string.');
220
225
  if (!this.#running) return task();
221
226
  return new Promise((resolve, reject) => {
222
227
  this.#queue.push({ marker: 'POINT_MARKER', task, resolve, reject, id });
@@ -235,8 +240,16 @@ class TinyPromiseQueue {
235
240
  * @param {number|null} [delay] Optional delay (in ms) before the task is executed.
236
241
  * @param {string} [id] Optional ID to identify the task in the queue.
237
242
  * @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
243
+ * @throws {Error} Throws if param is invalid.
238
244
  */
239
245
  enqueue(task, delay, id) {
246
+ if (typeof task !== 'function')
247
+ return Promise.reject(new Error('Task must be a function returning a Promise.'));
248
+ if (typeof delay !== 'undefined' && (typeof delay !== 'number' || delay < 0))
249
+ return Promise.reject(new Error('Delay must be a positive number or undefined.'));
250
+ if (typeof id !== 'undefined' && typeof id !== 'string')
251
+ throw new Error('The "id" parameter must be a string.');
252
+
240
253
  return new Promise((resolve, reject) => {
241
254
  this.#queue.push({ task, resolve, reject, id, delay });
242
255
  this.#processQueue();
@@ -249,9 +262,10 @@ class TinyPromiseQueue {
249
262
  *
250
263
  * @param {string} id The ID of the task to cancel.
251
264
  * @returns {boolean} True if a delay was cancelled and the task was removed.
265
+ * @throws {Error} Throws if `id` is not a string.
252
266
  */
253
267
  cancelTask(id) {
254
- if (!id) return false;
268
+ if (typeof id !== 'string') throw new Error('The "id" parameter must be a string.');
255
269
  let cancelled = false;
256
270
 
257
271
  if (id in this.#timeouts) {
@@ -1 +1 @@
1
- (()=>{"use strict";var e={d:(t,s)=>{for(var i in s)e.o(s,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:s[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyPromiseQueue:()=>s});const s=class{#e=[];#t=!1;#s={};#i=new Set;isRunning(){return this.#t}async#u(e){if(e&&"function"==typeof e.task&&"function"==typeof e.resolve&&"function"==typeof e.reject){const{task:t,resolve:s,reject:i,delay:u,id:n}=e;try{if(n&&this.#i.has(n))return i(new Error("The function was canceled on TinyPromiseQueue.")),this.#i.delete(n),this.#t=!1,void this.#n();u&&n&&await new Promise((e=>{const t=setTimeout((()=>{delete this.#s[n],e(null)}),u);this.#s[n]=t})),s(await t())}catch(e){i(e)}finally{this.#t=!1,this.#n()}}}async#r(){const e=[];for(;this.#e.length&&"POINT_MARKER"===this.#e[0]?.marker;)e.push(this.#e.shift());if(0===e.length)return this.#t=!1,void this.#n();await Promise.all(e.map((({task:e,resolve:t,reject:s,id:i})=>new Promise((async u=>{if(i&&this.#i.has(i))return this.#i.delete(i),s(new Error("The function was canceled on TinyPromiseQueue.")),void u(!0);await e().then(t).catch(s),u(!0)}))))),this.#t=!1,this.#n()}async#n(){if(!this.#t&&0!==this.#e.length)if(this.#t=!0,"string"!=typeof this.#e[0]?.marker||"POINT_MARKER"!==this.#e[0]?.marker){const e=this.#e.shift();this.#u(e)}else this.#r()}getIndexById(e){return this.#e.findIndex((t=>t.id===e))}getQueuedIds(){return this.#e.map(((e,t)=>({index:t,id:e.id}))).filter((e=>"string"==typeof e.id))}reorderQueue(e,t){if("number"!=typeof e||"number"!=typeof t||e<0||t<0||e>=this.#e.length||t>=this.#e.length)return;const[s]=this.#e.splice(e,1);this.#e.splice(t,0,s)}async enqueuePoint(e,t){return this.#t?new Promise(((s,i)=>{this.#e.push({marker:"POINT_MARKER",task:e,resolve:s,reject:i,id:t}),this.#n()})):e()}enqueue(e,t,s){return new Promise(((i,u)=>{this.#e.push({task:e,resolve:i,reject:u,id:s,delay:t}),this.#n()}))}cancelTask(e){if(!e)return!1;let t=!1;e in this.#s&&(clearTimeout(this.#s[e]),delete this.#s[e],t=!0);const s=this.getIndexById(e);if(-1!==s){const[e]=this.#e.splice(s,1);e?.reject?.(new Error("The function was canceled on TinyPromiseQueue.")),t=!0}return t&&this.#i.add(e),t}};window.TinyPromiseQueue=t.TinyPromiseQueue})();
1
+ (()=>{"use strict";var e={d:(t,i)=>{for(var r in i)e.o(i,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:i[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyPromiseQueue:()=>i});const i=class{#e=[];#t=!1;#i={};#r=new Set;isRunning(){return this.#t}async#s(e){if(e&&"function"==typeof e.task&&"function"==typeof e.resolve&&"function"==typeof e.reject){const{task:t,resolve:i,reject:r,delay:s,id:n}=e;try{if(n&&this.#r.has(n))return r(new Error("The function was canceled on TinyPromiseQueue.")),this.#r.delete(n),this.#t=!1,void this.#n();s&&n&&await new Promise((e=>{const t=setTimeout((()=>{delete this.#i[n],e(null)}),s);this.#i[n]=t})),i(await t())}catch(e){r(e)}finally{this.#t=!1,this.#n()}}}async#u(){const e=[];for(;this.#e.length&&"POINT_MARKER"===this.#e[0]?.marker;)e.push(this.#e.shift());if(0===e.length)return this.#t=!1,void this.#n();await Promise.all(e.map((({task:e,resolve:t,reject:i,id:r})=>new Promise((async s=>{if(r&&this.#r.has(r))return this.#r.delete(r),i(new Error("The function was canceled on TinyPromiseQueue.")),void s(!0);await e().then(t).catch(i),s(!0)}))))),this.#t=!1,this.#n()}async#n(){if(!this.#t&&0!==this.#e.length)if(this.#t=!0,"string"!=typeof this.#e[0]?.marker||"POINT_MARKER"!==this.#e[0]?.marker){const e=this.#e.shift();this.#s(e)}else this.#u()}getIndexById(e){return this.#e.findIndex((t=>t.id===e))}getQueuedIds(){return this.#e.map(((e,t)=>({index:t,id:e.id}))).filter((e=>"string"==typeof e.id))}reorderQueue(e,t){if("number"!=typeof e||"number"!=typeof t||e<0||t<0||e>=this.#e.length||t>=this.#e.length)return;const[i]=this.#e.splice(e,1);this.#e.splice(t,0,i)}async enqueuePoint(e,t){if("function"!=typeof e)return Promise.reject(new Error("Task must be a function returning a Promise."));if(void 0!==t&&"string"!=typeof t)throw new Error('The "id" parameter must be a string.');return this.#t?new Promise(((i,r)=>{this.#e.push({marker:"POINT_MARKER",task:e,resolve:i,reject:r,id:t}),this.#n()})):e()}enqueue(e,t,i){if("function"!=typeof e)return Promise.reject(new Error("Task must be a function returning a Promise."));if(void 0!==t&&("number"!=typeof t||t<0))return Promise.reject(new Error("Delay must be a positive number or undefined."));if(void 0!==i&&"string"!=typeof i)throw new Error('The "id" parameter must be a string.');return new Promise(((r,s)=>{this.#e.push({task:e,resolve:r,reject:s,id:i,delay:t}),this.#n()}))}cancelTask(e){if("string"!=typeof e)throw new Error('The "id" parameter must be a string.');let t=!1;e in this.#i&&(clearTimeout(this.#i[e]),delete this.#i[e],t=!0);const i=this.getIndexById(e);if(-1!==i){const[e]=this.#e.splice(i,1);e?.reject?.(new Error("The function was canceled on TinyPromiseQueue.")),t=!0}return t&&this.#r.add(e),t}};window.TinyPromiseQueue=t.TinyPromiseQueue})();
@@ -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
- * @typedef {{ exp: number, level: number, totalExp: number }} UserResult
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
- * @typedef {{ exp: number, level: number, totalExp: any }} UserEditor
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 {UserResult} The updated user object.
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 = this.expLevel * user.level;
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 = this.expLevel * user.level;
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 {{ exp: number, level: number }} user - The user object containing experience and level properties.
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.expLevel * p;
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
- return Math.floor(Math.random() * this.giveExp) + 1 * multi;
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 {{ level: number }} user - The user object containing the level.
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.expLevel * user.level;
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 {{ level: number }} user - The user object containing the level.
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
- return this.expLevel * user.level;
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 {UserResult} The updated user object.
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 {UserResult} The updated user object.
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 {UserResult} The updated user object.
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