@dxos/rpc 0.10.0 → 0.11.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/dist/lib/index.mjs +948 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/src/effect-rpc.d.ts +22 -0
- package/dist/types/src/effect-rpc.d.ts.map +1 -0
- package/dist/types/src/effect-rpc.test.d.ts +2 -0
- package/dist/types/src/effect-rpc.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -11
- package/src/effect-rpc.test.ts +93 -0
- package/src/effect-rpc.ts +179 -0
- package/src/index.ts +1 -0
- package/dist/lib/neutral/index.mjs +0 -751
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
import * as RpcClient from "@effect/rpc/RpcClient";
|
|
2
|
+
import * as RpcClientError from "@effect/rpc/RpcClientError";
|
|
3
|
+
import * as RpcMessage from "@effect/rpc/RpcMessage";
|
|
4
|
+
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
5
|
+
import * as RpcServer from "@effect/rpc/RpcServer";
|
|
6
|
+
import * as Duration from "effect/Duration";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import * as Mailbox from "effect/Mailbox";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import { log } from "@dxos/log";
|
|
12
|
+
import { StackTrace } from "@dxos/debug";
|
|
13
|
+
import { RpcClosedError, RpcNotOpenError, decodeError, encodeError } from "@dxos/protocols";
|
|
14
|
+
import { Event, Trigger, asyncTimeout, synchronized } from "@dxos/async";
|
|
15
|
+
import { Stream } from "@dxos/codec-protobuf";
|
|
16
|
+
import { ContextRpcCodec } from "@dxos/context";
|
|
17
|
+
import { invariant } from "@dxos/invariant";
|
|
18
|
+
import { schema } from "@dxos/protocols/proto";
|
|
19
|
+
import { exponentialBackoffInterval, isNode } from "@dxos/util";
|
|
20
|
+
import { MessageTrace } from "@dxos/protocols/proto/dxos/rpc";
|
|
21
|
+
//#region src/effect-rpc.ts
|
|
22
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/mesh/rpc/src/effect-rpc.ts";
|
|
23
|
+
/**
|
|
24
|
+
* Interval at which the client re-sends the initial Ping while waiting for the server to attach.
|
|
25
|
+
*/
|
|
26
|
+
var HANDSHAKE_RETRY_INTERVAL = Duration.millis(50);
|
|
27
|
+
/**
|
|
28
|
+
* Effect RPC protocols over a {@link RpcPort} — a transport-agnostic, reliable, ordered,
|
|
29
|
+
* binary message channel. Message envelopes are framed with msgpack; RPC payloads are expected
|
|
30
|
+
* to already be binary-safe (e.g. protobuf-encoded by the payload schemas).
|
|
31
|
+
*/
|
|
32
|
+
var subscribePort = (port) => Effect.gen(function* () {
|
|
33
|
+
const mailbox = yield* Mailbox.make();
|
|
34
|
+
const unsubscribe = port.subscribe((message) => {
|
|
35
|
+
mailbox.unsafeOffer(message);
|
|
36
|
+
});
|
|
37
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
38
|
+
unsubscribe?.();
|
|
39
|
+
}));
|
|
40
|
+
return mailbox;
|
|
41
|
+
});
|
|
42
|
+
var sendFrame = (port, frame) => frame === void 0 || typeof frame === "string" ? Effect.dieMessage("rpc-port protocol requires binary frames") : Effect.tryPromise({
|
|
43
|
+
try: async () => port.send(frame.slice()),
|
|
44
|
+
catch: (cause) => cause instanceof Error ? cause : new Error(String(cause))
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Client-side effect-rpc protocol over an {@link RpcPort}.
|
|
48
|
+
*
|
|
49
|
+
* Performs a Ping/Pong handshake on construction: the server answers Pings as soon as it is
|
|
50
|
+
* running, so construction blocks until the peer is reachable and fails fast under an outer
|
|
51
|
+
* timeout instead of buffering requests towards a peer that never attaches.
|
|
52
|
+
*/
|
|
53
|
+
var makeProtocolRpcPortClient = (port) => RpcClient.Protocol.make(Effect.fnUntraced(function* (writeResponse) {
|
|
54
|
+
const parser = RpcSerialization.msgPack.unsafeMake();
|
|
55
|
+
const mailbox = yield* subscribePort(port);
|
|
56
|
+
const decodeFrame = (frame) => Effect.try({
|
|
57
|
+
try: () => parser.decode(frame),
|
|
58
|
+
catch: (cause) => {
|
|
59
|
+
log.warn("rpc-port client: failed to decode frame", { cause }, {
|
|
60
|
+
"~LogMeta": "~LogMeta",
|
|
61
|
+
F: __dxlog_file$2,
|
|
62
|
+
L: 75,
|
|
63
|
+
S: this
|
|
64
|
+
});
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}).pipe(Effect.merge);
|
|
68
|
+
const send = (request) => Effect.suspend(() => sendFrame(port, parser.encode(request))).pipe(Effect.mapError((cause) => new RpcClientError.RpcClientError({
|
|
69
|
+
reason: "Protocol",
|
|
70
|
+
message: "Failed to send message over RpcPort",
|
|
71
|
+
cause
|
|
72
|
+
})));
|
|
73
|
+
yield* Effect.gen(function* () {
|
|
74
|
+
let connected = false;
|
|
75
|
+
while (!connected) {
|
|
76
|
+
yield* send(RpcMessage.constPing);
|
|
77
|
+
const frame = yield* mailbox.take.pipe(Effect.timeoutOption(HANDSHAKE_RETRY_INTERVAL));
|
|
78
|
+
if (Option.isNone(frame)) continue;
|
|
79
|
+
for (const response of yield* decodeFrame(frame.value)) if (response._tag === "Pong") connected = true;
|
|
80
|
+
else yield* writeResponse(response);
|
|
81
|
+
}
|
|
82
|
+
}).pipe(Effect.orDie);
|
|
83
|
+
yield* mailbox.take.pipe(Effect.flatMap(decodeFrame), Effect.flatMap((responses) => Effect.forEach(responses, writeResponse, { discard: true })), Effect.forever, Effect.orDie, Effect.interruptible, Effect.forkScoped);
|
|
84
|
+
return {
|
|
85
|
+
send,
|
|
86
|
+
supportsAck: true,
|
|
87
|
+
supportsTransferables: false
|
|
88
|
+
};
|
|
89
|
+
}));
|
|
90
|
+
var layerProtocolRpcPortClient = (port) => Layer.scoped(RpcClient.Protocol, makeProtocolRpcPortClient(port));
|
|
91
|
+
/**
|
|
92
|
+
* Server-side effect-rpc protocol over an {@link RpcPort}.
|
|
93
|
+
* The port carries a single logical client for the lifetime of the protocol.
|
|
94
|
+
*/
|
|
95
|
+
var makeProtocolRpcPortServer = (port) => RpcServer.Protocol.make(Effect.fnUntraced(function* (writeRequest) {
|
|
96
|
+
const parser = RpcSerialization.msgPack.unsafeMake();
|
|
97
|
+
const mailbox = yield* subscribePort(port);
|
|
98
|
+
const disconnects = yield* Mailbox.make();
|
|
99
|
+
const clientId = 0;
|
|
100
|
+
yield* mailbox.take.pipe(Effect.flatMap((frame) => Effect.try({
|
|
101
|
+
try: () => parser.decode(frame),
|
|
102
|
+
catch: (cause) => {
|
|
103
|
+
log.warn("rpc-port server: failed to decode frame", { cause }, {
|
|
104
|
+
"~LogMeta": "~LogMeta",
|
|
105
|
+
F: __dxlog_file$2,
|
|
106
|
+
L: 151,
|
|
107
|
+
S: this
|
|
108
|
+
});
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}).pipe(Effect.merge)), Effect.flatMap((requests) => Effect.forEach(requests, (request) => writeRequest(clientId, request), { discard: true })), Effect.forever, Effect.interruptible, Effect.forkScoped);
|
|
112
|
+
return {
|
|
113
|
+
disconnects,
|
|
114
|
+
send: (_clientId, response) => Effect.suspend(() => sendFrame(port, parser.encode(response))).pipe(Effect.orDie),
|
|
115
|
+
end: (_clientId) => Effect.void,
|
|
116
|
+
clientIds: Effect.sync(() => /* @__PURE__ */ new Set([clientId])),
|
|
117
|
+
initialMessage: Effect.succeed(Option.none()),
|
|
118
|
+
supportsAck: true,
|
|
119
|
+
supportsTransferables: false,
|
|
120
|
+
supportsSpanPropagation: false
|
|
121
|
+
};
|
|
122
|
+
}));
|
|
123
|
+
var layerProtocolRpcPortServer = (port) => Layer.scoped(RpcServer.Protocol, makeProtocolRpcPortServer(port));
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/errors.ts
|
|
126
|
+
var decodeRpcError = (err, rpcMethod) => decodeError(err, { appendStack: `\n at RPC ${rpcMethod} \n` + new StackTrace().getStack(1) });
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
129
|
+
function __decorate(decorators, target, key, desc) {
|
|
130
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
131
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
132
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
133
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/rpc.ts
|
|
137
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/rpc/src/rpc.ts";
|
|
138
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
139
|
+
var BYE_SEND_TIMEOUT = 2e3;
|
|
140
|
+
var CLOSE_TIMEOUT = 3e3;
|
|
141
|
+
var PendingRpcRequest = class {
|
|
142
|
+
resolve;
|
|
143
|
+
reject;
|
|
144
|
+
stream;
|
|
145
|
+
constructor(resolve, reject, stream) {
|
|
146
|
+
this.resolve = resolve;
|
|
147
|
+
this.reject = reject;
|
|
148
|
+
this.stream = stream;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var RpcMessageCodec;
|
|
152
|
+
var getRpcMessageCodec = () => RpcMessageCodec ??= schema.getCodecForType("dxos.rpc.RpcMessage");
|
|
153
|
+
/**
|
|
154
|
+
* A remote procedure call peer.
|
|
155
|
+
*
|
|
156
|
+
* Provides a away to make RPC calls and get a response back as a promise.
|
|
157
|
+
* Does not handle encoding/decoding and only works with byte buffers.
|
|
158
|
+
* For type safe approach see `createRpcClient` and `createRpcServer`.
|
|
159
|
+
*
|
|
160
|
+
* Must be connected with another instance on the other side via `send`/`receive` methods.
|
|
161
|
+
* Both sides must be opened before making any RPC calls.
|
|
162
|
+
*
|
|
163
|
+
* Errors inside the handler get serialized and sent to the other side.
|
|
164
|
+
*
|
|
165
|
+
* Inspired by JSON-RPC 2.0 https://www.jsonrpc.org/specification.
|
|
166
|
+
*/
|
|
167
|
+
var RpcPeer = class {
|
|
168
|
+
_params;
|
|
169
|
+
_outgoingRequests = /* @__PURE__ */ new Map();
|
|
170
|
+
_localStreams = /* @__PURE__ */ new Map();
|
|
171
|
+
_remoteOpenTrigger = new Trigger();
|
|
172
|
+
/**
|
|
173
|
+
* Triggered when the peer starts closing.
|
|
174
|
+
*/
|
|
175
|
+
_closingTrigger = new Trigger();
|
|
176
|
+
/**
|
|
177
|
+
* Triggered when peer receives a bye message.
|
|
178
|
+
*/
|
|
179
|
+
_byeTrigger = new Trigger();
|
|
180
|
+
_nextId = 0;
|
|
181
|
+
_state = "INITIAL";
|
|
182
|
+
_unsubscribeFromPort = void 0;
|
|
183
|
+
_clearOpenInterval = void 0;
|
|
184
|
+
constructor(params) {
|
|
185
|
+
this._params = {
|
|
186
|
+
timeout: void 0,
|
|
187
|
+
streamHandler: void 0,
|
|
188
|
+
noHandshake: false,
|
|
189
|
+
...params
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Open the peer. Required before making any calls.
|
|
194
|
+
*
|
|
195
|
+
* Will block before the other peer calls `open`.
|
|
196
|
+
*/
|
|
197
|
+
async open() {
|
|
198
|
+
if (this._state !== "INITIAL") return;
|
|
199
|
+
this._unsubscribeFromPort = this._params.port.subscribe(async (msg) => {
|
|
200
|
+
try {
|
|
201
|
+
await this._receive(msg);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
log.catch(err, void 0, {
|
|
204
|
+
"~LogMeta": "~LogMeta",
|
|
205
|
+
F: __dxlog_file$1,
|
|
206
|
+
L: 157,
|
|
207
|
+
S: this
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
this._state = "OPENING";
|
|
212
|
+
if (this._params.noHandshake) {
|
|
213
|
+
this._state = "OPENED";
|
|
214
|
+
this._remoteOpenTrigger.wake();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
log("sending open message", { state: this._state }, {
|
|
218
|
+
"~LogMeta": "~LogMeta",
|
|
219
|
+
F: __dxlog_file$1,
|
|
220
|
+
L: 169,
|
|
221
|
+
S: this
|
|
222
|
+
});
|
|
223
|
+
await this._sendMessage({ open: true });
|
|
224
|
+
if (this._state !== "OPENING") return;
|
|
225
|
+
this._clearOpenInterval = exponentialBackoffInterval(() => {
|
|
226
|
+
this._sendMessage({ open: true }).catch((err) => log.warn(err, void 0, {
|
|
227
|
+
"~LogMeta": "~LogMeta",
|
|
228
|
+
F: __dxlog_file$1,
|
|
229
|
+
L: 178,
|
|
230
|
+
S: this
|
|
231
|
+
}));
|
|
232
|
+
}, 50);
|
|
233
|
+
await Promise.race([this._remoteOpenTrigger.wait(), this._closingTrigger.wait()]);
|
|
234
|
+
this._clearOpenInterval?.();
|
|
235
|
+
if (this._state !== "OPENED") return;
|
|
236
|
+
log("resending open message", { state: this._state }, {
|
|
237
|
+
"~LogMeta": "~LogMeta",
|
|
238
|
+
F: __dxlog_file$1,
|
|
239
|
+
L: 192,
|
|
240
|
+
S: this
|
|
241
|
+
});
|
|
242
|
+
await this._sendMessage({ openAck: true });
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Close the peer.
|
|
246
|
+
* Stop taking or making requests.
|
|
247
|
+
* Will wait for confirmation from the other side.
|
|
248
|
+
* Any responses for RPC calls made before close will be delivered.
|
|
249
|
+
*/
|
|
250
|
+
async close({ timeout = CLOSE_TIMEOUT } = {}) {
|
|
251
|
+
if (this._state === "CLOSED") return;
|
|
252
|
+
this._abortRequests();
|
|
253
|
+
if (this._state === "OPENED" && !this._params.noHandshake) {
|
|
254
|
+
try {
|
|
255
|
+
this._state = "CLOSING";
|
|
256
|
+
await this._sendMessage({ bye: {} }, BYE_SEND_TIMEOUT);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
log("error closing peer, sending bye", { err }, {
|
|
259
|
+
"~LogMeta": "~LogMeta",
|
|
260
|
+
F: __dxlog_file$1,
|
|
261
|
+
L: 214,
|
|
262
|
+
S: this
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
log("closing waiting on bye", void 0, {
|
|
267
|
+
"~LogMeta": "~LogMeta",
|
|
268
|
+
F: __dxlog_file$1,
|
|
269
|
+
L: 217,
|
|
270
|
+
S: this
|
|
271
|
+
});
|
|
272
|
+
await this._byeTrigger.wait({ timeout });
|
|
273
|
+
} catch (err) {
|
|
274
|
+
log("error closing peer", { err }, {
|
|
275
|
+
"~LogMeta": "~LogMeta",
|
|
276
|
+
F: __dxlog_file$1,
|
|
277
|
+
L: 220,
|
|
278
|
+
S: this
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this._disposeAndClose();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Dispose the connection without waiting for the other side.
|
|
287
|
+
*/
|
|
288
|
+
async abort() {
|
|
289
|
+
if (this._state === "CLOSED") return;
|
|
290
|
+
this._abortRequests();
|
|
291
|
+
this._disposeAndClose();
|
|
292
|
+
}
|
|
293
|
+
_abortRequests() {
|
|
294
|
+
this._clearOpenInterval?.();
|
|
295
|
+
this._closingTrigger.wake();
|
|
296
|
+
for (const req of this._outgoingRequests.values()) req.reject(new RpcClosedError());
|
|
297
|
+
this._outgoingRequests.clear();
|
|
298
|
+
}
|
|
299
|
+
_disposeAndClose() {
|
|
300
|
+
this._unsubscribeFromPort?.();
|
|
301
|
+
this._unsubscribeFromPort = void 0;
|
|
302
|
+
this._clearOpenInterval?.();
|
|
303
|
+
this._state = "CLOSED";
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Handle incoming message. Should be called as the result of other peer's `send` callback.
|
|
307
|
+
*/
|
|
308
|
+
async _receive(msg) {
|
|
309
|
+
const decoded = getRpcMessageCodec().decode(msg, { preserveAny: true });
|
|
310
|
+
log.trace("received message", { type: Object.keys(decoded)[0] }, {
|
|
311
|
+
"~LogMeta": "~LogMeta",
|
|
312
|
+
F: __dxlog_file$1,
|
|
313
|
+
L: 264,
|
|
314
|
+
S: this
|
|
315
|
+
});
|
|
316
|
+
if (decoded.request) {
|
|
317
|
+
if (this._state !== "OPENED" && this._state !== "OPENING") {
|
|
318
|
+
log("received request while closed", void 0, {
|
|
319
|
+
"~LogMeta": "~LogMeta",
|
|
320
|
+
F: __dxlog_file$1,
|
|
321
|
+
L: 268,
|
|
322
|
+
S: this
|
|
323
|
+
});
|
|
324
|
+
await this._sendMessage({ response: {
|
|
325
|
+
id: decoded.request.id,
|
|
326
|
+
error: encodeError(new RpcClosedError())
|
|
327
|
+
} });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const req = decoded.request;
|
|
331
|
+
if (req.stream) {
|
|
332
|
+
log("stream request", { method: req.method }, {
|
|
333
|
+
"~LogMeta": "~LogMeta",
|
|
334
|
+
F: __dxlog_file$1,
|
|
335
|
+
L: 280,
|
|
336
|
+
S: this
|
|
337
|
+
});
|
|
338
|
+
this._callStreamHandler(req, (response) => {
|
|
339
|
+
log.trace("sending stream response", {
|
|
340
|
+
method: req.method,
|
|
341
|
+
response: response.payload?.type_url,
|
|
342
|
+
error: response.error,
|
|
343
|
+
close: response.close
|
|
344
|
+
}, {
|
|
345
|
+
"~LogMeta": "~LogMeta",
|
|
346
|
+
F: __dxlog_file$1,
|
|
347
|
+
L: 282,
|
|
348
|
+
S: this
|
|
349
|
+
});
|
|
350
|
+
this._sendMessage({ response }).catch((err) => {
|
|
351
|
+
log.warn("failed during close", err, {
|
|
352
|
+
"~LogMeta": "~LogMeta",
|
|
353
|
+
F: __dxlog_file$1,
|
|
354
|
+
L: 290,
|
|
355
|
+
S: this
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
log.trace("requesting...", { method: req.method }, {
|
|
361
|
+
"~LogMeta": "~LogMeta",
|
|
362
|
+
F: __dxlog_file$1,
|
|
363
|
+
L: 294,
|
|
364
|
+
S: this
|
|
365
|
+
});
|
|
366
|
+
const response = await this._callHandler(req);
|
|
367
|
+
log.trace("sending response", {
|
|
368
|
+
method: req.method,
|
|
369
|
+
response: response.payload?.type_url,
|
|
370
|
+
error: response.error
|
|
371
|
+
}, {
|
|
372
|
+
"~LogMeta": "~LogMeta",
|
|
373
|
+
F: __dxlog_file$1,
|
|
374
|
+
L: 297,
|
|
375
|
+
S: this
|
|
376
|
+
});
|
|
377
|
+
await this._sendMessage({ response });
|
|
378
|
+
}
|
|
379
|
+
} else if (decoded.response) {
|
|
380
|
+
if (this._state !== "OPENED") {
|
|
381
|
+
log("received response while closed", void 0, {
|
|
382
|
+
"~LogMeta": "~LogMeta",
|
|
383
|
+
F: __dxlog_file$1,
|
|
384
|
+
L: 306,
|
|
385
|
+
S: this
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const responseId = decoded.response.id;
|
|
390
|
+
invariant(typeof responseId === "number", void 0, {
|
|
391
|
+
"~LogMeta": "~LogMeta",
|
|
392
|
+
F: __dxlog_file$1,
|
|
393
|
+
L: 311,
|
|
394
|
+
S: this,
|
|
395
|
+
A: ["typeof responseId === 'number'", ""]
|
|
396
|
+
});
|
|
397
|
+
if (!this._outgoingRequests.has(responseId)) {
|
|
398
|
+
log.trace("received response with invalid id", { responseId }, {
|
|
399
|
+
"~LogMeta": "~LogMeta",
|
|
400
|
+
F: __dxlog_file$1,
|
|
401
|
+
L: 313,
|
|
402
|
+
S: this
|
|
403
|
+
});
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const item = this._outgoingRequests.get(responseId);
|
|
407
|
+
if (!item.stream) this._outgoingRequests.delete(responseId);
|
|
408
|
+
log.trace("response", { type_url: decoded.response.payload?.type_url }, {
|
|
409
|
+
"~LogMeta": "~LogMeta",
|
|
410
|
+
F: __dxlog_file$1,
|
|
411
|
+
L: 323,
|
|
412
|
+
S: this
|
|
413
|
+
});
|
|
414
|
+
item.resolve(decoded.response);
|
|
415
|
+
} else if (decoded.open) {
|
|
416
|
+
log("received open message", { state: this._state }, {
|
|
417
|
+
"~LogMeta": "~LogMeta",
|
|
418
|
+
F: __dxlog_file$1,
|
|
419
|
+
L: 326,
|
|
420
|
+
S: this
|
|
421
|
+
});
|
|
422
|
+
if (this._params.noHandshake) return;
|
|
423
|
+
await this._sendMessage({ openAck: true });
|
|
424
|
+
} else if (decoded.openAck) {
|
|
425
|
+
log("received openAck message", { state: this._state }, {
|
|
426
|
+
"~LogMeta": "~LogMeta",
|
|
427
|
+
F: __dxlog_file$1,
|
|
428
|
+
L: 333,
|
|
429
|
+
S: this
|
|
430
|
+
});
|
|
431
|
+
if (this._params.noHandshake) return;
|
|
432
|
+
this._state = "OPENED";
|
|
433
|
+
this._remoteOpenTrigger.wake();
|
|
434
|
+
} else if (decoded.streamClose) {
|
|
435
|
+
if (this._state !== "OPENED") {
|
|
436
|
+
log("received stream close while closed", void 0, {
|
|
437
|
+
"~LogMeta": "~LogMeta",
|
|
438
|
+
F: __dxlog_file$1,
|
|
439
|
+
L: 342,
|
|
440
|
+
S: this
|
|
441
|
+
});
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
log("received stream close", { id: decoded.streamClose.id }, {
|
|
445
|
+
"~LogMeta": "~LogMeta",
|
|
446
|
+
F: __dxlog_file$1,
|
|
447
|
+
L: 346,
|
|
448
|
+
S: this
|
|
449
|
+
});
|
|
450
|
+
invariant(typeof decoded.streamClose.id === "number", void 0, {
|
|
451
|
+
"~LogMeta": "~LogMeta",
|
|
452
|
+
F: __dxlog_file$1,
|
|
453
|
+
L: 347,
|
|
454
|
+
S: this,
|
|
455
|
+
A: ["typeof decoded.streamClose.id === 'number'", ""]
|
|
456
|
+
});
|
|
457
|
+
const stream = this._localStreams.get(decoded.streamClose.id);
|
|
458
|
+
if (!stream) {
|
|
459
|
+
log("no local stream", { id: decoded.streamClose.id }, {
|
|
460
|
+
"~LogMeta": "~LogMeta",
|
|
461
|
+
F: __dxlog_file$1,
|
|
462
|
+
L: 350,
|
|
463
|
+
S: this
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
this._localStreams.delete(decoded.streamClose.id);
|
|
468
|
+
await stream.close();
|
|
469
|
+
} else if (decoded.bye) {
|
|
470
|
+
this._byeTrigger.wake();
|
|
471
|
+
if (this._state !== "CLOSING" && this._state !== "CLOSED") {
|
|
472
|
+
log("replying to bye", void 0, {
|
|
473
|
+
"~LogMeta": "~LogMeta",
|
|
474
|
+
F: __dxlog_file$1,
|
|
475
|
+
L: 360,
|
|
476
|
+
S: this
|
|
477
|
+
});
|
|
478
|
+
this._state = "CLOSING";
|
|
479
|
+
await this._sendMessage({ bye: {} });
|
|
480
|
+
this._abortRequests();
|
|
481
|
+
this._disposeAndClose();
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
log.error("received malformed message", { msg }, {
|
|
485
|
+
"~LogMeta": "~LogMeta",
|
|
486
|
+
F: __dxlog_file$1,
|
|
487
|
+
L: 368,
|
|
488
|
+
S: this
|
|
489
|
+
});
|
|
490
|
+
throw new Error("Malformed message.");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Make RPC call. Will trigger a handler on the other side.
|
|
495
|
+
* Peer should be open before making this call.
|
|
496
|
+
*/
|
|
497
|
+
async call(method, request, options) {
|
|
498
|
+
log.trace("calling...", { method }, {
|
|
499
|
+
"~LogMeta": "~LogMeta",
|
|
500
|
+
F: __dxlog_file$1,
|
|
501
|
+
L: 378,
|
|
502
|
+
S: this
|
|
503
|
+
});
|
|
504
|
+
throwIfNotOpen(this._state);
|
|
505
|
+
let response;
|
|
506
|
+
try {
|
|
507
|
+
const id = this._nextId++;
|
|
508
|
+
const responseReceived = new Promise((resolve, reject) => {
|
|
509
|
+
this._outgoingRequests.set(id, new PendingRpcRequest(resolve, reject, false));
|
|
510
|
+
});
|
|
511
|
+
let traceContext;
|
|
512
|
+
try {
|
|
513
|
+
traceContext = options?.ctx ? ContextRpcCodec.encode(options.ctx) : void 0;
|
|
514
|
+
} catch (err) {
|
|
515
|
+
log.warn("failed to encode trace context", { err }, {
|
|
516
|
+
"~LogMeta": "~LogMeta",
|
|
517
|
+
F: __dxlog_file$1,
|
|
518
|
+
L: 393,
|
|
519
|
+
S: this
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const sending = this._sendMessage({ request: {
|
|
523
|
+
id,
|
|
524
|
+
method,
|
|
525
|
+
payload: request,
|
|
526
|
+
stream: false,
|
|
527
|
+
...traceContext ? { traceContext } : {}
|
|
528
|
+
} });
|
|
529
|
+
const timeout = options?.timeout ?? this._params.timeout;
|
|
530
|
+
const waiting = timeout === 0 ? responseReceived : asyncTimeout(responseReceived, timeout ?? DEFAULT_TIMEOUT);
|
|
531
|
+
await Promise.race([sending, waiting]);
|
|
532
|
+
response = await waiting;
|
|
533
|
+
invariant(response.id === id, void 0, {
|
|
534
|
+
"~LogMeta": "~LogMeta",
|
|
535
|
+
F: __dxlog_file$1,
|
|
536
|
+
L: 414,
|
|
537
|
+
S: this,
|
|
538
|
+
A: ["response.id === id", ""]
|
|
539
|
+
});
|
|
540
|
+
} catch (err) {
|
|
541
|
+
if (err instanceof RpcClosedError) {
|
|
542
|
+
const error = new RpcClosedError();
|
|
543
|
+
error.stack += `\n\n info: RPC client was closed at:\n${err.stack?.split("\n").slice(1).join("\n")}`;
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
548
|
+
if (response.payload) return response.payload;
|
|
549
|
+
else if (response.error) throw decodeRpcError(response.error, method);
|
|
550
|
+
else throw new Error("Malformed response.");
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Make RPC call with a streaming response.
|
|
554
|
+
* Will trigger a handler on the other side.
|
|
555
|
+
* Peer should be open before making this call.
|
|
556
|
+
*/
|
|
557
|
+
callStream(method, request, options) {
|
|
558
|
+
throwIfNotOpen(this._state);
|
|
559
|
+
const id = this._nextId++;
|
|
560
|
+
return new Stream(({ ready, next, close }) => {
|
|
561
|
+
const onResponse = (response) => {
|
|
562
|
+
if (response.streamReady) ready();
|
|
563
|
+
else if (response.close) close();
|
|
564
|
+
else if (response.error) close(decodeRpcError(response.error, method));
|
|
565
|
+
else if (response.payload) next(response.payload);
|
|
566
|
+
else throw new Error("Malformed response.");
|
|
567
|
+
};
|
|
568
|
+
const stack = new StackTrace();
|
|
569
|
+
const closeStream = (err) => {
|
|
570
|
+
if (!err) close();
|
|
571
|
+
else {
|
|
572
|
+
err.stack += `\n\nError happened in the stream at:\n${stack.getStack()}`;
|
|
573
|
+
close(err);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
this._outgoingRequests.set(id, new PendingRpcRequest(onResponse, closeStream, true));
|
|
577
|
+
let traceContext;
|
|
578
|
+
try {
|
|
579
|
+
traceContext = options?.ctx ? ContextRpcCodec.encode(options.ctx) : void 0;
|
|
580
|
+
} catch (err) {
|
|
581
|
+
log.warn("failed to encode trace context", { err }, {
|
|
582
|
+
"~LogMeta": "~LogMeta",
|
|
583
|
+
F: __dxlog_file$1,
|
|
584
|
+
L: 476,
|
|
585
|
+
S: this
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
this._sendMessage({ request: {
|
|
590
|
+
id,
|
|
591
|
+
method,
|
|
592
|
+
payload: request,
|
|
593
|
+
stream: true,
|
|
594
|
+
...traceContext ? { traceContext } : {}
|
|
595
|
+
} }).catch((err) => {
|
|
596
|
+
this._outgoingRequests.delete(id);
|
|
597
|
+
close(err);
|
|
598
|
+
});
|
|
599
|
+
} catch (err) {
|
|
600
|
+
this._outgoingRequests.delete(id);
|
|
601
|
+
throw err;
|
|
602
|
+
}
|
|
603
|
+
return () => {
|
|
604
|
+
this._sendMessage({ streamClose: { id } }).catch((err) => {
|
|
605
|
+
log.catch(err, void 0, {
|
|
606
|
+
"~LogMeta": "~LogMeta",
|
|
607
|
+
F: __dxlog_file$1,
|
|
608
|
+
L: 501,
|
|
609
|
+
S: this
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
this._outgoingRequests.delete(id);
|
|
613
|
+
};
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
async _sendMessage(message, timeout) {
|
|
617
|
+
log.trace("sending message", { type: Object.keys(message)[0] }, {
|
|
618
|
+
"~LogMeta": "~LogMeta",
|
|
619
|
+
F: __dxlog_file$1,
|
|
620
|
+
L: 509,
|
|
621
|
+
S: this
|
|
622
|
+
});
|
|
623
|
+
await this._params.port.send(getRpcMessageCodec().encode(message, { preserveAny: true }), timeout);
|
|
624
|
+
}
|
|
625
|
+
_getHandlerRpcOptions(req) {
|
|
626
|
+
let traceCtx;
|
|
627
|
+
if (req.traceContext) try {
|
|
628
|
+
traceCtx = ContextRpcCodec.decode(req.traceContext);
|
|
629
|
+
} catch (err) {
|
|
630
|
+
log.warn("failed to decode trace context", {
|
|
631
|
+
traceContext: req.traceContext,
|
|
632
|
+
err
|
|
633
|
+
}, {
|
|
634
|
+
"~LogMeta": "~LogMeta",
|
|
635
|
+
F: __dxlog_file$1,
|
|
636
|
+
L: 519,
|
|
637
|
+
S: this
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (!traceCtx && !this._params.handlerRpcOptions) return;
|
|
641
|
+
return {
|
|
642
|
+
...this._params.handlerRpcOptions,
|
|
643
|
+
...traceCtx ? { ctx: traceCtx } : {}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
async _callHandler(req) {
|
|
647
|
+
try {
|
|
648
|
+
invariant(typeof req.id === "number", void 0, {
|
|
649
|
+
"~LogMeta": "~LogMeta",
|
|
650
|
+
F: __dxlog_file$1,
|
|
651
|
+
L: 530,
|
|
652
|
+
S: this,
|
|
653
|
+
A: ["typeof req.id === 'number'", ""]
|
|
654
|
+
});
|
|
655
|
+
invariant(req.payload, void 0, {
|
|
656
|
+
"~LogMeta": "~LogMeta",
|
|
657
|
+
F: __dxlog_file$1,
|
|
658
|
+
L: 531,
|
|
659
|
+
S: this,
|
|
660
|
+
A: ["req.payload", ""]
|
|
661
|
+
});
|
|
662
|
+
invariant(req.method, void 0, {
|
|
663
|
+
"~LogMeta": "~LogMeta",
|
|
664
|
+
F: __dxlog_file$1,
|
|
665
|
+
L: 532,
|
|
666
|
+
S: this,
|
|
667
|
+
A: ["req.method", ""]
|
|
668
|
+
});
|
|
669
|
+
const response = await this._params.callHandler(req.method, req.payload, this._getHandlerRpcOptions(req));
|
|
670
|
+
return {
|
|
671
|
+
id: req.id,
|
|
672
|
+
payload: response
|
|
673
|
+
};
|
|
674
|
+
} catch (err) {
|
|
675
|
+
return {
|
|
676
|
+
id: req.id,
|
|
677
|
+
error: encodeError(err)
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
_callStreamHandler(req, callback) {
|
|
682
|
+
try {
|
|
683
|
+
invariant(this._params.streamHandler, "Requests with streaming responses are not supported.", {
|
|
684
|
+
"~LogMeta": "~LogMeta",
|
|
685
|
+
F: __dxlog_file$1,
|
|
686
|
+
L: 549,
|
|
687
|
+
S: this,
|
|
688
|
+
A: ["this._params.streamHandler", "'Requests with streaming responses are not supported.'"]
|
|
689
|
+
});
|
|
690
|
+
invariant(typeof req.id === "number", void 0, {
|
|
691
|
+
"~LogMeta": "~LogMeta",
|
|
692
|
+
F: __dxlog_file$1,
|
|
693
|
+
L: 550,
|
|
694
|
+
S: this,
|
|
695
|
+
A: ["typeof req.id === 'number'", ""]
|
|
696
|
+
});
|
|
697
|
+
invariant(req.payload, void 0, {
|
|
698
|
+
"~LogMeta": "~LogMeta",
|
|
699
|
+
F: __dxlog_file$1,
|
|
700
|
+
L: 551,
|
|
701
|
+
S: this,
|
|
702
|
+
A: ["req.payload", ""]
|
|
703
|
+
});
|
|
704
|
+
invariant(req.method, void 0, {
|
|
705
|
+
"~LogMeta": "~LogMeta",
|
|
706
|
+
F: __dxlog_file$1,
|
|
707
|
+
L: 552,
|
|
708
|
+
S: this,
|
|
709
|
+
A: ["req.method", ""]
|
|
710
|
+
});
|
|
711
|
+
const responseStream = this._params.streamHandler(req.method, req.payload, this._getHandlerRpcOptions(req));
|
|
712
|
+
responseStream.onReady(() => {
|
|
713
|
+
callback({
|
|
714
|
+
id: req.id,
|
|
715
|
+
streamReady: true
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
responseStream.subscribe((msg) => {
|
|
719
|
+
callback({
|
|
720
|
+
id: req.id,
|
|
721
|
+
payload: msg
|
|
722
|
+
});
|
|
723
|
+
}, (error) => {
|
|
724
|
+
if (error) callback({
|
|
725
|
+
id: req.id,
|
|
726
|
+
error: encodeError(error)
|
|
727
|
+
});
|
|
728
|
+
else callback({
|
|
729
|
+
id: req.id,
|
|
730
|
+
close: true
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
this._localStreams.set(req.id, responseStream);
|
|
734
|
+
} catch (err) {
|
|
735
|
+
callback({
|
|
736
|
+
id: req.id,
|
|
737
|
+
error: encodeError(err)
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
__decorate([synchronized], RpcPeer.prototype, "open", null);
|
|
743
|
+
var throwIfNotOpen = (state) => {
|
|
744
|
+
switch (state) {
|
|
745
|
+
case "OPENED": return;
|
|
746
|
+
case "INITIAL": throw new RpcNotOpenError();
|
|
747
|
+
case "CLOSED": throw new RpcClosedError();
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region src/service.ts
|
|
752
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/rpc/src/service.ts";
|
|
753
|
+
/**
|
|
754
|
+
* Groups multiple services together to be served by a single RPC peer.
|
|
755
|
+
*/
|
|
756
|
+
var createServiceBundle = (services) => services;
|
|
757
|
+
/**
|
|
758
|
+
* Type-safe RPC peer.
|
|
759
|
+
*/
|
|
760
|
+
var ProtoRpcPeer = class {
|
|
761
|
+
rpc;
|
|
762
|
+
_peer;
|
|
763
|
+
constructor(rpc, _peer) {
|
|
764
|
+
this.rpc = rpc;
|
|
765
|
+
this._peer = _peer;
|
|
766
|
+
}
|
|
767
|
+
async open() {
|
|
768
|
+
await this._peer.open();
|
|
769
|
+
}
|
|
770
|
+
async close() {
|
|
771
|
+
await this._peer.close();
|
|
772
|
+
}
|
|
773
|
+
async abort() {
|
|
774
|
+
await this._peer.abort();
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
/**
|
|
778
|
+
* Create type-safe RPC peer from a service bundle.
|
|
779
|
+
* Can both handle and issue requests.
|
|
780
|
+
*/
|
|
781
|
+
var createProtoRpcPeer = ({ requested, exposed, handlers, encodingOptions, ...rest }) => {
|
|
782
|
+
const exposedRpcs = {};
|
|
783
|
+
if (exposed) {
|
|
784
|
+
invariant(handlers, void 0, {
|
|
785
|
+
"~LogMeta": "~LogMeta",
|
|
786
|
+
F: __dxlog_file,
|
|
787
|
+
L: 93,
|
|
788
|
+
S: void 0,
|
|
789
|
+
A: ["handlers", ""]
|
|
790
|
+
});
|
|
791
|
+
for (const serviceName of Object.keys(exposed)) {
|
|
792
|
+
const serviceFqn = exposed[serviceName].serviceProto.fullName.slice(1);
|
|
793
|
+
const serviceProvider = handlers[serviceName];
|
|
794
|
+
exposedRpcs[serviceFqn] = exposed[serviceName].createServer(serviceProvider, encodingOptions);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const peer = new RpcPeer({
|
|
798
|
+
...rest,
|
|
799
|
+
callHandler: (method, request, options) => {
|
|
800
|
+
const [serviceName, methodName] = parseMethodName(method);
|
|
801
|
+
if (!exposedRpcs[serviceName]) throw new Error(`Service not supported: ${serviceName}`);
|
|
802
|
+
return exposedRpcs[serviceName].call(methodName, request, options);
|
|
803
|
+
},
|
|
804
|
+
streamHandler: (method, request, options) => {
|
|
805
|
+
const [serviceName, methodName] = parseMethodName(method);
|
|
806
|
+
if (!exposedRpcs[serviceName]) throw new Error(`Service not supported: ${serviceName}`);
|
|
807
|
+
return exposedRpcs[serviceName].callStream(methodName, request, options);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
const requestedRpcs = {};
|
|
811
|
+
if (requested) for (const serviceName of Object.keys(requested)) {
|
|
812
|
+
const serviceFqn = requested[serviceName].serviceProto.fullName.slice(1);
|
|
813
|
+
requestedRpcs[serviceName] = requested[serviceName].createClient({
|
|
814
|
+
call: (method, req, options) => peer.call(`${serviceFqn}.${method}`, req, options),
|
|
815
|
+
callStream: (method, req, options) => peer.callStream(`${serviceFqn}.${method}`, req, options)
|
|
816
|
+
}, encodingOptions);
|
|
817
|
+
}
|
|
818
|
+
return new ProtoRpcPeer(requestedRpcs, peer);
|
|
819
|
+
};
|
|
820
|
+
var parseMethodName = (method) => {
|
|
821
|
+
const separator = method.lastIndexOf(".");
|
|
822
|
+
const serviceName = method.slice(0, separator);
|
|
823
|
+
const methodName = method.slice(separator + 1);
|
|
824
|
+
if (serviceName.length === 0 || methodName.length === 0) throw new Error(`Invalid method: ${method}`);
|
|
825
|
+
return [serviceName, methodName];
|
|
826
|
+
};
|
|
827
|
+
/**
|
|
828
|
+
* Create a type-safe RPC client.
|
|
829
|
+
* @deprecated Use createProtoRpcPeer instead.
|
|
830
|
+
*/
|
|
831
|
+
var createRpcClient = (serviceDef, options) => {
|
|
832
|
+
const peer = new RpcPeer({
|
|
833
|
+
...options,
|
|
834
|
+
callHandler: () => {
|
|
835
|
+
throw new Error("Requests to client are not supported.");
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
return new ProtoRpcPeer(serviceDef.createClient({
|
|
839
|
+
call: peer.call.bind(peer),
|
|
840
|
+
callStream: peer.callStream.bind(peer)
|
|
841
|
+
}), peer);
|
|
842
|
+
};
|
|
843
|
+
/**
|
|
844
|
+
* Create a type-safe RPC server.
|
|
845
|
+
* @deprecated Use createProtoRpcPeer instead.
|
|
846
|
+
*/
|
|
847
|
+
var createRpcServer = ({ service, handlers, ...rest }) => {
|
|
848
|
+
const server = service.createServer(handlers);
|
|
849
|
+
return new RpcPeer({
|
|
850
|
+
...rest,
|
|
851
|
+
callHandler: server.call.bind(server),
|
|
852
|
+
streamHandler: server.callStream.bind(server)
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
/**
|
|
856
|
+
* Create type-safe RPC client from a service bundle.
|
|
857
|
+
* @deprecated Use createProtoRpcPeer instead.
|
|
858
|
+
*/
|
|
859
|
+
var createBundledRpcClient = (descriptors, options) => {
|
|
860
|
+
return createProtoRpcPeer({
|
|
861
|
+
requested: descriptors,
|
|
862
|
+
...options
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
/**
|
|
866
|
+
* Create type-safe RPC server from a service bundle.
|
|
867
|
+
* @deprecated Use createProtoRpcPeer instead.
|
|
868
|
+
*/
|
|
869
|
+
var createBundledRpcServer = ({ services, handlers, ...rest }) => {
|
|
870
|
+
const rpc = {};
|
|
871
|
+
for (const serviceName of Object.keys(services)) {
|
|
872
|
+
const serviceFqn = services[serviceName].serviceProto.fullName.slice(1);
|
|
873
|
+
rpc[serviceFqn] = services[serviceName].createServer(handlers[serviceName]);
|
|
874
|
+
}
|
|
875
|
+
return new RpcPeer({
|
|
876
|
+
...rest,
|
|
877
|
+
callHandler: (method, request) => {
|
|
878
|
+
const [serviceName, methodName] = parseMethodName(method);
|
|
879
|
+
if (!rpc[serviceName]) throw new Error(`Service not supported: ${serviceName}`);
|
|
880
|
+
return rpc[serviceName].call(methodName, request);
|
|
881
|
+
},
|
|
882
|
+
streamHandler: (method, request) => {
|
|
883
|
+
const [serviceName, methodName] = parseMethodName(method);
|
|
884
|
+
if (!rpc[serviceName]) throw new Error(`Service not supported: ${serviceName}`);
|
|
885
|
+
return rpc[serviceName].callStream(methodName, request);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
};
|
|
889
|
+
//#endregion
|
|
890
|
+
//#region src/testing.ts
|
|
891
|
+
/**
|
|
892
|
+
* Create bi-directionally linked ports.
|
|
893
|
+
*/
|
|
894
|
+
var createLinkedPorts = ({ delay } = {}) => {
|
|
895
|
+
let port1Received;
|
|
896
|
+
let port2Received;
|
|
897
|
+
const send = (handler, msg) => {
|
|
898
|
+
if (delay) setTimeout(() => handler?.(msg), delay);
|
|
899
|
+
else handler?.(msg);
|
|
900
|
+
};
|
|
901
|
+
return [{
|
|
902
|
+
send: (msg) => send(port2Received, msg),
|
|
903
|
+
subscribe: (cb) => {
|
|
904
|
+
port1Received = cb;
|
|
905
|
+
}
|
|
906
|
+
}, {
|
|
907
|
+
send: (msg) => send(port1Received, msg),
|
|
908
|
+
subscribe: (cb) => {
|
|
909
|
+
port2Received = cb;
|
|
910
|
+
}
|
|
911
|
+
}];
|
|
912
|
+
};
|
|
913
|
+
var encodeMessage = (msg) => isNode() ? Buffer.from(msg) : new TextEncoder().encode(msg);
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/trace.ts
|
|
916
|
+
var PortTracer = class {
|
|
917
|
+
_wrappedPort;
|
|
918
|
+
message = new Event();
|
|
919
|
+
_port;
|
|
920
|
+
constructor(_wrappedPort) {
|
|
921
|
+
this._wrappedPort = _wrappedPort;
|
|
922
|
+
this._port = {
|
|
923
|
+
send: (msg) => {
|
|
924
|
+
this.message.emit({
|
|
925
|
+
direction: MessageTrace.Direction.OUTGOING,
|
|
926
|
+
data: msg
|
|
927
|
+
});
|
|
928
|
+
return this._wrappedPort.send(msg);
|
|
929
|
+
},
|
|
930
|
+
subscribe: (cb) => {
|
|
931
|
+
return this._wrappedPort.subscribe((msg) => {
|
|
932
|
+
this.message.emit({
|
|
933
|
+
direction: MessageTrace.Direction.INCOMING,
|
|
934
|
+
data: msg
|
|
935
|
+
});
|
|
936
|
+
cb(msg);
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
get port() {
|
|
942
|
+
return this._port;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
//#endregion
|
|
946
|
+
export { PortTracer, ProtoRpcPeer, RpcPeer, createBundledRpcClient, createBundledRpcServer, createLinkedPorts, createProtoRpcPeer, createRpcClient, createRpcServer, createServiceBundle, decodeRpcError, encodeMessage, layerProtocolRpcPortClient, layerProtocolRpcPortServer, makeProtocolRpcPortClient, makeProtocolRpcPortServer, parseMethodName };
|
|
947
|
+
|
|
948
|
+
//# sourceMappingURL=index.mjs.map
|