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

RobotWebTools / rclnodejs / 19071410104

04 Nov 2025 02:08PM UTC coverage: 83.072% (+0.4%) from 82.711%
19071410104

Pull #1320

github

web-flow
Merge 9cad4567e into 3ad842cc4
Pull Request #1320: feat: add structured error handling with class error hierarchy

1032 of 1365 branches covered (75.6%)

Branch coverage included in aggregate %.

161 of 239 new or added lines in 25 files covered. (67.36%)

29 existing lines in 1 file now uncovered.

2354 of 2711 relevant lines covered (86.83%)

459.93 hits per line

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

81.25
/lib/context.js
1
// Copyright (c) 2019 Intel Corporation. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
'use strict';
16

17
const rclnodejs = require('./native_loader.js');
26✔
18
const { OperationError } = require('./errors.js');
26✔
19

20
let defaultContext = null;
26✔
21

22
/**
23
 * Encapsulates the lifecycle of an rcl environment from init to shutdown.
24
 * A Context serves as a container for a ROS2 RCL environment that holds
25
 * nodes and the resources created by the nodes, e.g.,
26
 * publishers, subscriptions, actions, services...v
27
 *
28
 * A context has 3 states:
29
 * ```
30
 * new Context() --> uninitialized -->
31
 * *                                  |
32
 *         ---------------------------
33
 *        |
34
 *        v
35
 * rcl.init(context) --> initialized ->
36
 *                                     |
37
 *         ----------------------------
38
 *        |
39
 *        v
40
 * rcl.shutdown(context)
41
 *        or
42
 * context.shutdown() ---> shutdown
43
 * ```
44
 * Must call rclnodejs.init(context) to initialize the context
45
 * to the usable 'initialized' (valid) state be using.
46
 */
