• 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

84.21
/lib/lifecycle.js
1
// Copyright (c) 2020 Wayne Parrott. 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 LifecyclePublisher = require('./lifecycle_publisher.js');
26✔
19
const loader = require('./interface_loader.js');
26✔
20
const Context = require('./context.js');
26✔
21
const Node = require('./node.js');
26✔
22
const NodeOptions = require('./node_options.js');
26✔
23
const Service = require('./service.js');
26✔
24
const { ValidationError } = require('./errors.js');
26✔
25

26
const SHUTDOWN_TRANSITION_LABEL =
27
  rclnodejs.getLifecycleShutdownTransitionLabel();
26✔
28

29
// An instance of State message constructor used for accessing State
30
// state machine constants. This interface is lazy initialized at runtime.
31
let StateInterface;
32

33
// An instance of Transition message constructor used for accessing Transition
34
// state machine constants. This interface is lazy initialized at runtime.
35
let TransitionInterface;
36

37
function getStateInterface() {
38
  if (!StateInterface) {
8✔
39
    StateInterface = loader.loadInterface('lifecycle_msgs/msg/State');
1✔
40
  }
41
  return StateInterface;
8✔
42
}
43

44
function getTransitionInterface() {
45
  if (!TransitionInterface) {
51✔
46
    TransitionInterface = loader.loadInterface('lifecycle_msgs/msg/Transition');
1✔
47
  }
48
  return TransitionInterface;
51✔
49
}
50

51
/**
52
 * @typedef SerializedState
53
 * @type {object}
54
 * @property {number} id - code identifying the type of this state.
55
 * @property {string} label - readable name of this state.
56
 */
57

58
/**
59
 * The state of the lifecycle state model.
60
 */
61
class State {
62
  /**
63
   * Create a state.
64
   * @param {number} id - The id value.
65
   * @param {string} label - The label value.
66
   */
67
  constructor(id, label) {
68
    this.id = id;
92✔
69
    this.label = label;
92✔
70
  }
71

72
  /**
73
   * Create a State from a SerializedState
74
   * @param {SerializedState} aSerializedState - The state object.
75
   * @returns {State} The State converted from a SerializdState
76
   * @private
77
   */
78
  static fromSerializedState(aSerializedState) {
79
    return new State(aSerializedState.id, aSerializedState.label);
55✔
80
  }
81
}
82

83
/**
84
 * The intermediate state of the lifecycle state model during a state
85
 * transition.
86
 */
87
class Transition extends State {}
88

89
/**
90
 * Describes a state transition.
91
 */
92
class TransitionDescription {
93
  /**
94
   * Create a transition description.
95
   *
96
   * @param {Transition} transition - The transition
97
   * @param {State} startState - The initial
98
   * @param {State} goalState - The target state of a transition activity
99
   */
100
  constructor(transition, startState, goalState) {
101
    this.transition = transition;
5✔
102
    this.startState = startState;
5✔
103
    this.goalState = goalState;
5✔
104
  }
105
}
106

107
/**
108
 * The values returned by TransitionCallback.
109
 * @readonly
110
 * @enum {number}
111
 */
112
const CallbackReturnCode = {
26✔
113
  get SUCCESS() {
114
    return getTransitionInterface().TRANSITION_CALLBACK_SUCCESS;
23✔
115
  },
116
  get FAILURE() {
117
    return getTransitionInterface().TRANSITION_CALLBACK_FAILURE;
×
118
  },
119
  get ERROR() {
120
    return getTransitionInterface().TRANSITION_CALLBACK_ERROR;
16✔
121
  },
122
};
123
Object.freeze(CallbackReturnCode);
26✔
124

125
/**
126
 * A ValueHolder for a CallbackReturnCode.
127
 */
