rclnodejs 1.8.1 → 1.8.3

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 (50) hide show
  1. package/index.js +33 -23
  2. package/lib/action/client.js +6 -0
  3. package/lib/action/server.js +1 -3
  4. package/lib/distro.js +2 -1
  5. package/lib/lifecycle.js +2 -1
  6. package/lib/lifecycle_publisher.js +2 -2
  7. package/lib/node.js +37 -11
  8. package/lib/parameter.js +6 -10
  9. package/lib/service.js +8 -4
  10. package/lib/time_source.js +3 -20
  11. package/package.json +4 -4
  12. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  13. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  14. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  15. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  16. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  17. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  18. package/rosidl_gen/generate_worker.js +3 -13
  19. package/rosidl_gen/idl_generator.js +210 -0
  20. package/rosidl_gen/index.js +3 -12
  21. package/rosidl_gen/packages.js +7 -4
  22. package/rosidl_gen/primitive_types.js +2 -2
  23. package/rosidl_parser/idl_parser.py +437 -0
  24. package/rosidl_parser/parser.py +2 -4
  25. package/rosidl_parser/rosidl_parser.js +27 -0
  26. package/src/executor.cpp +1 -0
  27. package/src/macros.h +2 -2
  28. package/src/rcl_action_client_bindings.cpp +18 -11
  29. package/src/rcl_action_server_bindings.cpp +24 -13
  30. package/src/rcl_bindings.cpp +1 -1
  31. package/src/rcl_client_bindings.cpp +13 -5
  32. package/src/rcl_context_bindings.cpp +7 -8
  33. package/src/rcl_guard_condition_bindings.cpp +12 -3
  34. package/src/rcl_lifecycle_bindings.cpp +53 -11
  35. package/src/rcl_node_bindings.cpp +11 -4
  36. package/src/rcl_publisher_bindings.cpp +12 -3
  37. package/src/rcl_service_bindings.cpp +12 -3
  38. package/src/rcl_subscription_bindings.cpp +24 -21
  39. package/src/rcl_timer_bindings.cpp +24 -9
  40. package/src/rcl_type_description_service_bindings.cpp +9 -1
  41. package/test_data_integrity.js +108 -0
  42. package/test_repro_exact.js +57 -0
  43. package/test_repro_hz.js +86 -0
  44. package/test_repro_pub.js +36 -0
  45. package/test_repro_stress.js +83 -0
  46. package/test_repro_sub.js +64 -0
  47. package/test_xproc_data.js +64 -0
  48. package/rosidl_convertor/README.md +0 -298
  49. package/rosidl_convertor/idl_convertor.js +0 -50
  50. package/rosidl_convertor/idl_convertor.py +0 -1250
@@ -52,7 +52,12 @@ function getSubFolder(filePath, amentExecuted) {
52
52
  }
53
53
 
