rclnodejs 1.6.0 → 1.8.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 (84) hide show
  1. package/binding.gyp +2 -0
  2. package/index.js +152 -0
  3. package/lib/action/client.js +109 -10
  4. package/lib/action/deferred.js +8 -2
  5. package/lib/action/server.js +10 -1
  6. package/lib/action/uuid.js +4 -1
  7. package/lib/client.js +218 -4
  8. package/lib/clock.js +182 -1
  9. package/lib/clock_change.js +49 -0
  10. package/lib/clock_event.js +88 -0
  11. package/lib/context.js +12 -2
  12. package/lib/duration.js +37 -12
  13. package/lib/errors.js +621 -0
  14. package/lib/event_handler.js +21 -4
  15. package/lib/interface_loader.js +52 -12
  16. package/lib/lifecycle.js +8 -2
  17. package/lib/logging.js +90 -3
  18. package/lib/message_introspector.js +123 -0
  19. package/lib/message_serialization.js +10 -2
  20. package/lib/message_validation.js +512 -0
  21. package/lib/native_loader.js +9 -4
  22. package/lib/node.js +403 -50
  23. package/lib/node_options.js +40 -1
  24. package/lib/observable_subscription.js +105 -0
  25. package/lib/parameter.js +172 -35
  26. package/lib/parameter_client.js +506 -0
  27. package/lib/parameter_watcher.js +309 -0
  28. package/lib/publisher.js +56 -1
  29. package/lib/qos.js +79 -5
  30. package/lib/rate.js +6 -1
  31. package/lib/serialization.js +7 -2
  32. package/lib/subscription.js +8 -0
  33. package/lib/time.js +136 -21
  34. package/lib/time_source.js +13 -4
  35. package/lib/timer.js +42 -0
  36. package/lib/utils.js +27 -1
  37. package/lib/validator.js +74 -19
  38. package/package.json +4 -2
  39. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  40. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  41. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  42. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  43. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  44. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  45. package/rosidl_gen/message_translator.js +0 -61
  46. package/scripts/config.js +1 -0
  47. package/src/addon.cpp +2 -0
  48. package/src/clock_event.cpp +268 -0
  49. package/src/clock_event.hpp +62 -0
  50. package/src/macros.h +2 -4
  51. package/src/rcl_action_server_bindings.cpp +21 -3
  52. package/src/rcl_bindings.cpp +59 -0
  53. package/src/rcl_context_bindings.cpp +5 -0
  54. package/src/rcl_graph_bindings.cpp +73 -0
  55. package/src/rcl_logging_bindings.cpp +158 -0
  56. package/src/rcl_node_bindings.cpp +14 -2
  57. package/src/rcl_publisher_bindings.cpp +12 -0
  58. package/src/rcl_service_bindings.cpp +7 -6
  59. package/src/rcl_subscription_bindings.cpp +51 -14
  60. package/src/rcl_time_point_bindings.cpp +135 -0
  61. package/src/rcl_timer_bindings.cpp +140 -0
  62. package/src/rcl_utilities.cpp +103 -2
  63. package/src/rcl_utilities.h +7 -1
  64. package/types/action_client.d.ts +27 -2
  65. package/types/base.d.ts +6 -0
  66. package/types/client.d.ts +65 -1
  67. package/types/clock.d.ts +86 -0
  68. package/types/clock_change.d.ts +27 -0
  69. package/types/clock_event.d.ts +51 -0
  70. package/types/errors.d.ts +496 -0
  71. package/types/index.d.ts +10 -0
  72. package/types/logging.d.ts +32 -0
  73. package/types/message_introspector.d.ts +75 -0
  74. package/types/message_validation.d.ts +183 -0
  75. package/types/node.d.ts +107 -0
  76. package/types/node_options.d.ts +13 -0
  77. package/types/observable_subscription.d.ts +39 -0
  78. package/types/parameter_client.d.ts +252 -0
  79. package/types/parameter_watcher.d.ts +104 -0
  80. package/types/publisher.d.ts +28 -1
  81. package/types/qos.d.ts +18 -0
  82. package/types/subscription.d.ts +6 -0
  83. package/types/timer.d.ts +18 -0
  84. package/types/validator.d.ts +86 -0