128
class CallbackReturnValue {
129
  /**
130
   * Creates a new instance.
131
   *
132
   * @param {number} [value=CallbackReturnCode.SUCCESS] - The value property.
133
   */
134
  constructor(value = CallbackReturnCode.SUCCESS) {
1✔
135
    this._value = value;
1✔
136
    this._errorMsg = null;
1✔
137
  }
138

139
  /**
140
   * Access the callbackReturnCode.
141
   * @returns {number} The CallbackReturnCode.
142
   */
143
  get value() {
144
    return this._value;
1✔
145
  }
146

147
  set value(value) {
148
    this._value = value;
1✔
149
  }
150

151
  /**
152
   * Access an optional error message when value is not SUCCESS.
153
   */
154
  get errorMsg() {
155
    return this._errorMsg;
×
156
  }
157

158
  /**
159
   * Assign the error message.
160
   * @param {string} msg - The error message.
161
   * @returns {unknown} void.
162
   */
163
  set errorMsg(msg) {
164
    this._errorMsg = msg;
×
165
  }
166

167
  /**
168
   * Overrides Object.valueOf() to return the 'value' property.
169
   * @returns {number} The property value.
170
   */
171
  valueOf() {
172
    return this.value;
×
173
  }
174

175
  /**
176
   * A predicate to test if the value is SUCCESS.
177
   * @returns {boolean} Return true if the value is SUCCESS; otherwise return false.
178
   */
179
  isSuccess() {
180
    return this.value === CallbackReturnCode.SUCCESS;
1✔
181
  }
182

183
  /**
184
   * A predicate to test if the value is FAILURE.
185
   * @returns {boolean} Return true if the value is FAILURE; otherwise return false.
186
   */
187
  isFailure() {
188
    return this.value === CallbackReturnCode.FAILURE;
×
189
  }
190

191
  /**
192
   * A predicate to test if the value is ERROR.
193
   * @returns {boolean} Return true if the value is ERROR; otherwise return false.
194
   */
195
  isError() {
196
    return this.value === CallbackReturnCode.ERROR;
×
197
  }
198

199
  /**
200
   * A predicate to test if an error message has been assigned.
201
   * @returns {boolean} Return true if an error message has been assigned; otherwise return false.
202
   */
203
  hasErrorMsg() {
204
    return !this.isSuccess() && this._errorMsg;
×
205
  }
206
}
207

208
/**
209
 * This callback is invoked when LifecycleNode is transitioning to a new state.
210
 * @callback TransitionCallback
211
 * @param {State} previousState - The previous node lifecycle state.
212
 * @return {CallbackReturnCode} - The result of the callback.
213
 *
214
 * @see [LifecycleNode.registerOnConfigure]{@link LifecycleNode#registerOnConfigure}
215
 * @see [LifecycleNode.registerOnCleanup]{@link LifecycleNode#registerOnCleanup}
216
 * @see [LifecycleNode.registerOnActivate]{@link LifecycleNode#registerOnActivate}
217
 * @see [LifecycleNode.registerOnDeactivate]{@link LifecycleNode#registerOnDeactivate}
218
 * @see [LifecycleNode.registerOnShutdown]{@link LifecycleNode#registerOnShutdown}
219
 * @see [LifecycleNode.registerOnError]{@link LifecycleNode#registerOnError}
220
 */
221

