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

moleculerjs / moleculer / 6486948241

11 Oct 2023 07:10PM UTC coverage: 93.839%. Remained the same
6486948241

push

github

icebob
fix constructor

5340 of 5917 branches covered (0.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

7226 of 7474 relevant lines covered (96.68%)

1494.17 hits per line

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

95.91
/src/context.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 util = require("util");
606✔
10
const { pick } = require("lodash");
606✔
11
const { RequestSkippedError, MaxCallLevelError } = require("./errors");
606✔
12

13
/**
14
 * @typedef {import("./service-broker")} ServiceBroker Moleculer Service Broker instance
15
 * @typedef {import("./service-broker").CallingOptions} CallingOptions Calling Options
16
 * @typedef {import("./registry/endpoint")} Endpoint Registry Endpoint
17
 * @typedef {import("./registry/endpoint-action")} ActionEndpoint Registry Action Endpoint
18
 * @typedef {import("./registry/endpoint-event")} EventEndpoint Registry Event Endpoint
19
 * @typedef {import("./tracing/span")} Span Tracing Span
20
 */
21

22
/**
23
 * Merge metadata
24
 *
25
 * @param {Context} ctx
26
 * @param {Object} newMeta
27
 */
28
function mergeMeta(ctx, newMeta) {
29
        if (newMeta) Object.assign(ctx.meta, newMeta);
90!
30
        return ctx.meta;
90✔
31
}
32

33
/**
34
 * Context class for action calls
35
 *
36
 * @class Context
37
 */
38
class Context {
39
        /**
40
         * Creates an instance of Context.
41
         *
42
         * @param {ServiceBroker} broker - Broker instance
43
         * @param {ActionEndpoint|EventEndpoint} endpoint
44
         *
45
         * @memberof Context
46
         */
47
        constructor(broker, endpoint) {
48
                this.broker = broker;
11,202✔
49
                if (this.broker) {
11,202✔
50
                        this.nodeID = this.broker.nodeID;
11,052✔
51
                        this.id = this.broker.generateUid();
11,052✔
52
                } else {
53
                        this.nodeID = null;
150✔
54
                }
55

56
                if (endpoint) {
11,202✔
57
                        this.setEndpoint(endpoint);
912✔
58
                } else {
59
                        this.endpoint = null;
10,290✔
60
                        this.service = null;
10,290✔
61
                        this.action = null;
10,290✔
62
                        this.event = null;
10,290✔
63
                }
64

65
                // The emitted event "user.created" because `ctx.event.name` can be "user.**"
66
                this.eventName = null;
11,202✔
67
                // Type of event ("emit" or "broadcast")
68
                this.eventType = null;
11,202✔
69
                // The groups of event
70
                this.eventGroups = null;
11,202✔
71

72
                /** @type {CallingOptions} */
73
                this.options = {
11,202✔
74
                        timeout: null,
75
                        retries: null
76
                };
77

78
                this.parentID = null;
11,202✔
79
                this.caller = null;
11,202✔
80

81
                this.level = 1;
11,202✔
82

83
                this.params = null;
11,202✔
84
                this.meta = {};
11,202✔
85
                this.headers = {};
11,202✔
86
                this.responseHeaders = {};
11,202✔
87
                this.locals = {};
11,202✔
88

89
                this.stream = null;
11,202✔
90

91
                this.requestID = this.id;
11,202✔
92

93
                this.tracing = null;
11,202✔
94
                this.span = null;
11,202✔
95
                this._spanStack = [];
11,202✔
96

97
                this.needAck = null;
11,202✔
98
                this.ackID = null;
11,202✔
99

100
                this.startHrTime = null;
11,202✔
101

102
                this.cachedResult = false;
11,202✔
103
        }
104

105
        /**
106
         * Create a new Context instance
107
         *
108
         * @param {ServiceBroker} broker
109
         * @param {ActionEndpoint|EventEndpoint} endpoint
110
         * @param {Object?} params
111
         * @param {CallingOptions} opts
112
         * @returns {Context}
113
         *
114
         * @static
115
         * @memberof Context
116
         */
117
        static create(broker, endpoint, params, opts = {}) {
381✔
118
                const ctx = new broker.ContextFactory(broker, endpoint);
8,832✔
119

120
                if (endpoint != null) ctx.setEndpoint(endpoint);
8,832✔
121

122
                if (params != null) {
8,832✔
123
                        let cloning = broker ? broker.options.contextParamsCloning : false;
6,864!
124
                        if (opts.paramsCloning != null) cloning = opts.paramsCloning;
6,864✔
125
                        ctx.setParams(params, cloning);
6,864✔
126
                }
127

128
                //Object.assign(ctx.options, opts);
129
                ctx.options = opts;
8,832✔
130

131
                // RequestID
132
                if (opts.requestID != null) ctx.requestID = opts.requestID;
8,832✔
133
                else if (opts.parentCtx != null && opts.parentCtx.requestID != null)
8,814✔
134
                        ctx.requestID = opts.parentCtx.requestID;
126✔
135

136
                // Meta
137
                if (opts.parentCtx != null && opts.parentCtx.meta != null)
8,832✔
138
                        ctx.meta = Object.assign({}, opts.parentCtx.meta || {}, opts.meta || {});
126!
139
                else if (opts.meta != null) ctx.meta = opts.meta;
8,706✔
140

141
                // Headers
142
                if (opts.headers) {
8,832✔
143
                        ctx.headers = opts.headers;
36✔
144
                }
145

146
                // ParentID, Level, Caller, Tracing
147
                if (opts.parentCtx != null) {
8,832✔
148
                        ctx.tracing = opts.parentCtx.tracing;
126✔
149
                        ctx.level = opts.parentCtx.level + 1;
126✔
150

151
                        if (opts.parentCtx.span) ctx.parentID = opts.parentCtx.span.id;
126✔
152
                        else ctx.parentID = opts.parentCtx.id;
54✔
153

154
                        if (opts.parentCtx.service) ctx.caller = opts.parentCtx.service.fullName;
126✔
155
                }
156

157
                // caller
158
                if (opts.caller) {
8,832✔
159
                        ctx.caller = opts.caller;
6✔
160
                }
161

162
                // Parent span
163
                if (opts.parentSpan != null) {
8,832✔
164
                        ctx.parentID = opts.parentSpan.id;
6✔
165
                        ctx.requestID = opts.parentSpan.traceID;
6✔
166
                        ctx.tracing = opts.parentSpan.sampled;
6✔
167
                }
168

169
                // Event acknowledgement
170
                if (opts.needAck) {
8,832!
171
                        ctx.needAck = opts.needAck;
×
172
                }
173

174
                return ctx;
8,832✔
175
        }
176

177
        /**
178
         * Copy itself without ID.
179
         * @param {Endpoint} ep
180
         * @returns {Context}
181
         */
182
        copy(ep) {
183
                const newCtx = new this.constructor(this.broker);
1,332✔
184

185
                newCtx.nodeID = this.nodeID;
1,332✔
186
                newCtx.setEndpoint(ep || this.endpoint);
1,332✔
187
                newCtx.options = this.options;
1,332✔
188
                newCtx.parentID = this.parentID;
1,332✔
189
                newCtx.caller = this.caller;
1,332✔
190
                newCtx.level = this.level;
1,332✔
191
                newCtx.params = this.params;
1,332✔
192
                newCtx.meta = this.meta;
1,332✔
193
                newCtx.headers = this.headers;
1,332✔
194
                newCtx.responseHeaders = this.responseHeaders;
1,332✔
195
                newCtx.locals = this.locals;
1,332✔
196
                newCtx.requestID = this.requestID;
1,332✔
197
                newCtx.tracing = this.tracing;
1,332✔
198
                newCtx.span = this.span;
1,332✔
199
                newCtx.needAck = this.needAck;
1,332✔
200
                newCtx.ackID = this.ackID;
1,332✔
201
                newCtx.eventName = this.eventName;
1,332✔
202
                newCtx.eventType = this.eventType;
1,332✔
203
                newCtx.eventGroups = this.eventGroups;
1,332✔
204
                newCtx.stream = this.stream;
1,332✔
205

206
                newCtx.cachedResult = this.cachedResult;
1,332✔
207

208
                return newCtx;
1,332✔
209
        }
210

211
        /**
212
         * Set endpoint of context
213
         *
214
         * @param {ActionEndpoint|EventEndpoint} endpoint
215
         * @memberof Context
216
         */
217
        setEndpoint(endpoint) {
218
                this.endpoint = endpoint;
4,122✔
219
                if (endpoint) {
4,122!
220
                        this.nodeID = endpoint.id;
4,122✔
221
                        if (endpoint.action) {
4,122✔
222
                                this.action = endpoint.action;
2,622✔
223
                                this.service = this.action.service;
2,622✔
224
                                this.event = null;
2,622✔
225
                        } else if (endpoint.event) {
1,500✔
226
                                this.event = endpoint.event;
1,362✔
227
                                this.service = this.event.service;
1,362✔
228
                                this.action = null;
1,362✔
229
                        }
230
                }
231
        }
232

233
        /**
234
         * Set params of context
235
         *
236
         * @param {Object} newParams
237
         * @param {Boolean} cloning
238
         *
239
         * @memberof Context
240
         */
241
        setParams(newParams, cloning = false) {
75✔
242
                if (cloning && newParams) this.params = Object.assign({}, newParams);
7,710✔
243
                else this.params = newParams;
7,704✔
244
        }
245

246
        /**
247
         * Call an other action. It creates a sub-context.
248
         *
249
         * @param {String} actionName
250
         * @param {Object?} params
251
         * @param {Object?} _opts
252
         * @returns {Promise}
253
         *
254
         * @example <caption>Call an other service with params & options</caption>
255
         * ctx.call("posts.get", { id: 12 }, { timeout: 1000 });
256
         *
257
         * @memberof Context
258
         */
259
        call(actionName, params, _opts) {
260
                const opts = Object.assign(
96✔
261
                        {
262
                                parentCtx: this
263
                        },
264
                        _opts
265
                );
266

267
                if (this.options.timeout > 0 && this.startHrTime) {
96✔
268
                        // Distributed timeout handling. Decrementing the timeout value with the elapsed time.
269
                        // If the timeout below 0, skip the call.
270
                        const diff = process.hrtime(this.startHrTime);
12✔
271
                        const duration = diff[0] * 1e3 + diff[1] / 1e6;
12✔
272
                        const distTimeout = this.options.timeout - duration;
12✔
273

274
                        if (distTimeout <= 0) {
12✔
275
                                return this.broker.Promise.reject(
6✔
276
                                        new RequestSkippedError({ action: actionName, nodeID: this.broker.nodeID })
277
                                );
278
                        }
279

280
                        if (!opts.timeout || distTimeout < opts.timeout) opts.timeout = distTimeout;
6!
281
                }
282

283
                // Max calling level check to avoid calling loops
284
                if (
90✔
285
                        this.broker.options.maxCallLevel > 0 &&
63✔
286
                        this.level >= this.broker.options.maxCallLevel
287
                ) {
288
                        return this.broker.Promise.reject(
6✔
289
                                new MaxCallLevelError({ nodeID: this.broker.nodeID, level: this.level })
290
                        );
291
                }
292

293
                let p = this.broker.call(actionName, params, opts);
84✔
294

295
                // Merge metadata with sub context metadata
296
                return p
84✔
297
                        .then(res => {
298
                                if (p.ctx) mergeMeta(this, p.ctx.meta);
78✔
299

300
                                return res;
78✔
301
                        })
302
                        .catch(err => {
303
                                if (p.ctx) mergeMeta(this, p.ctx.meta);
6!
304

305
                                return this.broker.Promise.reject(err);
6✔
306
                        });
307
        }
308

309
        mcall(def, _opts) {
310
                const opts = Object.assign(
54✔
311
                        {
312
                                parentCtx: this
313
                        },
314
                        _opts
315
                );
316

317
                if (this.options.timeout > 0 && this.startHrTime) {
54✔
318
                        // Distributed timeout handling. Decrementing the timeout value with the elapsed time.
319
                        // If the timeout below 0, skip the call.
320
                        const diff = process.hrtime(this.startHrTime);
18✔
321
                        const duration = diff[0] * 1e3 + diff[1] / 1e6;
18✔
322
                        const distTimeout = this.options.timeout - duration;
18✔
323

324
                        if (distTimeout <= 0) {
18✔
325
                                const action = (Array.isArray(def) ? def : Object.values(def))
12✔
326
                                        .map(d => d.action)
24✔
327
                                        .join(", ");
328
                                return this.broker.Promise.reject(
12✔
329
                                        new RequestSkippedError({ action, nodeID: this.broker.nodeID })
330
                                );
331
                        }
332

333
                        if (!opts.timeout || distTimeout < opts.timeout) opts.timeout = distTimeout;
6!
334
                }
335

336
                // Max calling level check to avoid calling loops
337
                if (
42✔
338
                        this.broker.options.maxCallLevel > 0 &&
42✔
339
                        this.level >= this.broker.options.maxCallLevel
340
                ) {
341
                        return this.broker.Promise.reject(
6✔
342
                                new MaxCallLevelError({ nodeID: this.broker.nodeID, level: this.level })
343
                        );
344
                }
345

346
                let p = this.broker.mcall(def, opts);
36✔
347

348
                // Merge metadata with sub context metadata
349
                return p
36✔
350
                        .then(res => {
351
                                if (Array.isArray(p.ctx) && p.ctx.length)
30✔
352
                                        p.ctx.forEach(ctx => mergeMeta(this, ctx.meta));
12✔
353

354
                                return res;
30✔
355
                        })
356
                        .catch(err => {
357
                                if (Array.isArray(p.ctx) && p.ctx.length)
6!
358
                                        p.ctx.forEach(ctx => mergeMeta(this, ctx.meta));
12✔
359

360
                                return this.broker.Promise.reject(err);
6✔
361
                        });
362
        }
363

364
        /**
365
         * Emit an event (grouped & balanced global event)
366
         *
367
         * @param {string} eventName
368
         * @param {any?} data
369
         * @param {Object?} opts
370
         * @returns {Promise}
371
         *
372
         * @example
373
         * ctx.emit("user.created", { entity: user, creator: ctx.meta.user });
374
         *
375
         * @memberof Context
376
         */
377
        emit(eventName, data, opts) {
378
                opts = opts ?? {};
30✔
379
                opts.parentCtx = this;
30✔
380

381
                if (opts.groups && !Array.isArray(opts.groups)) opts.groups = [opts.groups];
30✔
382

383
                return this.broker.emit(eventName, data, opts);
30✔
384
        }
385

386
        /**
387
         * Emit an event for all local & remote services
388
         *
389
         * @param {string} eventName
390
         * @param {any?} data
391
         * @param {Object?} opts
392
         * @returns {Promise}
393
         *
394
         * @example
395
         * ctx.broadcast("user.created", { entity: user, creator: ctx.meta.user });
396
         *
397
         * @memberof Context
398
         */
399
        broadcast(eventName, data, opts) {
400
                opts = opts ?? {};
36✔
401
                opts.parentCtx = this;
36✔
402

403
                if (opts.groups && !Array.isArray(opts.groups)) opts.groups = [opts.groups];
36✔
404

405
                return this.broker.broadcast(eventName, data, opts);
36✔
406
        }
407

408
        /**
409
         * Start a new child tracing span.
410
         *
411
         * @param {String} name
412
         * @param {Object?} opts
413
         * @returns {Span}
414
         * @memberof Context
415
         */
416
        startSpan(name, opts) {
417
                let span;
418
                if (this.span) {
114✔
419
                        span = this.span.startSpan(name, Object.assign({ ctx: this }, opts));
42✔
420
                } else {
421
                        span = this.broker.tracer.startSpan(name, Object.assign({ ctx: this }, opts));
72✔
422
                }
423

424
                this._spanStack.push(span);
114✔
425
                this.span = span;
114✔
426

427
                return span;
114✔
428
        }
429

430
        /**
431
         * Finish an active span.
432
         *
433
         * @param {Span} span
434
         * @param {Number?} time
435
         */
436
        finishSpan(span, time) {
437
                if (!span.isActive()) return;
120✔
438

439
                span.finish(time);
114✔
440

441
                const idx = this._spanStack.findIndex(sp => sp == span);
156✔
442
                if (idx !== -1) {
114!
443
                        this._spanStack.splice(idx, 1);
114✔
444
                        this.span = this._spanStack[this._spanStack.length - 1];
114✔
445
                } else {
446
                        /* istanbul ignore next */
447
                        this.service.logger.warn("This span is not assigned to this context", span);
448
                }
449
        }
450

451
        /**
452
         * Convert Context to a printable POJO object.
453
         */
454
        toJSON() {
455
                const res = pick(this, [
180✔
456
                        "id",
457
                        "nodeID",
458
                        "action.name",
459
                        "event.name",
460
                        "service.name",
461
                        "service.version",
462
                        "service.fullName",
463
                        "options",
464
                        "parentID",
465
                        "caller",
466
                        "level",
467
                        "params",
468
                        "meta",
469
                        "headers",
470
                        "responseHeaders",
471
                        //"locals",
472
                        "requestID",
473
                        "tracing",
474
                        "span",
475
                        "needAck",
476
                        "ackID",
477
                        "eventName",
478
                        "eventType",
479
                        "eventGroups",
480
                        "cachedResult"
481
                ]);
482

483
                return res;
180✔
484
        }
485

486
        /* istanbul ignore next */
487
        [util.inspect.custom](depth, options) {
488
                // https://nodejs.org/docs/latest-v8.x/api/util.html#util_custom_inspection_functions_on_objects
489
                if (depth < 0) {
490
                        return options.stylize("[Context]", "special");
491
                }
492

493
                const inner = util.inspect(this.toJSON(), options);
494
                return `${options.stylize("Context", "special")}< ${inner} >`;
495
        }
496
}
497

498
module.exports = Context;
606✔
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