54
54
  if (amentExecuted) {
55
- return filePath.match(/\w+\/share\/\w+\/(\w+)\//)[1];
55
+ const match = filePath.match(/\w+\/share\/\w+\/([\w-]+)\//);
56
+ if (match) {
57
+ // Handle non-standard subfolder names (e.g., msg-common, msg-ros2)
58
+ // by extracting only the base interface type before any hyphen.
59
+ return match[1].split('-')[0];
60
+ }
56
61
  }
57
62
  // If the |amentExecuted| equals to false, the file's extension will be assigned as
58
63
  // the name of sub folder.
@@ -145,9 +150,7 @@ async function generateMsgForSrv(filePath, interfaceInfo, pkgMap) {
145
150
  const arr = data.split(/-{3,}/);
146
151
  if (arr.length == 2) {
147
152
  const packagePath = path.join(serviceMsgPath, interfaceInfo.pkgName);
148
- if (!fs.existsSync(packagePath)) {
149
- fs.mkdirSync(packagePath);
150
- }
153
+ fs.mkdirSync(packagePath, { recursive: true });
151
154
 
152
155
  await fsp.writeFile(path.join(packagePath, requestMsgName), arr[0]);
153
156
  await fsp.writeFile(path.join(packagePath, responseMsgName), arr[1]);
@@ -26,14 +26,14 @@ const StringRefStruct = StructType({
26
26
 
27
27
  function initString(str, own = false) {
28
28
  if (own) {
29
- if ((!str) instanceof Buffer) {
29
+ if (!(str instanceof Buffer)) {
30
30
  throw new TypeError(
31
31
  'Invalid argument: should provide a Node Buffer to bindingsStringInit()'
32
32
  );
33
33
  }
34
34
  rclnodejs.initString(str);
35
35
  } else {
36
- if ((!str) instanceof StringRefStruct) {
36
+ if (!(str instanceof StringRefStruct)) {
37
37
  throw new TypeError(
38
38
  'Invalid argument: should provide a type of StringRefStruct'
39
39
  );
@@ -0,0 +1,437 @@
1
+ # Copyright (c) 2025, The Robot Web Tools Contributors
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
+ """
16
+ IDL file parser that uses rosidl_parser to directly parse .idl files and
17
+ produce the same JSON spec format as parser.py does for .msg files.
18
+
19
+ This eliminates the need for the intermediate .idl -> .msg conversion step.
20
+ """
21
+
22
+ import sys
23
+ import json
24
+
25
+ from rosidl_parser.parser import parse_idl_string
26
+ from rosidl_parser.definition import (
27
+ AbstractNestedType,
28
+ Action,
29
+ Array,
30
+ BasicType,
31
+ BoundedSequence,
32
+ BoundedString,
33
+ BoundedWString,
34
+ Message,
35
+ NamedType,
36
+ NamespacedType,
37
+ Service,
38
+ UnboundedSequence,
39
+ UnboundedString,
40
+ UnboundedWString,
41
+ )
42
+
43
+ # Mapping from IDL basic type names to ROS2 message type names
44
+ IDL_TYPE_TO_ROS2 = {
45
+ 'boolean': 'bool',
46
+ 'octet': 'byte',
47
+ 'char': 'char',
48
+ 'wchar': 'wchar',
49
+ 'int8': 'int8',
50
+ 'uint8': 'uint8',
51
+ 'int16': 'int16',
52
+ 'uint16': 'uint16',
53
+ 'int32': 'int32',
54
+ 'uint32': 'uint32',
55
+ 'int64': 'int64',
56
+ 'uint64': 'uint64',
57
+ 'float': 'float32',
58
+ 'double': 'float64',
59
+ 'long double': 'float64',
60
+ 'short': 'int16',
61
+ 'long': 'int32',
62
+ 'long long': 'int64',
63
+ 'unsigned short': 'uint16',
64
+ 'unsigned long': 'uint32',
65
+ 'unsigned long long': 'uint64',
66
+ }
67
+
68
+
69
+ def _map_basic_type(typename):
70
+ """Map an IDL basic type name to its ROS2 equivalent."""
71
+ return IDL_TYPE_TO_ROS2.get(typename, typename)
72
+
73
+
74
+ def _get_nestable_type_info(type_obj):
75
+ """
76
+ Extract type info from a nestable type (the inner type for arrays/sequences,
77
+ or the type itself for non-nested types).
78
+
79
+ Returns a dict with: pkgName, type, stringUpperBound, isPrimitiveType
80
+ """
81
+ if isinstance(type_obj, BasicType):
82
+ ros2_type = _map_basic_type(type_obj.typename)
83
+ return {
84
+ 'pkgName': None,
85
+ 'type': ros2_type,
86
+ 'stringUpperBound': None,
87
+ 'isPrimitiveType': True,
88
+ }
89
+ elif isinstance(type_obj, (UnboundedString, BoundedString)):
90
+ string_upper_bound = None
91
+ if isinstance(type_obj, BoundedString):
92
+ string_upper_bound = type_obj.maximum_size
93
+ return {
94
+ 'pkgName': None,
95
+ 'type': 'string',
96
+ 'stringUpperBound': string_upper_bound,
97
+ 'isPrimitiveType': True,
98
+ }
99
+ elif isinstance(type_obj, (UnboundedWString, BoundedWString)):
100
+ string_upper_bound = None
101
+ if isinstance(type_obj, BoundedWString):
102
+ string_upper_bound = type_obj.maximum_size
103
+ return {
104
+ 'pkgName': None,
105
+ 'type': 'wstring',
106
+ 'stringUpperBound': string_upper_bound,
107
+ 'isPrimitiveType': True,
108
+ }
109
+ elif isinstance(type_obj, NamespacedType):
110
+ # Convert Token objects to plain strings if needed
111
+ namespaces = [str(ns) for ns in type_obj.namespaces]
112
+ pkg_name = namespaces[0] if namespaces else None
113
+ return {
114
+ 'pkgName': pkg_name,
115
+ 'type': str(type_obj.name),
116
+ 'stringUpperBound': None,
117
+ 'isPrimitiveType': False,
118
+ }
119
+ elif isinstance(type_obj, NamedType):
120
+ # NamedType is typically resolved by typedefs, but handle it just in case
121
+ return {
122
+ 'pkgName': None,
123
+ 'type': str(type_obj.name),
124
+ 'stringUpperBound': None,
125
+ 'isPrimitiveType': False,
126
+ }
127
+ else:
128
+ raise ValueError(f'Unexpected nestable type: {type(type_obj).__name__}')
129
+
130
+
131
+ def _get_type_json(member_type):
132
+ """
133
+ Convert a rosidl_parser type object to the JSON format expected by
134
+ the rclnodejs templates.
135
+
136
+ Returns a dict matching the spec.fields[].type format:
137
+ {isArray, arraySize, isUpperBound, isDynamicArray, isFixedSizeArray,
138
+ pkgName, type, stringUpperBound, isPrimitiveType}
139
+ """
140
+ if isinstance(member_type, Array):
141
+ # Fixed-size array: e.g., float64[9]
142
+ inner_info = _get_nestable_type_info(member_type.value_type)
143
+ return {
144
+ 'isArray': True,
145
+ 'arraySize': member_type.size,
146
+ 'isUpperBound': False,
147
+ 'isDynamicArray': False,
148
+ 'isFixedSizeArray': True,
149
+ **inner_info,
150
+ }
151
+ elif isinstance(member_type, BoundedSequence):
152
+ # Bounded dynamic array: e.g., sequence<float64, 3>
153
+ inner_info = _get_nestable_type_info(member_type.value_type)
154
+ return {
155
+ 'isArray': True,
156
+ 'arraySize': member_type.maximum_size,
157
+ 'isUpperBound': True,
158
+ 'isDynamicArray': True,
159
+ 'isFixedSizeArray': False,
160
+ **inner_info,
161
+ }
162
+ elif isinstance(member_type, UnboundedSequence):
163
+ # Unbounded dynamic array: e.g., sequence<float64>
164
+ inner_info = _get_nestable_type_info(member_type.value_type)
165
+ return {
166
+ 'isArray': True,
167
+ 'arraySize': None,
168
+ 'isUpperBound': False,
169
+ 'isDynamicArray': True,
170
+ 'isFixedSizeArray': False,
171
+ **inner_info,
172
+ }
173
+ else:
174
+ # Non-array/sequence type
175
+ inner_info = _get_nestable_type_info(member_type)
176
+ return {
177
+ 'isArray': False,
178
+ 'arraySize': None,
179
+ 'isUpperBound': False,
180
+ 'isDynamicArray': False,
181
+ 'isFixedSizeArray': False,
182
+ **inner_info,
183
+ }
184
+
185
+
186
+ def _get_default_value(member):
187
+ """Extract default value from member annotations.
188
+
189
+ rosidl_parser returns array/sequence defaults as tuple-like strings,
190
+ e.g., "(0, 127, -128)" or "('', 'max value', 'min value')".
191
+ The .msg parser returns these as Python lists [0, 127, -128].
192
+ We need to convert the tuple-strings to proper lists.
193
+ """
194
+ try:
195
+ default_annotation = member.get_annotation_value('default')
196
+ if isinstance(default_annotation, dict) and 'value' in default_annotation:
197
+ value = default_annotation['value']
198
+ else:
199
+ value = default_annotation
200
+
201
+ # If the value is a string that looks like a tuple "(a, b, c)",
202
+ # parse it into a Python list
203
+ if isinstance(value, str) and value.startswith('(') and value.endswith(')'):
204
+ value = _parse_tuple_string(value, member)
205
+
206
+ return value
207
+ except ValueError:
208
+ return None
209
+
210
+
211
+ def _parse_tuple_string(s, member):
212
+ """
213
+ Parse a tuple-like string from rosidl_parser into a proper Python list.
214
+
215
+ Handles formats like:
216
+ "(0, 127, -128)" -> [0, 127, -128]
217
+ "(1.125, 0.0, -1.125)" -> [1.125, 0.0, -1.125]
218
+ "(False, True, False)" -> [False, True, False]
219
+ "('', 'max value', 'min value')" -> ['', 'max value', 'min value']
220
+ """
221
+ inner = s[1:-1] # Strip outer parentheses
222
+
223
+ # Determine the element type from the member's type
224
+ member_type = member.type
225
+ if isinstance(member_type, AbstractNestedType):
226
+ element_type = member_type.value_type
227
+ else:
228
+ element_type = member_type
229
+
230
+ # Parse string arrays specially (may contain commas in values)
231
+ if isinstance(element_type, (UnboundedString, BoundedString,
232
+ UnboundedWString, BoundedWString)):
233
+ return _parse_string_tuple(inner)
234
+
235
+ # For boolean arrays
236
+ if isinstance(element_type, BasicType) and element_type.typename == 'boolean':
237
+ parts = [p.strip() for p in inner.split(',')]
238
+ return [p == 'True' for p in parts]
239
+
240
+ # For numeric arrays
241
+ parts = [p.strip() for p in inner.split(',')]
242
+ result = []
243
+ for part in parts:
244
+ if not part:
245
+ continue
246
+ # Try int first, then float
247
+ try:
248
+ result.append(int(part))
249
+ except ValueError:
250
+ try:
251
+ result.append(float(part))
252
+ except ValueError:
253
+ result.append(part)
254
+ return result
255
+
256
+
257
+ def _parse_string_tuple(inner):
258
+ """Parse the inside of a tuple containing quoted strings.
259
+
260
+ e.g., "'', 'max value', 'min value'" -> ['', 'max value', 'min value']
261
+ """
262
+ result = []
263
+ i = 0
264
+ while i < len(inner):
265
+ if inner[i] == "'":
266
+ # Find matching closing quote
267
+ j = i + 1
268
+ while j < len(inner):
269
+ if inner[j] == "'":
270
+ result.append(inner[i + 1:j])
271
+ i = j + 1
272
+ break
273
+ j += 1
274
+ else:
275
+ break
276
+ elif inner[i] == '"':
277
+ j = i + 1
278
+ while j < len(inner):
279
+ if inner[j] == '"':
280
+ result.append(inner[i + 1:j])
281
+ i = j + 1
282
+ break
283
+ j += 1
284
+ else:
285
+ break
286
+ else:
287
+ i += 1
288
+ return result
289
+
290
+
291
+ def _get_constant_type_str(constant):
292
+ """Get the ROS2 type string for a constant."""
293
+ if isinstance(constant.type, BasicType):
294
+ return _map_basic_type(constant.type.typename)
295
+ elif isinstance(constant.type, (UnboundedString, BoundedString)):
296
+ return 'string'
297
+ elif isinstance(constant.type, (UnboundedWString, BoundedWString)):
298
+ return 'wstring'
299
+ return str(constant.type)
300
+
301
+
302
+ def get_json_from_message(msg, pkg_name=None):
303
+ """
304
+ Convert a rosidl_parser Message object to JSON spec matching
305
+ what parser.py produces for .msg files.
306
+ """
307
+ namespaced_type = msg.structure.namespaced_type
308
+ namespaces = [str(ns) for ns in namespaced_type.namespaces]
309
+ msg_name = str(namespaced_type.name)
310
+
311
+ if pkg_name is None:
312
+ pkg_name = namespaces[0] if namespaces else ''
313
+
314
+ # Build fields
315
+ fields = []
316
+ for member in msg.structure.members:
317
+ field = {
318
+ 'name': member.name,
319
+ 'type': _get_type_json(member.type),
320
+ 'default_value': _get_default_value(member),
321
+ }
322
+ fields.append(field)
323
+
324
+ # Build constants
325
+ constants = []
326
+ for constant in msg.constants:
327
+ constants.append({
328
+ 'type': _get_constant_type_str(constant),
329
+ 'name': constant.name,
330
+ 'value': constant.value,
331
+ })
332
+
333
+ return {
334
+ 'constants': constants,
335
+ 'fields': fields,
336
+ 'baseType': {
337
+ 'pkgName': pkg_name,
338
+ 'type': msg_name,
339
+ 'stringUpperBound': None,
340
+ 'isPrimitiveType': False,
341
+ },
342
+ 'msgName': msg_name,
343
+ }
344
+
345
+
346
+ def get_json_from_service(srv):
347
+ """
348
+ Convert a rosidl_parser Service object to JSON spec matching
349
+ what parser.py produces for .srv files.
350
+ """
351
+ namespaces = [str(ns) for ns in srv.namespaced_type.namespaces]
352
+ pkg_name = namespaces[0] if namespaces else ''
353
+ srv_name = str(srv.namespaced_type.name)
354
+
355
+ return {
356
+ 'pkgName': pkg_name,
357
+ 'srvName': srv_name,
358
+ 'request': get_json_from_message(srv.request_message, pkg_name),
359
+ 'response': get_json_from_message(srv.response_message, pkg_name),
360
+ }
361
+
362
+
363
+ def get_json_from_action(action):
364
+ """
365
+ Convert a rosidl_parser Action object to JSON spec matching
366
+ what parser.py produces for .action files.
367
+ """
368
+ namespaces = [str(ns) for ns in action.namespaced_type.namespaces]
369
+ pkg_name = namespaces[0] if namespaces else ''
370
+ action_name = str(action.namespaced_type.name)
371
+
372
+ return {
373
+ 'pkgName': pkg_name,
374
+ 'actionName': action_name,
375
+ 'goal': get_json_from_message(action.goal, pkg_name),
376
+ 'result': get_json_from_message(action.result, pkg_name),
377
+ 'feedback': get_json_from_message(action.feedback, pkg_name),
378
+ }
379
+
380
+
381
+ def parse_idl_file(idl_file_path):
382
+ """
383
+ Parse an .idl file and return a JSON object in the same format
384
+ as parser.py produces.
385
+
386
+ The return value depends on the interface type:
387
+ - Message: same format as parse_message_file
388
+ - Service: same format as parse_service_file
389
+ - Action: same format as parse_action_file
390
+
391
+ Returns a dict with 'type' indicating the interface kind and 'spec'
392
+ containing the JSON spec.
393
+ """
394
+ with open(idl_file_path, 'r') as f:
395
+ content_str = f.read()
396
+
397
+ content = parse_idl_string(content_str)
398
+
399
+ # Find the main element (Message, Service, or Action)
400
+ messages = content.get_elements_of_type(Message)
401
+ services = content.get_elements_of_type(Service)
402
+ actions = content.get_elements_of_type(Action)
403
+
404
+ if actions:
405
+ action = actions[0]
406
+ return {
407
+ 'type': 'action',
408
+ 'spec': get_json_from_action(action),
409
+ }
410
+ elif services:
411
+ service = services[0]
412
+ return {
413
+ 'type': 'service',
414
+ 'spec': get_json_from_service(service),
415
+ }
416
+ elif messages:
417
+ message = messages[0]
418
+ return {
419
+ 'type': 'message',
420
+ 'spec': get_json_from_message(message),
421
+ }
422
+ else:
423
+ raise ValueError(f'No Message, Service, or Action found in {idl_file_path}')
424
+
425
+
426
+ if __name__ == '__main__':
427
+ if len(sys.argv) < 2:
428
+ print('Usage: idl_parser.py <idl_file_path>', file=sys.stderr)
429
+ sys.exit(1)
430
+
431
+ try:
432
+ result = parse_idl_file(sys.argv[1])
433
+ print(json.dumps(result))
434
+ sys.exit(0)
435
+ except Exception as e:
436
+ print(str(e), file=sys.stderr)
437
+ sys.exit(1)
@@ -15,7 +15,6 @@
15
15
  import sys
16
16
  import json
17
17
 
18
- import rosidl_parser
19
18
  from rosidl_adapter import parser
20
19
 
21
20
  def get_json_object_from_base_type_object(base_type_obj):
@@ -41,15 +40,14 @@ def get_json_object_from_msg_spec_object(msg_spec_object):
41
40
  for constant in msg_spec_object.constants:
42
41
  constants.append({'type': constant.type, 'name': constant.name, 'value': constant.value})
43
42
 
44
- msg_name = {'msgName': msg_spec_object.msg_name}
45
43
  json_obj = {'constants': constants, 'fields': fields, 'baseType': get_json_object_from_base_type_object(msg_spec_object.base_type),
46
44
  'msgName': msg_spec_object.msg_name}
47
45
 
48
46
  return json_obj
49
47
 
50
48
  if __name__ == '__main__':
51
- if len(sys.argv) < 3:
52
- print('Wrong number of argments')
49
+ if len(sys.argv) < 4:
50
+ print('Usage: {} <command> <packageName> <filePath>'.format(sys.argv[0]), file=sys.stderr)
53
51
  sys.exit(1)
54
52
  try:
55
53
  parser_method = getattr(parser, sys.argv[1])
@@ -41,6 +41,33 @@ const rosidlParser = {
41
41
  return this._parseFile('parse_action_file', packageName, filePath);
42
42
  },
43
43
 
44
+ /**
45
+ * Parse an .idl file directly using rosidl_parser (no .msg conversion needed).
46
+ * Returns an object with { type: 'message'|'service'|'action', spec: ... }
47
+ * where spec matches the same format as parseMessageFile/parseServiceFile/parseActionFile.
48
+ */
49
+ parseIdlFile(filePath) {
50
+ return new Promise((resolve, reject) => {
51
+ const args = [path.join(__dirname, 'idl_parser.py'), filePath];
52
+ const [pythonExecutableFile, pythonExecutableArgs] = pythonExecutable;
53
+ execFile(
54
+ pythonExecutableFile,
55
+ pythonExecutableArgs.concat(args),
56
+ (err, stdout, stderr) => {
57
+ if (err) {
58
+ reject(
59
+ new Error(
60
+ `There was an error parsing IDL file "${filePath}": "${err}"; stderr was: ${stderr}`
61
+ )
62
+ );
63
+ } else {
64
+ resolve(this._parseJSONObject(stdout));
65
+ }
66
+ }
67
+ );
68
+ });
69
+ },
70
+
44
71
  _parseJSONObject(str) {
45
72
  // For nodejs >= `contextSupportedVersion`, we leverage context parameter to
46
73
  // convert unsafe integer to string, otherwise, json-bigint is used.
package/src/executor.cpp CHANGED
@@ -110,6 +110,7 @@ void Executor::Stop() {
110
110
 
111
111
  if (uv_is_active(reinterpret_cast<uv_handle_t*>(async_))) {
112
112
  static bool handle_closed = false;
113
+ handle_closed = false;
113
114
  uv_close(reinterpret_cast<uv_handle_t*>(async_),
114
115
  [](uv_handle_t* async) -> void {
115
116
  // Important Notice:
package/src/macros.h CHANGED
@@ -22,8 +22,8 @@
22
22
  #define CHECK_OP_AND_THROW_ERROR_IF_NOT_TRUE(op, lhs, rhs, message) \
23
23
  { \
24
24
  if (lhs op rhs) { \
25
- rcl_reset_error(); \
26
25
  Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
26
+ rcl_reset_error(); \
27
27
  return env.Undefined(); \
28
28
  } \
29
29
  }
@@ -31,8 +31,8 @@
31
31
  #define CHECK_OP_AND_THROW_ERROR_IF_NOT_TRUE_NO_RETURN(op, lhs, rhs, message) \
32
32
  { \
33
33
  if (lhs op rhs) { \
34
- rcl_reset_error(); \
35
34
  Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
35
+ rcl_reset_error(); \
36
36
  } \
37
37
  }
38
38
 
@@ -70,10 +70,17 @@ Napi::Value ActionCreateClient(const Napi::CallbackInfo& info) {
70
70
  malloc(sizeof(rcl_action_client_t)));
71
71
  *action_client = rcl_action_get_zero_initialized_client();
72
72
 
73
- THROW_ERROR_IF_NOT_EQUAL(
74
- rcl_action_client_init(action_client, node, ts, action_name.c_str(),
75
- &action_client_ops),
76
- RCL_RET_OK, rcl_get_error_string().str);
73
+ {
74
+ rcl_ret_t ret = rcl_action_client_init(
75
+ action_client, node, ts, action_name.c_str(), &action_client_ops);
76
+ if (RCL_RET_OK != ret) {
77
+ std::string error_msg = rcl_get_error_string().str;
78
+ rcl_reset_error();
79
+ free(action_client);
80
+ Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
81
+ return env.Undefined();
82
+ }
83
+ }
77
84
  auto js_obj = RclHandle::NewInstance(
78
85
  env, action_client, node_handle, [node, env](void* ptr) {
79
86
  rcl_action_client_t* action_client =
@@ -125,7 +132,7 @@ Napi::Value ActionSendGoalRequest(const Napi::CallbackInfo& info) {
125
132
  rcl_action_send_goal_request(action_client, buffer, &sequence_number),
126
133
  RCL_RET_OK, rcl_get_error_string().str);
127
134
 
128
- return Napi::Number::New(env, static_cast<int32_t>(sequence_number));
135
+ return Napi::Number::New(env, static_cast<double>(sequence_number));
129
136
  }
130
137
 
131
138
  Napi::Value ActionSendResultRequest(const Napi::CallbackInfo& info) {
@@ -142,7 +149,7 @@ Napi::Value ActionSendResultRequest(const Napi::CallbackInfo& info) {
142
149
  rcl_action_send_result_request(action_client, buffer, &sequence_number),
143
150
  RCL_RET_OK, rcl_get_error_string().str);
144
151
 
145
- return Napi::Number::New(env, static_cast<int32_t>(sequence_number));
152
+ return Napi::Number::New(env, static_cast<double>(sequence_number));
146
153
  }
147
154
 
148
155
  Napi::Value ActionTakeFeedback(const Napi::CallbackInfo& info) {
@@ -156,9 +163,9 @@ Napi::Value ActionTakeFeedback(const Napi::CallbackInfo& info) {
156
163
 
157
164
  rcl_ret_t ret = rcl_action_take_feedback(action_client, buffer);
158
165
  if (ret != RCL_RET_OK && ret != RCL_RET_ACTION_CLIENT_TAKE_FAILED) {
159
- Napi::Error::New(env, rcl_get_error_string().str)
160
- .ThrowAsJavaScriptException();
166
+ std::string error_msg = rcl_get_error_string().str;
161
167
  rcl_reset_error();
168
+ Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
162
169
  return Napi::Boolean::New(env, false);
163
170
  }
164
171
 
@@ -179,9 +186,9 @@ Napi::Value ActionTakeStatus(const Napi::CallbackInfo& info) {
179
186
 
180
187
  rcl_ret_t ret = rcl_action_take_status(action_client, buffer);
181
188
  if (ret != RCL_RET_OK && ret != RCL_RET_ACTION_CLIENT_TAKE_FAILED) {
189
+ std::string error_msg = rcl_get_error_string().str;
182
190
  rcl_reset_error();
183
- Napi::Error::New(env, rcl_get_error_string().str)
184
- .ThrowAsJavaScriptException();
191
+ Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
185
192
  return Napi::Boolean::New(env, false);
186
193
  }
187
194
 
@@ -240,7 +247,7 @@ Napi::Value ActionSendCancelRequest(const Napi::CallbackInfo& info) {
240
247
  rcl_action_send_cancel_request(action_client, buffer, &sequence_number),
241
248
  RCL_RET_OK, rcl_get_error_string().str);
242
249
 
243
- return Napi::Number::New(env, static_cast<int32_t>(sequence_number));
250
+ return Napi::Number::New(env, static_cast<double>(sequence_number));
244
251
  }
245
252
 
246
253
  #if ROS_VERSION >= 2505 // ROS2 >= Kilted