222
/**
223
 * A ROS2 managed Node that implements a well-defined life-cycle state model using the
224
 * {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
225
 * @extends Node
226
 *
227
 * This class implments the ROS2 life-cycle state-machine defined by the
228
 * {@link https://github.com/ros2/rclcpp/tree/master/rclcpp_lifecycle}|ROS2 Managed Nodes Design}
229
 * and parallels the {@link https://github.com/ros2/rclcpp/tree/master/rclcpp_lifecycle|rclcpp lifecycle node }
230
 * implementation.
231
 *
232
 * The design consists of four primary lifecycle states:
233
 *   UNCONFIGURED
234
 *   INACTIVE
235
 *   ACTIVE
236
 *   FINALIZED.
237
 *
238
 * Transitioning between states is accomplished using an action api:
239
 *   configure()
240
 *   activate()
241
 *   deactivate(),
242
 *   cleanup()
243
 *   shutdown()
244
 *
245
 * During a state transition, the state-machine is in one of the
246
 * intermediate transitioning states:
247
 *   CONFIGURING
248
 *   ACTIVATING
249
 *   DEACTIVATING
250
 *   CLEANINGUP
251
 *   SHUTTING_DOWN
252
 *   ERROR_PROCESSING
253
 *
254
 * Messaging:
255
 * State changes are published on the '<node_name>/transition_event' topic.
256
 * Lifecycle service interfaces are also implemented.
257
 *
258
 * You can introduce your own state specific behaviors in the form of a
259
 * {@link TransitionCallback} functions that you register using:
260
 *   registerOnConfigure(cb)
261
 *   registerOnActivate(cb)
262
 *   registerOnDeactivate(cb)
263
 *   registerOnCleanup(cb)
264
 *   registerOnShutdown(cb)
265
 *   registerOnError(cb)
266
 */
267

268
class LifecycleNode extends Node {
269
  /**
270
   * Create a managed Node that implements a well-defined life-cycle state
271
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
272
   * @param {string} nodeName - The name used to register in ROS.
273
   * @param {string} [namespace=''] - The namespace used in ROS.
274
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
275
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
276
   * @param {boolean} [enableCommunicationsInterface=true] - Enable lifecycle service interfaces, e.g., GetState.
277
   * @throws {Error} If the given context is not registered.
278
   */
279
  constructor(
280
    nodeName,
281
    namespace = '',
1✔
282
    context = Context.defaultContext(),
1✔
283
    options = NodeOptions.defaultOptions,
1✔
284
    enableCommunicationInterface = true
×
285
  ) {
286
    super(nodeName, namespace, context, options);
19✔
287
    this.init(enableCommunicationInterface);
19✔
288
  }
289

290
  init(enableCommunicationInterface) {
291
    // initialize native handle to rcl_lifecycle_state_machine
292
    this._stateMachineHandle = rclnodejs.createLifecycleStateMachine(
19✔
293
      this.handle,
294
      enableCommunicationInterface
295
    );
296

297
    // initialize Map<transitionId,TransitionCallback>
298
    this._callbackMap = new Map();
19✔
299

300
    if (!enableCommunicationInterface) {
19✔
301
      // do not create lifecycle services
302
      return;
1✔
303
    }
304

305
    // Setup and register the 4 native rcl lifecycle services thar are
306
    // part of _stateMachineHandle.
307
    let srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
18✔
308
      this._stateMachineHandle,
309
      'srv_get_state'
310
    );
311
    let service = new Service(
18✔
312
      this.handle,
313
      srvHandleObj.handle,
314
      srvHandleObj.name,
315
      loader.loadInterface('lifecycle_msgs/srv/GetState'),
316
      this._validateOptions(undefined),
317
      (request, response) => this._onGetState(request, response)
1✔
318
    );
319
    this._services.push(service);
18✔
320

321
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
18✔
322
      this._stateMachineHandle,
323
      'srv_get_available_states'
324
    );
325
    service = new Service(
18✔
326
      this.handle,
327
      srvHandleObj.handle,
328
      srvHandleObj.name,
329
      loader.loadInterface('lifecycle_msgs/srv/GetAvailableStates'),
330
      this._validateOptions(undefined),
331
      (request, response) => this._onGetAvailableStates(request, response)
1✔
332
    );
333
    this._services.push(service);
18✔
334

335
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
18✔
336
      this._stateMachineHandle,
337
      'srv_get_available_transitions'
338
    );
339
    service = new Service(
18✔
340
      this.handle,
341
      srvHandleObj.handle,
342
      srvHandleObj.name,
343
      loader.loadInterface('lifecycle_msgs/srv/GetAvailableTransitions'),
344
      this._validateOptions(undefined),
345
      (request, response) => this._onGetAvailableTransitions(request, response)
1✔
346
    );
