rclnodejs 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/index.js +79 -3
  2. package/lib/action/client.js +55 -9
  3. package/lib/action/deferred.js +8 -2
  4. package/lib/action/server.js +10 -1
  5. package/lib/action/uuid.js +4 -1
  6. package/lib/client.js +152 -3
  7. package/lib/clock.js +4 -1
  8. package/lib/context.js +12 -2
  9. package/lib/duration.js +37 -12
  10. package/lib/errors.js +571 -0
  11. package/lib/event_handler.js +21 -4
  12. package/lib/interface_loader.js +52 -12
  13. package/lib/lifecycle.js +8 -2
  14. package/lib/logging.js +12 -3
  15. package/lib/message_serialization.js +179 -0
  16. package/lib/native_loader.js +9 -4
  17. package/lib/node.js +283 -47
  18. package/lib/parameter.js +176 -45
  19. package/lib/parameter_client.js +506 -0
  20. package/lib/parameter_watcher.js +309 -0
  21. package/lib/qos.js +22 -5
  22. package/lib/rate.js +6 -1
  23. package/lib/serialization.js +7 -2
  24. package/lib/subscription.js +16 -1
  25. package/lib/time.js +136 -21
  26. package/lib/time_source.js +13 -4
  27. package/lib/utils.js +313 -0
  28. package/lib/validator.js +11 -12
  29. package/package.json +2 -7
  30. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  31. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  32. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  33. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  34. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  35. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  36. package/rosidl_convertor/idl_convertor.js +3 -2
  37. package/rosidl_gen/generate_worker.js +1 -1
  38. package/rosidl_gen/idl_generator.js +11 -24
  39. package/rosidl_gen/index.js +1 -1
  40. package/rosidl_gen/templates/action-template.js +68 -0
  41. package/rosidl_gen/templates/message-template.js +1113 -0
  42. package/rosidl_gen/templates/service-event-template.js +31 -0
  43. package/rosidl_gen/templates/service-template.js +44 -0
  44. package/rosidl_parser/rosidl_parser.js +2 -2
  45. package/third_party/ref-napi/lib/ref.js +0 -45
  46. package/types/base.d.ts +3 -0
  47. package/types/client.d.ts +36 -0
  48. package/types/errors.d.ts +447 -0
  49. package/types/index.d.ts +17 -0
  50. package/types/interfaces.d.ts +1910 -1
  51. package/types/node.d.ts +56 -1
  52. package/types/parameter_client.d.ts +252 -0
  53. package/types/parameter_watcher.d.ts +104 -0
  54. package/rosidl_gen/templates/CMakeLists.dot +0 -40
  55. package/rosidl_gen/templates/action.dot +0 -50
  56. package/rosidl_gen/templates/message.dot +0 -851
  57. package/rosidl_gen/templates/package.dot +0 -16
  58. package/rosidl_gen/templates/service.dot +0 -26
  59. package/rosidl_gen/templates/service_event.dot +0 -10
package/index.js CHANGED
@@ -18,7 +18,7 @@ const DistroUtils = require('./lib/distro.js');
18
18
  const RMWUtils = require('./lib/rmw.js');
19
19
  const { Clock, ROSClock } = require('./lib/clock.js');
20
20
  const ClockType = require('./lib/clock_type.js');
21
- const compareVersions = require('compare-versions');
21
+ const { compareVersions } = require('./lib/utils.js');
22
22
  const Context = require('./lib/context.js');
23
23
  const debug = require('debug')('rclnodejs');
24
24
  const Duration = require('./lib/duration.js');
@@ -47,6 +47,7 @@ const ActionUuid = require('./lib/action/uuid.js');
47
47
  const ClientGoalHandle = require('./lib/action/client_goal_handle.js');
48
48
  const { CancelResponse, GoalResponse } = require('./lib/action/response.js');
49
49
  const ServerGoalHandle = require('./lib/action/server_goal_handle.js');
