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.
- package/index.js +79 -3
- package/lib/action/client.js +55 -9
- package/lib/action/deferred.js +8 -2
- package/lib/action/server.js +10 -1
- package/lib/action/uuid.js +4 -1
- package/lib/client.js +152 -3
- package/lib/clock.js +4 -1
- package/lib/context.js +12 -2
- package/lib/duration.js +37 -12
- package/lib/errors.js +571 -0
- package/lib/event_handler.js +21 -4
- package/lib/interface_loader.js +52 -12
- package/lib/lifecycle.js +8 -2
- package/lib/logging.js +12 -3
- package/lib/message_serialization.js +179 -0
- package/lib/native_loader.js +9 -4
- package/lib/node.js +283 -47
- package/lib/parameter.js +176 -45
- package/lib/parameter_client.js +506 -0
- package/lib/parameter_watcher.js +309 -0
- package/lib/qos.js +22 -5
- package/lib/rate.js +6 -1
- package/lib/serialization.js +7 -2
- package/lib/subscription.js +16 -1
- package/lib/time.js +136 -21
- package/lib/time_source.js +13 -4
- package/lib/utils.js +313 -0
- package/lib/validator.js +11 -12
- package/package.json +2 -7
- package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
- package/rosidl_convertor/idl_convertor.js +3 -2
- package/rosidl_gen/generate_worker.js +1 -1
- package/rosidl_gen/idl_generator.js +11 -24
- package/rosidl_gen/index.js +1 -1
- package/rosidl_gen/templates/action-template.js +68 -0
- package/rosidl_gen/templates/message-template.js +1113 -0
- package/rosidl_gen/templates/service-event-template.js +31 -0
- package/rosidl_gen/templates/service-template.js +44 -0
- package/rosidl_parser/rosidl_parser.js +2 -2
- package/third_party/ref-napi/lib/ref.js +0 -45
- package/types/base.d.ts +3 -0
- package/types/client.d.ts +36 -0
- package/types/errors.d.ts +447 -0
- package/types/index.d.ts +17 -0
- package/types/interfaces.d.ts +1910 -1
- package/types/node.d.ts +56 -1
- package/types/parameter_client.d.ts +252 -0
- package/types/parameter_watcher.d.ts +104 -0
- package/rosidl_gen/templates/CMakeLists.dot +0 -40
- package/rosidl_gen/templates/action.dot +0 -50
- package/rosidl_gen/templates/message.dot +0 -851
- package/rosidl_gen/templates/package.dot +0 -16
- package/rosidl_gen/templates/service.dot +0 -26
- package/rosidl_gen/templates/service_event.dot +0 -10
package/lib/interface_loader.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const generator = require('../rosidl_gen/index.js');
|
|
20
|
+
const { TypeValidationError, ValidationError } = require('./errors.js');
|
|
20
21
|
|
|
21
22
|
let interfaceLoader = {
|
|
22
23
|
loadInterfaceByObject(obj) {
|
|
@@ -36,8 +37,13 @@ let interfaceLoader = {
|
|
|
36
37
|
!obj.type ||
|
|
37
38
|
!obj.name
|
|
38
39
|
) {
|
|
39
|
-
throw new
|
|
40
|
-
'
|
|
40
|
+
throw new TypeValidationError(
|
|
41
|
+
'interfaceObject',
|
|
42
|
+
obj,
|
|
43
|
+
'object with package, type, and name properties',
|
|
44
|
+
{
|
|
45
|
+
entityType: 'interface loader',
|
|
46
|
+
}
|
|
41
47
|
);
|
|
42
48
|
}
|
|
43
49
|
return this.loadInterface(obj.package, obj.type, obj.name);
|
|
@@ -45,9 +51,9 @@ let interfaceLoader = {
|
|
|
45
51
|
|
|
46
52
|
loadInterfaceByString(name) {
|
|
47
53
|
if (typeof name !== 'string') {
|
|
48
|
-
throw new
|
|
49
|
-
'
|
|
50
|
-
);
|
|
54
|
+
throw new TypeValidationError('name', name, 'string', {
|
|
55
|
+
entityType: 'interface loader',
|
|
56
|
+
});
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
// TODO(Kenny): more checks of the string argument
|
|
@@ -64,8 +70,15 @@ let interfaceLoader = {
|
|
|
64
70
|
return this.loadInterfaceByPath(packagePath, interfaces);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
throw new
|
|
68
|
-
'A string argument in expected in "package/type/message" format'
|
|
73
|
+
throw new ValidationError(
|
|
74
|
+
'A string argument in expected in "package/type/message" format',
|
|
75
|
+
{
|
|
76
|
+
code: 'INVALID_INTERFACE_FORMAT',
|
|
77
|
+
argumentName: 'name',
|
|
78
|
+
providedValue: name,
|
|
79
|
+
expectedType: 'string in "package/type/message" format',
|
|
80
|
+
entityType: 'interface loader',
|
|
81
|
+
}
|
|
69
82
|
);
|
|
70
83
|
},
|
|
71
84
|
|
|
@@ -174,8 +187,18 @@ let interfaceLoader = {
|
|
|
174
187
|
}
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
|
-
throw new
|
|
178
|
-
`The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}
|
|
190
|
+
throw new ValidationError(
|
|
191
|
+
`The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`,
|
|
192
|
+
{
|
|
193
|
+
code: 'MESSAGE_NOT_FOUND',
|
|
194
|
+
entityType: 'interface loader',
|
|
195
|
+
details: {
|
|
196
|
+
packageName: packageName,
|
|
197
|
+
type: type,
|
|
198
|
+
messageName: messageName,
|
|
199
|
+
searchPath: generator.generatedRoot,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
179
202
|
);
|
|
180
203
|
},
|
|
181
204
|
|
|
@@ -187,7 +210,14 @@ let interfaceLoader = {
|
|
|
187
210
|
} else if (typeof type === 'string') {
|
|
188
211
|
return this.loadInterfaceByString(type);
|
|
189
212
|
}
|
|
190
|
-
throw new
|
|
213
|
+
throw new ValidationError(
|
|
214
|
+
`The message required does not exist: ${type}`,
|
|
215
|
+
{
|
|
216
|
+
code: 'MESSAGE_NOT_FOUND',
|
|
217
|
+
entityType: 'interface loader',
|
|
218
|
+
details: { type: type },
|
|
219
|
+
}
|
|
220
|
+
);
|
|
191
221
|
}
|
|
192
222
|
if (packageName && type && messageName) {
|
|
193
223
|
let filePath = path.join(
|
|
@@ -208,8 +238,18 @@ let interfaceLoader = {
|
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
240
|
// We cannot parse `packageName`, `type` and `messageName` from the string passed.
|
|
211
|
-
throw new
|
|
212
|
-
`The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}
|
|
241
|
+
throw new ValidationError(
|
|
242
|
+
`The message required does not exist: ${packageName}, ${type}, ${messageName} at ${generator.generatedRoot}`,
|
|
243
|
+
{
|
|
244
|
+
code: 'MESSAGE_NOT_FOUND',
|
|
245
|
+
entityType: 'interface loader',
|
|
246
|
+
details: {
|
|
247
|
+
packageName: packageName,
|
|
248
|
+
type: type,
|
|
249
|
+
messageName: messageName,
|
|
250
|
+
searchPath: generator.generatedRoot,
|
|
251
|
+
},
|
|
252
|
+
}
|
|
213
253
|
);
|
|
214
254
|
},
|
|
215
255
|
};
|
package/lib/lifecycle.js
CHANGED
|
@@ -21,6 +21,7 @@ const Context = require('./context.js');
|
|
|
21
21
|
const Node = require('./node.js');
|
|
22
22
|
const NodeOptions = require('./node_options.js');
|
|
23
23
|
const Service = require('./service.js');
|
|
24
|
+
const { ValidationError } = require('./errors.js');
|
|
24
25
|
|
|
25
26
|
const SHUTDOWN_TRANSITION_LABEL =
|
|
26
27
|
rclnodejs.getLifecycleShutdownTransitionLabel();
|
|
@@ -707,8 +708,13 @@ class LifecycleNode extends Node {
|
|
|
707
708
|
);
|
|
708
709
|
|
|
709
710
|
if (!newStateObj) {
|
|
710
|
-
throw new
|
|
711
|
-
`No transition available from state ${transitionIdOrLabel}
|
|
711
|
+
throw new ValidationError(
|
|
712
|
+
`No transition available from state ${transitionIdOrLabel}`,
|
|
713
|
+
{
|
|
714
|
+
code: 'INVALID_TRANSITION',
|
|
715
|
+
entityType: 'lifecycle node',
|
|
716
|
+
details: { transitionIdOrLabel: transitionIdOrLabel },
|
|
717
|
+
}
|
|
712
718
|
);
|
|
713
719
|
}
|
|
714
720
|
|
package/lib/logging.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const rclnodejs = require('./native_loader.js');
|
|
19
|
+
const { TypeValidationError } = require('./errors.js');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Enum for LoggingSeverity
|
|
@@ -98,7 +99,10 @@ class Logging {
|
|
|
98
99
|
*/
|
|
99
100
|
setLoggerLevel(level) {
|
|
100
101
|
if (typeof level !== 'number') {
|
|
101
|
-
throw new
|
|
102
|
+
throw new TypeValidationError('level', level, 'number', {
|
|
103
|
+
entityType: 'logger',
|
|
104
|
+
entityName: this._name,
|
|
105
|
+
});
|
|
102
106
|
}
|
|
103
107
|
rclnodejs.setLoggerLevel(this._name, level);
|
|
104
108
|
}
|
|
@@ -164,7 +168,10 @@ class Logging {
|
|
|
164
168
|
|
|
165
169
|
_log(message, severity) {
|
|
166
170
|
if (typeof message !== 'string') {
|
|
167
|
-
throw new
|
|
171
|
+
throw new TypeValidationError('message', message, 'string', {
|
|
172
|
+
entityType: 'logger',
|
|
173
|
+
entityName: this._name,
|
|
174
|
+
});
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
let caller = new Caller();
|
|
@@ -204,7 +211,9 @@ class Logging {
|
|
|
204
211
|
*/
|
|
205
212
|
static getLogger(name) {
|
|
206
213
|
if (typeof name !== 'string') {
|
|
207
|
-
throw new
|
|
214
|
+
throw new TypeValidationError('name', name, 'string', {
|
|
215
|
+
entityType: 'logger',
|
|
216
|
+
});
|
|
208
217
|
}
|
|
209
218
|
return new Logging(name);
|
|
210
219
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
const { ValidationError } = require('./errors.js');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a value is a TypedArray
|
|
21
|
+
* @param {*} value - The value to check
|
|
22
|
+
* @returns {boolean} True if the value is a TypedArray
|
|
23
|
+
*/
|
|
24
|
+
function isTypedArray(value) {
|
|
25
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if a value needs JSON conversion (BigInt, functions, etc.)
|
|
30
|
+
* @param {*} value - The value to check
|
|
31
|
+
* @returns {boolean} True if the value needs special JSON handling
|
|
32
|
+
*/
|
|
33
|
+
function needsJSONConversion(value) {
|
|
34
|
+
return (
|
|
35
|
+
typeof value === 'bigint' ||
|
|
36
|
+
typeof value === 'function' ||
|
|
37
|
+
typeof value === 'undefined' ||
|
|
38
|
+
value === Infinity ||
|
|
39
|
+
value === -Infinity ||
|
|
40
|
+
(typeof value === 'number' && isNaN(value))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert a message to plain arrays (TypedArray -> regular Array)
|
|
46
|
+
* @param {*} obj - The object to convert
|
|
47
|
+
* @returns {*} The converted object with plain arrays
|
|
48
|
+
*/
|
|
49
|
+
function toPlainArrays(obj) {
|
|
50
|
+
if (obj === null || obj === undefined) {
|
|
51
|
+
return obj;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isTypedArray(obj)) {
|
|
55
|
+
return Array.from(obj);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (Array.isArray(obj)) {
|
|
59
|
+
return obj.map((item) => toPlainArrays(item));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const key in obj) {
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
66
|
+
result[key] = toPlainArrays(obj[key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return obj;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert a message to be fully JSON-safe
|
|
77
|
+
* @param {*} obj - The object to convert
|
|
78
|
+
* @returns {*} The JSON-safe converted object
|
|
79
|
+
*/
|
|
80
|
+
function toJSONSafe(obj) {
|
|
81
|
+
if (obj === null || obj === undefined) {
|
|
82
|
+
return obj;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isTypedArray(obj)) {
|
|
86
|
+
return Array.from(obj).map((item) => toJSONSafe(item));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (needsJSONConversion(obj)) {
|
|
90
|
+
if (typeof obj === 'bigint') {
|
|
91
|
+
// Convert BigInt to string with 'n' suffix to indicate it was a BigInt
|
|
92
|
+
return obj.toString() + 'n';
|
|
93
|
+
}
|
|
94
|
+
if (obj === Infinity) return 'Infinity';
|
|
95
|
+
if (obj === -Infinity) return '-Infinity';
|
|
96
|
+
if (typeof obj === 'number' && isNaN(obj)) return 'NaN';
|
|
97
|
+
if (typeof obj === 'undefined') return null;
|
|
98
|
+
if (typeof obj === 'function') return '[Function]';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(obj)) {
|
|
102
|
+
return obj.map((item) => toJSONSafe(item));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
106
|
+
const result = {};
|
|
107
|
+
for (const key in obj) {
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
109
|
+
result[key] = toJSONSafe(obj[key]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return obj;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Convert a message to a JSON string
|
|
120
|
+
* @param {*} obj - The object to convert
|
|
121
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
122
|
+
* @returns {string} The JSON string representation
|
|
123
|
+
*/
|
|
124
|
+
function toJSONString(obj, space) {
|
|
125
|
+
const jsonSafeObj = toJSONSafe(obj);
|
|
126
|
+
return JSON.stringify(jsonSafeObj, null, space);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Apply serialization mode conversion to a message object
|
|
131
|
+
* @param {*} message - The message object to convert
|
|
132
|
+
* @param {string} serializationMode - The serialization mode ('default', 'plain', 'json')
|
|
133
|
+
* @returns {*} The converted message
|
|
134
|
+
*/
|
|
135
|
+
function applySerializationMode(message, serializationMode) {
|
|
136
|
+
switch (serializationMode) {
|
|
137
|
+
case 'default':
|
|
138
|
+
// No conversion needed - use native rclnodejs behavior
|
|
139
|
+
return message;
|
|
140
|
+
|
|
141
|
+
case 'plain':
|
|
142
|
+
// Convert TypedArrays to regular arrays
|
|
143
|
+
return toPlainArrays(message);
|
|
144
|
+
|
|
145
|
+
case 'json':
|
|
146
|
+
// Convert to fully JSON-safe format
|
|
147
|
+
return toJSONSafe(message);
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
throw new ValidationError(
|
|
151
|
+
`Invalid serializationMode: ${serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
|
|
152
|
+
{
|
|
153
|
+
code: 'INVALID_SERIALIZATION_MODE',
|
|
154
|
+
argumentName: 'serializationMode',
|
|
155
|
+
providedValue: serializationMode,
|
|
156
|
+
expectedType: "'default' | 'plain' | 'json'",
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate serialization mode
|
|
164
|
+
* @param {string} mode - The serialization mode to validate
|
|
165
|
+
* @returns {boolean} True if valid
|
|
166
|
+
*/
|
|
167
|
+
function isValidSerializationMode(mode) {
|
|
168
|
+
return ['default', 'plain', 'json'].includes(mode);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
isTypedArray,
|
|
173
|
+
needsJSONConversion,
|
|
174
|
+
toPlainArrays,
|
|
175
|
+
toJSONSafe,
|
|
176
|
+
toJSONString,
|
|
177
|
+
applySerializationMode,
|
|
178
|
+
isValidSerializationMode,
|
|
179
|
+
};
|
package/lib/native_loader.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { execSync } = require('child_process');
|
|
20
|
+
const { NativeError } = require('./errors.js');
|
|
20
21
|
const bindings = require('bindings');
|
|
21
22
|
const debug = require('debug')('rclnodejs');
|
|
22
23
|
const { detectUbuntuCodename } = require('./utils');
|
|
@@ -100,8 +101,10 @@ function loadNativeAddon() {
|
|
|
100
101
|
return nativeModule;
|
|
101
102
|
} catch (compileError) {
|
|
102
103
|
debug('Forced compilation failed:', compileError.message);
|
|
103
|
-
throw new
|
|
104
|
-
`Failed to force build rclnodejs from source: ${compileError.message}
|
|
104
|
+
throw new NativeError(
|
|
105
|
+
`Failed to force build rclnodejs from source: ${compileError.message}`,
|
|
106
|
+
'Forced compilation',
|
|
107
|
+
{ cause: compileError }
|
|
105
108
|
);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
@@ -163,8 +166,10 @@ function loadNativeAddon() {
|
|
|
163
166
|
return nativeModule;
|
|
164
167
|
} catch (compileError) {
|
|
165
168
|
debug('Compilation failed:', compileError.message);
|
|
166
|
-
throw new
|
|
167
|
-
`Failed to build rclnodejs from source: ${compileError.message}
|
|
169
|
+
throw new NativeError(
|
|
170
|
+
`Failed to build rclnodejs from source: ${compileError.message}`,
|
|
171
|
+
'Compilation',
|
|
172
|
+
{ cause: compileError }
|
|
168
173
|
);
|
|
169
174
|
}
|
|
170
175
|
}
|