347
    this._services.push(service);
18✔
348

349
    srvHandleObj = rclnodejs.getLifecycleSrvNameAndHandle(
18✔
350
      this._stateMachineHandle,
351
      'srv_change_state'
352
    );
353
    service = new Service(
18✔
354
      this.handle,
355
      srvHandleObj.handle,
356
      srvHandleObj.name,
357
      loader.loadInterface('lifecycle_msgs/srv/ChangeState'),
358
      this._validateOptions(undefined),
359
      (request, response) => this._onChangeState(request, response)
1✔
360
    );
361
    this._services.push(service);
18✔
362
    this.syncHandles();
18✔
363
  }
364

365
  /**
366
   * Create a LifecyclePublisher.
367
   * @param {function|string|object} typeClass - The ROS message class,
368
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
369
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
370
   * @param {string} topic - The name of the topic.
371
   * @param {object} options - The options argument used to parameterize the publisher.
372
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
373
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
374
   * @return {LifecyclePublisher} - An instance of LifecyclePublisher.
375
   */
376
  createLifecyclePublisher(typeClass, topic, options) {
377
    return this._createPublisher(typeClass, topic, options, LifecyclePublisher);
3✔
378
  }
379

380
  /**
381
   * Access the current lifecycle state.
382
   * @returns {State} The current state.
383
   */
384
  get currentState() {
385
    let currentStateObj = rclnodejs.getCurrentLifecycleState(
24✔
386
      this._stateMachineHandle
387
    );
388
    return State.fromSerializedState(currentStateObj);
24✔
389
  }
390

391
  /**
392
   * Retrieve all registered states of the state-machine.
393
   * @returns {State[]} The states.
394
   */
395
  get availableStates() {
396
    let stateObjs = rclnodejs.getLifecycleStates(this._stateMachineHandle);
2✔
397
    let states = stateObjs.map(
2✔
398
      (stateObj) => new State(stateObj.id, stateObj.label)
22✔
399
    );
400
    return states;
2✔
401
  }
402

403
  /**
404
   * Retrieve all registered transitions of the state-machine.
405
   *
406
   * @returns {TransitionDescription[]} The registered TransitionDescriptions.
407
   */
408
  get transitions() {
409
    let transitionObjs = rclnodejs.getLifecycleTransitions(
×
410
      this._stateMachineHandle
411
    );
412
    let transitions = transitionObjs.map((transitionDescObj) => {
×
413
      let transition = new Transition(
×
414
        transitionDescObj.transition.id,
415
        transitionDescObj.transition.label
416
      );
417
      let startState = new State(
×
418
        transitionDescObj.start_state.id,
419
        transitionDescObj.start_state.label
420
      );
421
      let goalState = new State(
×
422
        transitionDescObj.goal_state.id,
423
        transitionDescObj.goal_state.label
424
      );
425
      return new TransitionDescription(transition, startState, goalState);
×
426
    });
427
    return transitions;
×
428
  }
429

430
  /**
431
   * Retrieve the valid transitions available from the current state of the
432
   * state-machine.
433
   *
434
   * @returns {TransitionDescription[]} The available TransitionDescriptions.
435
   */
436
  get availableTransitions() {
437
    let transitionObjs = rclnodejs.getAvailableLifecycleTransitions(
2✔
438
      this._stateMachineHandle
439
    );
440
    let transitions = transitionObjs.map((transitionDescObj) => {
2✔
441
      let transition = new Transition(
5✔
442
        transitionDescObj.transition.id,
443
        transitionDescObj.transition.label
444
      );
445
      let startState = new State(
5✔
446
        transitionDescObj.start_state.id,
447
        transitionDescObj.start_state.label
448
      );
449
      let goalState = new State(
5✔
450
        transitionDescObj.goal_state.id,
451
        transitionDescObj.goal_state.label
452
      );
453
      return new TransitionDescription(transition, startState, goalState);
5✔
454
    });
455
    return transitions;
2✔
456
  }