package/lib/time.js CHANGED
@@ -17,6 +17,7 @@
17
17
  const rclnodejs = require('./native_loader.js');
18
18
  const Duration = require('./duration.js');
19
19
  const ClockType = require('./clock_type.js');
20
+ const { TypeValidationError, RangeValidationError } = require('./errors.js');
20
21
  const S_TO_NS = 10n ** 9n;
21
22
 
22
23
  /**
@@ -36,29 +37,49 @@ class Time {
36
37
  clockType = ClockType.SYSTEM_TIME
37
38
  ) {
38
39
  if (typeof seconds !== 'bigint') {
39
- throw new TypeError('Invalid argument of seconds');
40
+ throw new TypeValidationError('seconds', seconds, 'bigint', {
41
+ entityType: 'time',
42
+ });
40
43
  }
41
44
 
42
45
  if (typeof nanoseconds !== 'bigint') {
43
- throw new TypeError('Invalid argument of nanoseconds');
46
+ throw new TypeValidationError('nanoseconds', nanoseconds, 'bigint', {
47
+ entityType: 'time',
48
+ });
44
49
  }
45
50
 
46
51
  if (typeof clockType !== 'number') {
47
- throw new TypeError('Invalid argument of clockType');
52
+ throw new TypeValidationError('clockType', clockType, 'number', {
53
+ entityType: 'time',
54
+ });
48
55
  }
49
56
 
50
57
  if (seconds < 0n) {
51
- throw new RangeError('seconds value must not be negative');
58
+ throw new RangeValidationError('seconds', seconds, '>= 0', {
59
+ entityType: 'time',
60
+ });
52
61
  }
53
62
 
54
63
  if (nanoseconds < 0n) {
55
- throw new RangeError('nanoseconds value must not be negative');
64
+ throw new RangeValidationError('nanoseconds', nanoseconds, '>= 0', {
65
+ entityType: 'time',
66
+ });
56
67
  }
57
68
 
58
69
  const total = seconds * S_TO_NS + nanoseconds;
59
70
  if (total >= 2n ** 63n) {
60
- throw new RangeError(
61
- 'Total nanoseconds value is too large to store in C time point.'
71
+ throw new RangeValidationError(
72
+ 'total nanoseconds',
73
+ total,
74
+ '< 2^63 (max C time point)',
75
+ {
76
+ entityType: 'time',
77
+ details: {
78
+ seconds: seconds,
79
+ nanoseconds: nanoseconds,
80
+ total: total,
81
+ },
82
+ }
62
83
  );
63
84
  }
64
85
  this._nanoseconds = total;
@@ -116,7 +137,9 @@ class Time {
116
137
  this._clockType
117
138
  );
118
139
  }
119
- throw new TypeError('Invalid argument');
140
+ throw new TypeValidationError('other', other, 'Duration', {
141
+ entityType: 'time',
142
+ });
120
143
  }
121
144
 
122
145
  /**
@@ -127,7 +150,18 @@ class Time {
127
150
  sub(other) {
128
151
  if (other instanceof Time) {
129
152
  if (other._clockType !== this._clockType) {
130
- throw new TypeError("Can't subtract times with different clock types");
153
+ throw new TypeValidationError(
154
+ 'other',
155
+ other,
156
+ `Time with clock type ${this._clockType}`,
157
+ {
158
+ entityType: 'time',
159
+ details: {
160
+ expectedClockType: this._clockType,
161
+ providedClockType: other._clockType,
162
+ },
163
+ }
164
+ );
131
165
  }
132
166
  return new Duration(0n, this._nanoseconds - other._nanoseconds);
133
167
  } else if (other instanceof Duration) {
@@ -137,7 +171,9 @@ class Time {
137
171
  this._clockType
138
172
  );
139
173
  }
140
- throw new TypeError('Invalid argument');
174
+ throw new TypeValidationError('other', other, 'Time or Duration', {
175
+ entityType: 'time',
176
+ });
141
177
  }
142
178
 
143
179
  /**
@@ -148,11 +184,24 @@ class Time {
148
184
  eq(other) {
149
185
  if (other instanceof Time) {
150
186
  if (other._clockType !== this._clockType) {
151
- throw new TypeError("Can't compare times with different clock types");
187
+ throw new TypeValidationError(
188
+ 'other',
189
+ other,
190
+ `Time with clock type ${this._clockType}`,
191
+ {
192
+ entityType: 'time',
193
+ details: {
194
+ expectedClockType: this._clockType,
195
+ providedClockType: other._clockType,
196
+ },
197
+ }
198
+ );
152
199
  }
153
200
  return this._nanoseconds === other.nanoseconds;
154
201
  }
155
- throw new TypeError('Invalid argument');
202
+ throw new TypeValidationError('other', other, 'Time', {
203
+ entityType: 'time',
204
+ });
156
205
  }
157
206
 
158
207
  /**
@@ -163,10 +212,24 @@ class Time {
163
212
  ne(other) {
164
213
  if (other instanceof Time) {
165
214
  if (other._clockType !== this._clockType) {
166
- throw new TypeError("Can't compare times with different clock types");
215
+ throw new TypeValidationError(
216
+ 'other',
217
+ other,
218
+ `Time with clock type ${this._clockType}`,
219
+ {
220
+ entityType: 'time',
221
+ details: {
222
+ expectedClockType: this._clockType,
223
+ providedClockType: other._clockType,
224
+ },
225
+ }
226
+ );
167
227
  }
168
228
  return this._nanoseconds !== other.nanoseconds;
169
229
  }
230
+ throw new TypeValidationError('other', other, 'Time', {
231
+ entityType: 'time',
232
+ });
170
233
  }
171
234
 
172
235
  /**
@@ -177,11 +240,24 @@ class Time {
177
240
  lt(other) {
178
241
  if (other instanceof Time) {
179
242
  if (other._clockType !== this._clockType) {
180
- throw new TypeError("Can't compare times with different clock types");
243
+ throw new TypeValidationError(
244
+ 'other',
245
+ other,
246
+ `Time with clock type ${this._clockType}`,
247
+ {
248
+ entityType: 'time',
249
+ details: {
250
+ expectedClockType: this._clockType,
251
+ providedClockType: other._clockType,
252
+ },
253
+ }
254
+ );
181
255
  }
182
256
  return this._nanoseconds < other.nanoseconds;
183
257
  }
184
- throw new TypeError('Invalid argument');
258
+ throw new TypeValidationError('other', other, 'Time', {
259
+ entityType: 'time',
260
+ });
185
261
  }
186
262
 
187
263
  /**
@@ -192,11 +268,24 @@ class Time {
192
268
  lte(other) {
193
269
  if (other instanceof Time) {
194
270
  if (other._clockType !== this._clockType) {
195
- throw new TypeError("Can't compare times with different clock types");
271
+ throw new TypeValidationError(
272
+ 'other',
273
+ other,
274
+ `Time with clock type ${this._clockType}`,
275
+ {
276
+ entityType: 'time',
277
+ details: {
278
+ expectedClockType: this._clockType,
279
+ providedClockType: other._clockType,
280
+ },
281
+ }
282
+ );
196
283
  }
197
284
  return this._nanoseconds <= other.nanoseconds;
198
285
  }
199
- throw new TypeError('Invalid argument');
286
+ throw new TypeValidationError('other', other, 'Time', {
287
+ entityType: 'time',
288
+ });
200
289
  }
201
290
 
202
291
  /**
@@ -207,11 +296,24 @@ class Time {
207
296
  gt(other) {
208
297
  if (other instanceof Time) {
209
298
  if (other._clockType !== this._clockType) {
210
- throw new TypeError("Can't compare times with different clock types");
299
+ throw new TypeValidationError(
300
+ 'other',
301
+ other,
302
+ `Time with clock type ${this._clockType}`,
303
+ {
304
+ entityType: 'time',
305
+ details: {
306
+ expectedClockType: this._clockType,
307
+ providedClockType: other._clockType,
308
+ },
309
+ }
310
+ );
211
311
  }
212
312
  return this._nanoseconds > other.nanoseconds;
213
313
  }
214
- throw new TypeError('Invalid argument');
314
+ throw new TypeValidationError('other', other, 'Time', {
315
+ entityType: 'time',
316
+ });
215
317
  }
216
318
 
217
319
  /**
@@ -222,11 +324,24 @@ class Time {
222
324
  gte(other) {
223
325
  if (other instanceof Time) {
224
326
  if (other._clockType !== this._clockType) {
225
- throw new TypeError("Can't compare times with different clock types");
327
+ throw new TypeValidationError(
328
+ 'other',
329
+ other,
330
+ `Time with clock type ${this._clockType}`,
331
+ {
332
+ entityType: 'time',
333
+ details: {
334
+ expectedClockType: this._clockType,
335
+ providedClockType: other._clockType,
336
+ },
337
+ }
338
+ );
226
339
  }
227
340
  return this._nanoseconds >= other.nanoseconds;
228
341
  }
229
- throw new TypeError('Invalid argument');
342
+ throw new TypeValidationError('other', other, 'Time', {
343
+ entityType: 'time',
344
+ });
230
345
  }
231
346
 
232
347
  /**
@@ -19,6 +19,7 @@ const { Clock, ROSClock } = require('./clock.js');
19
19
  const { ClockType } = Clock;
20
20
  const { Parameter, ParameterType } = require('./parameter.js');
21
21
  const Time = require('./time.js');
22
+ const { TypeValidationError, OperationError } = require('./errors.js');
22
23
 
23
24
  const USE_SIM_TIME_PARAM = 'use_sim_time';
24
25
  const CLOCK_TOPIC = '/clock';
@@ -102,7 +103,9 @@ class TimeSource {
102
103
  */