50
+ const { toJSONSafe, toJSONString } = require('./lib/message_serialization.js');
50
51
  const {
51
52
  getActionClientNamesAndTypesByNode,
52
53
  getActionServerNamesAndTypesByNode,
@@ -57,6 +58,9 @@ const {
57
58
  serializeMessage,
58
59
  deserializeMessage,
59
60
  } = require('./lib/serialization.js');
61
+ const ParameterClient = require('./lib/parameter_client.js');
62
+ const errors = require('./lib/errors.js');
63
+ const ParameterWatcher = require('./lib/parameter_watcher.js');
60
64
  const { spawn } = require('child_process');
61
65
 
62
66
  /**
@@ -216,6 +220,12 @@ let rcl = {
216
220
  /** {@link ParameterType} */
217
221
  ParameterType: ParameterType,
218
222
 
223
+ /** {@link ParameterClient} class */
224
+ ParameterClient: ParameterClient,
225
+
226
+ /** {@link ParameterWatcher} class */
227
+ ParameterWatcher: ParameterWatcher,
228
+
219
229
  /** {@link QoS} class */
220
230
  QoS: QoS,
221
231
 
@@ -364,8 +374,7 @@ let rcl = {
364
374
 
365
375
  const version = await getCurrentGeneratorVersion();
366
376
  const forced =
367
- version === null ||
368
- compareVersions.compare(version, generator.version(), '<');
377
+ version === null || compareVersions(version, generator.version(), '<');
369
378
  if (forced) {
370
379
  debug(
371
380
  'The generator will begin to create JavaScript code from ROS IDL files...'
@@ -539,6 +548,73 @@ let rcl = {
539
548
  * @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
540
549
  */
541
550
  ros2Launch: ros2Launch,
551
+
552
+ /**
553
+ * Convert a message object to be JSON-safe by converting TypedArrays to regular arrays
554
+ * and handling BigInt, Infinity, NaN, etc. for JSON serialization.
555
+ * @param {*} obj - The message object to convert
556
+ * @returns {*} A JSON-safe version of the object
557
+ */
558
+ toJSONSafe: toJSONSafe,
559
+
560
+ /**
561
+ * Convert a message object to a JSON string with proper handling of TypedArrays,
562
+ * BigInt, and other non-JSON-serializable values.
563
+ * @param {*} obj - The message object to convert
564
+ * @param {number} [space] - Space parameter for JSON.stringify formatting
565
+ * @returns {string} The JSON string representation
566
+ */
567
+ toJSONString: toJSONString,
568
+
569
+ // Error classes for structured error handling
570
+ /** {@link RclNodeError} - Base error class for all rclnodejs errors */
571
+ RclNodeError: errors.RclNodeError,
572
+
573
+ /** {@link ValidationError} - Error thrown when validation fails */
574
+ ValidationError: errors.ValidationError,
575
+ /** {@link TypeValidationError} - Type validation error */
576
+ TypeValidationError: errors.TypeValidationError,
577
+ /** {@link RangeValidationError} - Range/value validation error */
578
+ RangeValidationError: errors.RangeValidationError,
579
+ /** {@link NameValidationError} - ROS name validation error */
580
+ NameValidationError: errors.NameValidationError,
581
+
582
+ /** {@link OperationError} - Base class for operation/runtime errors */
583
+ OperationError: errors.OperationError,
584
+ /** {@link TimeoutError} - Request timeout error */
585
+ TimeoutError: errors.TimeoutError,
586
+ /** {@link AbortError} - Request abortion error */
587
+ AbortError: errors.AbortError,
588
+ /** {@link ServiceNotFoundError} - Service not available error */
589
+ ServiceNotFoundError: errors.ServiceNotFoundError,
590
+ /** {@link NodeNotFoundError} - Remote node not found error */
591
+ NodeNotFoundError: errors.NodeNotFoundError,
592
+
593
+ /** {@link ParameterError} - Base error for parameter operations */
594
+ ParameterError: errors.ParameterError,
595
+ /** {@link ParameterNotFoundError} - Parameter not found error */
596
+ ParameterNotFoundError: errors.ParameterNotFoundError,
597
+ /** {@link ParameterTypeError} - Parameter type mismatch error */
598
+ ParameterTypeError: errors.ParameterTypeError,
599
+ /** {@link ReadOnlyParameterError} - Read-only parameter modification error */
600
+ ReadOnlyParameterError: errors.ReadOnlyParameterError,
601
+
602
+ /** {@link TopicError} - Base error for topic operations */
603
+ TopicError: errors.TopicError,
604
+ /** {@link PublisherError} - Publisher-specific error */
605
+ PublisherError: errors.PublisherError,
606
+ /** {@link SubscriptionError} - Subscription-specific error */
607
+ SubscriptionError: errors.SubscriptionError,
608
+
609
+ /** {@link ActionError} - Base error for action operations */
610
+ ActionError: errors.ActionError,
611
+ /** {@link GoalRejectedError} - Goal rejected by action server */
612
+ GoalRejectedError: errors.GoalRejectedError,
613
+ /** {@link ActionServerNotFoundError} - Action server not found */
614
+ ActionServerNotFoundError: errors.ActionServerNotFoundError,
615
+
616
+ /** {@link NativeError} - Wraps errors from native C++ layer */
617
+ NativeError: errors.NativeError,
542
618
  };
543
619
 
544
620
  const _sigHandler = () => {
@@ -23,6 +23,11 @@ const DistroUtils = require('../distro.js');
23
23
  const Entity = require('../entity.js');
24
24
  const loader = require('../interface_loader.js');
25
25
  const QoS = require('../qos.js');
26
+ const {
27
+ TypeValidationError,
28
+ ActionError,
29
+ OperationError,
30
+ } = require('../errors.js');
26
31
 
27
32
  /**
28
33
  * @class - ROS Action client.
@@ -103,7 +108,14 @@ class ActionClient extends Entity {
103
108
  if (goalHandle.accepted) {
104
109
  let uuid = ActionUuid.fromMessage(goalHandle.goalId).toString();
105
110
  if (this._goalHandles.has(uuid)) {
106
- throw new Error(`Two goals were accepted with the same ID (${uuid})`);
111
+ throw new ActionError(
112
+ `Two goals were accepted with the same ID (${uuid})`,
113
+ this._actionName,
114
+ {
115
+ code: 'DUPLICATE_GOAL_ID',
116
+ details: { goalId: uuid },
117
+ }
118
+ );
107
119
  }
108
120
 
109
121
  this._goalHandles.set(uuid, goalHandle);
@@ -210,8 +222,14 @@ class ActionClient extends Entity {
210
222
  );
211
223
 
212
224
  if (this._pendingGoalRequests.has(sequenceNumber)) {
213
- throw new Error(
214
- `Sequence (${sequenceNumber}) conflicts with pending goal request`
225
+ throw new OperationError(
226
+ `Sequence (${sequenceNumber}) conflicts with pending goal request`,
227
+ {
228
+ code: 'SEQUENCE_CONFLICT',
229
+ entityType: 'action client',
230
+ entityName: this._actionName,
231
+ details: { sequenceNumber: sequenceNumber, requestType: 'goal' },
232
+ }
215
233
  );
216
234
  }
217
235
 
@@ -274,7 +292,15 @@ class ActionClient extends Entity {
274
292
  */
275
293
  _cancelGoal(goalHandle) {
276
294
  if (!(goalHandle instanceof ClientGoalHandle)) {
277
- throw new TypeError('Invalid argument, must be type of ClientGoalHandle');
295
+ throw new TypeValidationError(
296
+ 'goalHandle',
297
+ goalHandle,
298
+ 'ClientGoalHandle',
299
+ {
300
+ entityType: 'action client',
301
+ entityName: this._actionName,
302
+ }
303
+ );
278
304
  }
279
305
 
280
306
  let request = new ActionInterfaces.CancelGoal.Request();
@@ -290,8 +316,14 @@ class ActionClient extends Entity {
290
316
  request.serialize()
291
317
  );
292
318
  if (this._pendingCancelRequests.has(sequenceNumber)) {
293
- throw new Error(
294
- `Sequence (${sequenceNumber}) conflicts with pending cancel request`
319
+ throw new OperationError(
320
+ `Sequence (${sequenceNumber}) conflicts with pending cancel request`,
321
+ {
322
+ code: 'SEQUENCE_CONFLICT',
323
+ entityType: 'action client',
324
+ entityName: this._actionName,
325
+ details: { sequenceNumber: sequenceNumber, requestType: 'cancel' },
326
+ }
295
327
  );
296
328
  }
297
329
 
@@ -316,7 +348,15 @@ class ActionClient extends Entity {
316
348
  */
317
349
  _getResult(goalHandle) {
318
350
  if (!(goalHandle instanceof ClientGoalHandle)) {
319
- throw new TypeError('Invalid argument, must be type of ClientGoalHandle');
351
+ throw new TypeValidationError(
352
+ 'goalHandle',
353
+ goalHandle,
354
+ 'ClientGoalHandle',
355
+ {
356
+ entityType: 'action client',
357
+ entityName: this._actionName,
358
+ }
359
+ );
320
360
  }
321
361
 
322
362
  let request = new this.typeClass.impl.GetResultService.Request();
@@ -327,8 +367,14 @@ class ActionClient extends Entity {
327
367
  request.serialize()
328
368
  );
329
369
  if (this._pendingResultRequests.has(sequenceNumber)) {
330
- throw new Error(
331
- `Sequence (${sequenceNumber}) conflicts with pending result request`
370
+ throw new OperationError(
371
+ `Sequence (${sequenceNumber}) conflicts with pending result request`,
372
+ {
373
+ code: 'SEQUENCE_CONFLICT',
374
+ entityType: 'action client',
375
+ entityName: this._actionName,
376
+ details: { sequenceNumber: sequenceNumber, requestType: 'result' },
377
+ }
332
378
  );
333
379
  }
334
380
 
@@ -14,6 +14,8 @@
14
14
 
15
15
  'use strict';
16
16
 
17
+ const { TypeValidationError } = require('../errors.js');
18
+
17
19
  /**
18
20
  * @class - Wraps a promise allowing it to be resolved elsewhere.
19
21
  * @ignore
@@ -42,7 +44,9 @@ class Deferred {
42
44
  */
43
45
  beforeSetResultCallback(callback) {
44
46
  if (typeof callback !== 'function') {
45
- throw new TypeError('Invalid parameter');
47
+ throw new TypeValidationError('callback', callback, 'function', {
48
+ entityType: 'deferred promise',
49
+ });
46
50
  }
47
51
 
48
52
  this._beforeSetResultCallback = callback;
@@ -70,7 +74,9 @@ class Deferred {
70
74
  */
71
75
  setDoneCallback(callback) {
72
76
  if (typeof callback !== 'function') {
73
- throw new TypeError('Invalid parameter');
77
+ throw new TypeValidationError('callback', callback, 'function', {
78
+ entityType: 'deferred promise',
79
+ });
74
80
  }
75
81
 
76
82
  this._promise.finally(() => callback(this._result));
@@ -23,6 +23,7 @@ const { CancelResponse, GoalEvent, GoalResponse } = require('./response.js');
23
23
  const loader = require('../interface_loader.js');
24
24
  const QoS = require('../qos.js');
25
25
  const ServerGoalHandle = require('./server_goal_handle.js');
26
+ const { TypeValidationError } = require('../errors.js');
26
27
 
27
28
  /**
28
29
  * Execute the goal.
@@ -219,7 +220,15 @@ class ActionServer extends Entity {
219
220
  */
220
221
  registerExecuteCallback(executeCallback) {
221
222
  if (typeof executeCallback !== 'function') {
222
- throw new TypeError('Invalid argument');
223
+ throw new TypeValidationError(
224
+ 'executeCallback',
225
+ executeCallback,
226
+ 'function',
227
+ {
228
+ entityType: 'action server',
229
+ entityName: this._actionName,
230
+ }
231
+ );
223
232
  }
224
233
 
225
234
  this._callback = executeCallback;
@@ -16,6 +16,7 @@
16
16
 
17
17
  const ActionInterfaces = require('./interfaces.js');
18
18
  const { randomUUID } = require('crypto');
19
+ const { TypeValidationError } = require('../errors.js');
19
20
 
20
21
  /**
21
22
  * @class - Represents a unique identifier used by actions.
@@ -31,7 +32,9 @@ class ActionUuid {
31
32
  constructor(bytes) {
32
33
  if (bytes) {
33
34
  if (!(bytes instanceof Uint8Array)) {
34
- throw new Error('Invalid parameter');
35
+ throw new TypeValidationError('bytes', bytes, 'Uint8Array', {
36
+ entityType: 'action uuid',
37
+ });
35
38
  }
36
39
 
37
40
  this._bytes = bytes;
package/lib/client.js CHANGED
@@ -17,8 +17,58 @@
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const DistroUtils = require('./distro.js');
19
19
  const Entity = require('./entity.js');
20
+ const {
21
+ TypeValidationError,
22
+ TimeoutError,
23
+ AbortError,
24
+ } = require('./errors.js');
20
25
  const debug = require('debug')('rclnodejs:client');
21
26
 
27
+ // Polyfill for AbortSignal.any() for Node.js <= 20.3.0
28
+ // AbortSignal.any() was added in Node.js 20.3.0
29
+ // See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static
30
+ if (!AbortSignal.any) {
31
+ AbortSignal.any = function (signals) {
32
+ // Filter out null/undefined values and validate inputs
33
+ const validSignals = Array.isArray(signals)
34
+ ? signals.filter((signal) => signal != null)
35
+ : [];
36
+
37
+ // If no valid signals, return a never-aborting signal
38
+ if (validSignals.length === 0) {
39
+ return new AbortController().signal;
40
+ }
41
+
42
+ const controller = new AbortController();
43
+ const listeners = [];
44
+
45
+ // Cleanup function to remove all event listeners
46
+ const cleanup = () => {
47
+ listeners.forEach(({ signal, listener }) => {
48
+ signal.removeEventListener('abort', listener);
49
+ });
50
+ };
51
+
52
+ for (const signal of validSignals) {
53
+ if (signal.aborted) {
54
+ cleanup();
55
+ controller.abort(signal.reason);
56
+ return controller.signal;
57
+ }
58
+
59
+ const listener = () => {
60
+ cleanup();
61
+ controller.abort(signal.reason);
62
+ };
63
+
64
+ signal.addEventListener('abort', listener);
65
+ listeners.push({ signal, listener });
66
+ }
67
+
68
+ return controller.signal;
69
+ };
70
+ }
71
+
22
72
  /**
23
73
  * @class - Class representing a Client in ROS
24
74
  * @hideconstructor
@@ -33,7 +83,7 @@ class Client extends Entity {
33
83
  }
34
84
 
35
85
  /**
36
- * This callback is called when a resopnse is sent back from service
86
+ * This callback is called when a response is sent back from service
37
87
  * @callback ResponseCallback
38
88
  * @param {Object} response - The response sent from the service
39
89
  * @see [Client.sendRequest]{@link Client#sendRequest}
@@ -43,7 +93,7 @@ class Client extends Entity {
43
93
  */
44
94
 
45
95
  /**
46
- * Send the request and will be notified asynchronously if receiving the repsonse.
96
+ * Send the request and will be notified asynchronously if receiving the response.
47
97
  * @param {object} request - The request to be submitted.
48
98
  * @param {ResponseCallback} callback - Thc callback function for receiving the server response.
49
99
  * @return {undefined}
@@ -51,7 +101,10 @@ class Client extends Entity {
51
101
  */
52
102
  sendRequest(request, callback) {
53
103
  if (typeof callback !== 'function') {
54
- throw new TypeError('Invalid argument');
104
+ throw new TypeValidationError('callback', callback, 'function', {
105
+ entityType: 'service',
106
+ entityName: this._serviceName,
107
+ });
55
108
  }
56
109
 
57
110
  let requestToSend =
@@ -65,6 +118,102 @@ class Client extends Entity {
65
118
  this._sequenceNumberToCallbackMap.set(sequenceNumber, callback);
66
119
  }
67
120
 
121
+ /**
122
+ * Send the request and return a Promise that resolves with the response.
123
+ * @param {object} request - The request to be submitted.
124
+ * @param {object} [options] - Optional parameters for the request.
125
+ * @param {number} [options.timeout] - Timeout in milliseconds for the request.
126
+ * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
127
+ * @return {Promise<object>} Promise that resolves with the service response.
128
+ * @throws {module:rclnodejs.TimeoutError} If the request times out (when options.timeout is exceeded).
129
+ * @throws {module:rclnodejs.AbortError} If the request is manually aborted (via options.signal).
130
+ * @throws {Error} If the request fails for other reasons.
131
+ */
132
+ sendRequestAsync(request, options = {}) {
133
+ return new Promise((resolve, reject) => {
134
+ let sequenceNumber = null;
135
+ let isResolved = false;
136
+ let isTimeout = false;
137
+
138
+ const cleanup = () => {
139
+ if (sequenceNumber !== null) {
140
+ this._sequenceNumberToCallbackMap.delete(sequenceNumber);
141
+ }
142
+ isResolved = true;
143
+ };
144
+
145
+ let effectiveSignal = options.signal;
146
+
147
+ if (options.timeout !== undefined && options.timeout >= 0) {
148
+ const timeoutSignal = AbortSignal.timeout(options.timeout);
149
+
150
+ timeoutSignal.addEventListener('abort', () => {
151
+ isTimeout = true;
152
+ });
153
+
154
+ if (options.signal) {
155
+ effectiveSignal = AbortSignal.any([options.signal, timeoutSignal]);
156
+ } else {
157
+ effectiveSignal = timeoutSignal;
158
+ }
159
+ }
160
+
161
+ if (effectiveSignal) {
162
+ if (effectiveSignal.aborted) {
163
+ const error = isTimeout
164
+ ? new TimeoutError('Service request', options.timeout, {
165
+ entityType: 'service',
166
+ entityName: this._serviceName,
167
+ })
168
+ : new AbortError('Service request', undefined, {
169
+ entityType: 'service',
170
+ entityName: this._serviceName,
171
+ });
172
+ reject(error);
173
+ return;
174
+ }
175
+
176
+ effectiveSignal.addEventListener('abort', () => {
177
+ if (!isResolved) {
178
+ cleanup();
179
+ const error = isTimeout
180
+ ? new TimeoutError('Service request', options.timeout, {
181
+ entityType: 'service',
182
+ entityName: this._serviceName,
183
+ })
184
+ : new AbortError('Service request', undefined, {
185
+ entityType: 'service',
186
+ entityName: this._serviceName,
187
+ });
188
+ reject(error);
189
+ }
190
+ });
191
+ }
192
+
193
+ try {
194
+ let requestToSend =
195
+ request instanceof this._typeClass.Request
196
+ ? request
197
+ : new this._typeClass.Request(request);
198
+
199
+ let rawRequest = requestToSend.serialize();
200
+ sequenceNumber = rclnodejs.sendRequest(this._handle, rawRequest);
201
+
202
+ debug(`Client has sent a ${this._serviceName} request (async).`);
203
+
204
+ this._sequenceNumberToCallbackMap.set(sequenceNumber, (response) => {
205
+ if (!isResolved) {
206
+ cleanup();
207
+ resolve(response);
208
+ }
209
+ });
210
+ } catch (error) {
211
+ cleanup();
212
+ reject(error);
213
+ }
214
+ });
215
+ }
216
+
68
217
  processResponse(sequenceNumber, response) {
69
218
  if (this._sequenceNumberToCallbackMap.has(sequenceNumber)) {
70
219
  debug(`Client has received ${this._serviceName} response from service.`);
package/lib/clock.js CHANGED
@@ -17,6 +17,7 @@
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const Time = require('./time.js');
19
19
  const ClockType = require('./clock_type.js');
20
+ const { TypeValidationError } = require('./errors.js');
20
21
 
21
22
  /**
22
23
  * @class - Class representing a Clock in ROS
@@ -102,7 +103,9 @@ class ROSClock extends Clock {
102
103
 
103
104
  set rosTimeOverride(time) {
104
105
  if (!(time instanceof Time)) {
105
- throw new TypeError('Invalid argument, must be type of Time');
106
+ throw new TypeValidationError('time', time, 'Time', {
107
+ entityType: 'clock',
108
+ });
106
109
  }
107
110
  rclnodejs.setRosTimeOverride(this._handle, time._handle);
108
111
  }
package/lib/context.js CHANGED
@@ -15,6 +15,7 @@
15
15
  'use strict';
16
16
 
17
17
  const rclnodejs = require('./native_loader.js');
18
+ const { OperationError } = require('./errors.js');
18
19
 
19
20
  let defaultContext = null;
20
21
 
@@ -184,11 +185,20 @@ class Context {
184
185
 
185
186
  onNodeCreated(node) {
186
187
  if (!node) {
187
- throw new Error('Node must be defined to add to Context');
188
+ throw new OperationError('Node must be defined to add to Context', {
189
+ code: 'NODE_UNDEFINED',
190
+ entityType: 'context',
191
+ });
188
192
  }
189
193
 
190
194
  if (this.isShutdown()) {
191
- throw new Error('Can not add a Node to a Context that is shutdown');
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
+ );
192
202
  }
193
203
 
194
204
  if (this._nodes.includes(node)) {
package/lib/duration.js CHANGED
@@ -15,6 +15,7 @@
15
15
  'use strict';
16
16
 
17
17
  const rclnodejs = require('./native_loader.js');
18
+ const { TypeValidationError, RangeValidationError } = require('./errors.js');
18
19
  const S_TO_NS = 10n ** 9n;
19
20
 
20
21
  /**
@@ -29,17 +30,31 @@ class Duration {
29
30
  */
30
31
  constructor(seconds = 0n, nanoseconds = 0n) {
31
32
  if (typeof seconds !== 'bigint') {
32
- throw new TypeError('Invalid argument of seconds');
33
+ throw new TypeValidationError('seconds', seconds, 'bigint', {
34
+ entityType: 'duration',
35
+ });
33
36
  }
34
37
 
35
38
  if (typeof nanoseconds !== 'bigint') {
36
- throw new TypeError('Invalid argument of nanoseconds');
39
+ throw new TypeValidationError('nanoseconds', nanoseconds, 'bigint', {
40
+ entityType: 'duration',
41
+ });
37
42
  }
38
43
 
39
44
  const total = seconds * S_TO_NS + nanoseconds;
40
45
  if (total >= 2n ** 63n) {
41
- throw new RangeError(
42
- 'Total nanoseconds value is too large to store in C time point.'
46
+ throw new RangeValidationError(
47
+ 'total nanoseconds',
48
+ total,
49
+ '< 2^63 (max C duration)',
50
+ {
51
+ entityType: 'duration',
52
+ details: {
53
+ seconds: seconds,
54
+ nanoseconds: nanoseconds,
55
+ total: total,
56
+ },
57
+ }
43
58
  );
44
59
  }
45
60
 
@@ -67,9 +82,9 @@ class Duration {
67
82
  if (other instanceof Duration) {
68
83
  return this._nanoseconds === other.nanoseconds;
69
84
  }
70
- throw new TypeError(
71
- `Can't compare duration with object of type: ${other.constructor.name}`
72
- );
85
+ throw new TypeValidationError('other', other, 'Duration', {
86
+ entityType: 'duration',
87
+ });
73
88
  }
74
89
 
75
90
  /**
@@ -81,7 +96,9 @@ class Duration {
81
96
  if (other instanceof Duration) {
82
97
  return this._nanoseconds !== other.nanoseconds;
83
98
  }
84
- throw new TypeError('Invalid argument');
99
+ throw new TypeValidationError('other', other, 'Duration', {
100
+ entityType: 'duration',
101
+ });
85
102
  }
86
103
 
87
104
  /**
@@ -93,7 +110,9 @@ class Duration {
93
110
  if (other instanceof Duration) {
94
111
  return this._nanoseconds < other.nanoseconds;
95
112
  }
96
- throw new TypeError('Invalid argument');
113
+ throw new TypeValidationError('other', other, 'Duration', {
114
+ entityType: 'duration',
115
+ });
97
116
  }
98
117
 
99
118
  /**
@@ -105,7 +124,9 @@ class Duration {
105
124
  if (other instanceof Duration) {
106
125
  return this._nanoseconds <= other.nanoseconds;
107
126
  }
108
- throw new TypeError('Invalid argument');
127
+ throw new TypeValidationError('other', other, 'Duration', {
128
+ entityType: 'duration',
129
+ });
109
130
  }
110
131
 
111
132
  /**
@@ -117,7 +138,9 @@ class Duration {
117
138
  if (other instanceof Duration) {
118
139
  return this._nanoseconds > other.nanoseconds;
119
140
  }
120
- throw new TypeError('Invalid argument');
141
+ throw new TypeValidationError('other', other, 'Duration', {
142
+ entityType: 'duration',
143
+ });
121
144
  }
122
145
 
123
146
  /**
@@ -129,7 +152,9 @@ class Duration {
129
152
  if (other instanceof Duration) {
130
153
  return this._nanoseconds >= other.nanoseconds;
131
154
  }
132
- throw new TypeError('Invalid argument');
155
+ throw new TypeValidationError('other', other, 'Duration', {
156
+ entityType: 'duration',
157
+ });
133
158
  }
134
159
  }
135
160