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

moleculerjs / moleculer / 6997741482

26 Nov 2023 08:09PM UTC coverage: 94.161% (-0.001%) from 94.162%
6997741482

push

github

icebob
types: loggers

5314 of 5865 branches covered (0.0%)

Branch coverage included in aggregate %.

7201 of 7426 relevant lines covered (96.97%)

998.56 hits per line

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

92.27
/src/utils.js
1
// @ts-check
2
/*
3
 * moleculer
4
 * Copyright (c) 2023 MoleculerJS (https://github.com/moleculerjs/moleculer)
5
 * MIT Licensed
6
 */
7

8
"use strict";
9

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

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

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

23
const deprecateList = [];
696✔
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

230
                if (!utils.isFunction(P.prototype.timeout)) {
3,972✔
231
                        P.prototype.timeout = function (ms, message) {
612✔
232
                                let timer;
233
                                const timeout = new P((resolve, reject) => {
72✔
234
                                        timer = setTimeout(() => reject(new TimeoutError(message)), +ms);
72✔
235
                                });
236

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

249
                if (!utils.isFunction(P.mapSeries)) {
3,972✔
250
                        P.mapSeries = function (arr, fn) {
612✔
251
                                const promFn = Promise.method(fn);
24✔
252
                                const res = [];
24✔
253

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

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

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

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

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

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

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

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

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

351
                        pattern = "^" + pattern + "$";
174✔
352

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

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

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

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

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

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

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

442
                if (typeof v !== "string") {
144✔
443
                        return null;
12✔
444
                }
445

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

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

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

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

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

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

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

500
                return false;
426✔
501
        },
502

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

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

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

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

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

542
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