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/lib/errors.js ADDED
@@ -0,0 +1,571 @@
1
+ // Copyright (c) 2025 Mahmoud Alghalayini. 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
+ /**
18
+ * Base error class for all rclnodejs errors.
19
+ * Provides structured error information with context.
20
+ * @class
21
+ */
22
+ class RclNodeError extends Error {
23
+ /**
24
+ * @param {string} message - Human-readable error message
25
+ * @param {object} [options] - Additional error context
26
+ * @param {string} [options.code] - Machine-readable error code (e.g., 'TIMEOUT', 'INVALID_ARGUMENT')
27
+ * @param {string} [options.nodeName] - Name of the node where error occurred
28
+ * @param {string} [options.entityType] - Type of entity (publisher, subscription, client, etc.)
29
+ * @param {string} [options.entityName] - Name of the entity (topic name, service name, etc.)
30
+ * @param {Error} [options.cause] - Original error that caused this error
31
+ * @param {any} [options.details] - Additional error-specific details
32
+ */
33
+ constructor(message, options = {}) {
34
+ super(message);
35
+
36
+ // Maintains proper stack trace for where our error was thrown
37
+ Error.captureStackTrace(this, this.constructor);
38
+
39
+ this.name = this.constructor.name;
40
+ this.code = options.code || 'UNKNOWN_ERROR';
41
+ this.nodeName = options.nodeName;
42
+ this.entityType = options.entityType;
43
+ this.entityName = options.entityName;
44
+ this.details = options.details;
45
+
46
+ // Error chaining (ES2022 feature, Node.js 16.9+)
47
+ if (options.cause) {
48
+ this.cause = options.cause;
49
+ }
50
+
51
+ // Timestamp for logging/debugging
52
+ this.timestamp = new Date();
53
+ }
54
+
55
+ /**
56
+ * Returns a detailed error object for logging/serialization
57
+ * @return {object} Structured error information
58
+ */
59
+ toJSON() {
60
+ return {
61
+ name: this.name,
62
+ message: this.message,
63
+ code: this.code,
64
+ nodeName: this.nodeName,
65
+ entityType: this.entityType,
66
+ entityName: this.entityName,
67
+ details: this.details,
68
+ timestamp: this.timestamp.toISOString(),
69
+ stack: this.stack,
70
+ cause: this.cause
71
+ ? this.cause.toJSON?.() || this.cause.message
72
+ : undefined,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Returns a user-friendly error description
78
+ * @return {string} Formatted error string
79
+ */
80
+ toString() {
81
+ let str = `${this.name}: ${this.message}`;
82
+ if (this.code) str += ` [${this.code}]`;
83
+ if (this.nodeName) str += ` (node: ${this.nodeName})`;
84
+ if (this.entityName)
85
+ str += ` (${this.entityType || 'entity'}: ${this.entityName})`;
86
+ return str;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Error thrown when validation fails
92
+ * @class
93
+ * @extends RclNodeError
94
+ */
95
+ class ValidationError extends RclNodeError {
96
+ /**
97
+ * @param {string} message - Error message
98
+ * @param {object} [options] - Additional options
99
+ * @param {string} [options.argumentName] - Name of the argument that failed validation
100
+ * @param {any} [options.providedValue] - The value that was provided
101
+ * @param {string} [options.expectedType] - The expected type or format
102
+ * @param {string} [options.validationRule] - The validation rule that failed
103
+ */
104
+ constructor(message, options = {}) {
105
+ super(message, { code: 'VALIDATION_ERROR', ...options });
106
+ this.argumentName = options.argumentName;
107
+ this.providedValue = options.providedValue;
108
+ this.expectedType = options.expectedType;
109
+ this.validationRule = options.validationRule;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Type validation error
115
+ * @class
116
+ * @extends ValidationError
117
+ */
118
+ class TypeValidationError extends ValidationError {
119
+ /**
120
+ * @param {string} argumentName - Name of the argument
121
+ * @param {any} providedValue - The value that was provided
122
+ * @param {string} expectedType - The expected type
123
+ * @param {object} [options] - Additional options
124
+ */
125
+ constructor(argumentName, providedValue, expectedType, options = {}) {
126
+ super(
127
+ `Invalid type for '${argumentName}': expected ${expectedType}, got ${typeof providedValue}`,
128
+ {
129
+ code: 'INVALID_TYPE',
130
+ argumentName,
131
+ providedValue,
132
+ expectedType,
133
+ ...options,
134
+ }
135
+ );
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Range/value validation error
141
+ * @class
142
+ * @extends ValidationError
143
+ */
144
+ class RangeValidationError extends ValidationError {
145
+ /**
146
+ * @param {string} argumentName - Name of the argument
147
+ * @param {any} providedValue - The value that was provided
148
+ * @param {string} constraint - The constraint that was violated
149
+ * @param {object} [options] - Additional options
150
+ */
151
+ constructor(argumentName, providedValue, constraint, options = {}) {
152
+ super(
153
+ `Value '${providedValue}' for '${argumentName}' is out of range: ${constraint}`,
154
+ {
155
+ code: 'OUT_OF_RANGE',
156
+ argumentName,
157
+ providedValue,
158
+ validationRule: constraint,
159
+ ...options,
160
+ }
161
+ );
162
+ }
163
+ }
164
+
165
+ /**
166
+ * ROS name validation error (topics, nodes, services)
167
+ * @class
168
+ * @extends ValidationError
169
+ */
170
+ class NameValidationError extends ValidationError {
171
+ /**
172
+ * @param {string} name - The invalid name
173
+ * @param {string} nameType - Type of name (node, topic, service, etc.)
174
+ * @param {string} validationResult - The validation error message
175
+ * @param {number} invalidIndex - Index where validation failed
176
+ * @param {object} [options] - Additional options
177
+ */
178
+ constructor(name, nameType, validationResult, invalidIndex, options = {}) {
179
+ super(
180
+ `Invalid ${nameType} name '${name}': ${validationResult}` +
181
+ (invalidIndex >= 0 ? ` at index ${invalidIndex}` : ''),
182
+ {
183
+ code: 'INVALID_NAME',
184
+ argumentName: nameType,
185
+ providedValue: name,
186
+ details: { validationResult, invalidIndex },
187
+ ...options,
188
+ }
189
+ );
190
+ this.invalidIndex = invalidIndex;
191
+ this.validationResult = validationResult;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Base class for operation/runtime errors
197
+ * @class
198
+ * @extends RclNodeError
199
+ */
200
+ class OperationError extends RclNodeError {
201
+ /**
202
+ * @param {string} message - Error message
203
+ * @param {object} [options] - Additional options
204
+ */
205
+ constructor(message, options = {}) {
206
+ super(message, { code: 'OPERATION_ERROR', ...options });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Request timeout error
212
+ * @class
213
+ * @extends OperationError
214
+ */
215
+ class TimeoutError extends OperationError {
216
+ /**
217
+ * @param {string} operationType - Type of operation that timed out
218
+ * @param {number} timeoutMs - Timeout duration in milliseconds
219
+ * @param {object} [options] - Additional options
220
+ */
221
+ constructor(operationType, timeoutMs, options = {}) {
222
+ super(`${operationType} timeout after ${timeoutMs}ms`, {
223
+ code: 'TIMEOUT',
224
+ details: { timeoutMs, operationType },
225
+ ...options,
226
+ });
227
+ this.timeout = timeoutMs;
228
+ this.operationType = operationType;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Request abortion error
234
+ * @class
235
+ * @extends OperationError
236
+ */
237
+ class AbortError extends OperationError {
238
+ /**
239
+ * @param {string} operationType - Type of operation that was aborted
240
+ * @param {string} [reason] - Reason for abortion
241
+ * @param {object} [options] - Additional options
242
+ */
243
+ constructor(operationType, reason, options = {}) {
244
+ super(`${operationType} was aborted` + (reason ? `: ${reason}` : ''), {
245
+ code: 'ABORTED',
246
+ details: { operationType, reason },
247
+ ...options,
248
+ });
249
+ this.operationType = operationType;
250
+ this.abortReason = reason;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Service not available error
256
+ * @class
257
+ * @extends OperationError
258
+ */
259
+ class ServiceNotFoundError extends OperationError {
260
+ /**
261
+ * @param {string} serviceName - Name of the service
262
+ * @param {object} [options] - Additional options
263
+ */
264
+ constructor(serviceName, options = {}) {
265
+ super(`Service '${serviceName}' is not available`, {
266
+ code: 'SERVICE_NOT_FOUND',
267
+ entityType: 'service',
268
+ entityName: serviceName,
269
+ ...options,
270
+ });
271
+ this.serviceName = serviceName;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Remote node not found error
277
+ * @class
278
+ * @extends OperationError
279
+ */
280
+ class NodeNotFoundError extends OperationError {
281
+ /**
282
+ * @param {string} nodeName - Name of the node
283
+ * @param {object} [options] - Additional options
284
+ */
285
+ constructor(nodeName, options = {}) {
286
+ super(`Node '${nodeName}' not found or not available`, {
287
+ code: 'NODE_NOT_FOUND',
288
+ entityType: 'node',
289
+ entityName: nodeName,
290
+ ...options,
291
+ });
292
+ this.targetNodeName = nodeName;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Base error for parameter operations
298
+ * @class
299
+ * @extends RclNodeError
300
+ */
301
+ class ParameterError extends RclNodeError {
302
+ /**
303
+ * @param {string} message - Error message
304
+ * @param {string} parameterName - Name of the parameter
305
+ * @param {object} [options] - Additional options
306
+ */
307
+ constructor(message, parameterName, options = {}) {
308
+ super(message, {
309
+ code: 'PARAMETER_ERROR',
310
+ entityType: 'parameter',
311
+ entityName: parameterName,
312
+ ...options,
313
+ });
314
+ this.parameterName = parameterName;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Parameter not found error
320
+ * @class
321
+ * @extends ParameterError
322
+ */
323
+ class ParameterNotFoundError extends ParameterError {
324
+ /**
325
+ * @param {string} parameterName - Name of the parameter
326
+ * @param {string} nodeName - Name of the node
327
+ * @param {object} [options] - Additional options
328
+ */
329
+ constructor(parameterName, nodeName, options = {}) {
330
+ super(
331
+ `Parameter '${parameterName}' not found on node '${nodeName}'`,
332
+ parameterName,
333
+ {
334
+ code: 'PARAMETER_NOT_FOUND',
335
+ nodeName,
336
+ ...options,
337
+ }
338
+ );
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Parameter type mismatch error
344
+ * @class
345
+ * @extends ParameterError
346
+ */
347
+ class ParameterTypeError extends ParameterError {
348
+ /**
349
+ * @param {string} parameterName - Name of the parameter
350
+ * @param {string} expectedType - Expected parameter type
351
+ * @param {string} actualType - Actual parameter type
352
+ * @param {object} [options] - Additional options
353
+ */
354
+ constructor(parameterName, expectedType, actualType, options = {}) {
355
+ super(
356
+ `Type mismatch for parameter '${parameterName}': expected ${expectedType}, got ${actualType}`,
357
+ parameterName,
358
+ {
359
+ code: 'PARAMETER_TYPE_MISMATCH',
360
+ details: { expectedType, actualType },
361
+ ...options,
362
+ }
363
+ );
364
+ this.expectedType = expectedType;
365
+ this.actualType = actualType;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Read-only parameter modification error
371
+ * @class
372
+ * @extends ParameterError
373
+ */
374
+ class ReadOnlyParameterError extends ParameterError {
375
+ /**
376
+ * @param {string} parameterName - Name of the parameter
377
+ * @param {object} [options] - Additional options
378
+ */
379
+ constructor(parameterName, options = {}) {
380
+ super(
381
+ `Cannot modify read-only parameter '${parameterName}'`,
382
+ parameterName,
383
+ {
384
+ code: 'PARAMETER_READ_ONLY',
385
+ ...options,
386
+ }
387
+ );
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Base error for topic operations
393
+ * @class
394
+ * @extends RclNodeError
395
+ */
396
+ class TopicError extends RclNodeError {
397
+ /**
398
+ * @param {string} message - Error message
399
+ * @param {string} topicName - Name of the topic
400
+ * @param {object} [options] - Additional options
401
+ */
402
+ constructor(message, topicName, options = {}) {
403
+ super(message, {
404
+ code: 'TOPIC_ERROR',
405
+ entityType: 'topic',
406
+ entityName: topicName,
407
+ ...options,
408
+ });
409
+ this.topicName = topicName;
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Publisher-specific error
415
+ * @class
416
+ * @extends TopicError
417
+ */
418
+ class PublisherError extends TopicError {
419
+ /**
420
+ * @param {string} message - Error message
421
+ * @param {string} topicName - Name of the topic
422
+ * @param {object} [options] - Additional options
423
+ */
424
+ constructor(message, topicName, options = {}) {
425
+ super(message, topicName, {
426
+ code: 'PUBLISHER_ERROR',
427
+ entityType: 'publisher',
428
+ ...options,
429
+ });
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Subscription-specific error
435
+ * @class
436
+ * @extends TopicError
437
+ */
438
+ class SubscriptionError extends TopicError {
439
+ /**
440
+ * @param {string} message - Error message
441
+ * @param {string} topicName - Name of the topic
442
+ * @param {object} [options] - Additional options
443
+ */
444
+ constructor(message, topicName, options = {}) {
445
+ super(message, topicName, {
446
+ code: 'SUBSCRIPTION_ERROR',
447
+ entityType: 'subscription',
448
+ ...options,
449
+ });
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Base error for action operations
455
+ * @class
456
+ * @extends RclNodeError
457
+ */
458
+ class ActionError extends RclNodeError {
459
+ /**
460
+ * @param {string} message - Error message
461
+ * @param {string} actionName - Name of the action
462
+ * @param {object} [options] - Additional options
463
+ */
464
+ constructor(message, actionName, options = {}) {
465
+ super(message, {
466
+ code: 'ACTION_ERROR',
467
+ entityType: 'action',
468
+ entityName: actionName,
469
+ ...options,
470
+ });
471
+ this.actionName = actionName;
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Goal rejected by action server
477
+ * @class
478
+ * @extends ActionError
479
+ */
480
+ class GoalRejectedError extends ActionError {
481
+ /**
482
+ * @param {string} actionName - Name of the action
483
+ * @param {string} goalId - ID of the rejected goal
484
+ * @param {object} [options] - Additional options
485
+ */
486
+ constructor(actionName, goalId, options = {}) {
487
+ super(`Goal rejected by action server '${actionName}'`, actionName, {
488
+ code: 'GOAL_REJECTED',
489
+ details: { goalId },
490
+ ...options,
491
+ });
492
+ this.goalId = goalId;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Action server not found
498
+ * @class
499
+ * @extends ActionError
500
+ */
501
+ class ActionServerNotFoundError extends ActionError {
502
+ /**
503
+ * @param {string} actionName - Name of the action
504
+ * @param {object} [options] - Additional options
505
+ */
506
+ constructor(actionName, options = {}) {
507
+ super(`Action server '${actionName}' is not available`, actionName, {
508
+ code: 'ACTION_SERVER_NOT_FOUND',
509
+ ...options,
510
+ });
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Wraps errors from native C++ layer with additional context
516
+ * @class
517
+ * @extends RclNodeError
518
+ */
519
+ class NativeError extends RclNodeError {
520
+ /**
521
+ * @param {string} nativeMessage - Error message from C++ layer
522
+ * @param {string} operation - Operation that failed
523
+ * @param {object} [options] - Additional options
524
+ */
525
+ constructor(nativeMessage, operation, options = {}) {
526
+ super(`Native operation failed: ${operation} - ${nativeMessage}`, {
527
+ code: 'NATIVE_ERROR',
528
+ details: { nativeMessage, operation },
529
+ ...options,
530
+ });
531
+ this.nativeMessage = nativeMessage;
532
+ this.operation = operation;
533
+ }
534
+ }
535
+
536
+ module.exports = {
537
+ // Base error
538
+ RclNodeError,
539
+
540
+ // Validation errors
541
+ ValidationError,
542
+ TypeValidationError,
543
+ RangeValidationError,
544
+ NameValidationError,
545
+
546
+ // Operation errors
547
+ OperationError,
548
+ TimeoutError,
549
+ AbortError,
550
+ ServiceNotFoundError,
551
+ NodeNotFoundError,
552
+
553
+ // Parameter errors
554
+ ParameterError,
555
+ ParameterNotFoundError,
556
+ ParameterTypeError,
557
+ ReadOnlyParameterError,
558
+
559
+ // Topic errors
560
+ TopicError,
561
+ PublisherError,
562
+ SubscriptionError,
563
+
564
+ // Action errors
565
+ ActionError,
566
+ GoalRejectedError,
567
+ ActionServerNotFoundError,
568
+
569
+ // Native error
570
+ NativeError,
571
+ };
@@ -16,6 +16,7 @@
16
16
 
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const DistroUtils = require('./distro.js');
19
+ const { OperationError } = require('./errors.js');
19
20
  const Entity = require('./entity.js');
20
21
 
21
22
  /**
@@ -79,8 +80,16 @@ class EventHandler extends Entity {
79
80
  class PublisherEventCallbacks {
80
81
  constructor() {
81
82
  if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
82
- throw new Error(
83
- 'PublisherEventCallbacks is only available in ROS 2 Jazzy and later.'
83
+ throw new OperationError(
84
+ 'PublisherEventCallbacks is only available in ROS 2 Jazzy and later',
85
+ {
86
+ code: 'UNSUPPORTED_ROS_VERSION',
87
+ entityType: 'publisher event callbacks',
88
+ details: {
89
+ requiredVersion: 'jazzy',
90
+ currentVersion: DistroUtils.getDistroId(),
91
+ },
92
+ }
84
93
  );
85
94
  }
86
95
  this._deadline = null;
@@ -262,8 +271,16 @@ class PublisherEventCallbacks {
262
271
  class SubscriptionEventCallbacks {
263
272
  constructor() {
264
273
  if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
265
- throw new Error(
266
- 'SubscriptionEventCallbacks is only available in ROS 2 Jazzy and later.'
274
+ throw new OperationError(
275
+ 'SubscriptionEventCallbacks is only available in ROS 2 Jazzy and later',
276
+ {
277
+ code: 'UNSUPPORTED_ROS_VERSION',
278
+ entityType: 'subscription event callbacks',
279
+ details: {
280
+ requiredVersion: 'jazzy',
281
+ currentVersion: DistroUtils.getDistroId(),
282
+ },
283
+ }
267
284
  );
268
285
  }
269
286
  this._deadline = null;