@reldens/utils 0.46.0 → 0.48.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/lib/env-var.js CHANGED
@@ -4,12 +4,14 @@
4
4
  *
5
5
  */
6
6
 
7
+ const sc = require('./shortcuts');
8
+
7
9
  class EnvVar
8
10
  {
9
11
 
10
12
  string(obj, key, defaultValue)
11
13
  {
12
- const val = obj[key];
14
+ let val = obj[key];
13
15
  if('string' === typeof val){
14
16
  return val;
15
17
  }
@@ -18,7 +20,7 @@ class EnvVar
18
20
 
19
21
  nonEmptyString(obj, key, defaultValue)
20
22
  {
21
- const val = obj[key];
23
+ let val = obj[key];
22
24
  if('string' === typeof val && '' !== val.trim()){
23
25
  return val;
24
26
  }
@@ -27,16 +29,17 @@ class EnvVar
27
29
 
28
30
  number(obj, key, defaultValue)
29
31
  {
30
- const val = obj[key];
31
- if(!isNaN(Number(val)) && '' !== val && null !== val){
32
- return Number(val);
32
+ let val = obj[key];
33
+ let parsed = sc.parseNumber(val);
34
+ if(parsed){
35
+ return parsed;
33
36
  }
34
37
  return defaultValue;
35
38
  }
36
39
 
37
40
  boolean(obj, key, defaultValue)
38
41
  {
39
- const val = obj[key];
42
+ let val = obj[key];
40
43
  if('true' === val || '1' === val){
41
44
  return true;
42
45
  }
@@ -46,6 +49,51 @@ class EnvVar
46
49
  return defaultValue;
47
50
  }
48
51
 
52
+ array(obj, key, defaultValue, separator = ',')
53
+ {
54
+ let val = obj[key];
55
+ let parsed = sc.splitToArray(val, separator);
56
+ if(parsed){
57
+ return parsed;
58
+ }
59
+ return defaultValue;
60
+ }
61
+
62
+ url(obj, key, defaultValue)
63
+ {
64
+ let val = obj[key];
65
+ if(!sc.isString(val)){
66
+ return defaultValue;
67
+ }
68
+ if(sc.isValidUrl(val)){
69
+ return val;
70
+ }
71
+ return defaultValue;
72
+ }
73
+
74
+ json(obj, key, defaultValue)
75
+ {
76
+ let val = obj[key];
77
+ if(!sc.isString(val)){
78
+ return defaultValue;
79
+ }
80
+ return sc.parseJson(val, defaultValue);
81
+ }
82
+
83
+ integer(obj, key, defaultValue, min, max)
84
+ {
85
+ let val = this.number(obj, key, defaultValue);
86
+ if(val !== defaultValue && sc.isValidInteger(val, min, max)){
87
+ return val;
88
+ }
89
+ return defaultValue;
90
+ }
91
+
92
+ port(obj, key, defaultValue)
93
+ {
94
+ return this.integer(obj, key, defaultValue, 1, 65535);
95
+ }
96
+
49
97
  }
50
98
 
51
99
  module.exports = new EnvVar();
@@ -28,8 +28,21 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
28
28
  this.debug = false;
29
29
  }
30
30
 
31
+ validateEventKey(eventKey)
32
+ {
33
+ return !sc.hasDangerousKeys(null, eventKey);
34
+ }
35
+
31
36
  onWithKey(eventName, callback, uniqueRemoveKey, masterKey)
32
37
  {
38
+ if(!this.validateEventKey(eventName) || !this.validateEventKey(uniqueRemoveKey)){
39
+ Logger.critical('Invalid event key detected: '+eventName+' or '+uniqueRemoveKey);
40
+ return false;
41
+ }
42
+ if(masterKey && !this.validateEventKey(masterKey)){
43
+ Logger.critical('Invalid master key detected: '+masterKey);
44
+ return false;
45
+ }
33
46
  if(
34
47
  sc.hasOwn(this.eventsByRemoveKeys, uniqueRemoveKey)
35
48
  || (
@@ -62,6 +75,14 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
62
75
  */
63
76
  offWithKey(uniqueRemoveKey, masterKey)
64
77
  {
78
+ if(!this.validateEventKey(uniqueRemoveKey)){
79
+ Logger.critical('Invalid remove key detected: '+uniqueRemoveKey);
80
+ return false;
81
+ }
82
+ if(masterKey && !this.validateEventKey(masterKey)){
83
+ Logger.critical('Invalid master key detected: '+masterKey);
84
+ return false;
85
+ }
65
86
  if(masterKey && !sc.hasOwn(this.eventsByRemoveKeys, masterKey)){
66
87
  Logger.debug('Event not found by masterKey "'+masterKey+'".');
67
88
  return false;
@@ -74,8 +95,16 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
74
95
  ? this.eventsByRemoveKeys[masterKey][uniqueRemoveKey]
75
96
  : this.eventsByRemoveKeys[uniqueRemoveKey];
76
97
 
98
+ if(!this.validateEventKey(eventToRemove.eventName)){
99
+ Logger.critical('Invalid event name in stored event: '+eventToRemove.eventName);
100
+ return false;
101
+ }
77
102
  let dataArr = this.listeners(eventToRemove.eventName);
78
103
  let currentListenerIndex = dataArr.indexOf(eventToRemove.callback);
104
+ if(-1 === currentListenerIndex){
105
+ Logger.debug('Event listener not found in _events array.');
106
+ return false;
107
+ }
79
108
  this._events[eventToRemove.eventName].splice(currentListenerIndex, 1);
80
109
  if(0 === this._events[eventToRemove.eventName].length){
81
110
  delete this._events[eventToRemove.eventName];
@@ -95,6 +124,10 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
95
124
  */
96
125
  offByMasterKey(masterKey)
97
126
  {
127
+ if(!this.validateEventKey(masterKey)){
128
+ Logger.critical('Invalid master key detected: '+masterKey);
129
+ return false;
130
+ }
98
131
  if(!sc.hasOwn(this.eventsByRemoveKeys, masterKey)){
99
132
  Logger.debug('Events not found by masterKey "'+masterKey+'".');
100
133
  return false;
@@ -104,8 +137,14 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
104
137
  Logger.debug('Removing events by masterKey: '+masterKey, Object.keys(this.eventsByRemoveKeys[masterKey]));
105
138
  for(let uniqueRemoveKey of Object.keys(this.eventsByRemoveKeys[masterKey])){
106
139
  let eventToRemove = this.eventsByRemoveKeys[masterKey][uniqueRemoveKey];
140
+ if(!this.validateEventKey(eventToRemove.eventName)){
141
+ continue;
142
+ }
107
143
  let dataArr = this.listeners(eventToRemove.eventName);
108
144
  let currentListenerIndex = dataArr.indexOf(eventToRemove.callback);
145
+ if(-1 === currentListenerIndex){
146
+ continue;
147
+ }
109
148
  this._events[eventToRemove.eventName].splice(currentListenerIndex, 1);
110
149
  if(0 === this._events[eventToRemove.eventName].length){
111
150
  delete this._events[eventToRemove.eventName];
@@ -116,7 +155,11 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
116
155
 
117
156
  on(key, callback)
118
157
  {
119
- if(this.debug !== false){
158
+ if(!this.validateEventKey(key)){
159
+ Logger.critical('Invalid event key detected: '+key);
160
+ return false;
161
+ }
162
+ if(false !== this.debug){
120
163
  this.logDebugEvent(key, 'Listen');
121
164
  }
122
165
  super.on(key, callback);
@@ -124,7 +167,11 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
124
167
 
125
168
  async emit(key, ...args)
126
169
  {
127
- if(this.debug !== false){
170
+ if(!this.validateEventKey(key)){
171
+ Logger.critical('Invalid event key detected: '+key);
172
+ return false;
173
+ }
174
+ if(false !== this.debug){
128
175
  this.logDebugEvent(key, 'Fire');
129
176
  }
130
177
  await super.emit(key, ...args);
package/lib/logger.js CHANGED
@@ -4,6 +4,8 @@
4
4
  *
5
5
  */
6
6
 
7
+ const sc = require('./shortcuts');
8
+
7
9
  class Logger
8
10
  {
9
11
 
@@ -21,15 +23,14 @@ class Logger
21
23
 
22
24
  constructor()
23
25
  {
24
- // @TODO - BETA
25
- // - Implement different log systems (console.log, files logs, db log?).
26
- // - Implement notifications system (email?), and make it configurable for the different log levels.
27
26
  let context = this.context();
28
- this.enableTraceBack = '';
29
- this.logLevelBack = 3;
30
- this.forcedDisabled = Boolean(context.RELDENS_FORCED_DISABLED_LOGS || false);
31
- this.addTimeStamp = Boolean(context.RELDENS_INCLUDE_LOGS_TIMESTAMP || true);
27
+ this.enableTraceBack = sc.get(context, 'RELDENS_LOGS_ENABLE_TRACE_BACK', '');
28
+ this.logLevelBack = sc.get(context, 'RELDENS_LOGS_LEVEL_BACK', 3);
32
29
  this.callback = false;
30
+ this.forcedDisabled = Boolean(sc.get(context, 'RELDENS_LOGS_FORCED_DISABLED', false));
31
+ this.addTimeStamp = Boolean(sc.get(context, 'RELDENS_LOGS_INCLUDE_TIMESTAMP', true));
32
+ this.maxLogArgLength = sc.get(context, 'RELDENS_LOGS_MAX_ARGUMENT_LENGTH', 1000);
33
+ this.maxStackTraceLength = sc.get(context, 'RELDENS_LOGS_MAX_STACK_TRACE_LENGTH', 2000);
33
34
  }
34
35
 
35
36
  context()
@@ -94,7 +95,8 @@ class Logger
94
95
  log(levelLabel, ...args)
95
96
  {
96
97
  let date = !this.addTimeStamp ? '' : (new Date()).toISOString().slice(0, 19).replace('T', ' ')+' - ';
97
- this.logWithCallback(date+levelLabel.toUpperCase()+' -', ...args);
98
+ let sanitizedArgs = args.map(arg => sc.isString(arg) ? sc.cleanMessage(arg, this.maxLogArgLength) : arg);
99
+ this.logWithCallback(date+levelLabel.toUpperCase()+' -', ...sanitizedArgs);
98
100
  if(-1 !== this.enableTraceFor().indexOf('all') || -1 !== this.enableTraceFor().indexOf(levelLabel)){
99
101
  if('function' !== typeof Error?.captureStackTrace){
100
102
  this.logWithCallback('Error.captureStackTrace is not available.', typeof Error?.captureStackTrace);
@@ -102,7 +104,8 @@ class Logger
102
104
  }
103
105
  let stackHolder = {};
104
106
  Error.captureStackTrace(stackHolder, levelLabel);
105
- this.logWithCallback(stackHolder.stack);
107
+ let sanitizedStack = sc.cleanMessage(stackHolder.stack, this.maxStackTraceLength);
108
+ this.logWithCallback(sanitizedStack);
106
109
  }
107
110
  return this;
108
111
  }
@@ -43,6 +43,13 @@ class SchemaValidator extends ValidatorInterface
43
43
  }
44
44
  for(let i of Object.keys(schema)){
45
45
  let validate = schema[i];
46
+ if(validate.required && !sc.hasOwn(obj, i)){
47
+ Logger.debug('Required property missing.', i);
48
+ return false;
49
+ }
50
+ if(!sc.hasOwn(obj, i) && !validate.required){
51
+ continue;
52
+ }
46
53
  if(!this.isValidSchema(obj[i], validate, i)){
47
54
  return false;
48
55
  }
@@ -61,12 +68,36 @@ class SchemaValidator extends ValidatorInterface
61
68
  Logger.debug('No schema type provided.', schema, obj, objectKey);
62
69
  return false;
63
70
  }
71
+ if(schema.custom && sc.isFunction(schema.custom)){
72
+ if(!schema.custom(obj)){
73
+ Logger.debug('Custom validation failed.', objectKey, obj, schema);
74
+ return false;
75
+ }
76
+ }
77
+ if(schema.enum && sc.isArray(schema.enum)){
78
+ if(-1 === schema.enum.indexOf(obj)){
79
+ Logger.debug('Value not in enum.', objectKey, obj, schema.enum);
80
+ return false;
81
+ }
82
+ }
64
83
  switch(schema.type){
65
84
  case 'string':
66
85
  if(!sc.isString(obj)){
67
86
  Logger.debug('Object is not a string.', objectKey, obj, schema);
68
87
  return false;
69
88
  }
89
+ if(schema.min && obj.length < schema.min){
90
+ Logger.debug('String too short.', objectKey, obj.length, schema.min);
91
+ return false;
92
+ }
93
+ if(schema.max && obj.length > schema.max){
94
+ Logger.debug('String too long.', objectKey, obj.length, schema.max);
95
+ return false;
96
+ }
97
+ if(schema.pattern && !schema.pattern.test(obj)){
98
+ Logger.debug('String pattern mismatch.', objectKey, obj, schema.pattern);
99
+ return false;
100
+ }
70
101
  break;
71
102
  case 'number':
72
103
  if(!sc.isInt(obj)){
@@ -75,18 +106,42 @@ class SchemaValidator extends ValidatorInterface
75
106
  return false;
76
107
  }
77
108
  }
109
+ if(schema.min && obj < schema.min){
110
+ Logger.debug('Number too small.', objectKey, obj, schema.min);
111
+ return false;
112
+ }
113
+ if(schema.max && obj > schema.max){
114
+ Logger.debug('Number too large.', objectKey, obj, schema.max);
115
+ return false;
116
+ }
78
117
  break;
79
118
  case 'int':
80
119
  if(!sc.isInt(obj)){
81
120
  Logger.debug('Object is not an integer.', objectKey, obj, schema);
82
121
  return false;
83
122
  }
123
+ if(schema.min && obj < schema.min){
124
+ Logger.debug('Integer too small.', objectKey, obj, schema.min);
125
+ return false;
126
+ }
127
+ if(schema.max && obj > schema.max){
128
+ Logger.debug('Integer too large.', objectKey, obj, schema.max);
129
+ return false;
130
+ }
84
131
  break;
85
132
  case 'float':
86
133
  if(!sc.isFloat(obj)){
87
134
  Logger.debug('Object is not a float.', objectKey, obj, schema);
88
135
  return false;
89
136
  }
137
+ if(schema.min && obj < schema.min){
138
+ Logger.debug('Float too small.', objectKey, obj, schema.min);
139
+ return false;
140
+ }
141
+ if(schema.max && obj > schema.max){
142
+ Logger.debug('Float too large.', objectKey, obj, schema.max);
143
+ return false;
144
+ }
90
145
  break;
91
146
  case 'boolean':
92
147
  if(!sc.isBoolean(obj)){
@@ -105,6 +160,14 @@ class SchemaValidator extends ValidatorInterface
105
160
  Logger.debug('Object is not an array.', objectKey, obj, schema);
106
161
  return false;
107
162
  }
163
+ if(schema.min && obj.length < schema.min){
164
+ Logger.debug('Array too short.', objectKey, obj.length, schema.min);
165
+ return false;
166
+ }
167
+ if(schema.max && obj.length > schema.max){
168
+ Logger.debug('Array too long.', objectKey, obj.length, schema.max);
169
+ return false;
170
+ }
108
171
  if(schema.valuesType){
109
172
  for(let i of obj){
110
173
  if(!this.isValidSchema(i, {type: schema.valuesType}, objectKey)){
package/lib/shortcuts.js CHANGED
@@ -47,6 +47,11 @@ class Shortcuts
47
47
  return -1 !== dataArray.indexOf(value);
48
48
  }
49
49
 
50
+ isNotEmptyArray(dataArray)
51
+ {
52
+ return (this.isArray(dataArray) && 0 < dataArray.length);
53
+ }
54
+
50
55
  isFunction(callback)
51
56
  {
52
57
  return (callback && 'function' === typeof callback);
@@ -88,12 +93,32 @@ class Shortcuts
88
93
  return 'boolean' === typeof value;
89
94
  }
90
95
 
96
+ hasDangerousKeys(obj, key = null)
97
+ {
98
+ let dangerousKeys = ['__proto__', 'constructor', 'prototype'];
99
+ if(key){
100
+ return -1 !== dangerousKeys.indexOf(key);
101
+ }
102
+ if(!this.isObject(obj)){
103
+ return false;
104
+ }
105
+ for(let dangerousKey of dangerousKeys){
106
+ if(this.hasOwn(obj, dangerousKey)){
107
+ return true;
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+
91
113
  deepMergeProperties(target, source)
92
114
  {
93
115
  if(!this.isObject(target) || !this.isObject(source)){
94
116
  return false;
95
117
  }
96
118
  for(let key of Object.keys(source)){
119
+ if(this.hasDangerousKeys(null, key)){
120
+ continue;
121
+ }
97
122
  if(this.isObject(source[key])){
98
123
  if(!this.hasOwn(target, key)){
99
124
  target[key] = source[key];
@@ -152,8 +177,8 @@ class Shortcuts
152
177
  if(!sortField){
153
178
  return collection;
154
179
  }
155
- let directionValue = 'act' === direction ? 1 : -1;
156
- let directionOpposite = 'act' === direction ? -1 : 1;
180
+ let directionValue = 'asc' === direction ? 1 : -1;
181
+ let directionOpposite = 'asc' === direction ? -1 : 1;
157
182
  return collection.sort((a, b) => {
158
183
  return a[sortField] > b[sortField] ? directionValue : directionOpposite;
159
184
  });
@@ -177,11 +202,16 @@ class Shortcuts
177
202
 
178
203
  parseJson(jsonString, defaultReturn = false)
179
204
  {
205
+ let parsed;
180
206
  try {
181
- return JSON.parse(jsonString);
207
+ parsed = JSON.parse(jsonString);
182
208
  } catch (e) {
183
209
  return defaultReturn;
184
210
  }
211
+ if(this.hasDangerousKeys(parsed)){
212
+ return defaultReturn;
213
+ }
214
+ return parsed;
185
215
  }
186
216
 
187
217
  deepJsonClone(obj)
@@ -302,7 +332,7 @@ class Shortcuts
302
332
  let obj = {};
303
333
  for(let [key, value] of formData){
304
334
  if(obj[key] !== undefined){
305
- if(!Array.isArray(obj[key])){
335
+ if(!this.isArray(obj[key])){
306
336
  obj[key] = [obj[key]];
307
337
  }
308
338
  obj[key].push(value);
@@ -316,7 +346,7 @@ class Shortcuts
316
346
  removeFromArray(valuesArray, removeValues)
317
347
  {
318
348
  return valuesArray.filter((value) => {
319
- return removeValues.indexOf(value) === -1;
349
+ return -1 === removeValues.indexOf(value);
320
350
  });
321
351
  }
322
352
 
@@ -404,7 +434,7 @@ class Shortcuts
404
434
  if(!message){
405
435
  return '';
406
436
  }
407
- let text = message.toString().replace(/\\/g, '');
437
+ let text = message.replace(/\\/g, '').replace(/[\r\n\t]/g, ' ');
408
438
  if(0 < characterLimit){
409
439
  return text.substring(0, characterLimit);
410
440
  }
@@ -413,8 +443,7 @@ class Shortcuts
413
443
 
414
444
  slugify(text)
415
445
  {
416
- return (text || '')
417
- .replace(/&/g, ' and ')
446
+ return (text || '').replace(/&/g, ' and ')
418
447
  .replace(/[^a-zA-Z0-9]/g, ' ')
419
448
  .trim()
420
449
  .replace(/\s+/g, '-')
@@ -423,8 +452,7 @@ class Shortcuts
423
452
 
424
453
  isValidIsoCode(isoCode)
425
454
  {
426
- let isoLanguageCodeRegex = /^[a-zA-Z]{2}$/;
427
- return isoLanguageCodeRegex.test(isoCode);
455
+ return (/^[a-zA-Z]{2}$/).test(isoCode);
428
456
  }
429
457
 
430
458
  sanitize(input)
@@ -432,32 +460,207 @@ class Shortcuts
432
460
  if(!input){
433
461
  return '';
434
462
  }
435
- return input
436
- .replace(/&(?![a-zA-Z0-9#]+;)/g, '&amp;')
463
+ return input.replace(/&/g, '&amp;')
437
464
  .replace(/</g, '&lt;')
438
465
  .replace(/>/g, '&gt;')
439
466
  .replace(/"/g, '&quot;')
440
467
  .replace(/'/g, '&#x27;')
441
468
  .replace(/\//g, '&#x2F;')
442
- .replace(/`/g, '&#x60;');
469
+ .replace(/`/g, '&#x60;')
470
+ .replace(/\(/g, '&#x28;')
471
+ .replace(/\)/g, '&#x29;');
443
472
  }
444
473
 
445
474
  sanitizeUrl(url)
446
475
  {
447
- if(!url){
476
+ if(!url || !this.isString(url) || 2048 < url.length){
448
477
  return '';
449
478
  }
450
479
  let sanitized = url.trim();
451
- let urlPattern = /^(?:https?:\/\/|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
480
+ let urlPattern = /^https?:\/\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?::[1-9][0-9]{0,4})?(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*(?:\?(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:#(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?$/;
452
481
  if(!urlPattern.test(sanitized)){
453
482
  return '';
454
483
  }
455
- return sanitized
456
- .replace(/javascript:/gi, '')
484
+ return sanitized.replace(/javascript:/gi, '')
457
485
  .replace(/data:/gi, '')
458
486
  .replace(/vbscript:/gi, '');
459
487
  }
460
488
 
489
+ camelCase(str)
490
+ {
491
+ if(!this.isString(str) || 0 === str.length){
492
+ return str;
493
+ }
494
+ return str.replace(/[_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '');
495
+ }
496
+
497
+ capitalizedCamelCase(str)
498
+ {
499
+ if(!this.isString(str) || 0 === str.length){
500
+ return str;
501
+ }
502
+ let camelStr = this.camelCase(str);
503
+ return camelStr.charAt(0).toUpperCase() + camelStr.slice(1);
504
+ }
505
+
506
+ kebabCase(str)
507
+ {
508
+ return str.replace(/[_\s]+/g, '-');
509
+ }
510
+
511
+ capitalize(str)
512
+ {
513
+ if(!this.isString(str) || 0 === str.length){
514
+ return str;
515
+ }
516
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
517
+ }
518
+
519
+ chunk(array, size)
520
+ {
521
+ if(!this.isArray(array) || 0 >= size){
522
+ return [];
523
+ }
524
+ let result = [];
525
+ for(let i = 0; i < array.length; i += size){
526
+ result.push(array.slice(i, i + size));
527
+ }
528
+ return result;
529
+ }
530
+
531
+ flatten(array, depth = 1)
532
+ {
533
+ if(!this.isArray(array)){
534
+ return [];
535
+ }
536
+ return array.flat(depth);
537
+ }
538
+
539
+ unique(array)
540
+ {
541
+ if(!this.isArray(array)){
542
+ return [];
543
+ }
544
+ return [...new Set(array)];
545
+ }
546
+
547
+ clamp(value, min, max)
548
+ {
549
+ if(!this.isNumber(value) || !this.isNumber(min) || !this.isNumber(max)){
550
+ return value;
551
+ }
552
+ return Math.min(Math.max(value, min), max);
553
+ }
554
+
555
+ truncate(str, length, suffix = '...')
556
+ {
557
+ if(!this.isString(str) || str.length <= length){
558
+ return str;
559
+ }
560
+ return str.substring(0, length) + suffix;
561
+ }
562
+
563
+ pickProps(obj, props)
564
+ {
565
+ if(!this.isObject(obj) || !this.isArray(props)){
566
+ return {};
567
+ }
568
+ let result = {};
569
+ for(let prop of props){
570
+ if(this.hasDangerousKeys(null, prop)){
571
+ continue;
572
+ }
573
+ if(this.hasOwn(obj, prop)){
574
+ result[prop] = obj[prop];
575
+ }
576
+ }
577
+ return result;
578
+ }
579
+
580
+ omitProps(obj, props)
581
+ {
582
+ if(!this.isObject(obj) || !this.isArray(props)){
583
+ return obj;
584
+ }
585
+ let result = {};
586
+ for(let key of Object.keys(obj)){
587
+ if(-1 === props.indexOf(key)){
588
+ result[key] = obj[key];
589
+ }
590
+ }
591
+ return result;
592
+ }
593
+
594
+ debounce(func, wait)
595
+ {
596
+ if(!this.isFunction(func) || !this.isNumber(wait)){
597
+ return func;
598
+ }
599
+ let timeout;
600
+ return function executedFunction(...args) {
601
+ let later = () => {
602
+ clearTimeout(timeout);
603
+ func.apply(this, args);
604
+ };
605
+ clearTimeout(timeout);
606
+ timeout = setTimeout(later, wait);
607
+ };
608
+ }
609
+
610
+ throttle(func, limit)
611
+ {
612
+ if(!this.isFunction(func) || !this.isNumber(limit)){
613
+ return func;
614
+ }
615
+ let inThrottle;
616
+ return function executedFunction(...args) {
617
+ if(!inThrottle){
618
+ func.apply(this, args);
619
+ inThrottle = true;
620
+ setTimeout(() => inThrottle = false, limit);
621
+ }
622
+ };
623
+ }
624
+
625
+ isValidUrl(url)
626
+ {
627
+ if(!this.isString(url) || 2048 < url.length){
628
+ return false;
629
+ }
630
+ let urlPattern = /^https?:\/\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?::[1-9][0-9]{0,4})?(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*(?:\?(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:#(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?$/;
631
+ return urlPattern.test(url);
632
+ }
633
+
634
+ isValidInteger(value, min, max)
635
+ {
636
+ if(!this.isNumber(value) || !Number.isInteger(value)){
637
+ return false;
638
+ }
639
+ if('number' === typeof min && value < min){
640
+ return false;
641
+ }
642
+ if('number' === typeof max && value > max){
643
+ return false;
644
+ }
645
+ return true;
646
+ }
647
+
648
+ parseNumber(value)
649
+ {
650
+ if(!isNaN(Number(value)) && '' !== value && null !== value){
651
+ return Number(value);
652
+ }
653
+ return null;
654
+ }
655
+
656
+ splitToArray(str, separator = ',')
657
+ {
658
+ if(!this.isString(str) || '' === str.trim()){
659
+ return null;
660
+ }
661
+ return str.split(separator).map(item => item.trim()).filter(item => '' !== item);
662
+ }
663
+
461
664
  }
462
665
 
463
666
  module.exports = new Shortcuts();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/utils",
3
3
  "scope": "@reldens",
4
- "version": "0.46.0",
4
+ "version": "0.48.0",
5
5
  "description": "Reldens - Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",