47
class Context {
48
  /**
49
   * Access the list of usable (initialized/valid) contexts.
50
   * @returns {Context[]} Array of valid contexts
51
   */
52
  static get instances() {
53
    let contexts = [];
24✔
54
    for (const ctx of Context._instances) {
24✔
55
      if (ctx.isValid()) {
24!
56
        contexts.push(ctx);
24✔
57
      }
58
    }
59
    return contexts;
24✔
60
  }
61

62
  /**
63
   * Create a new instance in uninitialized state.
64
   * Call rcl.init(context) to initialize this context state for
65
   * use in creating nodes, etc.
66
   * @constructor
67
   * @param {bigint} - Optional, The domain ID of this context.
68
   */
69
  constructor(domainId) {
70
    this._handle = rclnodejs.createContext();
259✔
71
    this._isShutdown = false;
259✔
72
    this._nodes = [];
259✔
73
    this._domainId = domainId;
259✔
74
    Context._instances.push(this);
259✔
75
  }
76

77
  /**
78
   * Access the nodes managed by this context.
79
   * @returns {Node[]} The nodes.
80
   */
81
  get nodes() {
82
    return Array.from(this._nodes);
259✔
83
  }
84

85
  /**
86
   * Get the handle referencing the internal context object. Do not modify it yourself: only pass it to *rclnodejs* functions!
87
   * @returns {undefined} a reference to the internal context object
88
   */
89
  get handle() {
90
    return this._handle;
5,997✔
91
  }
92

93
  /**
94
   * Test if this context has not been initialized by rcl.init(context).
95
   * @returns {boolean} True if context has been initialized; otherwise false
96
   */
97
  isUninitialized() {
98
    return !this.isShutdown() && !this.isValid();
238✔
99
  }
100

101
  /**
102
   * Test if this context has been initialized, i.e., rcl.init(context),
103
   * and not shutdown.
104
   * @returns {boolean} True if context has been initialized; otherwise false
105
   */
106
  isInitialized() {
107
    return !this.isShutdown() && this.isValid();
277✔
108
  }
109

110
  /**
111
   * Test if this context has been shutdown, i.e., context.shutdown().
112
   * @returns {boolean} True if context has been shutdown; otherwise false
113
   */
114
  isShutdown() {
115
    return this._isShutdown;
1,463✔
116
  }
117

118
  /**
119
   * Test if this context is the default one.
120
   * @returns {boolean} whether this is the default context
121
   */
122
  isDefaultContext() {
123
    return this === defaultContext;
265✔
124
  }
125

126
  /**
127
   * Check that the context is in a usable state, i.e., it
128
   * has been initialized and not yet shutdown.
129
   * @returns {boolean} whether this context is (still) valid
130
   */
131
  isValid() {
132
    return rclnodejs.isContextValid(this.handle);
549✔
133
  }
134

135
  /**
136
   * Check that the context is valid.
137
   * @returns {boolean} whether this context is (still) valid
138
   *
139
   * @deprecated since 0.18.0, Use Context.isValid()
140
   */
141
  get isOk() {
142
    return this.isValid();
14✔
143
  }
144

145
  /**
146
   * Shut down the context including destroying all nodes.
147
   * @returns {undefined}
148
   * @throws {Error} If there is a problem shutting down the context.
149
   */
150
  shutdown() {
151
    if (this.isShutdown()) return;
255!
152

153
    // shutdown and remove all nodes
154
    for (const node of this.nodes) {
255✔
155
      node.destroy();
111✔
156
    }
157

158
    if (this.isInitialized()) {
255✔
159
      rclnodejs.shutdown(this.handle);
229✔
160
    }
161

162
    this._isShutdown = true;
255✔
163

164
    // remove context from _instances[]
165
    const index = Context._instances.indexOf(this);
255✔
166
    if (index > -1) {
255!
167
      Context._instances.splice(index, 1);
255✔
168
    }
169

170
    if (this.isDefaultContext()) {
255✔
171
      defaultContext = null;
244✔
172
    }
173
  }
174

175
  /**
176
   * Try to shut down the context.
177
   * @returns {undefined}
178
   * @throws {Error} If there is a problem shutting down the context.
179
   */
180
  tryShutdown() {
181
    if (this.isInitialized()) {
×
182
      this.shutdown();
×
183
    }
184
  }
185

186
  onNodeCreated(node) {
187
    if (!node) {
690!
NEW
188
      throw new OperationError('Node must be defined to add to Context', {
×
189
        code: 'NODE_UNDEFINED',
190
        entityType: 'context',
191
      });
192
    }
193

194
    if (this.isShutdown()) {
690!
NEW
195
      throw new OperationError(
×
196
        'Can not add a Node to a Context that is shutdown',
197
        {
198
          code: 'CONTEXT_SHUTDOWN',
199
          entityType: 'context',
200
        }
201
      );
202
    }
203

204
    if (this._nodes.includes(node)) {
690!
205
      // do nothing
206
      return;
×
207
    }
208

209
    this._nodes.push(node);
690✔
210
  }
211

212
  onNodeDestroyed(node) {
213
    if (!this._nodes) {
712!
214
      return;
×
215
    }
216

217
    // remove node from _nodes[]
218
    const index = this._nodes.indexOf(node);
712✔
219
    if (index > -1) {
712✔
220
      this._nodes.splice(index, 1);
690✔
221
    }
222
  }
223

224
  /**
225
   * Get the global default Context object.
226
   * @returns {Context} The default Context
227
   */
228
  static defaultContext() {
229
    if (defaultContext === null) {
1,172✔
230
      defaultContext = new Context();
244✔
231
    }
232
    return defaultContext;
1,172✔
233
  }
234

235
  /**
236
   * Get the domain ID of this context.
237
   * @returns {bigint} domain ID of this context
238
   */
239
  get domainId() {
240
    return rclnodejs.getDomainId(this.handle);
3✔
241
  }
242
}
243

244
Context._instances = [];
26✔
245

246
module.exports = Context;
26✔
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