457

458
  /**
459
   * Register a callback function to be invoked during the configure() action.
460
   * @param {TransitionCallback} cb - The callback function to invoke.
461
   * @returns {unknown} void.
462
   */
463
  registerOnConfigure(cb) {
464
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_CONFIGURING, cb);
2✔
465
  }
466

467
  /**
468
   * Register a callback function to be invoked during the activate() action.
469
   * @param {TransitionCallback} cb - The callback function to invoke.
470
   * @returns {unknown} void.
471
   */
472
  registerOnActivate(cb) {
473
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_ACTIVATING, cb);
1✔
474
  }
475

476
  /**
477
   * Register a callback function to be invoked during the deactivate() action.
478
   * @param {TransitionCallback} cb - The callback function to invoke.
479
   * @returns {unknown} void.
480
   */
481
  registerOnDeactivate(cb) {
482
    this._callbackMap.set(
1✔
483
      getStateInterface().TRANSITION_STATE_DEACTIVATING,
484
      cb
485
    );
486
  }
487

488
  /**
489
   * Register a callback function to be invoked during the cleanup() action.
490
   * @param {TransitionCallback} cb - The callback function to invoke.
491
   * @returns {unknown} void.
492
   */
493
  registerOnCleanup(cb) {
494
    this._callbackMap.set(getStateInterface().TRANSITION_STATE_CLEANINGUP, cb);
1✔
495
  }
496

497
  /**
498
   * Register a callback function to be invoked during the shutdown() action.
499
   * @param {TransitionCallback} cb - The callback function to invoke.
500
   * @returns {unknown} void
501
   */
502
  registerOnShutdown(cb) {
503
    this._callbackMap.set(
1✔
504
      getStateInterface().TRANSITION_STATE_SHUTTINGDOWN,
505
      cb
506
    );
507
  }
508

509
  /**
510
   * Register a callback function to be invoked when an error occurs during a
511
   * state transition.
512
   * @param {TransitionCallback} cb - The callback function to invoke.
513
   * @returns {unknown} void.
514
   */
515
  registerOnError(cb) {
516
    this._callbackMap.set(
2✔
517
      getStateInterface().TRANSITION_STATE_ERRORPROCESSING,
518
      cb
519
    );
520
  }
521

522
  /**
523
   * Initiate a transition from the UNCONFIGURED state to the INACTIVE state.
524
   * If an onConfigure callback has been registered it will be invoked.
525
   *
526
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
527
   * @returns {State} The new state, should be INACTIVE.
528
   * @throws {Error} If transition is invalid for the current state.
529
   */
530
  configure(callbackReturnValue) {
531
    return this._changeState(
6✔
532
      getTransitionInterface().TRANSITION_CONFIGURE,
533
      callbackReturnValue
534
    );
535
  }
536

537
  /**
538
   * Initiate a transition from the INACTIVE state to the ACTIVE state.
539
   * If an onActivate callback has been registered it will be invoked.
540
   *
541
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
542
   * @returns {State} The new state, should be ACTIVE.
543
   * @throws {Error} If transition is invalid for the current state.
544
   */
545
  activate(callbackReturnValue) {
546
    return this._changeState(
3✔
547
      getTransitionInterface().TRANSITION_ACTIVATE,
548
      callbackReturnValue
549
    );
550
  }
551

552
  /**
553
   * Initiate a transition from the ACTIVE state to the INACTIVE state.
554
   * If an onDeactivate callback has been registered it will be invoked.
555
   *
556
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
557
   * @returns {State} The new state, should be INACTIVE.
558
   * @throws {Error} If transition is invalid for the current state.
559
   */
560
  deactivate(callbackReturnValue) {
561
    return this._changeState(
2✔
562
      getTransitionInterface().TRANSITION_DEACTIVATE,
563
      callbackReturnValue
564
    );
565
  }
566

