@dxos/rpc 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/neutral/index.mjs +111 -283
- package/dist/lib/neutral/index.mjs.map +3 -3
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/rpc.d.ts +1 -3
- package/dist/types/src/rpc.d.ts.map +1 -1
- package/dist/types/src/service.d.ts +2 -2
- package/dist/types/src/service.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/src/trace.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -12
- package/src/rpc.ts +58 -20
- package/src/service.test.ts +125 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/rpc",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.abd8ff62ef",
|
|
4
4
|
"description": "A lightweight, transport-agnostic RPC implementation",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -20,22 +20,20 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"types": "dist/types/src/index.d.ts",
|
|
23
|
-
"typesVersions": {
|
|
24
|
-
"*": {}
|
|
25
|
-
},
|
|
26
23
|
"files": [
|
|
27
24
|
"dist",
|
|
28
25
|
"src"
|
|
29
26
|
],
|
|
30
27
|
"dependencies": {
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/util": "0.8.4-main.
|
|
28
|
+
"@dxos/async": "0.8.4-main.abd8ff62ef",
|
|
29
|
+
"@dxos/context": "0.8.4-main.abd8ff62ef",
|
|
30
|
+
"@dxos/codec-protobuf": "0.8.4-main.abd8ff62ef",
|
|
31
|
+
"@dxos/invariant": "0.8.4-main.abd8ff62ef",
|
|
32
|
+
"@dxos/debug": "0.8.4-main.abd8ff62ef",
|
|
33
|
+
"@dxos/log": "0.8.4-main.abd8ff62ef",
|
|
34
|
+
"@dxos/node-std": "0.8.4-main.abd8ff62ef",
|
|
35
|
+
"@dxos/util": "0.8.4-main.abd8ff62ef",
|
|
36
|
+
"@dxos/protocols": "0.8.4-main.abd8ff62ef"
|
|
39
37
|
},
|
|
40
38
|
"publishConfig": {
|
|
41
39
|
"access": "public"
|
package/src/rpc.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Trigger, asyncTimeout, synchronized } from '@dxos/async';
|
|
6
6
|
import { type Any, type ProtoCodec, type RequestOptions, Stream } from '@dxos/codec-protobuf';
|
|
7
|
+
import { type Context, ContextRpcCodec } from '@dxos/context';
|
|
7
8
|
import { StackTrace } from '@dxos/debug';
|
|
8
9
|
import { invariant } from '@dxos/invariant';
|
|
9
10
|
import { log } from '@dxos/log';
|
|
@@ -14,7 +15,7 @@ import { exponentialBackoffInterval } from '@dxos/util';
|
|
|
14
15
|
|
|
15
16
|
import { decodeRpcError } from './errors';
|
|
16
17
|
|
|
17
|
-
const DEFAULT_TIMEOUT =
|
|
18
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
18
19
|
const BYE_SEND_TIMEOUT = 2_000;
|
|
19
20
|
|
|
20
21
|
const DEBUG_CALLS = true;
|
|
@@ -260,7 +261,7 @@ export class RpcPeer {
|
|
|
260
261
|
*/
|
|
261
262
|
private async _receive(msg: Uint8Array): Promise<void> {
|
|
262
263
|
const decoded = getRpcMessageCodec().decode(msg, { preserveAny: true });
|
|
263
|
-
DEBUG_CALLS && log('received message', { type: Object.keys(decoded)[0] });
|
|
264
|
+
DEBUG_CALLS && log.trace('received message', { type: Object.keys(decoded)[0] });
|
|
264
265
|
|
|
265
266
|
if (decoded.request) {
|
|
266
267
|
if (this._state !== RpcState.OPENED && this._state !== RpcState.OPENING) {
|
|
@@ -290,10 +291,10 @@ export class RpcPeer {
|
|
|
290
291
|
});
|
|
291
292
|
});
|
|
292
293
|
} else {
|
|
293
|
-
DEBUG_CALLS && log('requesting...', { method: req.method });
|
|
294
|
+
DEBUG_CALLS && log.trace('requesting...', { method: req.method });
|
|
294
295
|
const response = await this._callHandler(req);
|
|
295
296
|
DEBUG_CALLS &&
|
|
296
|
-
log('sending response', {
|
|
297
|
+
log.trace('sending response', {
|
|
297
298
|
method: req.method,
|
|
298
299
|
response: response.payload?.type_url,
|
|
299
300
|
error: response.error,
|
|
@@ -309,7 +310,7 @@ export class RpcPeer {
|
|
|
309
310
|
const responseId = decoded.response.id;
|
|
310
311
|
invariant(typeof responseId === 'number');
|
|
311
312
|
if (!this._outgoingRequests.has(responseId)) {
|
|
312
|
-
log('received response with invalid id', { responseId });
|
|
313
|
+
log.trace('received response with invalid id', { responseId });
|
|
313
314
|
return; // Ignore requests with incorrect id.
|
|
314
315
|
}
|
|
315
316
|
|
|
@@ -319,7 +320,7 @@ export class RpcPeer {
|
|
|
319
320
|
this._outgoingRequests.delete(responseId);
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
DEBUG_CALLS && log('response', { type_url: decoded.response.payload?.type_url });
|
|
323
|
+
DEBUG_CALLS && log.trace('response', { type_url: decoded.response.payload?.type_url });
|
|
323
324
|
item.resolve(decoded.response);
|
|
324
325
|
} else if (decoded.open) {
|
|
325
326
|
log('received open message', { state: this._state });
|
|
@@ -374,7 +375,7 @@ export class RpcPeer {
|
|
|
374
375
|
* Peer should be open before making this call.
|
|
375
376
|
*/
|
|
376
377
|
async call(method: string, request: Any, options?: RequestOptions): Promise<Any> {
|
|
377
|
-
DEBUG_CALLS && log('calling...', { method });
|
|
378
|
+
DEBUG_CALLS && log.trace('calling...', { method });
|
|
378
379
|
throwIfNotOpen(this._state);
|
|
379
380
|
|
|
380
381
|
let response: Response;
|
|
@@ -385,6 +386,13 @@ export class RpcPeer {
|
|
|
385
386
|
this._outgoingRequests.set(id, new PendingRpcRequest(resolve, reject, false));
|
|
386
387
|
});
|
|
387
388
|
|
|
389
|
+
let traceContext;
|
|
390
|
+
try {
|
|
391
|
+
traceContext = options?.ctx ? ContextRpcCodec.encode(options.ctx) : undefined;
|
|
392
|
+
} catch (err) {
|
|
393
|
+
log.warn('failed to encode trace context', { err });
|
|
394
|
+
}
|
|
395
|
+
|
|
388
396
|
// Send request call.
|
|
389
397
|
const sending = this._sendMessage({
|
|
390
398
|
request: {
|
|
@@ -392,6 +400,7 @@ export class RpcPeer {
|
|
|
392
400
|
method,
|
|
393
401
|
payload: request,
|
|
394
402
|
stream: false,
|
|
403
|
+
...(traceContext ? { traceContext } : {}),
|
|
395
404
|
},
|
|
396
405
|
});
|
|
397
406
|
|
|
@@ -460,16 +469,30 @@ export class RpcPeer {
|
|
|
460
469
|
|
|
461
470
|
this._outgoingRequests.set(id, new PendingRpcRequest(onResponse, closeStream, true));
|
|
462
471
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
472
|
+
let traceContext;
|
|
473
|
+
try {
|
|
474
|
+
traceContext = options?.ctx ? ContextRpcCodec.encode(options.ctx) : undefined;
|
|
475
|
+
} catch (err) {
|
|
476
|
+
log.warn('failed to encode trace context', { err });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
this._sendMessage({
|
|
481
|
+
request: {
|
|
482
|
+
id,
|
|
483
|
+
method,
|
|
484
|
+
payload: request,
|
|
485
|
+
stream: true,
|
|
486
|
+
...(traceContext ? { traceContext } : {}),
|
|
487
|
+
},
|
|
488
|
+
}).catch((err) => {
|
|
489
|
+
this._outgoingRequests.delete(id);
|
|
490
|
+
close(err);
|
|
491
|
+
});
|
|
492
|
+
} catch (err) {
|
|
493
|
+
this._outgoingRequests.delete(id);
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
473
496
|
|
|
474
497
|
return () => {
|
|
475
498
|
this._sendMessage({
|
|
@@ -483,17 +506,32 @@ export class RpcPeer {
|
|
|
483
506
|
}
|
|
484
507
|
|
|
485
508
|
private async _sendMessage(message: RpcMessage, timeout?: number): Promise<void> {
|
|
486
|
-
DEBUG_CALLS && log('sending message', { type: Object.keys(message)[0] });
|
|
509
|
+
DEBUG_CALLS && log.trace('sending message', { type: Object.keys(message)[0] });
|
|
487
510
|
await this._params.port.send(getRpcMessageCodec().encode(message, { preserveAny: true }), timeout);
|
|
488
511
|
}
|
|
489
512
|
|
|
513
|
+
private _getHandlerRpcOptions(req: Request): RequestOptions | undefined {
|
|
514
|
+
let traceCtx: Context | undefined;
|
|
515
|
+
if (req.traceContext) {
|
|
516
|
+
try {
|
|
517
|
+
traceCtx = ContextRpcCodec.decode(req.traceContext);
|
|
518
|
+
} catch (err) {
|
|
519
|
+
log.warn('failed to decode trace context', { traceContext: req.traceContext, err });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (!traceCtx && !this._params.handlerRpcOptions) {
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
return { ...this._params.handlerRpcOptions, ...(traceCtx ? { ctx: traceCtx } : {}) };
|
|
526
|
+
}
|
|
527
|
+
|
|
490
528
|
private async _callHandler(req: Request): Promise<Response> {
|
|
491
529
|
try {
|
|
492
530
|
invariant(typeof req.id === 'number');
|
|
493
531
|
invariant(req.payload);
|
|
494
532
|
invariant(req.method);
|
|
495
533
|
|
|
496
|
-
const response = await this._params.callHandler(req.method, req.payload, this.
|
|
534
|
+
const response = await this._params.callHandler(req.method, req.payload, this._getHandlerRpcOptions(req));
|
|
497
535
|
return {
|
|
498
536
|
id: req.id,
|
|
499
537
|
payload: response,
|
|
@@ -513,7 +551,7 @@ export class RpcPeer {
|
|
|
513
551
|
invariant(req.payload);
|
|
514
552
|
invariant(req.method);
|
|
515
553
|
|
|
516
|
-
const responseStream = this._params.streamHandler(req.method, req.payload, this.
|
|
554
|
+
const responseStream = this._params.streamHandler(req.method, req.payload, this._getHandlerRpcOptions(req));
|
|
517
555
|
responseStream.onReady(() => {
|
|
518
556
|
callback({
|
|
519
557
|
id: req.id,
|
package/src/service.test.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { beforeEach, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { latch, sleep } from '@dxos/async';
|
|
8
|
-
import { Stream } from '@dxos/codec-protobuf
|
|
8
|
+
import { type RequestOptions, Stream } from '@dxos/codec-protobuf';
|
|
9
|
+
import { Context, TRACE_SPAN_ATTRIBUTE } from '@dxos/context';
|
|
9
10
|
import { schema } from '@dxos/protocols/proto';
|
|
10
11
|
import {
|
|
11
12
|
type TestRpcResponse,
|
|
@@ -584,4 +585,127 @@ describe('Protobuf service', () => {
|
|
|
584
585
|
);
|
|
585
586
|
await expect(promise).rejects.toThrow(/Timeout/);
|
|
586
587
|
});
|
|
588
|
+
|
|
589
|
+
test('W3C trace context propagates to handler options', async ({ expect }) => {
|
|
590
|
+
const traceContext = {
|
|
591
|
+
traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
|
|
592
|
+
tracestate: 'vendorkey=vendorvalue',
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const [alicePort, bobPort] = createLinkedPorts();
|
|
596
|
+
let receivedCtx: Context | undefined;
|
|
597
|
+
|
|
598
|
+
const server = createProtoRpcPeer({
|
|
599
|
+
exposed: {
|
|
600
|
+
TestService: schema.getService('example.testing.rpc.TestService'),
|
|
601
|
+
},
|
|
602
|
+
handlers: {
|
|
603
|
+
TestService: {
|
|
604
|
+
testCall: async (req: any, options?: RequestOptions) => {
|
|
605
|
+
receivedCtx = options?.ctx;
|
|
606
|
+
return { data: 'responseData' };
|
|
607
|
+
},
|
|
608
|
+
voidCall: async () => {},
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
port: alicePort,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const callerCtx = new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: traceContext } });
|
|
615
|
+
|
|
616
|
+
const client = createProtoRpcPeer({
|
|
617
|
+
requested: {
|
|
618
|
+
TestService: schema.getService('example.testing.rpc.TestService'),
|
|
619
|
+
},
|
|
620
|
+
port: bobPort,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
await Promise.all([server.open(), client.open()]);
|
|
624
|
+
|
|
625
|
+
await client.rpc.TestService.testCall({ data: 'requestData' }, { ctx: callerCtx });
|
|
626
|
+
|
|
627
|
+
expect(receivedCtx).toBeInstanceOf(Context);
|
|
628
|
+
const received = receivedCtx!.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
629
|
+
expect(received.traceparent).toEqual(traceContext.traceparent);
|
|
630
|
+
expect(received.tracestate).toEqual(traceContext.tracestate);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test('handler receives no ctx when caller sends no trace context', async ({ expect }) => {
|
|
634
|
+
const [alicePort, bobPort] = createLinkedPorts();
|
|
635
|
+
let receivedOptions: RequestOptions | undefined;
|
|
636
|
+
|
|
637
|
+
const server = createProtoRpcPeer({
|
|
638
|
+
exposed: {
|
|
639
|
+
TestService: schema.getService('example.testing.rpc.TestService'),
|
|
640
|
+
},
|
|
641
|
+
handlers: {
|
|
642
|
+
TestService: {
|
|
643
|
+
testCall: async (req: any, options?: RequestOptions) => {
|
|
644
|
+
receivedOptions = options;
|
|
645
|
+
return { data: 'responseData' };
|
|
646
|
+
},
|
|
647
|
+
voidCall: async () => {},
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
port: alicePort,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const client = createProtoRpcPeer({
|
|
654
|
+
requested: {
|
|
655
|
+
TestService: schema.getService('example.testing.rpc.TestService'),
|
|
656
|
+
},
|
|
657
|
+
port: bobPort,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
await Promise.all([server.open(), client.open()]);
|
|
661
|
+
|
|
662
|
+
await client.rpc.TestService.testCall({ data: 'requestData' });
|
|
663
|
+
|
|
664
|
+
expect(receivedOptions).toBeUndefined();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test('W3C trace context propagates on streaming RPC', async ({ expect }) => {
|
|
668
|
+
const traceContext = {
|
|
669
|
+
traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const [alicePort, bobPort] = createLinkedPorts();
|
|
673
|
+
let receivedCtx: Context | undefined;
|
|
674
|
+
|
|
675
|
+
const server = createProtoRpcPeer({
|
|
676
|
+
exposed: {
|
|
677
|
+
TestStreamService: schema.getService('example.testing.rpc.TestStreamService'),
|
|
678
|
+
},
|
|
679
|
+
handlers: {
|
|
680
|
+
TestStreamService: {
|
|
681
|
+
testCall: (_req: any, options?: RequestOptions) => {
|
|
682
|
+
receivedCtx = options?.ctx;
|
|
683
|
+
return new Stream<{ data: string }>(({ next, close }) => {
|
|
684
|
+
next({ data: 'streamData' });
|
|
685
|
+
close();
|
|
686
|
+
});
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
port: alicePort,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const callerCtx = new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: traceContext } });
|
|
694
|
+
|
|
695
|
+
const client = createProtoRpcPeer({
|
|
696
|
+
requested: {
|
|
697
|
+
TestStreamService: schema.getService('example.testing.rpc.TestStreamService'),
|
|
698
|
+
},
|
|
699
|
+
port: bobPort,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
await Promise.all([server.open(), client.open()]);
|
|
703
|
+
|
|
704
|
+
const stream = client.rpc.TestStreamService.testCall({ data: 'requestData' }, { ctx: callerCtx });
|
|
705
|
+
await Stream.consumeData(stream);
|
|
706
|
+
|
|
707
|
+
expect(receivedCtx).toBeInstanceOf(Context);
|
|
708
|
+
const received = receivedCtx!.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
709
|
+
expect(received.traceparent).toEqual(traceContext.traceparent);
|
|
710
|
+
});
|
|
587
711
|
});
|