• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

moleculerjs / moleculer / 6448314179

08 Oct 2023 02:52PM UTC coverage: 93.839%. Remained the same
6448314179

push

github

icebob
fixing typing errors

5340 of 5917 branches covered (0.0%)

Branch coverage included in aggregate %.

7225 of 7473 relevant lines covered (96.68%)

1492.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.71
/src/utils.js
1
/*
2
 * moleculer
3
 * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer)
4
 * MIT Licensed
5
 */
6

7
"use strict";
8

9
const kleur = require("kleur");
696✔
10
const os = require("os");
696✔
11
const path = require("path");
696✔
12
const fs = require("fs");
696✔
13
const { ExtendableError } = require("./errors");
696✔
14

15
const lut = [];
696✔
16
for (let i = 0; i < 256; i++) {
696✔
17
        lut[i] = (i < 16 ? "0" : "") + i.toString(16);
178,176✔
18
}
19

20
const RegexCache = new Map();
696✔
21

22
const deprecateList = [];
696✔
23

24
const byteMultipliers = {
696✔
25
        b: 1,
26
        kb: 1 << 10,
27
        mb: 1 << 20,
28
        gb: 1 << 30,
29
        tb: Math.pow(1024, 4),
30
        pb: Math.pow(1024, 5)
31
};
32
// eslint-disable-next-line security/detect-unsafe-regex
33
const parseByteStringRe = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
696✔
34

35
class TimeoutError extends ExtendableError {}
36

37
/**
38
 * Circular replacing of unsafe properties in object
39
 *
40
 * @param {Object=} options List of options to change circularReplacer behaviour
41
 * @param {number=} options.maxSafeObjectSize Maximum size of objects for safe object converting
42
 * @return {function(...[*]=)}
43
 */
44
function circularReplacer(options = { maxSafeObjectSize: Infinity }) {
30✔
45
        const seen = new WeakSet();
1,020✔
46
        return function (key, value) {
1,020✔
47
                if (typeof value === "object" && value !== null) {
74,490✔
48
                        const objectType = (value.constructor && value.constructor.name) || typeof value;
24,312!
49

50
                        if (
24,312✔
51
                                options.maxSafeObjectSize &&
12,291✔
52
                                "length" in value &&
53
                                value.length > options.maxSafeObjectSize
54
                        ) {
55
                                return `[${objectType} ${value.length}]`;
6✔
56
                        }
57

58
                        if (
24,306✔
59
                                options.maxSafeObjectSize &&
12,276✔
60
                                "size" in value &&
61
                                value.size > options.maxSafeObjectSize
62
                        ) {
63
                                return `[${objectType} ${value.size}]`;
6✔
64
                        }
65

66
                        if (seen.has(value)) {
24,300✔
67
                                //delete this[key];
68
                                return;
48✔
69
                        }
70
                        seen.add(value);
24,252✔
71
                }
72
                return value;
74,430✔
73
        };
74
}
75

76
const units = ["h", "m", "s", "ms", "μs", "ns"];
696✔
77
const divisors = [60 * 60 * 1000, 60 * 1000, 1000, 1, 1e-3, 1e-6];
696✔
78

