UNPKG

16.1 kBJavaScriptView Raw
1'use strict';
2
3const crypto = require('crypto');
4
5function deepClone(object, seen = new WeakMap()) {
6 // Primitives (treat Functions as primitives)
7 if (Object(object) !== object || object instanceof Function) {
8 return object;
9 }
10
11 // Cyclic references
12 if (seen.has(object)) {
13 return seen.get(object);
14 }
15
16 let result;
17 if (object instanceof Buffer) {
18 result = Buffer.from(object);
19 } else if (object instanceof Date) {
20 result = new Date(object);
21 } else if (object instanceof RegExp) {
22 result = new RegExp(object.source, object.flags);
23 } else if (object.constructor) {
24 result = new object.constructor();
25 } else {
26 result = Object.create(null);
27 }
28
29 seen.set(object, result);
30
31 if (object instanceof Buffer) {
32 return result;
33 } else if (object instanceof Map) {
34 object.forEach((value, key) => { return result.set(key, deepClone(value, seen)); });
35 } else if (object instanceof Set) {
36 object.forEach(value => { return result.add(deepClone(value, seen)); });
37 } else {
38 for (const key in object) {
39 result[key] = deepClone(object[key], seen);
40 }
41 }
42
43 return result;
44}
45
46function merge(objectA, objectB, createNew = false, seen) {
47 if (createNew) {
48 objectA = deepClone(objectA);
49 }
50
51 seen = new Set(seen);
52
53 const keys = Object.getOwnPropertyNames(objectB);
54 for (const key of keys) {
55 if (typeof objectB[key] === 'object' && !seen.has(objectB[key])) {
56 if (typeof objectA[key] === 'object') {
57 objectA[key] = merge(objectA[key], objectB[key], createNew, seen);
58 } else if (createNew) {
59 objectA[key] = deepClone(objectB[key]);
60 } else {
61 objectA[key] = objectB[key];
62 }
63
64 seen.add(objectB[key]);
65 } else {
66 objectA[key] = objectB[key];
67 }
68 }
69 return objectA;
70}
71
72const arrayPartRegExp = /([^]+)\[(\d+)\]$/;
73
74function resolve(object, path = '', delimiter = '.') {
75 if (!object || !path) {
76 return undefined;
77 }
78
79 const parts = path.trim().split(delimiter);
80
81 for (const part of parts) {
82 if (arrayPartRegExp.test(part)) {
83 const match = part.match(arrayPartRegExp);
84 const subpart = match[1];
85 const index = Number(match[2]);
86
87 object = object[subpart];
88 if (!object) {
89 return object;
90 }
91
92 object = object[index];
93 if (!object) {
94 return object;
95 }
96 } else {
97 object = object[part];
98 if (!object) {
99 return object;
100 }
101 }
102 }
103 return object;
104}
105
106function resolves(object, path = '', delimiter = '.') {
107 if (!object || !path) {
108 return false;
109 }
110
111 const parts = path.trim().split(delimiter);
112
113 for (const part of parts) {
114 if (arrayPartRegExp.test(part)) {
115 const match = part.match(arrayPartRegExp);
116 const subpart = match[1];
117 const index = Number(match[2]);
118
119 object = object[subpart];
120 if (!object) {
121 return false;
122 }
123
124 object = object[index];
125 if (!object) {
126 return false;
127 }
128 } else {
129 object = object[part];
130 if (!object) {
131 return false;
132 }
133 }
134 }
135 return true;
136}
137
138function formatBytes(bytes, decimals = 2) {
139 if (bytes === 0) {
140 return '0 Bytes';
141 }
142 const kilobyte = 1024;
143 const places = decimals + 1;
144 const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
145 const index = Math.floor(Math.log(bytes) / Math.log(kilobyte));
146 return `${ parseFloat((bytes / Math.pow(kilobyte, index)).toFixed(places)) } ${ sizes[index] }`;
147}
148
149function camelize(string) {
150 return string.replace(/^.*?:+/, '').
151 replace(/[-:]/g, ' ').
152 replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
153 if (Number(match) === 0) {
154 return '';
155 }
156 return index === 0 ? match.toLowerCase() : match.toUpperCase();
157 });
158}
159
160function formatNumber(value) {
161 value = Number(value);
162 if (Number.isNaN(value) || Number === Infinity) {
163 return value.toString();
164 }
165
166 const words = [ 'zero', 'one', 'two', 'three', 'four',
167 'five', 'six', 'seven', 'eight', 'nine', 'ten' ];
168
169 if (Number.isInteger(value)) {
170 if (words[value]) {
171 return words[value];
172 }
173 } else {
174 value = precisionRound(value);
175 }
176
177 return value.toString().
178 split(/(?=(?:\d{3})+(?:\.|$))/g).
179 join( ',' );
180}
181
182function sha1(input) {
183 if (typeof input !== 'string') {
184 input = JSON.stringify(input);
185 }
186 return crypto.createHash('sha1').update(input).
187 digest('hex');
188}
189
190function sha256(input) {
191 if (typeof input !== 'string') {
192 input = JSON.stringify(input);
193 }
194 return crypto.createHash('sha256').update(input).
195 digest('hex');
196}
197
198function encrypt(text, secret, algorithm = 'aes-256-cbc', ivLength = 16) {
199 let encrypted = null;
200 secret = secret.replace(/-/g, '').substring(0, 32);
201 const iv = crypto.randomBytes(ivLength);
202 const cipher = crypto.createCipheriv(algorithm, secret, iv);
203 encrypted = cipher.update(text);
204 encrypted = Buffer.concat([ encrypted, cipher.final() ]);
205 encrypted = `${ iv.toString('hex') }:${ encrypted.toString('hex') }`;
206
207 return encrypted;
208}
209
210function decrypt(text, secret, algorithm = 'aes-256-cbc') {
211 let decrypted = null;
212 secret = secret.replace(/-/g, '').substring(0, 32);
213 const textParts = text.split(':');
214 const iv = Buffer.from(textParts.shift(), 'hex');
215 const encryptedText = Buffer.from(textParts.join(':'), 'hex');
216 const decipher = crypto.createDecipheriv(algorithm, secret, iv);
217 decrypted = decipher.update(encryptedText);
218
219 decrypted = Buffer.concat([ decrypted, decipher.final() ]).toString();
220
221 return decrypted;
222}
223
224const noop = () => { return undefined; };
225
226function callback(next, nextTick = false) {
227 if (typeof next === 'function') {
228 if (nextTick) {
229 return function(...args) {
230 process.nextTick(() => {
231 next(...args);
232 });
233 };
234 }
235 return next;
236 }
237 return noop;
238}
239
240function once(func) {
241 if (typeof func !== 'function') {
242 return noop;
243 }
244
245 let invoked = false;
246
247 return (...args) => {
248 if (!invoked) {
249 invoked = true;
250 return func(...args);
251 }
252 return false;
253 };
254}
255
256function timestamp(date) {
257 if (date) {
258 return new Date(date).getTime();
259 }
260 return Date.now();
261}
262
263function precisionRound(number, precision = 2) {
264 const factor = Math.pow(10, precision);
265 return Math.round(number * factor) / factor;
266}
267
268function functionType(func) {
269 const flags = {
270 function: func instanceof Function,
271 name: undefined,
272 native: false,
273 bound: false,
274 plain: false,
275 arrow: false
276 };
277
278 if (flags.function) {
279 flags.name = func.name || '(anonymous)';
280 flags.native = func.toString().trim().
281 endsWith('() { [native code] }');
282 flags.bound = flags.native && flags.name.startsWith('bound ');
283 flags.plain = !flags.native && func.hasOwnProperty('prototype');
284 flags.arrow = !(flags.native || flags.plain);
285 }
286
287 return flags;
288}
289
290function set(object, propertyPath, value) {
291 const parts = propertyPath.trim().split(/\./);
292 const key = parts.pop();
293
294 for (const part of parts) {
295 if (object[part] === undefined) {
296 object[part] = {};
297 }
298
299 object = object[part];
300
301 if (!object) {
302 return false;
303 }
304 }
305
306 object[key] = value;
307
308 return true;
309}
310
311function remove(object, propertyPath, removeEmptyContainer = false) {
312 const parts = propertyPath.trim().split(/\./);
313 const key = parts.pop();
314
315 let parent;
316 let parentKey;
317
318 for (const part of parts) {
319 parent = object;
320 parentKey = part;
321
322 object = object[part];
323
324 if (!object) {
325 return false;
326 }
327 }
328
329 delete object[key];
330
331 if (removeEmptyContainer && size(object) === 0) {
332 delete parent[parentKey];
333 }
334
335 return true;
336}
337
338function flatten(object, prefix = '', container = {}) {
339 if (typeof object !== 'object') {
340 container[prefix] = object;
341 return container;
342 }
343
344 if (prefix.length) {
345 prefix += '.';
346 }
347
348 for (const key in object) {
349 const pathKey = prefix + key;
350
351 if (Array.isArray(object[key])) {
352 container[`${ pathKey }$type`] = 'Array';
353 const array = object[key];
354 for (let i = 0; i < array.length; i++) {
355 flatten(array[i], `${ pathKey }.${ i }`, container);
356 }
357 } else if (typeof object[key] === 'object' && object[key] !== null) {
358 container[`${ pathKey }$type`] = 'Object';
359 flatten(object[key], pathKey, container);
360 } else {
361 container[ pathKey ] = object[key];
362 }
363 }
364 return container;
365}
366
367function expand(container, object = {}) {
368 for (const key in container) {
369 const parts = key.split(/\./);
370 const property = parts.pop();
371
372 let chunk = object;
373 for (const part of parts) {
374 if (!chunk[part]) {
375 chunk[part] = {};
376 }
377
378 chunk = chunk[part];
379 }
380
381 if (property.endsWith('$type')) {
382 const name = property.replace(/\$type$/, '');
383 if (container[key] === 'Object') {
384 chunk[name] = {};
385 } else if (container[key] === 'Array') {
386 chunk[name] = [];
387 } else {
388 // Unknown type
389 }
390 } else {
391 chunk[property] = container[key];
392 }
393 }
394 return object;
395}
396
397function setTypes(object) {
398 for (const key in object) {
399 const value = object[key];
400
401 if (parseInt(value, 10).toString() === value) {
402 object[key] = parseInt(value, 10);
403 } else if (parseFloat(value, 10).toString() === value) {
404 object[key] = parseFloat(value, 10);
405 } else if (value === 'true') {
406 object[key] = true;
407 } else if (value === 'false') {
408 object[key] = false;
409 }
410 }
411 return object;
412}
413
414function size(object) {
415 if (typeof object === 'object') {
416 return Object.getOwnPropertyNames(object).length;
417 }
418 return 0;
419}
420
421function project(object, projection) {
422 let sum = 0;
423 for (const key in projection) {
424 sum += projection[key] ? 1 : 0;
425 }
426
427 if (sum === 0) { // selective removal
428 const result = deepClone(object);
429 for (const key in projection) {
430 remove(result, key, true);
431 }
432 return result;
433 }
434 // selective inclusion
435 const result = {};
436
437 for (const key in projection) {
438 if (projection[key]) {
439 if (typeof projection[key] === 'string') { // key change
440 set(result, projection[key], resolve(object, key));
441 } else if (typeof projection[key] === 'function') { // value transform
442 set(result, key, projection[key](resolve(object, key)));
443 } else {
444 set(result, key, resolve(object, key)); // simple projection
445 }
446 }
447 }
448 return result;
449}
450
451function filter(object, check, include = true, path) {
452 if (typeof object !== 'object') {
453 return object;
454 }
455
456 function test(fullpath) {
457 if (Array.isArray(check)) {
458 if (check.includes(fullpath)) {
459 return include;
460 }
461 return !include;
462 } else if (check instanceof Map || check instanceof Set ||
463 check instanceof WeakMap || check instanceof WeakSet) {
464 if (check.has(fullpath)) {
465 return include;
466 }
467 return !include;
468 } else if (typeof check === 'function') {
469 if (check(fullpath)) {
470 return include;
471 } return !include;
472 } else if (check instanceof RegExp) {
473 if (check.test(fullpath)) {
474 return include;
475 }
476 return !include;
477 } else if (typeof check === 'object') {
478 if (resolve(check, fullpath)) {
479 return include;
480 }
481 return !include;
482 }
483 return !include;
484 }
485
486 include = Boolean(include);
487
488 const clone = Array.isArray(object) ? [ ] : { };
489 for (const prop in object) {
490 const fullpath = path ? `${ path }.${ prop }` : prop;
491
492 let value = object[prop];
493
494 if (test(fullpath)) {
495 if (typeof value === 'object') {
496 value = filter(value, check, include, fullpath);
497 if (Array.isArray(value) && value.length !== 0 ||
498 Object.keys(value).length !== 0) {
499 clone[prop] = value;
500 }
501 } else {
502 clone[prop] = value;
503 }
504 }
505 }
506
507 return clone;
508}
509
510function deepEqual(actual, expected) {
511 if (actual === null || actual === undefined ||
512 expected === null || expected === undefined) {
513 return actual === expected;
514 }
515
516 if (actual.constructor !== expected.constructor) {
517 return false;
518 }
519
520 if (actual instanceof Function) {
521 return actual === expected;
522 }
523
524 if (actual instanceof RegExp) {
525 return actual === expected;
526 }
527
528 if (actual instanceof Set) {
529 if (actual.size !== expected.size) {
530 return false;
531 }
532
533 for (const entry of actual) {
534 if (!expected.has(entry)) {
535 return false;
536 }
537 }
538
539 return true;
540 }
541
542 if (actual instanceof Map) {
543 if (actual.size !== expected.size) {
544 return false;
545 }
546
547 for (const [ key, value ] of actual) {
548 if (!expected.has(key) &&
549 deepEqual(value, expected.get(key))) {
550 return false;
551 }
552 }
553
554 return true;
555 }
556
557 if (actual === expected || actual.valueOf() === expected.valueOf()) {
558 return true;
559 }
560
561 if (Array.isArray(actual) && actual.length !== expected.length) {
562 return false;
563 }
564
565 if (actual instanceof Date) {
566 return false;
567 }
568
569 if (!(actual instanceof Object)) {
570 return false;
571 }
572
573 if (!(expected instanceof Object)) {
574 return false;
575 }
576
577 const properties = Object.keys(actual);
578 return Object.keys(expected).every((i) => {
579 return properties.indexOf(i) !== -1;
580 }) && properties.every((i) => {
581 return deepEqual(actual[i], expected[i]);
582 });
583}
584
585function debounce(func, wait) {
586 let timeout;
587
588 return (...args) => {
589 if (timeout) {
590 clearTimeout(timeout);
591 }
592
593 timeout = setTimeout(() => {
594 func(...args);
595 }, wait || 500);
596 };
597}
598
599function milliseconds(value) {
600 if (typeof value === 'number') {
601 return value;
602 } else if (typeof value === 'string') {
603 let millis = 0;
604 value.replace(/(\d+\.?\d*)\s*([mshd]+)/g, (match, time, unit) => {
605 time = Number(time) || 0;
606 if (unit === 'ms') {
607 time *= 1;
608 } else if (unit === 's') {
609 time *= 1000;
610 } else if (unit === 'm') {
611 time *= 1000 * 60;
612 } else if (unit === 'h') {
613 time *= 1000 * 60 * 60;
614 } else if (unit === 'd') {
615 time *= 1000 * 60 * 60 * 24;
616 }
617
618 millis += time;
619 });
620
621 return Math.ceil(millis);
622 }
623 return 0;
624}
625
626function duration(diff, {
627 units = 'd h m', separator = ', ', empty = 'less than a minute'
628} = {}) {
629 const days = Math.floor(diff / 86400000);
630 diff = diff % 86400000;
631 const hours = Math.floor(diff / 3600000);
632 diff = diff % 3600000;
633 const minutes = Math.floor(diff / 60000);
634 diff = diff % 60000;
635 const seconds = Math.floor(diff / 1000);
636
637 const parts = [];
638 if (days > 0 && units.includes('d')) {
639 if (days === 1) {
640 parts.push(`${ days } day`);
641 } else {
642 parts.push(`${ days } day`);
643 }
644 }
645 if (hours > 0 && units.includes('h')) {
646 if (hours === 1) {
647 parts.push(`${ hours } hour`);
648 } else {
649 parts.push(`${ hours } hours`);
650 }
651 }
652 if (minutes > 0 && units.includes('m')) {
653 if (minutes === 1) {
654 parts.push(`${ minutes } minute`);
655 } else {
656 parts.push(`${ minutes } minutes`);
657 }
658 }
659
660 if (seconds > 0 && units.includes('s')) {
661 if (seconds === 1) {
662 parts.push(`${ seconds } second`);
663 } else {
664 parts.push(`${ seconds } seconds`);
665 }
666 }
667
668 if (parts.length) {
669 return parts.join(separator);
670 }
671 return empty;
672}
673
674function ordinal (value) {
675 value = Number(value);
676
677 const tens = value % 10;
678 const hundreds = value % 100;
679
680 if (tens === 1 && hundreds !== 11) {
681 return `${ value }st`;
682 } else if (tens === 2 && hundreds !== 12) {
683 return `${ value }nd`;
684 } else if (tens === 3 && hundreds !== 13) {
685 return `${ value }rd`;
686 }
687 return `${ value }th`;
688}
689
690module.exports = {
691 callback,
692 camelize,
693 debounce,
694 decrypt,
695 deepClone,
696 deepEqual,
697 duration,
698 encrypt,
699 expand,
700 filter,
701 flatten,
702 formatBytes,
703 formatNumber,
704 functionType,
705 merge,
706 milliseconds,
707 noop,
708 nop: noop,
709 once,
710 ordinal,
711 precisionRound,
712 project,
713 remove,
714 resolve,
715 resolves,
716 set,
717 setTypes,
718 sha1,
719 sha256,
720 size,
721 timestamp
722};