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

moleculerjs / moleculer / #2013

13 Jan 2024 11:34AM UTC coverage: 93.889%. Remained the same
#2013

push

web-flow
Merge pull request #1258 from 0x0a0d/transit-ifx

Transit improve

3984 of 4504 branches covered (0.0%)

Branch coverage included in aggregate %.

7354 of 7572 relevant lines covered (97.12%)

248.07 hits per line

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

82.23
/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");
117✔
10
const os = require("os");
117✔
11
const path = require("path");
117✔
12
const fs = require("fs");
117✔
13
const { ExtendableError } = require("./errors");
117✔
14

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

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

22
const deprecateList = [];
117✔
23

24
const byteMultipliers = {
117✔
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;
117✔
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 }) {
10✔
45
        const seen = new WeakSet();
170✔
46
        return function (key, value) {
170✔
47
                if (typeof value === "object" && value !== null) {
12,146✔
48
                        const objectType = (value.constructor && value.constructor.name) || typeof value;
4,052!
49

50
                        if (
4,052✔
51
                                options.maxSafeObjectSize &&
4,097✔
52
                                "length" in value &&
53
                                value.length > options.maxSafeObjectSize
54
                        ) {
55
                                return `[${objectType} ${value.length}]`;
1✔
56
                        }
57

58
                        if (
4,051✔
59
                                options.maxSafeObjectSize &&
4,092✔
60
                                "size" in value &&
61
                                value.size > options.maxSafeObjectSize
62
                        ) {
63
                                return `[${objectType} ${value.size}]`;
1✔
64
                        }
65

66
                        if (seen.has(value)) {
4,050✔
67
                                //delete this[key];
68
                                return;
8✔
69
                        }
70
                        seen.add(value);
4,042✔
71
                }
72
                return value;
12,136✔
73
        };
74
}
75

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

79
const utils = {
117✔
80
        isFunction(fn) {
81
                return typeof fn === "function";
59,562✔
82
        },
83

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

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

92
        isPlainObject(o) {
93
                return o != null
1,520✔
94
                        ? Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null
1,435✔
95
                        : false;
96
        },
97

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

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

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

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

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

117
        // Fast UUID generator: e7 https://jsperf.com/uuid-generator-opt/18
118
        generateToken() {
119
                const d0 = (Math.random() * 0xffffffff) | 0;
2,167✔
120
                const d1 = (Math.random() * 0xffffffff) | 0;
2,167✔
121
                const d2 = (Math.random() * 0xffffffff) | 0;
2,167✔
122
                const d3 = (Math.random() * 0xffffffff) | 0;
2,167✔
123
                return (
2,167✔
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;
256✔
149
                const idx = arr.indexOf(item);
253✔
150
                if (idx !== -1) arr.splice(idx, 1);
253✔
151

152
                return arr;
253✔
153
        },
154

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

164
        /**
165
         * Get list of local IPs
166
         *
167
         * @returns
168
         */
169
        getIpList() {
170
                const list = [];
622✔
171
                const ilist = [];
622✔
172
                const interfaces = os.networkInterfaces();
622✔
173
                for (let iface in interfaces) {
622✔
174
                        for (let i in interfaces[iface]) {
941✔
175
                                const f = interfaces[iface][i];
946✔
176
                                if (f.family === "IPv4") {
946✔
177
                                        if (f.internal) {
941✔
178
                                                ilist.push(f.address);
471✔
179
                                                break;
471✔
180
                                        } else {
181
                                                list.push(f.address);
470✔
182
                                                break;
470✔
183
                                        }
184
                                }
185
                        }
186
                }
187
                return list.length > 0 ? list : ilist;
622✔
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";
24✔
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)) {
681✔
208
                        // Based on https://github.com/petkaantonov/bluebird/blob/master/src/method.js#L8
209
                        P.method = function (fn) {
103✔
210
                                return function () {
4,761✔
211
                                        try {
320✔
212
                                                const val = fn.apply(this, arguments);
320✔
213
                                                return P.resolve(val);
317✔
214
                                        } catch (err) {
215
                                                return P.reject(err);
3✔
216
                                        }
217
                                };
218
                        };
219
                }
220

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

232
                if (!utils.isFunction(P.prototype.timeout)) {
681✔
233
                        P.TimeoutError = TimeoutError;
103✔
234

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

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

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

258
                                return arr
4✔
259
                                        .reduce((p, item, i) => {
260
                                                return p.then(r => {
12✔
261
                                                        res[i] = r;
11✔
262
                                                        return promFn(item, i);
11✔
263
                                                });
264
                                        }, P.resolve())
265
                                        .then(r => {
266
                                                res[arr.length] = r;
2✔
267
                                                return res.slice(1);
2✔
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) {
5✔
283
                return settled ? promise.allSettled(promises) : promise.all(promises);
6✔
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) {
3,743✔
310
                        // Exact match (eg. "prefix.event")
311
                        const firstStarPosition = pattern.indexOf("*");
3,731✔
312
                        if (firstStarPosition == -1) {
3,731✔
313
                                return pattern === text;
3,124✔
314
                        }
315

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

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

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

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

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

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

357
                        // eslint-disable-next-line security/detect-non-literal-regexp
358
                        regex = new RegExp(pattern, "");
29✔
359
                        RegexCache.set(origPattern, regex);
29✔
360
                }
361
                return regex.test(text);
54✔
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)));
170✔
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(".");
11✔
402
                const part = parts.shift();