79
const utils = {
696✔
80
        isFunction(fn) {
81
                return typeof fn === "function";
343,692✔
82
        },
83

84
        isString(s) {
85
                return typeof s === "string" || s instanceof String;
79,194✔
86
        },
87

88
        isObject(o) {
89
                return o !== null && typeof o === "object" && !(o instanceof String);
132,054✔
90
        },
91

92
        isPlainObject(o) {
93
                return o != null
9,162✔
94
                        ? Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null
4,326✔
95
                        : false;
96
        },
97

98
        isDate(d) {
99
                return d instanceof Date && !Number.isNaN(d.getTime());
4,566✔
100
        },
101

102
        flatten(arr) {
103
                return Array.prototype.reduce.call(arr, (a, b) => a.concat(b), []);
726✔
104
        },
105

106
        humanize(milli) {
107
                if (milli == null) return "?";
732✔
108

109
                for (let i = 0; i < divisors.length; i++) {
726✔
110
                        const val = milli / divisors[i];
2,986✔
111
                        if (val >= 1.0) return "" + Math.floor(val) + units[i];
2,986✔
112
                }
113

114
                return "now";
62✔
115
        },
116

117
        // Fast UUID generator: e7 https://jsperf.com/uuid-generator-opt/18
118
        generateToken() {
119
                const d0 = (Math.random() * 0xffffffff) | 0;
12,894✔
120
                const d1 = (Math.random() * 0xffffffff) | 0;
12,894✔
121
                const d2 = (Math.random() * 0xffffffff) | 0;
12,894✔
122
                const d3 = (Math.random() * 0xffffffff) | 0;
12,894✔
123
                return (
12,894✔
124
                        lut[d0 & 0xff] +
125
                        lut[(d0 >> 8) & 0xff] +
126
                        lut[(d0 >> 16) & 0xff] +
127
                        lut[(d0 >> 24) & 0xff] +
128
                        "-" +
129
                        lut[d1 & 0xff] +
130
                        lut[(d1 >> 8) & 0xff] +
131
                        "-" +
132
                        lut[((d1 >> 16) & 0x0f) | 0x40] +
133
                        lut[(d1 >> 24) & 0xff] +
134
                        "-" +
135
                        lut[(d2 & 0x3f) | 0x80] +
136
                        lut[(d2 >> 8) & 0xff] +
137
                        "-" +
138
                        lut[(d2 >> 16) & 0xff] +
139
                        lut[(d2 >> 24) & 0xff] +
140
                        lut[d3 & 0xff] +
141
                        lut[(d3 >> 8) & 0xff] +
142
                        lut[(d3 >> 16) & 0xff] +
143
                        lut[(d3 >> 24) & 0xff]
144
                );
145
        },
146

147
        removeFromArray(arr, item) {
148
                if (!arr || arr.length == 0) return arr;
1,536✔
149
                const idx = arr.indexOf(item);
1,518✔
150
                if (idx !== -1) arr.splice(idx, 1);
1,518✔
151

152
                return arr;
1,518✔
153
        },
154

155
        /**
156
         * Get default NodeID (computerName)
157
         *
158
         * @returns
159
         */
160
        getNodeID() {
161
                return os.hostname().toLowerCase() + "-" + process.pid;
1,848✔
162
        },
163

164
        /**
165
         * Get list of local IPs
166
         *
167
         * @returns
168
         */
169
        getIpList() {
170
                const list = [];
3,624✔
171
                const ilist = [];
3,624✔
172
                const interfaces = os.networkInterfaces();
3,624✔
173
                for (let iface in interfaces) {
3,624✔
174
                        for (let i in interfaces[iface]) {
9,084✔
175
                                const f = interfaces[iface][i];
16,044✔
176
                                if (f.family === "IPv4") {
16,044✔
177
                                        if (f.internal) {
9,084✔
178
                                                ilist.push(f.address);
4,350✔
179
                                                break;
4,350✔
180
                                        } else {
181
                                                list.push(f.address);
4,734✔
182
                                                break;
4,734✔
183
                                        }
184
                                }
185
                        }
186
                }
187
                return list.length > 0 ? list : ilist;
3,624✔
188
        },
189

190
        /**
191
         * Check the param is a Promise instance
192
         *
193
         * @param {any} p
194
         * @returns
195
         */
196
        isPromise(p) {
197
                return p != null && typeof p.then === "function";
144✔
198
        },
199

200
        /**
201
         * Polyfill a Promise library with missing Bluebird features.
202
         *
203
         *
204
         * @param {PromiseClass} P
205
         */
206
        polyfillPromise(P) {
207
                if (!utils.isFunction(P.method)) {
3,972✔
208
                        // Based on https://github.com/petkaantonov/bluebird/blob/master/src/method.js#L8
209
                        P.method = function (fn) {
612✔
210
                                return function () {
27,762✔
211
                                        try {
1,920✔
212
                                                const val = fn.apply(this, arguments);
1,920✔
213
                                                return P.resolve(val);
1,902✔
214
                                        } catch (err) {
215
                                                return P.reject(err);
18✔
216
                                        }
217
                                };
218
                        };
219
                }
220

221
                if (!utils.isFunction(P.delay)) {
3,972✔
222
                        // Based on https://github.com/petkaantonov/bluebird/blob/master/src/timers.js#L15
223
                        P.delay = function (ms) {
612✔
224
                                return new P(resolve => setTimeout(resolve, +ms));
2,232✔
225
                        };
226
                        P.prototype.delay = function (ms) {
612✔
227
                                return this.then(res => P.delay(ms).then(() => res));
834✔
228
                                //return this.then(res => new P(resolve => setTimeout(() => resolve(res), +ms)));
229
                        };
230
                }
231

232
                if (!utils.isFunction(P.prototype.timeout)) {
3,972✔
233
                        P.TimeoutError = TimeoutError;
612✔
234

235
                        P.prototype.timeout = function (ms, message) {
612✔
236
                                let timer;
237
                                const timeout = new P((resolve, reject) => {
72✔
238
                                        timer = setTimeout(() => reject(new P.TimeoutError(message)), +ms);
72✔
239
                                });
240

241
                                return P.race([timeout, this])
72✔
242
                                        .then(value => {
243
                                                clearTimeout(timer);
12✔
244
                                                return value;
12✔
245
                                        })
246
                                        .catch(err => {
247
                                                clearTimeout(timer);
60✔
248
                                                throw err;
60✔
249
                                        });
250
                        };
251
                }
252

253
                if (!utils.isFunction(P.mapSeries)) {
3,972✔
254
                        P.mapSeries = function (arr, fn) {
612✔
255
                                const promFn = Promise.method(fn);
24✔
256
                                const res = [];
24✔
257

258
                                return arr
24✔
259
                                        .reduce((p, item, i) => {
260
                                                return p.then(r => {
72✔
261
                                                        res[i] = r;
66✔
262
                                                        return promFn(item, i);
66✔
263
                                                });
264
                                        }, P.resolve())
265
                                        .then(r => {
266
                                                res[arr.length] = r;
12✔
267
                                                return res.slice(1);
12✔
268
                                        });
269
                        };
270
                }
271
        },
272

273
        /**
274
         * Promise control
275
         * if you'd always like to know the result of each promise
276
         *
277
         * @param {Array} promises
278
         * @param {Boolean} settled set true for result of each promise with reject
279
         * @param {Object} promise
280
         * @return {Promise<{[p: string]: PromiseSettledResult<*>}>|Promise<unknown[]>}
281
         */
282
        promiseAllControl(promises, settled = false, promise = Promise) {
15✔
283
                return settled ? promise.allSettled(promises) : promise.all(promises);
36✔
284
        },
285

286
        /**
287
         * Clear `require` cache. Used for service hot reloading
288
         *
289
         * @param {String} filename
290
         */
291
        clearRequireCache(filename) {
292
                /* istanbul ignore next */
293
                Object.keys(require.cache).forEach(function (key) {
294
                        if (key == filename) {
295
                                delete require.cache[key];
296
                        }
297
                });
298
        },
299

300
        /**
301
         * String matcher to handle dot-separated event/action names.
302
         *
303
         * @param {String} text
304
         * @param {String} pattern
305
         * @returns {Boolean}
306
         */
307
        match(text, pattern) {
308
                // Simple patterns
309
                if (pattern.indexOf("?") == -1) {
22,464✔
310
                        // Exact match (eg. "prefix.event")
311
                        const firstStarPosition = pattern.indexOf("*");
22,392✔
312
                        if (firstStarPosition == -1) {
22,392✔
313
                                return pattern === text;
18,744✔
314
                        }
315

316
                        // Eg. "prefix**"
317
                        const len = pattern.length;
3,648✔
318
                        if (len > 2 && pattern.endsWith("**") && firstStarPosition > len - 3) {
3,648✔
319
                                pattern = pattern.substring(0, len - 2);
1,236✔
320
                                return text.startsWith(pattern);
1,236✔
321
                        }
322

323
                        // Eg. "prefix*"
324
                        if (len > 1 && pattern.endsWith("*") && firstStarPosition > len - 2) {
2,412✔
325
                                pattern = pattern.substring(0, len - 1);
2,022✔
326
                                if (text.startsWith(pattern)) {
2,022✔
327
                                        return text.indexOf(".", len) == -1;
492✔
328
                                }
329
                                return false;
1,530✔
330
                        }
331

332
                        // Accept simple text, without point character (*)
333
                        if (len == 1 && firstStarPosition == 0) {
390✔
334
                                return text.indexOf(".") == -1;
18✔
335
                        }
336

337
                        // Accept all inputs (**)
338
                        if (len == 2 && firstStarPosition == 0 && pattern.lastIndexOf("*") == 1) {
372✔
339
                                return true;
120✔
340
                        }
341
                }
342

343
                // Regex (eg. "prefix.ab?cd.*.foo")
344
                const origPattern = pattern;
324✔
345
                let regex = RegexCache.get(origPattern);
324✔
346
                if (regex == null) {
324✔
347
                        if (pattern.startsWith("$")) {
174✔
348
                                pattern = "\\" + pattern;
18✔
349
                        }
350
                        pattern = pattern.replace(/\?/g, ".");
174✔
351
                        pattern = pattern.replace(/\*\*/g, "§§§");
174✔
352
                        pattern = pattern.replace(/\*/g, "[^\\.]*");
174✔
353
                        pattern = pattern.replace(/§§§/g, ".*");
174✔
354

355
                        pattern = "^" + pattern + "$";
174✔
356

357
                        // eslint-disable-next-line security/detect-non-literal-regexp
358
                        regex = new RegExp(pattern, "");
174✔
359
                        RegexCache.set(origPattern, regex);
174✔
360
                }
361
                return regex.test(text);
324✔
362
        },
363

364
        /**
365
         * Deprecate a method or property
366
         *
367
         * @param {Object|Function|String} prop
368
         * @param {String} msg
369
         */
370
        deprecate(prop, msg) {
371
                if (arguments.length == 1) msg = prop;
×
372

373
                if (deprecateList.indexOf(prop) === -1) {
×
374
                        // eslint-disable-next-line no-console
375
                        console.warn(kleur.yellow().bold(`DeprecationWarning: ${msg}`));
×
376
                        deprecateList.push(prop);
×
377
                }
378
        },
379

380
        /**
381
         * Remove circular references & Functions from the JS object
382
         *
383
         * @param {Object|Array} obj
384
         * @param {Object=} options List of options to change circularReplacer behaviour
385
         * @param {number=} options.maxSafeObjectSize List of options to change circularReplacer behaviour
386
         * @returns {Object|Array}
387
         */
388
        safetyObject(obj, options) {
389
                return JSON.parse(JSON.stringify(obj, circularReplacer(options)));
1,020✔
390
        },
391

392
        /**
393
         * Sets a variable on an object based on its dot path.
394
         *
395
         * @param {Object} obj
396
         * @param {String} path
397
         * @param {*} value
398
         * @returns {Object}
399
         */
400
        dotSet(obj, path, value) {
401
                const parts = path.split(".");
66✔
402
                const part = parts.shift();
66✔
403
                if (parts.length > 0) {
66✔
404
                        if (!Object.prototype.hasOwnProperty.call(obj, part)) {
36✔
405
                                obj[part] = {};
12✔
406
                        } else if (obj[part] == null) {
24✔
407
                                obj[part] = {};
6✔
408
                        } else {
409
                                if (typeof obj[part] !== "object") {
18✔
410
                                        throw new Error("Value already set and it's not an object");
6✔
411
                                }
412
                        }
413
                        obj[part] = utils.dotSet(obj[part], parts.join("."), value);
30✔
414
                        return obj;
24✔
415
                }
416
                obj[path] = value;
30✔
417
                return obj;
30✔
418
        },
419

420
        /**
421
         * Make directories recursively
422
         * @param {String} p - directory path
423
         */
424
        makeDirs(p) {
425
                p.split(path.sep).reduce((prevPath, folder) => {
×
426
                        const currentPath = path.join(prevPath, folder, path.sep);
×
427
                        if (!fs.existsSync(currentPath)) {
×
428
                                fs.mkdirSync(currentPath);
×
429
                        }
430
                        return currentPath;
×
431
                }, "");
432
        },
433

434
        /**
435
         * Parse a byte string to number of bytes. E.g "1kb" -> 1024
436
         * Credits: https://github.com/visionmedia/bytes.js
437
         *
438
         * @param {String} v
439
         * @returns {Number}
440
         */
441
        parseByteString(v) {
442
                if (typeof v === "number" && !isNaN(v)) {
210✔
443
                        return v;
66✔
444
                }
445

446
                if (typeof v !== "string") {
144✔
447
                        return null;
12✔
448
                }
449

450
                // Test if the string passed is valid
451
                let results = parseByteStringRe.exec(v);
132✔
452
                let floatValue;
453
                let unit = "b";
132✔
454

455
                if (!results) {
132✔
456
                        // Nothing could be extracted from the given string
457
                        floatValue = parseInt(v, 10);
78✔
458
                        if (Number.isNaN(floatValue)) return null;
78✔
459

460
                        unit = "b";
54✔
461
                } else {
462
                        // Retrieve the value and the unit
463
                        floatValue = parseFloat(results[1]);
54✔
464
                        unit = results[4].toLowerCase();
54✔
465
                }
466

467
                return Math.floor(byteMultipliers[unit] * floatValue);
108✔
468
        },
469

470
        /**
471
         * Get the name of constructor of an object.
472
         *
473
         * @param {Object} obj
474
         * @returns {String}
475
         */
476
        getConstructorName(obj) {
477
                if (obj == null) return undefined;
28,908!
478

479
                let target = obj.prototype;
28,908✔
480
                if (target && target.constructor && target.constructor.name) {
28,908✔
481
                        return target.constructor.name;
5,466✔
482
                }
483
                if (obj.constructor && obj.constructor.name) {
23,442!
484
                        return obj.constructor.name;
23,442✔
485
                }
486
                return undefined;
×
487
        },
488

489
        /**
490
         * Check whether the instance is an instance of the given class.
491
         *
492
         * @param {Object} instance
493
         * @param {Object} baseClass
494
         * @returns {Boolean}
495
         */
496
        isInheritedClass(instance, baseClass) {
497
                const baseClassName = module.exports.getConstructorName(baseClass);
870✔
498
                let proto = instance;
870✔
499
                while ((proto = Object.getPrototypeOf(proto))) {
870✔
500
                        const protoName = module.exports.getConstructorName(proto);
1,302✔
501
                        if (baseClassName == protoName) return true;
1,302✔
502
                }
503

504
                return false;
426✔
505
        },
506

507
        /**
508
         * Detects the argument names of a function.
509
         * Credits: https://github.com/sindresorhus/fn-args
510
         *
511
         * @param {Function} function_
512
         * @returns {Array<String>}
513
         */
514
        functionArguments(function_) {
515
                if (typeof function_ !== "function") {
432!
516
                        throw new TypeError("Expected a function");
×
517
                }
518

519
                const commentRegex = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm;
432✔
520
                const quotes = ["`", '"', "'"];
432✔
521

522
                const functionSource = function_.toString().replace(commentRegex, ""); // Function with no comments
432✔
523

524
                let functionWithNoDefaults = "";
432✔
525
                let depth = 0; // () [] {}
432✔
526
                let index = 0;
432✔
527

528
                // To remove default values we can not use regexp because finite automaton can not handle such
529
                // things as (potential) infinity-nested blocks (), [], {}
530

531
                // Remove default values
532
                for (; index < functionSource.length && functionSource.charAt(index) !== ")"; index += 1) {
432✔
533
                        // Exiting if an arrow occurs. Needed when arrow function without '()'.
534
                        if (functionSource.startsWith("=>", index)) {
6,204✔
535
                                functionWithNoDefaults = functionSource;
6✔
536
                                index = functionSource.length;
6✔
537
                                break;
6✔
538
                        }
539

540
                        // If we found a default value - skip it
541
                        if (functionSource.charAt(index) === "=") {
6,198!
542
                                for (
×
543
                                        ;
544
                                        index < functionSource.length &&
×
545
                                        ((functionSource.charAt(index) !== "," &&
546
                                                functionSource.charAt(index) !== ")") ||
547
                                                depth !== 0);
548
                                        index += 1
549
                                ) {
550
                                        // Skip all quotes
551
                                        let wasQuote = false;
×
552

553
                                        for (const quote of quotes) {
×
554
                                                if (functionSource.charAt(index) === quote) {
×
555
                                                        index += 1;
×
556

557
                                                        for (
×
558
                                                                ;
559
                                                                index < functionSource.length &&
×
560
                                                                functionSource.charAt(index) !== quote;
561

562
                                                        ) {
563
                                                                index += 1;
×
564
                                                        }
565

566
                                                        wasQuote = true;
×
567
                                                        break;
×
568
                                                }
569
                                        }
570

571
                                        // If any quote type was skipped, start the cycle again
572
                                        if (wasQuote) {
×
573
                                                continue;
×
574
                                        }
575

576
                                        switch (
×
577
                                                functionSource.charAt(index) // Keeps correct depths of all types of parenthesises
578
                                        ) {
579
                                                case "(":
580
                                                case "[":
581
                                                case "{":
582
                                                        depth += 1;
×
583
                                                        break;
×
584
                                                case ")":
585
                                                case "]":
586
                                                case "}":
587
                                                        depth -= 1;
×
588
                                                        break;
×
589
                                                default:
590
                                        }
591
                                }
592

593
                                if (functionSource.charAt(index) === ",") {
×
594
                                        functionWithNoDefaults += ",";
×
595
                                }
596

597
                                if (functionSource.charAt(index) === ")") {
×
598
                                        // Quits from the cycle immediately
599
                                        functionWithNoDefaults += ")";
×
600
                                        break;
×
601
                                }
602
                        } else {
603
                                functionWithNoDefaults += functionSource.charAt(index);
6,198✔
604
                        }
605
                }
606

607
                if (index < functionSource.length && functionSource.charAt(index) === ")") {
432✔
608
                        functionWithNoDefaults += ")";
426✔
609
                }
610

611
                // The first part matches parens-less arrow functions
612
                // The second part matches the rest
613
                const regexFnArguments = /^(?:async)?([^=()]+)=|\(([^)]+)\)/;
432✔
614

615
                const match = regexFnArguments.exec(functionWithNoDefaults);
432✔
616

617
                return match
432✔
618
                        ? (match[1] || match[2])
105✔
619
                                        .split(",")
620
                                        .map(x => x.trim())
168✔
621
                                        .filter(Boolean)
622
                        : [];
623
        },
624

625
        /**
626
         * Creates a duplicate-free version of an array
627
         *
628
         * @param {Array<String|Number>} arr
629
         * @returns {Array<String|Number>}
630
         */
631
        uniq(arr) {
632
                return [...new Set(arr)];
90✔
633
        },
634

635
        /**
636
         * Produces a random floating number between the inclusive lower and upper bounds.
637
         *
638
         * @param {Number} a
639
         * @param {Number} b
640
         * @returns {Number}
641
         */
642
        random(a = 1, b = 0) {
×
643
                const lower = Math.min(a, b);
×
644
                const upper = Math.max(a, b);
×
645

646
                return lower + Math.random() * (upper - lower);
×
647
        },
648

649
        /**
650
         * Produces a random integer number between the inclusive lower and upper bounds.
651
         *
652
         * @param {Number} a
653
         * @param {Number} b
654
         * @returns {Number}
655
         */
656
        randomInt(a = 1, b = 0) {
135!
657
                const lower = Math.ceil(Math.min(a, b));
342✔
658
                const upper = Math.floor(Math.max(a, b));
342✔
659

660
                return Math.floor(lower + Math.random() * (upper - lower + 1));
342✔
661
        }
662
};
663

664
module.exports = utils;
696✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc