derafu-js 0.1.0

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/src/utils.js ADDED
@@ -0,0 +1,353 @@
1
+ /*! Utility functions | (c) 2025 Derafu DEV | MIT */
2
+
3
+ // Object where utility functions will be assigned.
4
+ const Utils = {};
5
+
6
+ /**
7
+ * Generates a secure password using alphanumeric characters and special
8
+ * symbols.
9
+ * If the browser supports crypto.getRandomValues(), it is used to ensure
10
+ * cryptographic randomness. Otherwise, Math.random() is used as a less secure
11
+ * alternative.
12
+ *
13
+ * @param {number} length - Desired password length. Default length is 12
14
+ * characters.
15
+ * @returns {string} Password generated securely.
16
+ */
17
+ Utils.generatePassword = function (length) {
18
+ 'use strict';
19
+
20
+ // Default length of 12 characters, considered secure for most purposes.
21
+ length = length || 12;
22
+
23
+ // Warning if the requested length is less than 8 characters.
24
+ if (length < 8) {
25
+ console.warn(
26
+ 'Password length is less than 8 characters. A minimum ' +
27
+ 'length of 8 characters is recommended for greater security.',
28
+ );
29
+ }
30
+
31
+ const charset =
32
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~`|}{[]:;?><,./-=';
33
+ let password = '';
34
+
35
+ if (window.crypto && window.crypto.getRandomValues) {
36
+ const array = new Uint32Array(length);
37
+ window.crypto.getRandomValues(array);
38
+
39
+ for (let i = 0; i < length; i++) {
40
+ password += charset.charAt(array[i] % charset.length);
41
+ }
42
+ } else {
43
+ // If for some reason the browser doesn't support crypto, which is
44
+ // unlikely in modern browsers.
45
+ for (let i = 0; i < length; i++) {
46
+ password += charset.charAt(
47
+ Math.floor(Math.random() * charset.length),
48
+ );
49
+ }
50
+ }
51
+
52
+ return password;
53
+ };
54
+
55
+ /**
56
+ * Gets the value of a cookie by its name.
57
+ *
58
+ * @param {string} name - Cookie name.
59
+ * @returns {string|null} Cookie value or null if not found.
60
+ */
61
+ Utils.getCookie = function (name) {
62
+ 'use strict';
63
+
64
+ // Check if we're in a browser environment.
65
+ if (typeof document === 'undefined') {
66
+ // Node.js environment - cookies not available.
67
+ return null;
68
+ }
69
+
70
+ const cookieArray = document.cookie.split(';');
71
+ for (let i = 0; i < cookieArray.length; i++) {
72
+ const cookiePair = cookieArray[i].split('=');
73
+ if (name === cookiePair[0].trim()) {
74
+ return decodeURIComponent(cookiePair[1]);
75
+ }
76
+ }
77
+ return null;
78
+ };
79
+
80
+ /**
81
+ * Gets the user's language, either from a cookie or from the browser, and
82
+ * verifies if it's in a list of allowed languages. If not, returns a default
83
+ * language.
84
+ *
85
+ * @param {string} [cookieName] - Cookie name to search for. Default is
86
+ * 'app_user_language'.
87
+ * @returns {string} Language code to use.
88
+ */
89
+ Utils.getUserLanguage = function (cookieName) {
90
+ 'use strict';
91
+ if (Utils._userLanguage) {
92
+ return Utils._userLanguage;
93
+ }
94
+
95
+ // List of allowed languages (the first language will be the default).
96
+ // Format: Accept-Language HTTP header (generally represented in lowercase).
97
+ const allowedLanguages = ['es-cl', 'es-es', 'en-us'];
98
+
99
+ // Check if we're in a browser environment.
100
+ if (typeof navigator === 'undefined') {
101
+ // Node.js environment - return default language.
102
+ Utils._userLanguage = allowedLanguages[0];
103
+ return allowedLanguages[0];
104
+ }
105
+
106
+ // Try to get language from a cookie.
107
+ if (cookieName === undefined) {
108
+ cookieName = 'app_user_language';
109
+ }
110
+ const cookieLanguage = Utils.getCookie(cookieName);
111
+ if (cookieLanguage) {
112
+ const cookieLanguageLower = cookieLanguage.toLowerCase();
113
+ if (allowedLanguages.includes(cookieLanguageLower)) {
114
+ Utils._userLanguage = cookieLanguageLower;
115
+ return cookieLanguageLower;
116
+ }
117
+ }
118
+
119
+ // Get browser language.
120
+ const browserLanguage = navigator.language.toLowerCase();
121
+ if (allowedLanguages.includes(browserLanguage)) {
122
+ Utils._userLanguage = browserLanguage;
123
+ return browserLanguage;
124
+ }
125
+
126
+ // Return default language if browser language is not in the list.
127
+ Utils._userLanguage = allowedLanguages[0];
128
+ return allowedLanguages[0];
129
+ };
130
+ Utils._userLanguage = null;
131
+
132
+ /**
133
+ * Method that formats a number using thousands separator. If 'decimalDigits' is
134
+ * not specified, 0 is assumed. If 'true' is passed, 2 decimals are used.
135
+ *
136
+ * @param {number} n - Number to format.
137
+ * @param {number|boolean} decimalDigits - Number of decimal digits or true to
138
+ * use 2. If not provided, 0 is assumed.
139
+ * @param {string} [language] - Language to use for number formatting. Default
140
+ * is obtained with Utils.getUserLanguage().
141
+ * @returns {string} Number formatted according to locale.
142
+ */
143
+ Utils.num = function (n, decimalDigits, language) {
144
+ 'use strict';
145
+ if (typeof n === 'undefined') {
146
+ return '0';
147
+ }
148
+
149
+ if (typeof n === 'string') {
150
+ n = n.includes('.') ? parseFloat(n) : parseInt(n, 10);
151
+ }
152
+
153
+ if (typeof n !== 'number' || isNaN(n)) {
154
+ console.error('num: The provided argument is not a valid number.');
155
+ return n;
156
+ }
157
+
158
+ if (decimalDigits === undefined) {
159
+ decimalDigits = 0;
160
+ }
161
+ decimalDigits = decimalDigits === true ? 2 : decimalDigits;
162
+ if (
163
+ typeof decimalDigits !== 'number' ||
164
+ isNaN(decimalDigits) ||
165
+ decimalDigits < 0
166
+ ) {
167
+ console.error(
168
+ 'num: The provided number of decimal digits is not valid.',
169
+ );
170
+ return n;
171
+ }
172
+
173
+ const options = {
174
+ minimumFractionDigits: decimalDigits,
175
+ maximumFractionDigits: decimalDigits,
176
+ };
177
+ if (language === undefined) {
178
+ language = Utils.getUserLanguage();
179
+ }
180
+
181
+ // Map language codes to proper locales for number formatting.
182
+ let locale = language;
183
+ if (language === 'es-cl') {
184
+ locale = 'es-CL';
185
+ } else if (language === 'es-es') {
186
+ locale = 'es-ES';
187
+ } else if (language === 'en-us') {
188
+ locale = 'en-US';
189
+ }
190
+
191
+ return new Intl.NumberFormat(locale, options).format(n);
192
+ };
193
+
194
+ /**
195
+ * Checks if an object, array, or primitive value is empty. Considers as empty
196
+ * any null, undefined, empty string, NaN number, invalid date, empty array, a
197
+ * literal object without properties, or an HTML element of type input, select,
198
+ * or textarea with empty value.
199
+ *
200
+ * @param {Object|Array|Date|String|Number|HTMLElement} obj - Object, array,
201
+ * primitive value, or HTML element to check.
202
+ * @returns {boolean} Returns true if empty, otherwise false.
203
+ */
204
+ Utils.empty = function (obj) {
205
+ 'use strict';
206
+ if (obj === null || obj === undefined || obj === '') {
207
+ return true;
208
+ }
209
+ if (typeof obj === 'number' && isNaN(obj)) {
210
+ return true;
211
+ }
212
+ if (obj instanceof Date) {
213
+ if (isNaN(obj.getTime())) {
214
+ return true;
215
+ } else {
216
+ return false;
217
+ }
218
+ }
219
+ if (Array.isArray(obj)) {
220
+ return obj.length === 0;
221
+ }
222
+ if (
223
+ obj instanceof HTMLInputElement ||
224
+ obj instanceof HTMLSelectElement ||
225
+ obj instanceof HTMLTextAreaElement
226
+ ) {
227
+ return obj.value === '';
228
+ }
229
+ if (typeof obj === 'object') {
230
+ return Object.keys(obj).length === 0;
231
+ }
232
+ return false;
233
+ };
234
+
235
+ /**
236
+ * Checks if a value is an integer representation.
237
+ * Considers integers both numbers and strings that can be converted to integers.
238
+ *
239
+ * @param {string|number} value - Value to check.
240
+ * @returns {boolean} Returns true if the value is an integer, false otherwise.
241
+ */
242
+ Utils.isInt = function (value) {
243
+ 'use strict';
244
+ return typeof value === 'number'
245
+ ? Number.isInteger(value)
246
+ : Number.isInteger(parseFloat(value)) &&
247
+ !isNaN(value) &&
248
+ parseFloat(value).toString() === value.toString();
249
+ };
250
+
251
+ /**
252
+ * Checks if a value is a floating-point number representation. Considers
253
+ * floating-point numbers both numbers and strings that can be converted to
254
+ * floating-point numbers, excluding integers.
255
+ *
256
+ * @param {string|number} value - Value to check.
257
+ * @returns {boolean} Returns true if the value is a floating-point number,
258
+ * false otherwise.
259
+ */
260
+ Utils.isFloat = function (value) {
261
+ 'use strict';
262
+
263
+ // If it's a number, but not an integer, then it's a floating-point number.
264
+ if (typeof value === 'number') {
265
+ return !Number.isInteger(value) && !isNaN(value);
266
+ }
267
+
268
+ // If it's a string, check if it's a floating-point number.
269
+ // First, discard strings that cannot be converted to numbers.
270
+ if (typeof value === 'string' && !isNaN(value) && value.trim() !== '') {
271
+ // Then, discard integers.
272
+ if (Utils.isInt(value)) {
273
+ return false;
274
+ }
275
+ // Finally, check that the string contains a decimal point.
276
+ return value.includes('.');
277
+ }
278
+
279
+ // For all other cases, it's not a floating-point number.
280
+ return false;
281
+ };
282
+
283
+ /**
284
+ * Generates an object with the values of the specified keys from a source object.
285
+ *
286
+ * @param {Array} keys - List of keys whose values are desired to be extracted
287
+ * from the source object.
288
+ * @param {Object} source - Source object from which values will be extracted.
289
+ * @returns {Object} - Object with the values of the specified keys, where each
290
+ * key is the original key name and the value is the corresponding value in the
291
+ * source object.
292
+ */
293
+ Utils.keyValues = function (keys, source) {
294
+ const values = {};
295
+ keys.forEach(function (key) {
296
+ values[key] = source[key];
297
+ });
298
+ return values;
299
+ };
300
+
301
+ /**
302
+ * Gets the value of an object based on a selector that allows concatenating
303
+ * literal texts and object property values.
304
+ *
305
+ * The selector can include:
306
+ *
307
+ * - Literal texts between double quotes "".
308
+ * - Object properties between parentheses ().
309
+ *
310
+ * @param {Object} obj - The object from which values will be extracted.
311
+ * @param {string} path - The selector that specifies how to access and
312
+ * concatenate object values.
313
+ * @returns {string} - The result of concatenating the values specified by the
314
+ * selector.
315
+ */
316
+ Utils.selector = function (obj, path) {
317
+ const regex = /\(([^)]+)\)|"([^"]*)"/g;
318
+ let match;
319
+ let result = '';
320
+ while ((match = regex.exec(path)) !== null) {
321
+ // It's a selector, get value from object.
322
+ if (match[1]) {
323
+ result +=
324
+ match[1]
325
+ .split('.')
326
+ .reduce((acc, part) => acc && acc[part], obj) || '';
327
+ }
328
+ // It's a literal text, add as is.
329
+ else if (match[2]) {
330
+ result += match[2];
331
+ }
332
+ }
333
+ // Return obtained result.
334
+ return result.trim();
335
+ };
336
+
337
+ /**
338
+ * Rounds a number to a specified number of decimal places with precise decimal
339
+ * arithmetic. This function avoids floating-point precision issues that can
340
+ * occur with standard Math.round().
341
+ *
342
+ * @param {number} n - The number to round.
343
+ * @param {number} decimals - The number of decimal places to round to.
344
+ * @returns {number} The rounded number.
345
+ */
346
+ Utils.round = function (n, decimals) {
347
+ return Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
348
+ };
349
+
350
+ // Export module for use in Node.js.
351
+ if (typeof module === 'object' && module.exports) {
352
+ module.exports = Utils;
353
+ }
@@ -0,0 +1,66 @@
1
+ /*! Chilean validation | (c) 2025 Derafu DEV | MIT */
2
+
3
+ // Object where Chilean RUT validation functions will be assigned.
4
+ const ValidationL10nCl = {};
5
+
6
+ /**
7
+ * Validates and optionally formats a Chilean RUT (Rol Único Tributario).
8
+ * Verifies that the check digit is correct and can return the formatted RUT.
9
+ *
10
+ * @param {string|number} value - The RUT to validate, can include dots, commas,
11
+ * and dash.
12
+ * @param {boolean} [format=false] - If true, returns the formatted RUT with
13
+ * thousands separator and dash. If false or not provided, only validates the RUT.
14
+ * @returns {boolean|string} If format is false, returns true if the RUT is
15
+ * valid, otherwise false. If format is true, returns the formatted RUT if
16
+ * valid, otherwise false.
17
+ */
18
+ ValidationL10nCl.rut = function (value, format) {
19
+ 'use strict';
20
+ const dv = value.slice(-1).toUpperCase();
21
+ const rut = value
22
+ .replace(/\./g, '')
23
+ .replace(/,/g, '')
24
+ .replace(/-/, '')
25
+ .slice(0, -1);
26
+ if (dv !== 'K' && isNaN(parseInt(dv, 10))) {
27
+ return false;
28
+ }
29
+ if (dv !== ValidationL10nCl.rut.dv(rut)) {
30
+ return false;
31
+ }
32
+ return format === true
33
+ ? `${
34
+ typeof Utils !== 'undefined'
35
+ ? Utils.num(rut, 0, 'es-CL')
36
+ : rut.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
37
+ }-${dv}`
38
+ : true;
39
+ };
40
+
41
+ /**
42
+ * Calculates the check digit for a Chilean RUT (Rol Único Tributario).
43
+ * Uses the standard algorithm to calculate the digit based on the RUT number.
44
+ *
45
+ * @param {string|number} numero - RUT without dots or check digit.
46
+ * @returns {string} RUT check digit.
47
+ */
48
+ ValidationL10nCl.rut.dv = function (numero) {
49
+ 'use strict';
50
+ let suma = 0;
51
+ let factor = 2;
52
+ const rutReverso = numero.toString().split('').reverse().join('');
53
+
54
+ for (const digito of rutReverso) {
55
+ suma += parseInt(digito, 10) * factor;
56
+ factor = factor === 7 ? 2 : factor + 1;
57
+ }
58
+
59
+ const dv = 11 - (suma % 11);
60
+ return dv === 11 ? '0' : dv === 10 ? 'K' : dv.toString();
61
+ };
62
+
63
+ // Export module for use in node.js.
64
+ if (typeof module === 'object' && module.exports) {
65
+ module.exports = ValidationL10nCl;
66
+ }