567
  /**
568
   * Initiate a transition from the INACTIVE state to the UNCONFIGURED state.
569
   * If an onCleanup callback has been registered it will be invoked.
570
   *
571
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
572
   * @returns {State} The new state, should be INACTIVE.
573
   * @throws {Error} If transition is invalid for the current state.
574
   */
575
  cleanup(callbackReturnValue) {
576
    return this._changeState(
2✔
577
      getTransitionInterface().TRANSITION_CLEANUP,
578
      callbackReturnValue
579
    );
580
  }
581

582
  /**
583
   * Initiate a transition to the FINALIZED state from any of the following
584
   * states: UNCONFIGURED, INACTIVE or ACTIVE state. If an onConfigure
585
   * callback has been registered it will be invoked.
586
   *
587
   * @param {CallbackReturnValue?} callbackReturnValue - value holder for the CallbackReturnCode returned from the callback.
588
   * @returns {State} The new state, should be FINALIZED.
589
   * @throws {Error} If transition is invalid for the current state.
590
   */
591
  shutdown(callbackReturnValue) {
592
    return this._changeState(SHUTDOWN_TRANSITION_LABEL, callbackReturnValue);
2✔
593
  }
594

595
  /**
596
   * Check if state machine is initialized.
597
   *
598
   * @returns {boolean} true if the state machine is initialized; otherwise false.
599
   */
600
  get isInitialized() {
601
    return rclnodejs.isInitialized(this._stateMachineHandle);
1✔
602
  }
603

604
  /**
605
   * Log the state machine data.
606
   *
607
   * @returns {undefined} void.
608
   */
609
  print() {
610
    rclnodejs.print(this._stateMachineHandle);
1✔
611
  }
612

613
  /**
614
   * The GetState service handler.
615
   * @param {Object} request - The GetState service request.
616
   * @param {Object} response - The GetState service response.
617
   * @returns {unknown} void.
618
   * @private
619
   */
620
  _onGetState(request, response) {
621
    let result = response.template;
1✔
622

623
    result.current_state = this.currentState;
1✔
624

625
    response.send(result);
1✔
626
  }
627

628
  /**
629
   * The GetAvailableStates service handler.
630
   * @param {Object} request - The GetAvailableStates service request.
631
   * @param {Object} response - The GetAvailableStates service response.
632
   * @returns {unknown} void.
633
   * @private
634
   */
635
  _onGetAvailableStates(request, response) {
636
    let result = response.template;
1✔
637

638
    result.available_states = this.availableStates;
1✔
639

640
    response.send(result);
1✔
641
  }
642

643
  /**
644
   * The GetAvailableTransitions service handler.
645
   * @param {Object} request - The GetAvailableTransitions service request
646
   * @param {Object} response - The GetAvailableTranactions service response.
647
   * @returns {unknown} void.
648
   */
649
  _onGetAvailableTransitions(request, response) {
650
    let result = response.template;
1✔
651

652
    result.available_transitions = this.availableTransitions;
1✔
653

654
    response.send(result);
1✔
655
  }
656

657
  /**
658
   * The ChangeState service handler.
659
   * @param {Object} request - The ChangeState service request.
660
   * @param {Object} response - The ChangeState service response
661
   * @returns {unknown} void.
662
   * @private
663
   */
664
  _onChangeState(request, response) {
665
    let result = response.template;
1✔
666

667
    let transitionId = request.transition.id;
1✔
668
    if (request.transition.label) {
1!
669
      let transitionObj = rclnodejs.getLifecycleTransitionByLabel(
1✔
670
        this._stateMachineHandle,
671
        request.transition.label
672
      );
673
      if (transitionObj.id) {
1!
674
        transitionId = transitionObj.id;
1✔
675
      } else {
676
        result.success = false;
×
677
        response.send(result);
×
678
        return;
×
679
      }
680
    }
681

682
    let callbackReturnValue = new CallbackReturnValue();
1✔
683
    this._changeState(transitionId, callbackReturnValue);
1✔
684

685
    result.success = callbackReturnValue.isSuccess();
1✔
686
    response.send(result);
1✔
687
  }
