UNPKG

20.8 kBJavaScriptView Raw
1'use strict';
2
3const crypto = require('crypto');
4
5//////////
6
7const ansiPattern = [
8 '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
9 '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
10].join('|');
11
12const ansiRegExp = new RegExp(ansiPattern, 'g');
13
14const arrayPartRegExp = /([^]+)\[(\d+)\]$/;
15
16const noop = () => undefined;
17
18//////////
19
20function callback (next, nextTick = false) {
21 if (typeof next === 'function') {
22 if (nextTick) {
23 return function (...args) {
24 process.nextTick(() => {
25 next(...args);
26 });
27 };
28 }
29 return next;
30 }
31 return noop;
32}
33
34function camelize (string) {
35 return string.replace(/^.*?:+/, '').
36 replace(/[-:]/g, ' ').
37 replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
38 if (Number(match) === 0) {
39 return '';
40 }
41 return index === 0 ? match.toLowerCase() : match.toUpperCase();
42 });
43}
44
45function debounce (func, wait) {
46 let timeout;
47
48 return (...args) => {
49 if (timeout) {
50 clearTimeout(timeout);
51 }
52
53 timeout = setTimeout(() => {
54 func(...args);
55 }, wait || 500);
56 };
57}
58
59function decrypt (text, secret, algorithm = 'aes-256-cbc') {
60 let decrypted = null;
61 secret = secret.replace(/-/g, '').substring(0, 32);
62 const textParts = text.split(':');
63 const iv = Buffer.from(textParts.shift(), 'hex');
64 const encryptedText = Buffer.from(textParts.join(':'), 'hex');
65 const decipher = crypto.createDecipheriv(algorithm, secret, iv);
66 decrypted = decipher.update(encryptedText);
67
68 decrypted = Buffer.concat([ decrypted, decipher.final() ]).toString();
69
70 return decrypted;
71}
72
73function deepClone (object, seen = new WeakMap()) {
74 // Primitives (treat Functions as primitives)
75 if (Object(object) !== object || object instanceof Function) {
76 return object;
77 }
78
79 // Cyclic references
80 if (seen.has(object)) {
81 return seen.get(object);
82 }
83
84 let result;
85 if (object instanceof Buffer) {
86 result = Buffer.from(object);
87 } else if (object instanceof Date) {
88 result = new Date(object);
89 } else if (object instanceof RegExp) {
90 result = new RegExp(object.source, object.flags);
91 } else if (object.constructor) {
92 result = new object.constructor();
93 } else {
94 result = Object.create(null);
95 }
96
97 seen.set(object, result);
98
99 if (object instanceof Buffer) {
100 return result;
101 } else if (object instanceof Map) {
102 object.forEach((value, key) => result.set(key, deepClone(value, seen)));
103 } else if (object instanceof Set) {
104 object.forEach(value => result.add(deepClone(value, seen)));
105 } else {
106 for (const key in object) {
107 result[key] = deepClone(object[key], seen);
108 }
109 }
110
111 return result;
112}
113
114function deepEqual (actual, expected) {
115 if (actual === null || actual === undefined ||
116 expected === null || expected === undefined) {
117 return actual === expected;
118 }
119
120 if (actual.constructor !== expected.constructor) {
121 return false;
122 }
123
124 if (actual instanceof Function) {
125 return actual === expected;
126 }
127
128 if (actual instanceof RegExp) {
129 return actual === expected;
130 }
131
132 if (actual instanceof Set) {
133 if (actual.size !== expected.size) {
134 return false;
135 }
136
137 for (const entry of actual) {
138 if (!expected.has(entry)) {
139 return false;
140 }
141 }
142
143 return true;
144 }
145
146 if (actual instanceof Map) {
147 if (actual.size !== expected.size) {
148 return false;
149 }
150
151 for (const [ key, value ] of actual) {
152 if (!expected.has(key) &&
153 deepEqual(value, expected.get(key))) {
154 return false;
155 }
156 }
157
158 return true;
159 }
160
161 if (actual === expected || actual.valueOf() === expected.valueOf()) {
162 return true;
163 }
164
165 if (Array.isArray(actual) && actual.length !== expected.length) {
166 return false;
167 }
168
169 if (actual instanceof Date) {
170 return false;
171 }
172
173 if (!(actual instanceof Object)) {
174 return false;
175 }
176
177 if (!(expected instanceof Object)) {
178 return false;
179 }
180
181 const properties = Object.keys(actual);
182 return Object.keys(expected).every((i) => properties.indexOf(i) !== -1) && properties.every((i) => deepEqual(actual[i], expected[i]));
183}
184
185function distinct (array, selector) {
186 if (typeof selector !== 'function') {
187 return Array.from(new Set(array));
188 }
189
190 const items = new Set();
191
192 return array.filter(item => {
193 if (items.has(selector(item))) {
194 return false;
195 }
196 items.add(selector(item));
197 return true;
198 });
199}
200
201function dividePath (path, delimiter = /[.]/) {
202 const parts = [];
203
204 for (const part of path.trim().split(delimiter)) {
205 if (arrayPartRegExp.test(part)) {
206 const match = part.match(arrayPartRegExp);
207 const subpart = match[1];
208 const index = Number(match[2]);
209 parts.push(subpart, index);
210 } else {
211 parts.push(part);
212 }
213 }
214
215 return parts;
216}
217
218function duration (diff, {
219 units = 'd h m', separator = ', ', empty = 'less than a minute',
220} = {}) {
221 const days = Math.floor(diff / 86400000);
222 diff = diff % 86400000;
223 const hours = Math.floor(diff / 3600000);
224 diff = diff % 3600000;
225 const minutes = Math.floor(diff / 60000);
226 diff = diff % 60000;
227 const seconds = Math.floor(diff / 1000);
228
229 const parts = [];
230 if (days > 0 && units.includes('d')) {
231 if (days === 1) {
232 parts.push(`${ days } day`);
233 } else {
234 parts.push(`${ days } days`);
235 }
236 }
237 if (hours > 0 && units.includes('h')) {
238 if (hours === 1) {
239 parts.push(`${ hours } hour`);
240 } else {
241 parts.push(`${ hours } hours`);
242 }
243 }
244 if (minutes > 0 && units.includes('m')) {
245 if (minutes === 1) {
246 parts.push(`${ minutes } minute`);
247 } else {
248 parts.push(`${ minutes } minutes`);
249 }
250 }
251
252 if (seconds > 0 && units.includes('s')) {
253 if (seconds === 1) {
254 parts.push(`${ seconds } second`);
255 } else {
256 parts.push(`${ seconds } seconds`);
257 }
258 }
259
260 if (parts.length) {
261 return parts.join(separator);
262 }
263 return empty;
264}
265
266function encrypt (text, secret, algorithm = 'aes-256-cbc', ivLength = 16) {
267 let encrypted = null;
268 secret = secret.replace(/-/g, '').substring(0, 32);
269 const iv = crypto.randomBytes(ivLength);
270 const cipher = crypto.createCipheriv(algorithm, secret, iv);
271 encrypted = cipher.update(text);
272 encrypted = Buffer.concat([ encrypted, cipher.final() ]);
273 encrypted = `${ iv.toString('hex') }:${ encrypted.toString('hex') }`;
274
275 return encrypted;
276}
277
278function expand (container, object = {}) {
279 for (const key in container) {
280 const parts = key.split(/\./);
281 const property = parts.pop();
282
283 let chunk = object;
284 for (const part of parts) {
285 if (!chunk[part]) {
286 chunk[part] = {};
287 }
288
289 chunk = chunk[part];
290 }
291
292 if (property.endsWith('$type')) {
293 const name = property.replace(/\$type$/, '');
294 if (container[key] === 'Object') {
295 chunk[name] = {};
296 } else if (container[key] === 'Array') {
297 chunk[name] = [];
298 } else {
299 // Unknown type
300 }
301 } else {
302 chunk[property] = container[key];
303 }
304 }
305 return object;
306}
307
308function filter (object, check, include = true, path) {
309 if (typeof object !== 'object') {
310 return object;
311 }
312
313 function test (fullpath) {
314 if (Array.isArray(check)) {
315 if (check.includes(fullpath)) {
316 return include;
317 }
318 return !include;
319 } else if (check instanceof Map || check instanceof Set ||
320 check instanceof WeakMap || check instanceof WeakSet) {
321 if (check.has(fullpath)) {
322 return include;
323 }
324 return !include;
325 } else if (typeof check === 'function') {
326 if (check(fullpath)) {
327 return include;
328 } return !include;
329 } else if (check instanceof RegExp) {
330 if (check.test(fullpath)) {
331 return include;
332 }
333 return !include;
334 } else if (typeof check === 'object') {
335 if (resolve(check, fullpath)) {
336 return include;
337 }
338 return !include;
339 }
340 return !include;
341 }
342
343 include = Boolean(include);
344
345 const clone = Array.isArray(object) ? [ ] : { };
346 for (const prop in object) {
347 const fullpath = path ? `${ path }.${ prop }` : prop;
348
349 let value = object[prop];
350
351 if (test(fullpath)) {
352 if (typeof value === 'object') {
353 value = filter(value, check, include, fullpath);
354 if (Array.isArray(value) && value.length !== 0 ||
355 Object.keys(value).length !== 0) {
356 clone[prop] = value;
357 }
358 } else {
359 clone[prop] = value;
360 }
361 }
362 }
363
364 return clone;
365}
366
367function flatten (object, {
368 container = {}, delimiter = '.', prefix = '', types = true,
369} = {}) {
370 if (typeof object !== 'object') {
371 container[prefix] = object;
372 return container;
373 }
374
375 if (prefix.length && prefix !== delimiter) {
376 prefix += delimiter;
377 }
378
379 for (const key in object) {
380 const pathKey = prefix + key;
381
382 if (Array.isArray(object[key])) {
383 if (types) {
384 container[`${ pathKey }$type`] = 'Array';
385 }
386
387 const array = object[key];
388 for (let i = 0; i < array.length; i++) {
389 flatten(array[i], {
390 container,
391 delimiter,
392 prefix: `${ pathKey }${ delimiter }${ i }`,
393 types,
394 });
395 }
396 } else if (typeof object[key] === 'object' && object[key] !== null) {
397 if (types) {
398 container[`${ pathKey }$type`] = 'Object';
399 }
400 flatten(object[key], {
401 container,
402 delimiter,
403 prefix: pathKey,
404 types,
405 });
406 } else {
407 container[ pathKey ] = object[key];
408 }
409 }
410 return container;
411}
412
413function formatBytes (bytes, decimals = 2) {
414 if (bytes === 0) {
415 return '0 Bytes';
416 }
417 const kilobyte = 1024;
418 const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
419 const index = Math.floor(Math.log(bytes) / Math.log(kilobyte));
420 return `${ parseFloat((bytes / Math.pow(kilobyte, index)).toFixed(decimals)) } ${ sizes[index] }`;
421}
422
423function formatNumber (value, { numeral = false } = {}) {
424 value = Number(value);
425 if (Number.isNaN(value) || Number === Infinity) {
426 return value.toString();
427 }
428
429 const words = [
430 'zero', 'one', 'two', 'three', 'four',
431 'five', 'six', 'seven', 'eight', 'nine', 'ten',
432 ];
433
434 if (Number.isInteger(value)) {
435 if (words[value] && !numeral) {
436 return words[value];
437 }
438 } else {
439 value = precisionRound(value);
440 }
441
442 return value.toString().
443 split(/(?=(?:\d{3})+(?:\.|$))/g).
444 join( ',' );
445}
446
447function functionType (func) {
448 const flags = {
449 function: func instanceof Function,
450 name: undefined,
451 native: false,
452 bound: false,
453 plain: false,
454 arrow: false,
455 };
456
457 if (flags.function) {
458 flags.name = func.name || '(anonymous)';
459 flags.native = func.toString().trim().
460 endsWith('() { [native code] }');
461 flags.bound = flags.native && flags.name.startsWith('bound ');
462 flags.plain = !flags.native && func.hasOwnProperty('prototype');
463 flags.arrow = !(flags.native || flags.plain);
464 }
465
466 return flags;
467}
468
469const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
470const isNativeRegExp = RegExp(`^${
471 Function.prototype.toString.call(Object.prototype.hasOwnProperty).
472 replace(reRegExpChar, '\\$&').
473 replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?')
474}$`);
475
476function isNative (value) {
477 return isObject(value) && isNativeRegExp.test(value);
478}
479
480function isNumber (value) {
481 if (typeof value === 'number') {
482 return value - value === 0;
483 }
484
485 if (typeof value === 'string' && value.trim() !== '') {
486 return Number.isFinite(Number(value));
487 }
488
489 return false;
490}
491
492function isObject (value) {
493 const type = typeof value;
494 return value !== null && (type === 'object' || type === 'function');
495}
496
497function isPrimitive (value) {
498 if (typeof value === 'object') {
499 return value === null;
500 }
501 return typeof value !== 'function';
502}
503
504function merge (objectA, objectB, createNew = false, seen) {
505 if (createNew) {
506 objectA = deepClone(objectA);
507 }
508
509 seen = new Set(seen);
510
511 const keys = Object.getOwnPropertyNames(objectB);
512 for (const key of keys) {
513 if (objectB[key] === null || Array.isArray(objectB[key]) || objectB[key] instanceof Buffer ||
514 objectB[key] instanceof Date || objectB[key] instanceof Map || objectB[key] instanceof Set ||
515 objectB[key] instanceof RegExp) {
516 if (createNew) {
517 objectA[key] = deepClone(objectB[key]);
518 } else {
519 objectA[key] = objectB[key];
520 }
521 } else if (typeof objectB[key] === 'object' && !seen.has(objectB[key])) {
522 if (typeof objectA[key] === 'object') {
523 objectA[key] = merge(objectA[key], objectB[key], createNew, seen);
524 } else if (createNew) {
525 objectA[key] = deepClone(objectB[key]);
526 } else {
527 objectA[key] = objectB[key];
528 }
529
530 seen.add(objectB[key]);
531 } else if (createNew) {
532 objectA[key] = deepClone(objectB[key]);
533 } else {
534 objectA[key] = objectB[key];
535 }
536 }
537 return objectA;
538}
539
540function milliseconds (value) {
541 if (typeof value === 'number') {
542 return value;
543 } else if (typeof value === 'string') {
544 let millis = 0;
545 value.replace(/(\d+\.?\d*)\s*([mshd]+)/g, (match, time, unit) => {
546 time = Number(time) || 0;
547 if (unit === 'ms') {
548 time *= 1;
549 } else if (unit === 's') {
550 time *= 1000;
551 } else if (unit === 'm') {
552 time *= 1000 * 60;
553 } else if (unit === 'h') {
554 time *= 1000 * 60 * 60;
555 } else if (unit === 'd') {
556 time *= 1000 * 60 * 60 * 24;
557 }
558
559 millis += time;
560 });
561
562 return Math.ceil(millis);
563 }
564 return 0;
565}
566
567function once (func) {
568 if (typeof func !== 'function') {
569 return noop;
570 }
571
572 let invoked = false;
573
574 return (...args) => {
575 if (!invoked) {
576 invoked = true;
577 return func(...args);
578 }
579 return false;
580 };
581}
582
583function ordinal (value) {
584 value = Number(value);
585
586 const tens = value % 10;
587 const hundreds = value % 100;
588
589 if (tens === 1 && hundreds !== 11) {
590 return `${ value }st`;
591 } else if (tens === 2 && hundreds !== 12) {
592 return `${ value }nd`;
593 } else if (tens === 3 && hundreds !== 13) {
594 return `${ value }rd`;
595 }
596 return `${ value }th`;
597}
598
599async function poll (fn, options = {}, done) {
600 const interval = typeof options === 'number' ? options : options.interval || 1000;
601 const retries = typeof options.retries === 'number' ? options.retries : Infinity;
602 const validate = typeof options.validate === 'function' ? options.validate : (x) => Boolean(x);
603
604 if (fn.length === 1) { // function takes a callback
605 const originalFn = fn;
606
607 fn = new Promise((resolve, reject) => {
608 originalFn((error, result) => {
609 if (error) {
610 return reject(error);
611 }
612 return resolve(result);
613 });
614 });
615 }
616
617 let attempts = 0;
618 const poller = async (resolve, reject) => {
619 const result = await fn();
620 attempts++;
621
622 if (validate(result)) {
623 return resolve(result);
624 } else if (attempts > retries) {
625 return reject(new Error('Exceeded max retries'));
626 }
627 return setTimeout(poller, interval, resolve, reject);
628 };
629
630 if (typeof done === 'function') {
631 return new Promise(poller).
632 then((result) => done(null, result)).
633 catch((error) => done(error));
634 }
635 return new Promise(poller);
636}
637
638function precisionRound (number, precision = 2) {
639 const factor = Math.pow(10, precision);
640 return Math.round(number * factor) / factor;
641}
642
643function project (object, projection) {
644 let sum = 0;
645 for (const key in projection) {
646 sum += projection[key] ? 1 : 0;
647 }
648
649 if (sum === 0) { // selective removal
650 const result = deepClone(object);
651 for (const key in projection) {
652 remove(result, key, true);
653 }
654 return result;
655 }
656 // selective inclusion
657 const result = {};
658
659 for (const key in projection) {
660 if (projection[key]) {
661 if (typeof projection[key] === 'string') { // key change
662 set(result, projection[key], resolve(object, key));
663 } else if (typeof projection[key] === 'function') { // value transform
664 set(result, key, projection[key](resolve(object, key)));
665 } else {
666 set(result, key, resolve(object, key)); // simple projection
667 }
668 }
669 }
670 return result;
671}
672
673function range (start, end, increment = 1) {
674 if (end < start) {
675 increment *= -1;
676 }
677
678 const result = [ ];
679
680 for (let i = start; i <= end; i += increment) {
681 result.push(i);
682 }
683
684 return result;
685}
686
687function remove (object, propertyPath, removeEmptyContainer = false) {
688 if (Array.isArray(propertyPath)) {
689 for (const path of propertyPath) {
690 remove(object, path, removeEmptyContainer);
691 }
692 return true;
693 }
694
695 const parts = propertyPath.trim().split(/\./);
696 const key = parts.pop();
697
698 let parent;
699 let parentKey;
700
701 for (const part of parts) {
702 parent = object;
703 parentKey = part;
704
705 object = object[part];
706
707 if (!object) {
708 return false;
709 }
710 }
711
712 delete object[key];
713
714 if (removeEmptyContainer && size(object) === 0) {
715 delete parent[parentKey];
716 }
717
718 return true;
719}
720
721function resolve (object, path = '', delimiter) {
722 if (!object || !path) {
723 return undefined;
724 }
725
726 const parts = dividePath(path, delimiter);
727
728 for (const part of parts) {
729 object = object[part];
730 if (!object) {
731 return object;
732 }
733 }
734 return object;
735}
736
737function resolves (object, path = '', delimiter) {
738 if (!object || !path) {
739 return false;
740 }
741
742 const parts = dividePath(path, delimiter);
743
744 for (const part of parts) {
745 object = object[part];
746 if (!object) {
747 return false;
748 }
749 }
750 return true;
751}
752
753function set (object, path, value, delimiter) {
754 if (!object || !path) {
755 return false;
756 }
757
758 const parts = dividePath(path, delimiter);
759 const key = parts.pop();
760
761 for (const part of parts) {
762 if (object[part] === undefined) {
763 if (typeof part === 'number') {
764 object[part] = [];
765 } else {
766 object[part] = {};
767 }
768 }
769
770 object = object[part];
771
772 if (!object) {
773 return false;
774 }
775 }
776
777 object[key] = value;
778
779 return true;
780}
781
782function setTypes (object) {
783 for (const key in object) {
784 const value = object[key];
785
786 if (parseInt(value, 10).toString() === value) {
787 object[key] = parseInt(value, 10);
788 } else if (parseFloat(value, 10).toString() === value) {
789 object[key] = parseFloat(value, 10);
790 } else if (value === 'true') {
791 object[key] = true;
792 } else if (value === 'false') {
793 object[key] = false;
794 }
795 }
796 return object;
797}
798
799function sha1 (input) {
800 if (typeof input !== 'string') {
801 input = JSON.stringify(input);
802 }
803 return crypto.createHash('sha1').update(input).
804 digest('hex');
805}
806
807function sha256 (input) {
808 if (typeof input !== 'string') {
809 input = JSON.stringify(input);
810 }
811 return crypto.createHash('sha256').update(input).
812 digest('hex');
813}
814
815function size (object) {
816 if (typeof object === 'object') {
817 return Object.getOwnPropertyNames(object).length;
818 }
819 return 0;
820}
821
822function stripAnsi (string) {
823 return string.replace(ansiRegExp, '');
824}
825
826function times (count = 1, func, ...args) {
827 for (let i = 0; i < count; i++) {
828 func(...args);
829 }
830}
831
832function timestamp (date) {
833 if (date) {
834 return new Date(date).getTime();
835 }
836 return Date.now();
837}
838
839function toBoolean (value) {
840 if (value instanceof Map || value instanceof Set) {
841 return value.size > 0;
842 } else if (Array.isArray(value)) {
843 return value.length > 0;
844 } else if (typeof value === 'object' && value !== null) {
845 return Object.keys(value).length > 0;
846 } else if (typeof value === 'string') {
847 value = value.toLowerCase();
848
849 if (value === 'true' || value === 'yes' || value === '1') {
850 return true;
851 } else if (value === 'false' || value === 'no' || value === '0') {
852 return false;
853 }
854 }
855
856 return Boolean(value);
857}
858
859const regExpPattern = /^\/(.*?)\/([gim]*)$/;
860const escapePattern = /[|\\{}()[\]^$+*?.]/g;
861
862function toRegExp (string) {
863 const parts = string.match(regExpPattern);
864 if (parts) {
865 return new RegExp(parts[1], parts[2]);
866 }
867 return new RegExp(`^${ string.replace(escapePattern, '\\$&') }$`);
868}
869
870function unique (array) {
871 return Array.from(new Set(array));
872}
873
874module.exports = {
875 ansiRegExp,
876 callback,
877 camelize,
878 debounce,
879 decrypt,
880 deepClone,
881 deepEqual,
882 dividePath,
883 distinct,
884 duration,
885 encrypt,
886 expand,
887 filter,
888 flatten,
889 formatBytes,
890 formatNumber,
891 functionType,
892 isNative,
893 isNumber,
894 isObject,
895 isPrimitive,
896 merge,
897 milliseconds,
898 noop,
899 nop: noop,
900 once,
901 ordinal,
902 poll,
903 precisionRound,
904 project,
905 range,
906 remove,
907 resolve,
908 resolves,
909 set,
910 setTypes,
911 sha1,
912 sha256,
913 size,
914 stripAnsi,
915 times,
916 timestamp,
917 toBoolean,
918 toRegExp,
919 unique,
920};