@openrois/sdk 0.0.1-stage → 0.1.0-alpha.1
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/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +269 -0
- package/dist/chunk-AEO3U7Y7.js +534 -0
- package/dist/chunk-AEO3U7Y7.js.map +1 -0
- package/dist/rois-client.d.ts +266 -0
- package/dist/rois-client.js +355 -0
- package/dist/rois-client.js.map +1 -0
- package/dist/transport-kMEIcw5y.d.ts +383 -0
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +17 -0
- package/dist/transport.js.map +1 -0
- package/package.json +76 -8
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/// <reference lib="esnext.disposable" />
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { CommandUnitSchema, ConcurrentCommandsSchema, HRIEngineProfileType, HRIComponentProfile, RoISNotification, RoISNotificationMap, Result, Parameter, ReturnCode } from '@openrois/interfaces';
|
|
4
|
+
export { COMPONENT_REF, COMPONENT_TYPE, allOf, componentRef, componentType, componentTypeUrn, eq, like, quote } from '@openrois/interfaces';
|
|
5
|
+
import { T as TransportOptions, a as TypedEmitter, W as WebSocketTransport } from './transport-kMEIcw5y.js';
|
|
6
|
+
export { C as ConnectionError, R as RequestTimeoutError, b as RpcError, c as TransportError } from './transport-kMEIcw5y.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* RoISClient: the client of the OpenRoIS JavaScript SDK.
|
|
10
|
+
*
|
|
11
|
+
* One method per operation of the RoIS method catalog, named after the IDL
|
|
12
|
+
* operation and taking its `in` parameters in IDL order:
|
|
13
|
+
*
|
|
14
|
+
* System connect, disconnect, getProfile, getErrorDetail
|
|
15
|
+
* Command search, bind, bindAny, release, getParameter, setParameter,
|
|
16
|
+
* execute, getCommandResult
|
|
17
|
+
* Query query
|
|
18
|
+
* Event subscribe, unsubscribe, getEventDetail
|
|
19
|
+
*
|
|
20
|
+
* A streaming component is driven with these same methods. Carrying its media
|
|
21
|
+
* is planned.
|
|
22
|
+
*
|
|
23
|
+
* Every call goes through one path that validates the params and the result
|
|
24
|
+
* against the catalog schemas from @openrois/interfaces and turns a return code
|
|
25
|
+
* other than OK into a RoISError. Notifications from the engine are validated
|
|
26
|
+
* the same way before listeners receive their params.
|
|
27
|
+
*
|
|
28
|
+
* Usage:
|
|
29
|
+
* import { RoISClient, componentRef } from "@openrois/sdk";
|
|
30
|
+
*
|
|
31
|
+
* const client = await RoISClient.connect("ws://localhost:8765");
|
|
32
|
+
* const { profile, component_profiles } = await client.getProfile();
|
|
33
|
+
*
|
|
34
|
+
* const head = componentRef("reachy_real/head");
|
|
35
|
+
* const status = await client.query("component_status", head);
|
|
36
|
+
*
|
|
37
|
+
* client.on("rois.event.notify_event", (params) => console.log(params.event_type));
|
|
38
|
+
* const subscribeId = await client.subscribe("person_detected", head);
|
|
39
|
+
*
|
|
40
|
+
* const statuses = new Map<string, string>();
|
|
41
|
+
* client.on("rois.command.completed", ({ command_id, status }) => {
|
|
42
|
+
* statuses.set(command_id, status);
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await client.bind("reachy_real/head");
|
|
46
|
+
* const [commandId] = await client.execute([
|
|
47
|
+
* { component_ref: "reachy_real/head", command_type: "start" },
|
|
48
|
+
* ]);
|
|
49
|
+
*
|
|
50
|
+
* await client.disconnect();
|
|
51
|
+
*
|
|
52
|
+
* Wire binding: docs/rois-reference.md section 17.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** Options for RoISClient.connect(). */
|
|
56
|
+
interface ClientOptions {
|
|
57
|
+
/** Transport options: timeouts and a custom WebSocket factory. */
|
|
58
|
+
transport?: TransportOptions;
|
|
59
|
+
}
|
|
60
|
+
/** The get_profile result of an engine that answered OK. */
|
|
61
|
+
interface EngineProfile {
|
|
62
|
+
/** The engine profile, as HRIEngineProfileType in XML-Profiles.xsd defines it. */
|
|
63
|
+
profile: HRIEngineProfileType;
|
|
64
|
+
/**
|
|
65
|
+
* The profile of every component in `profile.component_ids`, keyed by fully
|
|
66
|
+
* qualified ref. An OpenRoIS extension (docs/rois-reference.md section 17.3).
|
|
67
|
+
*/
|
|
68
|
+
component_profiles: Record<string, HRIComponentProfile>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One command for execute(): a CommandUnit without its `command_id`, which the
|
|
72
|
+
* engine assigns (spec 8.2.2.2).
|
|
73
|
+
*/
|
|
74
|
+
type CommandUnitInput = Omit<z.input<typeof CommandUnitSchema>, "command_id">;
|
|
75
|
+
/**
|
|
76
|
+
* Commands for execute() in branches that run at the same time. Each branch runs
|
|
77
|
+
* its commands in order, and the next item waits for every branch.
|
|
78
|
+
*/
|
|
79
|
+
type ConcurrentCommandsInput = Omit<z.input<typeof ConcurrentCommandsSchema>, "branch_list"> & {
|
|
80
|
+
branch_list: {
|
|
81
|
+
command_list: CommandUnitInput[];
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
/** One item of the command_unit_list that execute() takes. */
|
|
85
|
+
type CommandUnitSequenceItemInput = CommandUnitInput | ConcurrentCommandsInput;
|
|
86
|
+
/** The params a listener receives for each catalog notification. */
|
|
87
|
+
type NotificationParams<N extends RoISNotification> = RoISNotificationMap[N]["params"];
|
|
88
|
+
/** The listener arguments of every event the client emits. */
|
|
89
|
+
type RoISClientEvents = {
|
|
90
|
+
[N in RoISNotification]: [params: NotificationParams<N>];
|
|
91
|
+
} & {
|
|
92
|
+
close: [code: number, reason: string];
|
|
93
|
+
error: [error: Error];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Raised when a RoIS operation returns a code other than OK.
|
|
97
|
+
*
|
|
98
|
+
* A RoIS failure is a normal result on the wire, so this error carries the
|
|
99
|
+
* return code for callers to branch on (BAD_PARAMETER, UNSUPPORTED,
|
|
100
|
+
* OUT_OF_RESOURCES, TIMEOUT, ERROR).
|
|
101
|
+
*/
|
|
102
|
+
declare class RoISError extends Error {
|
|
103
|
+
/** The return code of the result. */
|
|
104
|
+
readonly returnCode: ReturnCode;
|
|
105
|
+
/** The JSON-RPC method that returned it. */
|
|
106
|
+
readonly method: string;
|
|
107
|
+
constructor(returnCode: ReturnCode, method: string);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A connection to an OpenRoIS engine, with one method per RoIS operation.
|
|
111
|
+
*
|
|
112
|
+
* Create it with RoISClient.connect(). One client is one session: bindings and
|
|
113
|
+
* subscriptions end with it, and nothing reconnects by itself.
|
|
114
|
+
*
|
|
115
|
+
* Listen with on() and off():
|
|
116
|
+
* "rois.event.notify_event" NotifyEventParams, for every event of a subscription
|
|
117
|
+
* "rois.command.completed" CompletedParams, when a command ends
|
|
118
|
+
* "rois.system.notify_error" NotifyErrorParams, when the engine reports an error
|
|
119
|
+
* "rois.system.profile_changed" ProfileChangedParams, when components join or leave
|
|
120
|
+
* "close" (code, reason) when the connection closes
|
|
121
|
+
* "error" Error, for a message that cannot be used or a listener
|
|
122
|
+
* that throws. With no listener it goes to the console.
|
|
123
|
+
*/
|
|
124
|
+
declare class RoISClient extends TypedEmitter<RoISClientEvents> {
|
|
125
|
+
/** The WebSocket transport this client sends on. */
|
|
126
|
+
private readonly wire;
|
|
127
|
+
/** Whether the connect handshake succeeded and disconnect has not run. */
|
|
128
|
+
private connected;
|
|
129
|
+
/** The disconnect in flight, which overlapping callers share. */
|
|
130
|
+
private disconnecting;
|
|
131
|
+
private constructor();
|
|
132
|
+
/**
|
|
133
|
+
* The underlying transport, for JSON-RPC methods outside the catalog. The
|
|
134
|
+
* client does not validate what is sent through it.
|
|
135
|
+
*/
|
|
136
|
+
get transport(): WebSocketTransport;
|
|
137
|
+
/**
|
|
138
|
+
* Open a connection to an engine and run rois.system.connect.
|
|
139
|
+
*
|
|
140
|
+
* @param url - The engine's WebSocket URL, for example "ws://localhost:8765".
|
|
141
|
+
* @throws ConnectionError if the WebSocket cannot be opened.
|
|
142
|
+
* @throws RoISError if the engine answers connect with a code other than OK.
|
|
143
|
+
*/
|
|
144
|
+
static connect(url: string, options?: ClientOptions): Promise<RoISClient>;
|
|
145
|
+
/**
|
|
146
|
+
* Run rois.system.disconnect and close the connection. The connection closes
|
|
147
|
+
* even when the engine answers with an error. Safe to call more than once. Calls
|
|
148
|
+
* that overlap send one rois.system.disconnect and share its outcome.
|
|
149
|
+
*/
|
|
150
|
+
disconnect(): Promise<void>;
|
|
151
|
+
/** Run disconnect() when a block declared with `await using` ends. */
|
|
152
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Read the engine profile and the profiles of its components.
|
|
155
|
+
*
|
|
156
|
+
* @param condition - Selects the components the profile lists. Empty lists all.
|
|
157
|
+
*/
|
|
158
|
+
getProfile(condition?: string): Promise<EngineProfile>;
|
|
159
|
+
/**
|
|
160
|
+
* Read the details of an error from a rois.system.notify_error notification.
|
|
161
|
+
*
|
|
162
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
163
|
+
*/
|
|
164
|
+
getErrorDetail(errorId: string, condition?: string): Promise<Result[]>;
|
|
165
|
+
/**
|
|
166
|
+
* Find components.
|
|
167
|
+
*
|
|
168
|
+
* @param condition - Selects the components. Empty matches every component.
|
|
169
|
+
* @returns Their fully qualified refs.
|
|
170
|
+
*/
|
|
171
|
+
search(condition?: string): Promise<string[]>;
|
|
172
|
+
/** Reserve a component for this client. */
|
|
173
|
+
bind(componentRef: string): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Reserve one of the components a condition selects, chosen by the engine.
|
|
176
|
+
*
|
|
177
|
+
* @returns The ref of the reserved component.
|
|
178
|
+
*/
|
|
179
|
+
bindAny(condition?: string): Promise<string>;
|
|
180
|
+
/** Release a component reserved with bind() or bindAny(). */
|
|
181
|
+
release(componentRef: string): Promise<void>;
|
|
182
|
+
/** Read the current value of every parameter of a component. */
|
|
183
|
+
getParameter(componentRef: string): Promise<Parameter[]>;
|
|
184
|
+
/**
|
|
185
|
+
* Set parameters of a component.
|
|
186
|
+
*
|
|
187
|
+
* @returns The command_id the engine assigned to the change. Its
|
|
188
|
+
* rois.command.completed notification reports when the change took effect.
|
|
189
|
+
*/
|
|
190
|
+
setParameter(componentRef: string, parameters: Parameter[]): Promise<string>;
|
|
191
|
+
/**
|
|
192
|
+
* Run commands in order. A ConcurrentCommands item runs its branches at the
|
|
193
|
+
* same time, and each branch runs its commands in order.
|
|
194
|
+
*
|
|
195
|
+
* The engine answers as soon as it accepts the commands, with the command_id
|
|
196
|
+
* it assigned to each one. Each command then ends with a
|
|
197
|
+
* rois.command.completed notification carrying that command_id, which
|
|
198
|
+
* arrives after the answer, and getCommandResult() reads its results.
|
|
199
|
+
*
|
|
200
|
+
* @param commandUnitList - The commands, without command ids.
|
|
201
|
+
* @returns The command_id the engine assigned to every command, in the order
|
|
202
|
+
* the list gives them, with the commands of a ConcurrentCommands item branch
|
|
203
|
+
* by branch.
|
|
204
|
+
*/
|
|
205
|
+
execute(commandUnitList: CommandUnitSequenceItemInput[]): Promise<string[]>;
|
|
206
|
+
/**
|
|
207
|
+
* Read the results of a command.
|
|
208
|
+
*
|
|
209
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
210
|
+
*/
|
|
211
|
+
getCommandResult(commandId: string, condition?: string): Promise<Result[]>;
|
|
212
|
+
/**
|
|
213
|
+
* Run a query and wait for its results.
|
|
214
|
+
*
|
|
215
|
+
* The engine sends the query to the one component that declares `queryType`
|
|
216
|
+
* and matches the condition. It returns UNSUPPORTED when none does, and
|
|
217
|
+
* BAD_PARAMETER when several do.
|
|
218
|
+
*
|
|
219
|
+
* @param queryType - The query name from a component profile, for example
|
|
220
|
+
* "component_status".
|
|
221
|
+
* @param condition - Selects the component, for example
|
|
222
|
+
* componentRef("reachy_real/head").
|
|
223
|
+
*/
|
|
224
|
+
query(queryType: string, condition?: string): Promise<Result[]>;
|
|
225
|
+
/**
|
|
226
|
+
* Subscribe to an event. Its occurrences arrive as rois.event.notify_event
|
|
227
|
+
* notifications, which carry the subscribe_id this returns.
|
|
228
|
+
*
|
|
229
|
+
* The engine subscribes to the one component that declares `eventType` and
|
|
230
|
+
* matches the condition, with the same rules as query().
|
|
231
|
+
*
|
|
232
|
+
* @returns The subscribe_id, for unsubscribe().
|
|
233
|
+
*/
|
|
234
|
+
subscribe(eventType: string, condition?: string): Promise<string>;
|
|
235
|
+
/**
|
|
236
|
+
* Cancel a subscription. The engine ignores an id it does not know, as RoIS
|
|
237
|
+
* requires.
|
|
238
|
+
*/
|
|
239
|
+
unsubscribe(subscribeId: string): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* Read the payload of an event until it expires.
|
|
242
|
+
*
|
|
243
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
244
|
+
*/
|
|
245
|
+
getEventDetail(eventId: string, condition?: string): Promise<Result[]>;
|
|
246
|
+
/** The work of disconnect(), once for all the calls that overlap. */
|
|
247
|
+
private endSession;
|
|
248
|
+
/** Run a catalog method on a connected client. */
|
|
249
|
+
private call;
|
|
250
|
+
/**
|
|
251
|
+
* Validate the params, send the request, validate the result and check its
|
|
252
|
+
* return code. Invalid params throw before anything is sent, and a result
|
|
253
|
+
* that does not match the catalog throws instead of reaching the caller.
|
|
254
|
+
*/
|
|
255
|
+
private request;
|
|
256
|
+
/**
|
|
257
|
+
* Re-emit what the transport receives. Catalog notifications are validated
|
|
258
|
+
* and emitted with their params. Methods outside the catalog stay on the
|
|
259
|
+
* transport's "notification" event.
|
|
260
|
+
*/
|
|
261
|
+
private forwardTransportEvents;
|
|
262
|
+
/** Validate a catalog notification and emit its params. */
|
|
263
|
+
private dispatchNotification;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { type ClientOptions, type CommandUnitInput, type CommandUnitSequenceItemInput, type ConcurrentCommandsInput, type EngineProfile, type NotificationParams, RoISClient, type RoISClientEvents, RoISError, TransportOptions };
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectionError,
|
|
3
|
+
RequestTimeoutError,
|
|
4
|
+
RpcError,
|
|
5
|
+
TransportError,
|
|
6
|
+
TypedEmitter,
|
|
7
|
+
WebSocketTransport
|
|
8
|
+
} from "./chunk-AEO3U7Y7.js";
|
|
9
|
+
|
|
10
|
+
// src/rois-client.ts
|
|
11
|
+
import {
|
|
12
|
+
RoISMethods,
|
|
13
|
+
RoISMethodSchemas,
|
|
14
|
+
RoISNotificationSchemas
|
|
15
|
+
} from "@openrois/interfaces";
|
|
16
|
+
import {
|
|
17
|
+
COMPONENT_REF,
|
|
18
|
+
COMPONENT_TYPE,
|
|
19
|
+
allOf,
|
|
20
|
+
componentRef,
|
|
21
|
+
componentType,
|
|
22
|
+
componentTypeUrn,
|
|
23
|
+
eq,
|
|
24
|
+
like,
|
|
25
|
+
quote
|
|
26
|
+
} from "@openrois/interfaces";
|
|
27
|
+
var RoISError = class extends Error {
|
|
28
|
+
/** The return code of the result. */
|
|
29
|
+
returnCode;
|
|
30
|
+
/** The JSON-RPC method that returned it. */
|
|
31
|
+
method;
|
|
32
|
+
constructor(returnCode, method) {
|
|
33
|
+
super(`${method} failed with ${returnCode}`);
|
|
34
|
+
this.name = "RoISError";
|
|
35
|
+
this.returnCode = returnCode;
|
|
36
|
+
this.method = method;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var RoISClient = class _RoISClient extends TypedEmitter {
|
|
40
|
+
/** The WebSocket transport this client sends on. */
|
|
41
|
+
wire;
|
|
42
|
+
/** Whether the connect handshake succeeded and disconnect has not run. */
|
|
43
|
+
connected = false;
|
|
44
|
+
/** The disconnect in flight, which overlapping callers share. */
|
|
45
|
+
disconnecting = null;
|
|
46
|
+
constructor(wire) {
|
|
47
|
+
super();
|
|
48
|
+
this.wire = wire;
|
|
49
|
+
this.forwardTransportEvents();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The underlying transport, for JSON-RPC methods outside the catalog. The
|
|
53
|
+
* client does not validate what is sent through it.
|
|
54
|
+
*/
|
|
55
|
+
get transport() {
|
|
56
|
+
return this.wire;
|
|
57
|
+
}
|
|
58
|
+
// -----------------------------------------------------------------------
|
|
59
|
+
// SystemIF
|
|
60
|
+
// -----------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* Open a connection to an engine and run rois.system.connect.
|
|
63
|
+
*
|
|
64
|
+
* @param url - The engine's WebSocket URL, for example "ws://localhost:8765".
|
|
65
|
+
* @throws ConnectionError if the WebSocket cannot be opened.
|
|
66
|
+
* @throws RoISError if the engine answers connect with a code other than OK.
|
|
67
|
+
*/
|
|
68
|
+
static async connect(url, options) {
|
|
69
|
+
const wire = new WebSocketTransport(options?.transport);
|
|
70
|
+
await wire.connect(url);
|
|
71
|
+
const client = new _RoISClient(wire);
|
|
72
|
+
try {
|
|
73
|
+
await client.request(RoISMethods.Connect, {});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
wire.close();
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
client.connected = true;
|
|
79
|
+
return client;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Run rois.system.disconnect and close the connection. The connection closes
|
|
83
|
+
* even when the engine answers with an error. Safe to call more than once. Calls
|
|
84
|
+
* that overlap send one rois.system.disconnect and share its outcome.
|
|
85
|
+
*/
|
|
86
|
+
async disconnect() {
|
|
87
|
+
this.disconnecting ??= this.endSession().finally(() => {
|
|
88
|
+
this.disconnecting = null;
|
|
89
|
+
});
|
|
90
|
+
return this.disconnecting;
|
|
91
|
+
}
|
|
92
|
+
/** Run disconnect() when a block declared with `await using` ends. */
|
|
93
|
+
async [Symbol.asyncDispose]() {
|
|
94
|
+
await this.disconnect();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Read the engine profile and the profiles of its components.
|
|
98
|
+
*
|
|
99
|
+
* @param condition - Selects the components the profile lists. Empty lists all.
|
|
100
|
+
*/
|
|
101
|
+
async getProfile(condition = "") {
|
|
102
|
+
const result = await this.call(RoISMethods.GetProfile, { condition });
|
|
103
|
+
if (result.profile === null) {
|
|
104
|
+
throw new TransportError("rois.system.get_profile returned OK without a profile");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
profile: result.profile,
|
|
108
|
+
component_profiles: result.component_profiles ?? {}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the details of an error from a rois.system.notify_error notification.
|
|
113
|
+
*
|
|
114
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
115
|
+
*/
|
|
116
|
+
async getErrorDetail(errorId, condition = "") {
|
|
117
|
+
const result = await this.call(RoISMethods.GetErrorDetail, {
|
|
118
|
+
error_id: errorId,
|
|
119
|
+
condition
|
|
120
|
+
});
|
|
121
|
+
return result.results ?? [];
|
|
122
|
+
}
|
|
123
|
+
// -----------------------------------------------------------------------
|
|
124
|
+
// CommandIF
|
|
125
|
+
// -----------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Find components.
|
|
128
|
+
*
|
|
129
|
+
* @param condition - Selects the components. Empty matches every component.
|
|
130
|
+
* @returns Their fully qualified refs.
|
|
131
|
+
*/
|
|
132
|
+
async search(condition = "") {
|
|
133
|
+
const result = await this.call(RoISMethods.Search, { condition });
|
|
134
|
+
return result.component_ref_list ?? [];
|
|
135
|
+
}
|
|
136
|
+
/** Reserve a component for this client. */
|
|
137
|
+
async bind(componentRef2) {
|
|
138
|
+
await this.call(RoISMethods.Bind, { component_ref: componentRef2 });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Reserve one of the components a condition selects, chosen by the engine.
|
|
142
|
+
*
|
|
143
|
+
* @returns The ref of the reserved component.
|
|
144
|
+
*/
|
|
145
|
+
async bindAny(condition = "") {
|
|
146
|
+
const result = await this.call(RoISMethods.BindAny, { condition });
|
|
147
|
+
return result.component_ref;
|
|
148
|
+
}
|
|
149
|
+
/** Release a component reserved with bind() or bindAny(). */
|
|
150
|
+
async release(componentRef2) {
|
|
151
|
+
await this.call(RoISMethods.Release, { component_ref: componentRef2 });
|
|
152
|
+
}
|
|
153
|
+
/** Read the current value of every parameter of a component. */
|
|
154
|
+
async getParameter(componentRef2) {
|
|
155
|
+
const result = await this.call(RoISMethods.GetParameter, {
|
|
156
|
+
component_ref: componentRef2
|
|
157
|
+
});
|
|
158
|
+
return result.parameters ?? [];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Set parameters of a component.
|
|
162
|
+
*
|
|
163
|
+
* @returns The command_id the engine assigned to the change. Its
|
|
164
|
+
* rois.command.completed notification reports when the change took effect.
|
|
165
|
+
*/
|
|
166
|
+
async setParameter(componentRef2, parameters) {
|
|
167
|
+
const result = await this.call(RoISMethods.SetParameter, {
|
|
168
|
+
component_ref: componentRef2,
|
|
169
|
+
parameters
|
|
170
|
+
});
|
|
171
|
+
return result.command_id;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Run commands in order. A ConcurrentCommands item runs its branches at the
|
|
175
|
+
* same time, and each branch runs its commands in order.
|
|
176
|
+
*
|
|
177
|
+
* The engine answers as soon as it accepts the commands, with the command_id
|
|
178
|
+
* it assigned to each one. Each command then ends with a
|
|
179
|
+
* rois.command.completed notification carrying that command_id, which
|
|
180
|
+
* arrives after the answer, and getCommandResult() reads its results.
|
|
181
|
+
*
|
|
182
|
+
* @param commandUnitList - The commands, without command ids.
|
|
183
|
+
* @returns The command_id the engine assigned to every command, in the order
|
|
184
|
+
* the list gives them, with the commands of a ConcurrentCommands item branch
|
|
185
|
+
* by branch.
|
|
186
|
+
*/
|
|
187
|
+
async execute(commandUnitList) {
|
|
188
|
+
const result = await this.call(RoISMethods.Execute, { command_unit_list: commandUnitList });
|
|
189
|
+
return (result.command_unit_list ?? []).flatMap(
|
|
190
|
+
(item) => "branch_list" in item ? item.branch_list.flatMap((branch) => branch.command_list.map((unit) => unit.command_id)) : [item.command_id]
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Read the results of a command.
|
|
195
|
+
*
|
|
196
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
197
|
+
*/
|
|
198
|
+
async getCommandResult(commandId, condition = "") {
|
|
199
|
+
const result = await this.call(RoISMethods.GetCommandResult, {
|
|
200
|
+
command_id: commandId,
|
|
201
|
+
condition
|
|
202
|
+
});
|
|
203
|
+
return result.results ?? [];
|
|
204
|
+
}
|
|
205
|
+
// -----------------------------------------------------------------------
|
|
206
|
+
// QueryIF
|
|
207
|
+
// -----------------------------------------------------------------------
|
|
208
|
+
/**
|
|
209
|
+
* Run a query and wait for its results.
|
|
210
|
+
*
|
|
211
|
+
* The engine sends the query to the one component that declares `queryType`
|
|
212
|
+
* and matches the condition. It returns UNSUPPORTED when none does, and
|
|
213
|
+
* BAD_PARAMETER when several do.
|
|
214
|
+
*
|
|
215
|
+
* @param queryType - The query name from a component profile, for example
|
|
216
|
+
* "component_status".
|
|
217
|
+
* @param condition - Selects the component, for example
|
|
218
|
+
* componentRef("reachy_real/head").
|
|
219
|
+
*/
|
|
220
|
+
async query(queryType, condition = "") {
|
|
221
|
+
const result = await this.call(RoISMethods.Query, {
|
|
222
|
+
query_type: queryType,
|
|
223
|
+
condition
|
|
224
|
+
});
|
|
225
|
+
return result.results ?? [];
|
|
226
|
+
}
|
|
227
|
+
// -----------------------------------------------------------------------
|
|
228
|
+
// EventIF
|
|
229
|
+
// -----------------------------------------------------------------------
|
|
230
|
+
/**
|
|
231
|
+
* Subscribe to an event. Its occurrences arrive as rois.event.notify_event
|
|
232
|
+
* notifications, which carry the subscribe_id this returns.
|
|
233
|
+
*
|
|
234
|
+
* The engine subscribes to the one component that declares `eventType` and
|
|
235
|
+
* matches the condition, with the same rules as query().
|
|
236
|
+
*
|
|
237
|
+
* @returns The subscribe_id, for unsubscribe().
|
|
238
|
+
*/
|
|
239
|
+
async subscribe(eventType, condition = "") {
|
|
240
|
+
const result = await this.call(RoISMethods.Subscribe, {
|
|
241
|
+
event_type: eventType,
|
|
242
|
+
condition
|
|
243
|
+
});
|
|
244
|
+
return result.subscribe_id;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Cancel a subscription. The engine ignores an id it does not know, as RoIS
|
|
248
|
+
* requires.
|
|
249
|
+
*/
|
|
250
|
+
async unsubscribe(subscribeId) {
|
|
251
|
+
await this.call(RoISMethods.Unsubscribe, { subscribe_id: subscribeId });
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Read the payload of an event until it expires.
|
|
255
|
+
*
|
|
256
|
+
* @param condition - Filter on the results. Must be empty for now.
|
|
257
|
+
*/
|
|
258
|
+
async getEventDetail(eventId, condition = "") {
|
|
259
|
+
const result = await this.call(RoISMethods.GetEventDetail, {
|
|
260
|
+
event_id: eventId,
|
|
261
|
+
condition
|
|
262
|
+
});
|
|
263
|
+
return result.results ?? [];
|
|
264
|
+
}
|
|
265
|
+
// -----------------------------------------------------------------------
|
|
266
|
+
// Private: calls
|
|
267
|
+
// -----------------------------------------------------------------------
|
|
268
|
+
/** The work of disconnect(), once for all the calls that overlap. */
|
|
269
|
+
async endSession() {
|
|
270
|
+
if (!this.connected) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
await this.call(RoISMethods.Disconnect, {});
|
|
275
|
+
} finally {
|
|
276
|
+
this.connected = false;
|
|
277
|
+
this.wire.close();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** Run a catalog method on a connected client. */
|
|
281
|
+
async call(method, params) {
|
|
282
|
+
if (!this.connected) {
|
|
283
|
+
throw new TransportError(`Cannot call ${method}: client is not connected`);
|
|
284
|
+
}
|
|
285
|
+
return this.request(method, params);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Validate the params, send the request, validate the result and check its
|
|
289
|
+
* return code. Invalid params throw before anything is sent, and a result
|
|
290
|
+
* that does not match the catalog throws instead of reaching the caller.
|
|
291
|
+
*/
|
|
292
|
+
async request(method, params) {
|
|
293
|
+
const schema = RoISMethodSchemas[method];
|
|
294
|
+
const validParams = schema.params.parse(params);
|
|
295
|
+
const raw = await this.wire.send(method, validParams);
|
|
296
|
+
const result = schema.result.parse(raw);
|
|
297
|
+
const returnCode = result.return_code;
|
|
298
|
+
if (returnCode !== "OK") {
|
|
299
|
+
throw new RoISError(returnCode, method);
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
// -----------------------------------------------------------------------
|
|
304
|
+
// Private: notifications
|
|
305
|
+
// -----------------------------------------------------------------------
|
|
306
|
+
/**
|
|
307
|
+
* Re-emit what the transport receives. Catalog notifications are validated
|
|
308
|
+
* and emitted with their params. Methods outside the catalog stay on the
|
|
309
|
+
* transport's "notification" event.
|
|
310
|
+
*/
|
|
311
|
+
forwardTransportEvents() {
|
|
312
|
+
this.wire.on("notification", ({ method, params }) => {
|
|
313
|
+
if (Object.hasOwn(RoISNotificationSchemas, method)) {
|
|
314
|
+
this.dispatchNotification(method, params);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
this.wire.on("close", (code, reason) => {
|
|
318
|
+
this.connected = false;
|
|
319
|
+
this.emit("close", code, reason);
|
|
320
|
+
});
|
|
321
|
+
this.wire.on("error", (error) => {
|
|
322
|
+
this.emit("error", error);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/** Validate a catalog notification and emit its params. */
|
|
326
|
+
dispatchNotification(method, rawParams) {
|
|
327
|
+
const parsed = RoISNotificationSchemas[method].params.safeParse(rawParams ?? {});
|
|
328
|
+
if (!parsed.success) {
|
|
329
|
+
this.emit(
|
|
330
|
+
"error",
|
|
331
|
+
new TransportError(`Invalid params in ${method} notification: ${parsed.error.message}`)
|
|
332
|
+
);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
this.emit(method, ...[parsed.data]);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
export {
|
|
339
|
+
COMPONENT_REF,
|
|
340
|
+
COMPONENT_TYPE,
|
|
341
|
+
ConnectionError,
|
|
342
|
+
RequestTimeoutError,
|
|
343
|
+
RoISClient,
|
|
344
|
+
RoISError,
|
|
345
|
+
RpcError,
|
|
346
|
+
TransportError,
|
|
347
|
+
allOf,
|
|
348
|
+
componentRef,
|
|
349
|
+
componentType,
|
|
350
|
+
componentTypeUrn,
|
|
351
|
+
eq,
|
|
352
|
+
like,
|
|
353
|
+
quote
|
|
354
|
+
};
|
|
355
|
+
//# sourceMappingURL=rois-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rois-client.ts"],"sourcesContent":["/**\n * RoISClient: the client of the OpenRoIS JavaScript SDK.\n *\n * One method per operation of the RoIS method catalog, named after the IDL\n * operation and taking its `in` parameters in IDL order:\n *\n * System connect, disconnect, getProfile, getErrorDetail\n * Command search, bind, bindAny, release, getParameter, setParameter,\n * execute, getCommandResult\n * Query query\n * Event subscribe, unsubscribe, getEventDetail\n *\n * A streaming component is driven with these same methods. Carrying its media\n * is planned.\n *\n * Every call goes through one path that validates the params and the result\n * against the catalog schemas from @openrois/interfaces and turns a return code\n * other than OK into a RoISError. Notifications from the engine are validated\n * the same way before listeners receive their params.\n *\n * Usage:\n * import { RoISClient, componentRef } from \"@openrois/sdk\";\n *\n * const client = await RoISClient.connect(\"ws://localhost:8765\");\n * const { profile, component_profiles } = await client.getProfile();\n *\n * const head = componentRef(\"reachy_real/head\");\n * const status = await client.query(\"component_status\", head);\n *\n * client.on(\"rois.event.notify_event\", (params) => console.log(params.event_type));\n * const subscribeId = await client.subscribe(\"person_detected\", head);\n *\n * const statuses = new Map<string, string>();\n * client.on(\"rois.command.completed\", ({ command_id, status }) => {\n * statuses.set(command_id, status);\n * });\n *\n * await client.bind(\"reachy_real/head\");\n * const [commandId] = await client.execute([\n * { component_ref: \"reachy_real/head\", command_type: \"start\" },\n * ]);\n *\n * await client.disconnect();\n *\n * Wire binding: docs/rois-reference.md section 17.\n */\n\n// 1. External packages\nimport type { z } from \"zod\";\n\n// 2. Internal packages (@openrois/*)\nimport {\n RoISMethods,\n RoISMethodSchemas,\n RoISNotificationSchemas,\n} from \"@openrois/interfaces\";\n\nimport type {\n CommandUnitSchema,\n ConcurrentCommandsSchema,\n HRIComponentProfile,\n HRIEngineProfileType,\n Parameter,\n Result,\n ReturnCode,\n RoISMethod,\n RoISMethodMap,\n RoISNotification,\n RoISNotificationMap,\n} from \"@openrois/interfaces\";\n\n// 3. Local modules\nimport { TypedEmitter } from \"./emitter\";\nimport {\n WebSocketTransport,\n TransportError,\n type TransportOptions,\n} from \"./transport\";\n\n// The condition builders, so a client needs no second import to write conditions.\nexport {\n COMPONENT_REF,\n COMPONENT_TYPE,\n allOf,\n componentRef,\n componentType,\n componentTypeUrn,\n eq,\n like,\n quote,\n} from \"@openrois/interfaces\";\nexport {\n ConnectionError,\n RequestTimeoutError,\n RpcError,\n TransportError,\n type TransportOptions,\n} from \"./transport\";\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/** Options for RoISClient.connect(). */\nexport interface ClientOptions {\n /** Transport options: timeouts and a custom WebSocket factory. */\n transport?: TransportOptions;\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** The get_profile result of an engine that answered OK. */\nexport interface EngineProfile {\n /** The engine profile, as HRIEngineProfileType in XML-Profiles.xsd defines it. */\n profile: HRIEngineProfileType;\n /**\n * The profile of every component in `profile.component_ids`, keyed by fully\n * qualified ref. An OpenRoIS extension (docs/rois-reference.md section 17.3).\n */\n component_profiles: Record<string, HRIComponentProfile>;\n}\n\n/**\n * One command for execute(): a CommandUnit without its `command_id`, which the\n * engine assigns (spec 8.2.2.2).\n */\nexport type CommandUnitInput = Omit<z.input<typeof CommandUnitSchema>, \"command_id\">;\n\n/**\n * Commands for execute() in branches that run at the same time. Each branch runs\n * its commands in order, and the next item waits for every branch.\n */\nexport type ConcurrentCommandsInput = Omit<\n z.input<typeof ConcurrentCommandsSchema>,\n \"branch_list\"\n> & {\n branch_list: { command_list: CommandUnitInput[] }[];\n};\n\n/** One item of the command_unit_list that execute() takes. */\nexport type CommandUnitSequenceItemInput = CommandUnitInput | ConcurrentCommandsInput;\n\n/** The params a listener receives for each catalog notification. */\nexport type NotificationParams<N extends RoISNotification> = RoISNotificationMap[N][\"params\"];\n\n/** The listener arguments of every event the client emits. */\nexport type RoISClientEvents = {\n [N in RoISNotification]: [params: NotificationParams<N>];\n} & {\n close: [code: number, reason: string];\n error: [error: Error];\n};\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Raised when a RoIS operation returns a code other than OK.\n *\n * A RoIS failure is a normal result on the wire, so this error carries the\n * return code for callers to branch on (BAD_PARAMETER, UNSUPPORTED,\n * OUT_OF_RESOURCES, TIMEOUT, ERROR).\n */\nexport class RoISError extends Error {\n /** The return code of the result. */\n readonly returnCode: ReturnCode;\n\n /** The JSON-RPC method that returned it. */\n readonly method: string;\n\n constructor(returnCode: ReturnCode, method: string) {\n super(`${method} failed with ${returnCode}`);\n this.name = \"RoISError\";\n this.returnCode = returnCode;\n this.method = method;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** The params a caller passes for a catalog method, before defaults apply. */\ntype MethodParams<M extends RoISMethod> = z.input<(typeof RoISMethodSchemas)[M][\"params\"]>;\n\n/** The validated result of a catalog method. */\ntype MethodResult<M extends RoISMethod> = RoISMethodMap[M][\"result\"];\n\n/**\n * The schemas of one catalog method, typed for that method. The catalog map is\n * a record of different schema types, which TypeScript cannot index with a\n * generic key and still call, so call() reads its entry through this view.\n */\ninterface MethodSchema<M extends RoISMethod> {\n params: z.ZodType<RoISMethodMap[M][\"params\"], MethodParams<M>>;\n result: z.ZodType<MethodResult<M>>;\n}\n\n// ---------------------------------------------------------------------------\n// RoISClient\n// ---------------------------------------------------------------------------\n\n/**\n * A connection to an OpenRoIS engine, with one method per RoIS operation.\n *\n * Create it with RoISClient.connect(). One client is one session: bindings and\n * subscriptions end with it, and nothing reconnects by itself.\n *\n * Listen with on() and off():\n * \"rois.event.notify_event\" NotifyEventParams, for every event of a subscription\n * \"rois.command.completed\" CompletedParams, when a command ends\n * \"rois.system.notify_error\" NotifyErrorParams, when the engine reports an error\n * \"rois.system.profile_changed\" ProfileChangedParams, when components join or leave\n * \"close\" (code, reason) when the connection closes\n * \"error\" Error, for a message that cannot be used or a listener\n * that throws. With no listener it goes to the console.\n */\nexport class RoISClient extends TypedEmitter<RoISClientEvents> {\n /** The WebSocket transport this client sends on. */\n private readonly wire: WebSocketTransport;\n\n /** Whether the connect handshake succeeded and disconnect has not run. */\n private connected: boolean = false;\n\n /** The disconnect in flight, which overlapping callers share. */\n private disconnecting: Promise<void> | null = null;\n\n private constructor(wire: WebSocketTransport) {\n super();\n this.wire = wire;\n this.forwardTransportEvents();\n }\n\n /**\n * The underlying transport, for JSON-RPC methods outside the catalog. The\n * client does not validate what is sent through it.\n */\n get transport(): WebSocketTransport {\n return this.wire;\n }\n\n // -----------------------------------------------------------------------\n // SystemIF\n // -----------------------------------------------------------------------\n\n /**\n * Open a connection to an engine and run rois.system.connect.\n *\n * @param url - The engine's WebSocket URL, for example \"ws://localhost:8765\".\n * @throws ConnectionError if the WebSocket cannot be opened.\n * @throws RoISError if the engine answers connect with a code other than OK.\n */\n static async connect(url: string, options?: ClientOptions): Promise<RoISClient> {\n const wire = new WebSocketTransport(options?.transport);\n await wire.connect(url);\n const client = new RoISClient(wire);\n try {\n await client.request(RoISMethods.Connect, {});\n } catch (error) {\n wire.close();\n throw error;\n }\n client.connected = true;\n return client;\n }\n\n /**\n * Run rois.system.disconnect and close the connection. The connection closes\n * even when the engine answers with an error. Safe to call more than once. Calls\n * that overlap send one rois.system.disconnect and share its outcome.\n */\n async disconnect(): Promise<void> {\n this.disconnecting ??= this.endSession().finally(() => {\n this.disconnecting = null;\n });\n return this.disconnecting;\n }\n\n /** Run disconnect() when a block declared with `await using` ends. */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.disconnect();\n }\n\n /**\n * Read the engine profile and the profiles of its components.\n *\n * @param condition - Selects the components the profile lists. Empty lists all.\n */\n async getProfile(condition: string = \"\"): Promise<EngineProfile> {\n const result = await this.call(RoISMethods.GetProfile, { condition });\n if (result.profile === null) {\n throw new TransportError(\"rois.system.get_profile returned OK without a profile\");\n }\n return {\n profile: result.profile as HRIEngineProfileType,\n component_profiles: result.component_profiles ?? {},\n };\n }\n\n /**\n * Read the details of an error from a rois.system.notify_error notification.\n *\n * @param condition - Filter on the results. Must be empty for now.\n */\n async getErrorDetail(errorId: string, condition: string = \"\"): Promise<Result[]> {\n const result = await this.call(RoISMethods.GetErrorDetail, {\n error_id: errorId,\n condition,\n });\n return result.results ?? [];\n }\n\n // -----------------------------------------------------------------------\n // CommandIF\n // -----------------------------------------------------------------------\n\n /**\n * Find components.\n *\n * @param condition - Selects the components. Empty matches every component.\n * @returns Their fully qualified refs.\n */\n async search(condition: string = \"\"): Promise<string[]> {\n const result = await this.call(RoISMethods.Search, { condition });\n return result.component_ref_list ?? [];\n }\n\n /** Reserve a component for this client. */\n async bind(componentRef: string): Promise<void> {\n await this.call(RoISMethods.Bind, { component_ref: componentRef });\n }\n\n /**\n * Reserve one of the components a condition selects, chosen by the engine.\n *\n * @returns The ref of the reserved component.\n */\n async bindAny(condition: string = \"\"): Promise<string> {\n const result = await this.call(RoISMethods.BindAny, { condition });\n return result.component_ref;\n }\n\n /** Release a component reserved with bind() or bindAny(). */\n async release(componentRef: string): Promise<void> {\n await this.call(RoISMethods.Release, { component_ref: componentRef });\n }\n\n /** Read the current value of every parameter of a component. */\n async getParameter(componentRef: string): Promise<Parameter[]> {\n const result = await this.call(RoISMethods.GetParameter, {\n component_ref: componentRef,\n });\n return result.parameters ?? [];\n }\n\n /**\n * Set parameters of a component.\n *\n * @returns The command_id the engine assigned to the change. Its\n * rois.command.completed notification reports when the change took effect.\n */\n async setParameter(componentRef: string, parameters: Parameter[]): Promise<string> {\n const result = await this.call(RoISMethods.SetParameter, {\n component_ref: componentRef,\n parameters,\n });\n return result.command_id;\n }\n\n /**\n * Run commands in order. A ConcurrentCommands item runs its branches at the\n * same time, and each branch runs its commands in order.\n *\n * The engine answers as soon as it accepts the commands, with the command_id\n * it assigned to each one. Each command then ends with a\n * rois.command.completed notification carrying that command_id, which\n * arrives after the answer, and getCommandResult() reads its results.\n *\n * @param commandUnitList - The commands, without command ids.\n * @returns The command_id the engine assigned to every command, in the order\n * the list gives them, with the commands of a ConcurrentCommands item branch\n * by branch.\n */\n async execute(commandUnitList: CommandUnitSequenceItemInput[]): Promise<string[]> {\n const result = await this.call(RoISMethods.Execute, { command_unit_list: commandUnitList });\n return (result.command_unit_list ?? []).flatMap((item) =>\n \"branch_list\" in item\n ? item.branch_list.flatMap((branch) => branch.command_list.map((unit) => unit.command_id))\n : [item.command_id],\n );\n }\n\n /**\n * Read the results of a command.\n *\n * @param condition - Filter on the results. Must be empty for now.\n */\n async getCommandResult(commandId: string, condition: string = \"\"): Promise<Result[]> {\n const result = await this.call(RoISMethods.GetCommandResult, {\n command_id: commandId,\n condition,\n });\n return result.results ?? [];\n }\n\n // -----------------------------------------------------------------------\n // QueryIF\n // -----------------------------------------------------------------------\n\n /**\n * Run a query and wait for its results.\n *\n * The engine sends the query to the one component that declares `queryType`\n * and matches the condition. It returns UNSUPPORTED when none does, and\n * BAD_PARAMETER when several do.\n *\n * @param queryType - The query name from a component profile, for example\n * \"component_status\".\n * @param condition - Selects the component, for example\n * componentRef(\"reachy_real/head\").\n */\n async query(queryType: string, condition: string = \"\"): Promise<Result[]> {\n const result = await this.call(RoISMethods.Query, {\n query_type: queryType,\n condition,\n });\n return result.results ?? [];\n }\n\n // -----------------------------------------------------------------------\n // EventIF\n // -----------------------------------------------------------------------\n\n /**\n * Subscribe to an event. Its occurrences arrive as rois.event.notify_event\n * notifications, which carry the subscribe_id this returns.\n *\n * The engine subscribes to the one component that declares `eventType` and\n * matches the condition, with the same rules as query().\n *\n * @returns The subscribe_id, for unsubscribe().\n */\n async subscribe(eventType: string, condition: string = \"\"): Promise<string> {\n const result = await this.call(RoISMethods.Subscribe, {\n event_type: eventType,\n condition,\n });\n return result.subscribe_id;\n }\n\n /**\n * Cancel a subscription. The engine ignores an id it does not know, as RoIS\n * requires.\n */\n async unsubscribe(subscribeId: string): Promise<void> {\n await this.call(RoISMethods.Unsubscribe, { subscribe_id: subscribeId });\n }\n\n /**\n * Read the payload of an event until it expires.\n *\n * @param condition - Filter on the results. Must be empty for now.\n */\n async getEventDetail(eventId: string, condition: string = \"\"): Promise<Result[]> {\n const result = await this.call(RoISMethods.GetEventDetail, {\n event_id: eventId,\n condition,\n });\n return result.results ?? [];\n }\n\n // -----------------------------------------------------------------------\n // Private: calls\n // -----------------------------------------------------------------------\n\n /** The work of disconnect(), once for all the calls that overlap. */\n private async endSession(): Promise<void> {\n if (!this.connected) {\n return;\n }\n try {\n await this.call(RoISMethods.Disconnect, {});\n } finally {\n this.connected = false;\n this.wire.close();\n }\n }\n\n /** Run a catalog method on a connected client. */\n private async call<M extends RoISMethod>(\n method: M,\n params: MethodParams<M>,\n ): Promise<MethodResult<M>> {\n if (!this.connected) {\n throw new TransportError(`Cannot call ${method}: client is not connected`);\n }\n return this.request(method, params);\n }\n\n /**\n * Validate the params, send the request, validate the result and check its\n * return code. Invalid params throw before anything is sent, and a result\n * that does not match the catalog throws instead of reaching the caller.\n */\n private async request<M extends RoISMethod>(\n method: M,\n params: MethodParams<M>,\n ): Promise<MethodResult<M>> {\n const schema = RoISMethodSchemas[method] as unknown as MethodSchema<M>;\n const validParams = schema.params.parse(params) as Record<string, unknown>;\n const raw = await this.wire.send(method, validParams);\n const result = schema.result.parse(raw);\n const returnCode = (result as { return_code: ReturnCode }).return_code;\n if (returnCode !== \"OK\") {\n throw new RoISError(returnCode, method);\n }\n return result;\n }\n\n // -----------------------------------------------------------------------\n // Private: notifications\n // -----------------------------------------------------------------------\n\n /**\n * Re-emit what the transport receives. Catalog notifications are validated\n * and emitted with their params. Methods outside the catalog stay on the\n * transport's \"notification\" event.\n */\n private forwardTransportEvents(): void {\n this.wire.on(\"notification\", ({ method, params }) => {\n if (Object.hasOwn(RoISNotificationSchemas, method)) {\n this.dispatchNotification(method as RoISNotification, params);\n }\n });\n\n this.wire.on(\"close\", (code, reason) => {\n this.connected = false;\n this.emit(\"close\", code, reason);\n });\n\n this.wire.on(\"error\", (error) => {\n this.emit(\"error\", error);\n });\n }\n\n /** Validate a catalog notification and emit its params. */\n private dispatchNotification<N extends RoISNotification>(method: N, rawParams: unknown): void {\n const parsed = RoISNotificationSchemas[method].params.safeParse(rawParams ?? {});\n if (!parsed.success) {\n this.emit(\n \"error\",\n new TransportError(`Invalid params in ${method} notification: ${parsed.error.message}`),\n );\n return;\n }\n this.emit(method, ...([parsed.data] as unknown as RoISClientEvents[N]));\n }\n}\n"],"mappings":"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","names":["componentRef"]}
|