688

689
  /**
690
   * Transition to a new lifecycle state.
691
   * @param {number|string} transitionIdOrLabel - The id or label of the target transition.
692
   * @param {CallbackReturnValue} callbackReturnValue - An out parameter that holds the CallbackReturnCode.
693
   * @returns {State} The new state.
694
   * @throws {Error} If transition is invalid for the current state.
695
   * @private
696
   */
697
  _changeState(transitionIdOrLabel, callbackReturnValue) {
698
    let initialState = this.currentState;
15✔
699
    let newStateObj =
700
      typeof transitionIdOrLabel === 'number'
15✔
701
        ? rclnodejs.triggerLifecycleTransitionById(
702
            this._stateMachineHandle,
703
            transitionIdOrLabel
704
          )
705
        : rclnodejs.triggerLifecycleTransitionByLabel(
706
            this._stateMachineHandle,
707
            transitionIdOrLabel
708
          );
709

710
    if (!newStateObj) {
15!
NEW
711
      throw new ValidationError(
×
712
        `No transition available from state ${transitionIdOrLabel}`,
713
        {
714
          code: 'INVALID_TRANSITION',
715
          entityType: 'lifecycle node',
716
          details: { transitionIdOrLabel: transitionIdOrLabel },
717
        }
718
      );
719
    }
720

721
    let newState = State.fromSerializedState(newStateObj);
15✔
722

723
    let cbResult = this._executeCallback(newState, initialState);
15✔
724
    if (callbackReturnValue) callbackReturnValue.value = cbResult;
15✔
725

726
    let transitioningLabel = this._transitionId2Label(cbResult);
15✔
727
    newState = State.fromSerializedState(
15✔
728
      rclnodejs.triggerLifecycleTransitionByLabel(
729
        this._stateMachineHandle,
730
        transitioningLabel
731
      )
732
    );
733

734
    if (cbResult == CallbackReturnCode.ERROR) {
15✔
735
      cbResult = this._executeCallback(this.currentState, initialState);
1✔
736
      if (callbackReturnValue) callbackReturnValue.value = cbResult;
1!
737

738
      transitioningLabel = this._transitionId2Label(cbResult);
1✔
739
      newState = State.fromSerializedState(
1✔
740
        rclnodejs.triggerLifecycleTransitionByLabel(
741
          this._stateMachineHandle,
742
          transitioningLabel
743
        )
744
      );
745
    }
746

747
    return newState;
15✔
748
  }
749

750
  /**
751
   * Execute the callback function registered with a transition action,
752
   * e.g. registerOnConfigure(cb).
753
   * @param {State} state - The state to which the callback is
754
   * @param {State} prevState - The start state of the transition.
755
   * @returns {CallbackReturnCode} The callback return code.
756
   * @private
757
   */
758
  _executeCallback(state, prevState) {
759
    let result = CallbackReturnCode.SUCCESS;
16✔
760
    let callback = this._callbackMap.get(state.id);
16✔
761

762
    if (callback) {
16✔
763
      try {
7✔
764
        result = callback(prevState);
7✔
765
      } catch (err) {
766
        console.log('CB exception occured: ', err);
×
767
        result = CallbackReturnCode.ERROR;
×
768
      }
769
    }
770

771
    return result;
16✔
772
  }
773

774
  /**
775
   * Find the label for the transition with id == transitionId.
776
   * @param {number} transitionId - A transition id.
777
   * @returns {string} The label of the transition with id.
778
   * @private
779
   */
780
  _transitionId2Label(transitionId) {
781
    return rclnodejs.getLifecycleTransitionIdToLabel(transitionId);
16✔
782
  }
783
}
784

785
const Lifecycle = {
26✔
786
  CallbackReturnCode,
787
  CallbackReturnValue,
788
  LifecycleNode,
789
  State,
790
  Transition,
791
  TransitionDescription,
792
};
793

794
module.exports = Lifecycle;
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