11✔
403
                if (parts.length > 0) {
11✔
404
                        if (!Object.prototype.hasOwnProperty.call(obj, part)) {
6✔
405
                                obj[part] = {};
2✔
406
                        } else if (obj[part] == null) {
4✔
407
                                obj[part] = {};
1✔
408
                        } else {
409
                                if (typeof obj[part] !== "object") {
3✔
410
                                        throw new Error("Value already set and it's not an object");
1✔
411
                                }
412
                        }
413
                        obj[part] = utils.dotSet(obj[part], parts.join("."), value);
5✔
414
                        return obj;
4✔
415
                }
416
                obj[path] = value;
5✔
417
                return obj;
5✔
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)) {
35✔
443
                        return v;
11✔
444
                }
445

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

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

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

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

467
                return Math.floor(byteMultipliers[unit] * floatValue);
18✔
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;
4,937!
478

479
                let target = obj.prototype;
4,937✔
480
                if (target && target.constructor && target.constructor.name) {
4,937✔
481
                        return target.constructor.name;
932✔
482
                }
483
                if (obj.constructor && obj.constructor.name) {
4,005!
484
                        return obj.constructor.name;
4,005✔
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);
146✔
498
                let proto = instance;
146✔
499
                while ((proto = Object.getPrototypeOf(proto))) {
146✔
500
                        const protoName = module.exports.getConstructorName(proto);
218✔
501
                        if (baseClassName == protoName) return true;
218✔
502
                }
503

504
                return false;
72✔
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") {
72!
516
                        throw new TypeError("Expected a function");
×
517
                }
518

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

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

524
                let functionWithNoDefaults = "";
72✔
525
                let depth = 0; // () [] {}
72✔
526
                let index = 0;
72✔
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) {
72✔
533
                        // Exiting if an arrow occurs. Needed when arrow function without '()'.
534
                        if (functionSource.startsWith("=>", index)) {
1,034✔
535
                                functionWithNoDefaults = functionSource;
1✔
536
                                index = functionSource.length;
1✔
537
                                break;
1✔
538
                        }
539

540
                        // If we found a default value - skip it
541
                        if (functionSource.charAt(index) === "=") {
1,033!
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);
1,033✔
604
                        }
605
                }
606

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

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

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

617
                return match
72✔
618
                        ? (match[1] || match[2])
35✔
619
                                        .split(",")
620
                                        .map(x => x.trim())
28✔
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)];
15✔
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) {
46!
657
                const lower = Math.ceil(Math.min(a, b));
58✔
658
                const upper = Math.floor(Math.max(a, b));
58✔
659

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

664
module.exports = utils;
117✔
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