103
104
  attachNode(node) {
104
105
  if ((!node) instanceof rclnodejs.ShadowNode) {
105
- throw new TypeError('Invalid argument, must be type of Node');
106
+ throw new TypeValidationError('node', node, 'Node', {
107
+ entityType: 'time source',
108
+ });
106
109
  }
107
110
 
108
111
  if (this._node) {
@@ -150,8 +153,12 @@ class TimeSource {
150
153
  detachNode() {
151
154
  if (this._clockSubscription) {
152
155
  if (!this._node) {
153
- throw new Error(
154
- 'Unable to destroy previously created clock subscription'
156
+ throw new OperationError(
157
+ 'Unable to destroy previously created clock subscription',
158
+ {
159
+ code: 'NO_NODE_ATTACHED',
160
+ entityType: 'time source',
161
+ }
155
162
  );
156
163
  }
157
164
  this._node.destroySubscription(this._clockSubscription);
@@ -167,7 +174,9 @@ class TimeSource {
167
174
  */
168
175
  attachClock(clock) {
169
176
  if (!(clock instanceof ROSClock)) {
170
- throw new TypeError('Only clocks with type ROS_TIME can be attached.');
177
+ throw new TypeValidationError('clock', clock, 'ROSClock', {
178
+ entityType: 'time source',
179
+ });
171
180
  }
172
181
  clock.rosTimeOverride = this._lastTimeSet;
173
182
  clock.isRosTimeActive = this._isRosTimeActive;
package/lib/timer.js CHANGED
@@ -88,6 +88,19 @@ class Timer {
88
88
  return rclnodejs.timerGetTimeUntilNextCall(this._handle);
89
89
  }
90
90
 
91
+ /**
92
+ * Get the absolute time in nanoseconds when the next callback is due.
93
+ * Note: Only available on ROS2 distributions after Humble.
94
+ * @return {bigint | null} - The next call time in nanoseconds, or null if the timer is canceled.
95
+ * Returns undefined if not supported on current ROS2 distribution.
96
+ */
97
+ getNextCallTime() {
98
+ if (typeof rclnodejs.getTimerNextCallTime !== 'function') {
99
+ return undefined;
100
+ }
101
+ return rclnodejs.getTimerNextCallTime(this._handle);
102
+ }
103
+
91
104
  /**
92
105
  * Change the timer period.
93
106
  * @param {bigint} period - The new period in nanoseconds.
@@ -105,6 +118,35 @@ class Timer {
105
118
  return rclnodejs.getTimerPeriod(this._handle);
106
119
  }
107
120
 
121
+ /**
122
+ * Set the on reset callback.
123
+ * @param {function} callback - The callback to be called when the timer is reset.
124
+ * @return {undefined}
125
+ */
126
+ setOnResetCallback(callback) {
127
+ if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
128
+ console.warn(
129
+ 'setOnResetCallback is not supported by this version of ROS 2'
130
+ );
131
+ return;
132
+ }
133
+ rclnodejs.setTimerOnResetCallback(this._handle, callback);
134
+ }
135
+
136
+ /**
137
+ * Clear the on reset callback.
138
+ * @return {undefined}
139
+ */
140
+ clearOnResetCallback() {
141
+ if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
142
+ console.warn(
143
+ 'clearOnResetCallback is not supported by this version of ROS 2'
144
+ );
145
+ return;
146
+ }
147
+ rclnodejs.clearTimerOnResetCallback(this._handle);
148
+ }
149
+
108
150
  /**
109
151
  * Call a timer and starts counting again, retrieves actual and expected call time.
110
152
  * @return {object} - The timer information.
package/lib/utils.js CHANGED
@@ -15,6 +15,7 @@
15
15
  const fs = require('fs');
16
16
  const fsPromises = require('fs/promises');
17
17
  const path = require('path');
18
+ const { ValidationError } = require('./errors.js');
18
19
 
19
20
  /**
20
21
  * Ensure directory exists, create recursively if needed (async)
@@ -182,6 +183,25 @@ function detectUbuntuCodename() {
182
183
  }
183
184
  }
184
185
 
186
+ /**
187
+ * Normalize a ROS 2 node name by removing the leading slash if present.
188
+ *
189
+ * ROS 2 node names may be specified with or without a leading slash depending
190
+ * on the context. This utility ensures consistent representation without the
191
+ * leading slash, which is the standard format for most ROS 2 APIs.
192
+ *
193
+ * @param {string} nodeName - The node name to normalize
194
+ * @returns {string} The normalized node name without leading slash
195
+ *
196
+ * @example
197
+ * normalizeNodeName('my_node') // 'my_node'
198
+ * normalizeNodeName('/my_node') // 'my_node'
199
+ * normalizeNodeName('/ns/my_node') // 'ns/my_node'
200
+ */
201
+ function normalizeNodeName(nodeName) {
202
+ return nodeName.startsWith('/') ? nodeName.substring(1) : nodeName;
203
+ }
204
+
185
205
  /**
186
206
  * Check if two numbers are equal within a given tolerance.
187
207
  *
@@ -294,7 +314,12 @@ function compareVersions(version1, version2, operator) {
294
314
  case '!==':
295
315
  return cmp !== 0;
296
316
  default:
297
- throw new Error(`Invalid operator: ${operator}`);
317
+ throw new ValidationError(`Invalid operator: ${operator}`, {
318
+ code: 'INVALID_OPERATOR',
319
+ argumentName: 'operator',
320
+ providedValue: operator,
321
+ expectedType: "'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'",
322
+ });
298
323
  }
299
324
  }
300
325
 
@@ -302,6 +327,7 @@ module.exports = {
302
327
  // General utilities
303
328
  detectUbuntuCodename,
304
329
  isClose,
330
+ normalizeNodeName,
305
331
 
306
332
  // File system utilities (async)
307
333
  ensureDir,
package/lib/validator.js CHANGED
@@ -15,84 +15,139 @@
15
15
  'use strict';
16
16
 
17
17
  const rclnodejs = require('./native_loader.js');
18
+ const { TypeValidationError, NameValidationError } = require('./errors.js');
18
19
 
19
20
  /**
20
21
  * An object - Representing a validator in ROS.
21
22
  * @exports validator
22
23
  */
23
24
  let validator = {
24
- _createErrorFromValidation: function (result) {
25
- let err = new Error(result[0]);
26
- err.invalidIndex = result[1];
27
- return err;
25
+ _createErrorFromValidation: function (result, nameValue, nameType) {
26
+ return new NameValidationError(nameValue, nameType, result[0], result[1]);
28
27
  },
29
28
 
30
29
  /**
31
30
  * Validate a given topic or service name, and throw an error if invalid.
32
- * @param {string} topic - The name of topic/service. and it must be fully-qualified and already expanded.
33
- * @return {boolean} - True if it is valid.
31
+ * @param {string} topic - The name of topic/service. Must be fully-qualified and already expanded.
32
+ * @returns {true} Always returns true if valid.
33
+ * @throws {TypeValidationError} If topic is not a string.
34
+ * @throws {NameValidationError} If the topic name is invalid.
34
35
  */
35
36
  validateFullTopicName(topic) {
36
37
  if (typeof topic !== 'string') {
37
- throw new TypeError('Invalid argument');
38
+ throw new TypeValidationError('topic', topic, 'string');
38
39
  }
39
40
 
40
41
  let result = rclnodejs.validateFullTopicName(topic);
41
42
  if (result === null) {
42
43
  return true;
43
44
  }
44
- throw this._createErrorFromValidation(result);
45
+ throw this._createErrorFromValidation(result, topic, 'topic');
46
+ },
47
+
48
+ /**
49
+ * Check if a fully-qualified topic name is valid without throwing.
50
+ * @param {string} topic - The name of topic/service. Must be fully-qualified and already expanded.
51
+ * @returns {boolean} True if valid, false otherwise.
52
+ */
53
+ isValidFullTopicName(topic) {
54
+ if (typeof topic !== 'string') {
55
+ return false;
56
+ }
57
+ return rclnodejs.validateFullTopicName(topic) === null;
45
58
  },
46
59
 
47
60
  /**
48
61
  * Validate a given node name, and throw an error if invalid.
49
62
  * @param {string} name - The name of node.
50
- * @return {boolean} - True if it is valid.
63
+ * @returns {true} Always returns true if valid.
64
+ * @throws {TypeValidationError} If name is not a string.
65
+ * @throws {NameValidationError} If the node name is invalid.
51
66
  */
52
67
  validateNodeName(name) {
53
68
  if (typeof name !== 'string') {
54
- throw new TypeError('Invalid argument');
69
+ throw new TypeValidationError('name', name, 'string');
55
70
  }
56
71
 
57
72
  let result = rclnodejs.validateNodeName(name);
58
73
  if (result === null) {
59
74
  return true;
60
75
  }
61
- throw this._createErrorFromValidation(result);
76
+ throw this._createErrorFromValidation(result, name, 'node');
77
+ },
78
+
79
+ /**
80
+ * Check if a node name is valid without throwing.
81
+ * @param {string} name - The name of node.
82
+ * @returns {boolean} True if valid, false otherwise.
83
+ */
84
+ isValidNodeName(name) {
85
+ if (typeof name !== 'string') {
86
+ return false;
87
+ }
88
+ return rclnodejs.validateNodeName(name) === null;
62
89
  },
63
90
 
64
91
  /**
65
92
  * Validate a given topic or service name, and throw an error if invalid.
66
- * @param {string} topic - The name of topic/service and does not have to be fully-qualified and is not expanded.
67
- * @return {boolean} - True if it is valid.
93
+ * @param {string} topic - The name of topic/service. Does not have to be fully-qualified.
94
+ * @returns {true} Always returns true if valid.
95
+ * @throws {TypeValidationError} If topic is not a string.
96
+ * @throws {NameValidationError} If the topic name is invalid.
68
97
  */
69
98
  validateTopicName(topic) {
70
99
  if (typeof topic !== 'string') {
71
- throw new TypeError('Invalid argument');
100
+ throw new TypeValidationError('topic', topic, 'string');
72
101
  }
73
102
 
74
103
  let result = rclnodejs.validateTopicName(topic);
75
104
  if (result === null) {
76
105
  return true;
77
106
  }
78
- throw this._createErrorFromValidation(result);
107
+ throw this._createErrorFromValidation(result, topic, 'topic');
108
+ },
109
+
110
+ /**
111
+ * Check if a topic name is valid without throwing.
112
+ * @param {string} topic - The name of topic/service. Does not have to be fully-qualified.
113
+ * @returns {boolean} True if valid, false otherwise.
114
+ */
115
+ isValidTopicName(topic) {
116
+ if (typeof topic !== 'string') {
117
+ return false;
118
+ }
119
+ return rclnodejs.validateTopicName(topic) === null;
79
120
  },
80
121
 
81
122
  /**
82
123
  * Validate a given namespace, and throw an error if invalid.
83
- * @param {string} namespace - The namespace to be validated
84
- * @return {boolean} - True if it is valid.
124
+ * @param {string} namespace - The namespace to be validated.
125
+ * @returns {true} Always returns true if valid.
126
+ * @throws {TypeValidationError} If namespace is not a string.
127
+ * @throws {NameValidationError} If the namespace is invalid.
85
128
  */
86
129
  validateNamespace(namespace) {
87
130
  if (typeof namespace !== 'string') {
88
- throw new TypeError('Invalid argument');
131
+ throw new TypeValidationError('namespace', namespace, 'string');
89
132
  }
90
133
 
91
134
  let result = rclnodejs.validateNamespace(namespace);
92
135
  if (result === null) {
93
136
  return true;
94
137
  }
95
- throw this._createErrorFromValidation(result);
138
+ throw this._createErrorFromValidation(result, namespace, 'namespace');
139
+ },
140
+
141
+ /**
142
+ * Check if a namespace is valid without throwing.
143
+ * @param {string} namespace - The namespace to be validated.
144
+ * @returns {boolean} True if valid, false otherwise.
145
+ */
146
+ isValidNamespace(namespace) {
147
+ if (typeof namespace !== 'string') {
148
+ return false;
149
+ }
150
+ return rclnodejs.validateNamespace(namespace) === null;
96
151
  },
97
152
  };
98
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.36.0",
52
- "@types/node": "^24.5.2",
52
+ "@types/node": "^25.0.2",
53
53
  "@typescript-eslint/eslint-plugin": "^8.18.0",
54
54
  "@typescript-eslint/parser": "^8.18.0",
55
55
  "clang-format": "^1.8.0",
@@ -64,6 +64,7 @@
64
64
  "jsdoc": "^4.0.4",
65
65
  "lint-staged": "^16.2.0",
66
66
  "mocha": "^11.0.2",
67
+ "node-gyp": "^12.1.0",
67
68
  "nyc": "^17.1.0",
68
69
  "prebuildify": "^6.0.1",
69
70
  "rimraf": "^6.0.1",
@@ -79,6 +80,7 @@
79
80
  "debug": "^4.4.0",
80
81
  "json-bigint": "^1.0.0",
81
82
  "node-addon-api": "^8.3.1",
83
+ "rxjs": "^7.8.1",
82
84
  "walk": "^2.3.15"
83
85
  },
84
86
  "husky": {