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.
- package/binding.gyp +2 -0
- package/index.js +152 -0
- package/lib/action/client.js +109 -10
- 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 +218 -4
- package/lib/clock.js +182 -1
- package/lib/clock_change.js +49 -0
- package/lib/clock_event.js +88 -0
- package/lib/context.js +12 -2
- package/lib/duration.js +37 -12
- package/lib/errors.js +621 -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 +90 -3
- package/lib/message_introspector.js +123 -0
- package/lib/message_serialization.js +10 -2
- package/lib/message_validation.js +512 -0
- package/lib/native_loader.js +9 -4
- package/lib/node.js +403 -50
- package/lib/node_options.js +40 -1
- package/lib/observable_subscription.js +105 -0
- package/lib/parameter.js +172 -35
- package/lib/parameter_client.js +506 -0
- package/lib/parameter_watcher.js +309 -0
- package/lib/publisher.js +56 -1
- package/lib/qos.js +79 -5
- package/lib/rate.js +6 -1
- package/lib/serialization.js +7 -2
- package/lib/subscription.js +8 -0
- package/lib/time.js +136 -21
- package/lib/time_source.js +13 -4
- package/lib/timer.js +42 -0
- package/lib/utils.js +27 -1
- package/lib/validator.js +74 -19
- package/package.json +4 -2
- 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_gen/message_translator.js +0 -61
- package/scripts/config.js +1 -0
- package/src/addon.cpp +2 -0
- package/src/clock_event.cpp +268 -0
- package/src/clock_event.hpp +62 -0
- package/src/macros.h +2 -4
- package/src/rcl_action_server_bindings.cpp +21 -3
- package/src/rcl_bindings.cpp +59 -0
- package/src/rcl_context_bindings.cpp +5 -0
- package/src/rcl_graph_bindings.cpp +73 -0
- package/src/rcl_logging_bindings.cpp +158 -0
- package/src/rcl_node_bindings.cpp +14 -2
- package/src/rcl_publisher_bindings.cpp +12 -0
- package/src/rcl_service_bindings.cpp +7 -6
- package/src/rcl_subscription_bindings.cpp +51 -14
- package/src/rcl_time_point_bindings.cpp +135 -0
- package/src/rcl_timer_bindings.cpp +140 -0
- package/src/rcl_utilities.cpp +103 -2
- package/src/rcl_utilities.h +7 -1
- package/types/action_client.d.ts +27 -2
- package/types/base.d.ts +6 -0
- package/types/client.d.ts +65 -1
- package/types/clock.d.ts +86 -0
- package/types/clock_change.d.ts +27 -0
- package/types/clock_event.d.ts +51 -0
- package/types/errors.d.ts +496 -0
- package/types/index.d.ts +10 -0
- package/types/logging.d.ts +32 -0
- package/types/message_introspector.d.ts +75 -0
- package/types/message_validation.d.ts +183 -0
- package/types/node.d.ts +107 -0
- package/types/node_options.d.ts +13 -0
- package/types/observable_subscription.d.ts +39 -0
- package/types/parameter_client.d.ts +252 -0
- package/types/parameter_watcher.d.ts +104 -0
- package/types/publisher.d.ts +28 -1
- package/types/qos.d.ts +18 -0
- package/types/subscription.d.ts +6 -0
- package/types/timer.d.ts +18 -0
- package/types/validator.d.ts +86 -0
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,8 @@
|
|
|
16
16
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const rclnodejs = require('./native_loader.js');
|
|
19
|
+
const { TypeValidationError } = require('./errors.js');
|
|
20
|
+
const Context = require('./context.js');
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Enum for LoggingSeverity
|
|
@@ -88,6 +90,8 @@ class Caller {
|
|
|
88
90
|
class Logging {
|
|
89
91
|
constructor(name) {
|
|
90
92
|
this._name = name;
|
|
93
|
+
this._parentName = null;
|
|
94
|
+
this._subName = null;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
@@ -98,7 +102,10 @@ class Logging {
|
|
|
98
102
|
*/
|
|
99
103
|
setLoggerLevel(level) {
|
|
100
104
|
if (typeof level !== 'number') {
|
|
101
|
-
throw new
|
|
105
|
+
throw new TypeValidationError('level', level, 'number', {
|
|
106
|
+
entityType: 'logger',
|
|
107
|
+
entityName: this._name,
|
|
108
|
+
});
|
|
102
109
|
}
|
|
103
110
|
rclnodejs.setLoggerLevel(this._name, level);
|
|
104
111
|
}
|
|
@@ -164,7 +171,10 @@ class Logging {
|
|
|
164
171
|
|
|
165
172
|
_log(message, severity) {
|
|
166
173
|
if (typeof message !== 'string') {
|
|
167
|
-
throw new
|
|
174
|
+
throw new TypeValidationError('message', message, 'string', {
|
|
175
|
+
entityType: 'logger',
|
|
176
|
+
entityName: this._name,
|
|
177
|
+
});
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
let caller = new Caller();
|
|
@@ -196,6 +206,50 @@ class Logging {
|
|
|
196
206
|
return this._name;
|
|
197
207
|
}
|
|
198
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Create a child logger.
|
|
211
|
+
* @param {string} name - name of the child logger.
|
|
212
|
+
* @function
|
|
213
|
+
* @return {Logging} Return the child logger object.
|
|
214
|
+
*/
|
|
215
|
+
getChild(name) {
|
|
216
|
+
if (typeof name !== 'string' || !name) {
|
|
217
|
+
throw new Error('Child logger name must be a non-empty string.');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let fullname = name;
|
|
221
|
+
if (this._name) {
|
|
222
|
+
fullname = this._name + '.' + name;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const logger = new Logging(fullname);
|
|
226
|
+
if (this._name) {
|
|
227
|
+
if (
|
|
228
|
+
rclnodejs.addRosoutSublogger &&
|
|
229
|
+
rclnodejs.addRosoutSublogger(this._name, name)
|
|
230
|
+
) {
|
|
231
|
+
logger._parentName = this._name;
|
|
232
|
+
logger._subName = name;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return logger;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Destroy the logger and remove it from the parent logger if it is a child logger.
|
|
240
|
+
* @function
|
|
241
|
+
* @return {undefined}
|
|
242
|
+
*/
|
|
243
|
+
destroy() {
|
|
244
|
+
if (this._parentName && this._subName) {
|
|
245
|
+
if (rclnodejs.removeRosoutSublogger) {
|
|
246
|
+
rclnodejs.removeRosoutSublogger(this._parentName, this._subName);
|
|
247
|
+
}
|
|
248
|
+
this._parentName = null;
|
|
249
|
+
this._subName = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
199
253
|
/**
|
|
200
254
|
* Create a logger by name.
|
|
201
255
|
* @param {string} name - name of the logger.
|
|
@@ -204,10 +258,43 @@ class Logging {
|
|
|
204
258
|
*/
|
|
205
259
|
static getLogger(name) {
|
|
206
260
|
if (typeof name !== 'string') {
|
|
207
|
-
throw new
|
|
261
|
+
throw new TypeValidationError('name', name, 'string', {
|
|
262
|
+
entityType: 'logger',
|
|
263
|
+
});
|
|
208
264
|
}
|
|
209
265
|
return new Logging(name);
|
|
210
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configure the logging system with the given context.
|
|
270
|
+
* @param {Context} context - The context to configure logging for.
|
|
271
|
+
* @function
|
|
272
|
+
* @return {undefined}
|
|
273
|
+
*/
|
|
274
|
+
static configure(context) {
|
|
275
|
+
if (!(context instanceof Context)) {
|
|
276
|
+
throw new TypeValidationError('context', context, 'Context');
|
|
277
|
+
}
|
|
278
|
+
rclnodejs.loggingConfigure(context.handle);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Shutdown the logging system.
|
|
283
|
+
* @function
|
|
284
|
+
* @return {undefined}
|
|
285
|
+
*/
|
|
286
|
+
static shutdown() {
|
|
287
|
+
rclnodejs.loggingFini();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get the logging directory.
|
|
292
|
+
* @function
|
|
293
|
+
* @return {string} - The logging directory.
|
|
294
|
+
*/
|
|
295
|
+
static getLoggingDirectory() {
|
|
296
|
+
return rclnodejs.getLoggingDirectory();
|
|
297
|
+
}
|
|
211
298
|
}
|
|
212
299
|
|
|
213
300
|
module.exports = Logging;
|
|
@@ -0,0 +1,123 @@
|
|
|
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 loader = require('./interface_loader.js');
|
|
18
|
+
const { toPlainObject } = require('../rosidl_gen/message_translator.js');
|
|
19
|
+
const { TypeValidationError } = require('./errors.js');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A utility class for inspecting ROS 2 message structure without using loader.loadInterface directly.
|
|
23
|
+
* Provides access to message schema, field names, and default values.
|
|
24
|
+
*/
|
|
25
|
+
class MessageIntrospector {
|
|
26
|
+
#typeClass;
|
|
27
|
+
#typeName;
|
|
28
|
+
#defaultsCache;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new MessageIntrospector for a ROS 2 message type.
|
|
32
|
+
* @param {string} typeName - The full message type name (e.g., 'geometry_msgs/msg/Twist')
|
|
33
|
+
* @throws {TypeValidationError} If typeName is not a non-empty string
|
|
34
|
+
* @throws {Error} If the message type cannot be loaded
|
|
35
|
+
*/
|
|
36
|
+
constructor(typeName) {
|
|
37
|
+
if (!typeName || typeof typeName !== 'string') {
|
|
38
|
+
throw new TypeValidationError('typeName', typeName, 'non-empty string', {
|
|
39
|
+
entityType: 'MessageIntrospector',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.#typeName = typeName;
|
|
43
|
+
this.#typeClass = loader.loadInterface(typeName);
|
|
44
|
+
this.#defaultsCache = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the full message type name.
|
|
49
|
+
* @returns {string} The message type name (e.g., 'geometry_msgs/msg/Twist')
|
|
50
|
+
*/
|
|
51
|
+
get typeName() {
|
|
52
|
+
return this.#typeName;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the underlying ROS message class.
|
|
57
|
+
* @returns {Function} The message type class constructor
|
|
58
|
+
*/
|
|
59
|
+
get typeClass() {
|
|
60
|
+
return this.#typeClass;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the field names of the message.
|
|
65
|
+
* @returns {string[]} Array of field names
|
|
66
|
+
*/
|
|
67
|
+
get fields() {
|
|
68
|
+
const def = this.#typeClass.ROSMessageDef;
|
|
69
|
+
return def.fields.map((f) => f.name);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the ROSMessageDef schema for the message type.
|
|
74
|
+
* @returns {object} The message definition schema
|
|
75
|
+
*/
|
|
76
|
+
get schema() {
|
|
77
|
+
return this.#typeClass.ROSMessageDef;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the default values for all fields.
|
|
82
|
+
* Creates a new instance of the message and converts it to a plain object.
|
|
83
|
+
* Result is cached for performance.
|
|
84
|
+
* @returns {object} A plain object with all default values
|
|
85
|
+
*/
|
|
86
|
+
get defaults() {
|
|
87
|
+
if (this.#defaultsCache === null) {
|
|
88
|
+
const instance = new this.#typeClass();
|
|
89
|
+
this.#defaultsCache = toPlainObject(instance);
|
|
90
|
+
}
|
|
91
|
+
return this.#deepClone(this.#defaultsCache);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Deep clone an object.
|
|
96
|
+
* @param {any} obj - Object to clone
|
|
97
|
+
* @returns {any} Cloned object
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
#deepClone(obj) {
|
|
101
|
+
if (obj === null || typeof obj !== 'object') {
|
|
102
|
+
return obj;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(obj)) {
|
|
106
|
+
return obj.map((item) => this.#deepClone(item));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
|
|
110
|
+
return obj.slice();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cloned = {};
|
|
114
|
+
for (const key in obj) {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
116
|
+
cloned[key] = this.#deepClone(obj[key]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return cloned;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = MessageIntrospector;
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
+
const { ValidationError } = require('./errors.js');
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* Check if a value is a TypedArray
|
|
19
21
|
* @param {*} value - The value to check
|
|
@@ -145,8 +147,14 @@ function applySerializationMode(message, serializationMode) {
|
|
|
145
147
|
return toJSONSafe(message);
|
|
146
148
|
|
|
147
149
|
default:
|
|
148
|
-
throw new
|
|
149
|
-
`Invalid serializationMode: ${serializationMode}. Valid modes are: 'default', 'plain', 'json'
|
|
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
|
+
}
|
|
150
158
|
);
|
|
151
159
|
}
|
|
152
160
|
}
|