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

moleculerjs / moleculer / 29819832096

21 Jul 2026 09:49AM UTC coverage: 94.047% (+0.3%) from 93.705%
29819832096

Pull #1342

github

web-flow
Merge 2660dc9cb into c32e3eff8
Pull Request #1342: Forward termination signals to cluster workers in runner

5339 of 5898 branches covered (90.52%)

Branch coverage included in aggregate %.

7221 of 7457 relevant lines covered (96.84%)

515.86 hits per line

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

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

7
"use strict";
8

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

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

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

22
const deprecateList = [];
230✔
23

24
const byteMultipliers = {
230✔
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;
230✔
34

35
/**
36
 * Circular replacing of unsafe properties in object
37
 *
38
 * @param {object} options List of options to change circularReplacer behaviour
39
 * @param {number?} [options.maxSafeObjectSize = Infinity] Maximum size of objects for safe object converting
40
 * @return {(key: string, value: any) => any}
41
 */
42
function circularReplacer(options = { maxSafeObjectSize: Infinity }) {
10✔
43
        const seen = new WeakSet();
340✔
44
        return function (key, value) {
340✔
45
                if (typeof value === "object" && value !== null) {
24,694✔
46
                        const objectType = (value.constructor && value.constructor.name) || typeof value;
8,104!
47

48
                        if (
8,104✔
49
                                options.maxSafeObjectSize &&
4,097✔
50
                                "length" in value &&
51
                                value.length > options.maxSafeObjectSize
52
                        ) {
53
                                return `[${objectType} ${value.length}]`;
2✔
54
                        }
55

56
                        if (
8,102✔
57
                                options.maxSafeObjectSize &&
4,092✔
58
                                "size" in value &&
59
                                value.size > options.maxSafeObjectSize
60
                        ) {
61
                                return `[${objectType} ${value.size}]`;
2✔
62
                        }
63

64
                        if (seen.has(value)) {
8,100✔
65
                                //delete this[key];
66
                                return;
16✔
67
                        }
68
                        seen.add(value);
8,084✔
69
                }
70
                return value;
24,674✔
71
        };
72
}
73

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

77
const utils = {
230✔
78
        isFunction(fn) {
79
                return typeof fn === "function";
164,248✔
80
        },
81

82
        isString(s) {
83
                return typeof s === "string" || s instanceof String;
26,576✔
84
        },
85

86
        isObject(o) {
87
                return o !== null && typeof o === "object" && !(o instanceof String);
43,062✔
88
        },
89

90
        isPlainObject(o) {
91
                return o != null
3,050✔
92
                        ? Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null
1,440✔
93
                        : false;
94
        },
95

96
        isDate(d) {
97
                return d instanceof Date && !Number.isNaN(d.getTime());
1,524✔
98
        },
99

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

104
        humanize(milli) {
105
                if (milli == null) return "?";
246✔
106

107
                for (let i = 0; i < divisors.length; i++) {
244✔
108
                        const val = milli / divisors[i];
1,002✔
109
                        if (val >= 1.0) return "" + Math.floor(val) + units[i];
1,002✔
110
                }
111

112
                return "now";
20✔
113
        },
114

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

145
        removeFromArray(arr, item) {
146
                if (!arr || arr.length === 0) return arr;
512✔
147
                const idx = arr.indexOf(item);
506✔
148
                if (idx !== -1) arr.splice(idx, 1);
506✔
149

150
                return arr;
506✔
151
        },
152

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

162
        /**
163
         * Get list of local IPs
164
         *
165
         * @returns
166
         */
167
        getIpList() {
168
                const list = [];
1,212✔
169
                const ilist = [];
1,212✔
170
                const interfaces = os.networkInterfaces();
1,212✔
171
                for (let iface in interfaces) {
1,212✔
172
                        for (let i in interfaces[iface]) {
2,630✔
173
                                const f = interfaces[iface]?.[i];
3,912✔
174
                                if (f.family === "IPv4") {
3,912✔
175
                                        if (f.internal) {
2,630✔
176
                                                ilist.push(f.address);
1,544✔
177
                                                break;
1,544✔
178
                                        } else {
179
                                                list.push(f.address);
1,086✔
180
                                                break;
1,086✔
181
                                        }
182
                                }
183
                        }
184
                }
185
                return list.length > 0 ? list : ilist;
1,212✔
186
        },
187

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

198
        /**
199
         * Polyfill a Promise library with missing Bluebird features.
200
         *
201
         * @param {typeof Promise} P
202
         */
203
        polyfillPromise(P) {
204
                if (!utils.isFunction(P.method)) {
1,330✔
205
                        // Based on https://github.com/petkaantonov/bluebird/blob/master/src/method.js#L8
206
                        P.method = function (fn) {
202✔
207
                                return function () {
9,298✔
208
                                        try {
644✔
209
                                                const val = fn.apply(this, arguments);
644✔
210
                                                return P.resolve(val);
638✔
211
                                        } catch (err) {
212
                                                return P.reject(err);
6✔
213
                                        }
214
                                };
215
                        };
216
                }
217

218
                if (!utils.isFunction(P.delay)) {
1,330✔
219
                        // Based on https://github.com/petkaantonov/bluebird/blob/master/src/timers.js#L15
220
                        P.delay = function (ms) {
202✔
221
                                return new P(resolve => setTimeout(() => resolve(null), +ms));
852✔
222
                        };
223
                        P.prototype.delay = function (ms) {
202✔
224
                                return this.then(res => P.delay(ms).then(() => res));
384✔
225
                                //return this.then(res => new P(resolve => setTimeout(() => resolve(res), +ms)));
226
                        };
227
                }
228

229
                if (!utils.isFunction(P.prototype.timeout)) {
1,330✔
230
                        P.prototype.timeout = function (ms, message) {
202✔
231
                                let timer;
232
                                const timeout = new P((resolve, reject) => {
24✔
233
                                        timer = setTimeout(() => reject(new TimeoutError(message)), +ms);
24✔
234
                                });
235

236
                                return P.race([timeout, this])
24✔
237
                                        .then(value => {
238
                                                clearTimeout(timer);
4✔
239
                                                return value;
4✔
240
                                        })
241
                                        .catch(err => {
242
                                                clearTimeout(timer);
20✔
243
                                                throw err;
20✔
244
                                        });
245
                        };
246
                }
247

248
                if (!utils.isFunction(P.mapSeries)) {
1,330✔
249
                        P.mapSeries = function (arr, fn) {
202✔
250
                                const promFn = Promise.method(fn);
8✔
251
                                const res = [];
8✔
252

253
                                return arr
8✔
254
                                        .reduce((p, item, i) => {
255
                                                return p.then(r => {
24✔
256
                                                        res[i] = r;
22✔
257
                                                        return promFn(item, i);
22✔
258
                                                });
259
                                        }, P.resolve())
260
                                        .then(r => {
261
                                                res[arr.length] = r;
4✔
262
                                                return res.slice(1);
4✔
263
                                        });
264
                        };
265
                }
266
        },
267

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

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

295
        /**
296
         * String matcher to handle dot-separated event/action names.
297
         *
298
         * @param {String} text
299
         * @param {String} pattern
300
         * @returns {Boolean}
301
         */
302
        match(text, pattern) {
303
                // Simple patterns
304
                if (pattern.indexOf("?") == -1) {
7,930✔
305
                        // Exact match (eg. "prefix.event")
306
                        const firstStarPosition = pattern.indexOf("*");
7,906✔
307
                        if (firstStarPosition == -1) {
7,906✔
308
                                return pattern === text;
6,632✔
309
                        }
310

311
                        // Eg. "prefix**"
312
                        const len = pattern.length;
1,274✔
313
                        if (len > 2 && pattern.endsWith("**") && firstStarPosition > len - 3) {
1,274✔
314
                                pattern = pattern.substring(0, len - 2);
414✔
315
                                return text.startsWith(pattern);
414✔
316
                        }
317

318
                        // Eg. "prefix*"
319
                        if (len > 1 && pattern.endsWith("*") && firstStarPosition > len - 2) {
860✔
320
                                pattern = pattern.substring(0, len - 1);
730✔
321
                                if (text.startsWith(pattern)) {
730✔
322
                                        return text.indexOf(".", len) == -1;
164✔
323
                                }
324
                                return false;
566✔
325
                        }
326

327
                        // Accept simple text, without point character (*)
328
                        if (len == 1 && firstStarPosition === 0) {
130✔
329
                                return text.indexOf(".") == -1;
6✔
330
                        }
331

332
                        // Accept all inputs (**)
333
                        if (len == 2 && firstStarPosition == 0 && pattern.lastIndexOf("*") == 1) {
124✔
334
                                return true;
40✔
335
                        }
336
                }
337

338
                // Regex (eg. "prefix.ab?cd.*.foo")
339
                const origPattern = pattern;
108✔
340
                let regex = RegexCache.get(origPattern);
108✔
341
                if (regex == null) {
108✔
342
                        if (pattern.startsWith("$")) {
58✔
343
                                pattern = "\\" + pattern;
6✔
344
                        }
345
                        pattern = pattern.replace(/\?/g, ".");
58✔
346
                        pattern = pattern.replace(/\*\*/g, "§§§");
58✔
347
                        pattern = pattern.replace(/\*/g, "[^\\.]*");
58✔
348
                        pattern = pattern.replace(/§§§/g, ".*");
58✔
349

350
                        pattern = "^" + pattern + "$";
58✔
351

352
                        // eslint-disable-next-line security/detect-non-literal-regexp
353
                        regex = new RegExp(pattern, "");
58✔
354
                        RegexCache.set(origPattern, regex);
58✔
355
                }
356
                return regex.test(text);
108✔
357
        },
358

359
        /**
360
         * Deprecate a method or property
361
         *
362
         * @param {Object|Function|String} prop
363
         * @param {String} msg
364
         */
365
        deprecate(prop, msg) {
366
                if (arguments.length == 1) msg = prop;
×
367

368
                if (deprecateList.indexOf(prop) === -1) {
×
369
                        // eslint-disable-next-line no-console
370
                        console.warn(kleur.yellow().bold(`DeprecationWarning: ${msg}`));
×
371
                        deprecateList.push(prop);
×
372
                }
373
        },
374

375
        /**
376
         * Remove circular references & Functions from the JS object
377
         *
378
         * @param {Object|Array} obj
379
         * @param {object} options List of options to change circularReplacer behaviour
380
         * @param {number?} [options.maxSafeObjectSize = Infinity] Maximum size of objects for safe object converting
381
         * @returns {Object|Array}
382
         */
383
        safetyObject(obj, options) {
384
                return JSON.parse(JSON.stringify(obj, circularReplacer(options)));
340✔
385
        },
386

387
        /**
388
         * Sets a variable on an object based on its dot path.
389
         *
390
         * @param {Record<string,any>} obj
391
         * @param {String} path
392
         * @param {*} value
393
         * @returns {Object}
394
         */
395
        dotSet(obj, path, value) {
396
                const parts = path.split(".");
22✔
397
                const part = parts.shift();
22✔
398
                if (part && parts.length > 0) {
22✔
399
                        if (!Object.prototype.hasOwnProperty.call(obj, part)) {
12✔
400
                                obj[part] = {};
4✔
401
                        } else if (obj[part] == null) {
8✔
402
                                obj[part] = {};
2✔
403
                        } else {
404
                                if (typeof obj[part] !== "object") {
6✔
405
                                        throw new Error("Value already set and it's not an object");
2✔
406
                                }
407
                        }
408
                        obj[part] = utils.dotSet(obj[part], parts.join("."), value);
10✔
409
                        return obj;
8✔
410
                }
411
                obj[path] = value;
10✔
412
                return obj;
10✔
413
        },
414

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

429
        /**
430
         * Parse a byte string to number of bytes. E.g "1kb" -> 1024
431
         * Credits: https://github.com/visionmedia/bytes.js
432
         *
433
         * @param {String|number} v
434
         * @returns {Number|null}
435
         */
436
        parseByteString(v) {
437
                if (typeof v === "number" && !isNaN(v)) {
70✔
438
                        return v;
22✔
439
                }
440

441
                if (typeof v !== "string") {
48✔
442
                        return null;
4✔
443
                }
444

445
                // Test if the string passed is valid
446
                let results = parseByteStringRe.exec(v);
44✔
447
                let floatValue;
448
                let unit;
449

450
                if (!results) {
44✔
451
                        // Nothing could be extracted from the given string
452
                        floatValue = parseInt(v, 10);
26✔
453
                        if (Number.isNaN(floatValue)) return null;
26✔
454

455
                        unit = "b";
18✔
456
                } else {
457
                        // Retrieve the value and the unit
458
                        floatValue = parseFloat(results[1]);
18✔
459
                        unit = results[4].toLowerCase();
18✔
460
                }
461

462
                return Math.floor(byteMultipliers[unit] * floatValue);
36✔
463
        },
464

465
        /**
466
         * Get the name of constructor of an object.
467
         *
468
         * @param {Object} obj
469
         * @returns {String|undefined}
470
         */
471
        getConstructorName(obj) {
472
                if (obj == null) return undefined;
9,678!
473

474
                let target = obj.prototype;
9,678✔
475
                if (target && target.constructor && target.constructor.name) {
9,678✔
476
                        return target.constructor.name;
1,830✔
477
                }
478
                if (obj.constructor && obj.constructor.name) {
7,848!
479
                        return obj.constructor.name;
7,848✔
480
                }
481
                return undefined;
×
482
        },
483

484
        /**
485
         * Check whether the instance is an instance of the given class.
486
         *
487
         * @param {Object} instance
488
         * @param {Object} baseClass
489
         * @returns {Boolean}
490
         */
491
        isInheritedClass(instance, baseClass) {
492
                const baseClassName = module.exports.getConstructorName(baseClass);
288✔
493
                let proto = instance;
288✔
494
                while ((proto = Object.getPrototypeOf(proto))) {
288✔
495
                        const protoName = module.exports.getConstructorName(proto);
432✔
496
                        if (baseClassName == protoName) return true;
432✔
497
                }
498

499
                return false;
140✔
500
        },
501

502
        /**
503
         * Creates a duplicate-free version of an array
504
         *
505
         * @param {Array<String|Number>} arr
506
         * @returns {Array<String|Number>}
507
         */
508
        uniq(arr) {
509
                return [...new Set(arr)];
30✔
510
        },
511

512
        /**
513
         * Produces a random floating number between the inclusive lower and upper bounds.
514
         *
515
         * @param {Number} a
516
         * @param {Number} b
517
         * @returns {Number}
518
         */
519
        random(a = 1, b = 0) {
×
520
                const lower = Math.min(a, b);
×
521
                const upper = Math.max(a, b);
×
522

523
                return lower + Math.random() * (upper - lower);
×
524
        },
525

526
        /**
527
         * Produces a random integer number between the inclusive lower and upper bounds.
528
         *
529
         * @param {Number} a
530
         * @param {Number} b
531
         * @returns {Number}
532
         */
533
        randomInt(a = 1, b = 0) {
46!
534
                const lower = Math.ceil(Math.min(a, b));
116✔
535
                const upper = Math.floor(Math.max(a, b));
116✔
536

537
                return Math.floor(lower + Math.random() * (upper - lower + 1));
116✔
538
        }
539
};
540

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

© 2026 Coveralls, Inc