starpc 0.3.5 → 0.4.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.
Files changed (73) hide show
  1. package/Makefile +22 -13
  2. package/README.md +4 -0
  3. package/dist/echo/client-test.js +1 -1
  4. package/dist/echo/{echo.d.ts → echo.pb.d.ts} +0 -0
  5. package/dist/echo/{echo.js → echo.pb.js} +0 -1
  6. package/dist/echo/index.d.ts +1 -1
  7. package/dist/echo/index.js +1 -1
  8. package/dist/echo/server.d.ts +1 -1
  9. package/dist/rpcstream/rpcstream.d.ts +19 -0
  10. package/dist/rpcstream/rpcstream.js +94 -0
  11. package/dist/rpcstream/rpcstream.pb.d.ts +85 -0
  12. package/dist/rpcstream/rpcstream.pb.js +160 -0
  13. package/dist/srpc/broadcast-channel.js +0 -10
  14. package/dist/srpc/client-rpc.d.ts +1 -1
  15. package/dist/srpc/client.d.ts +5 -5
  16. package/dist/srpc/client.js +21 -25
  17. package/dist/srpc/common-rpc.d.ts +2 -2
  18. package/dist/srpc/common-rpc.js +1 -15
  19. package/dist/srpc/conn-duplex.js +1 -3
  20. package/dist/srpc/conn.d.ts +1 -1
  21. package/dist/srpc/conn.js +0 -4
  22. package/dist/srpc/handler.js +12 -14
  23. package/dist/srpc/index.d.ts +2 -1
  24. package/dist/srpc/index.js +1 -0
  25. package/dist/srpc/message-port.js +0 -10
  26. package/dist/srpc/mux.js +4 -2
  27. package/dist/srpc/observable-source.d.ts +9 -0
  28. package/dist/srpc/observable-source.js +25 -0
  29. package/dist/srpc/packet.d.ts +1 -1
  30. package/dist/srpc/packet.js +1 -1
  31. package/dist/srpc/{rpcproto.d.ts → rpcproto.pb.d.ts} +0 -0
  32. package/dist/srpc/{rpcproto.js → rpcproto.pb.js} +0 -0
  33. package/dist/srpc/server-rpc.d.ts +1 -1
  34. package/dist/srpc/server-rpc.js +0 -2
  35. package/dist/srpc/server.d.ts +4 -4
  36. package/dist/srpc/server.js +6 -7
  37. package/dist/srpc/stream.d.ts +1 -1
  38. package/dist/srpc/websocket.js +1 -3
  39. package/e2e/debug.test +0 -0
  40. package/e2e/e2e_test.go +20 -2
  41. package/echo/client-test.ts +1 -1
  42. package/echo/{echo.ts → echo.pb.ts} +0 -0
  43. package/echo/index.ts +1 -1
  44. package/echo/server.ts +1 -1
  45. package/go.mod +2 -2
  46. package/go.sum +2 -2
  47. package/integration/integration.ts +2 -2
  48. package/package.json +4 -3
  49. package/srpc/broadcast-channel.ts +1 -1
  50. package/srpc/client-rpc.go +8 -3
  51. package/srpc/client-rpc.ts +1 -1
  52. package/srpc/client.go +4 -4
  53. package/srpc/client.ts +27 -25
  54. package/srpc/common-rpc.ts +2 -2
  55. package/srpc/conn-duplex.ts +1 -1
  56. package/srpc/conn.ts +1 -1
  57. package/srpc/handler.ts +14 -10
  58. package/srpc/index.ts +2 -2
  59. package/srpc/{rpc-stream.go → msg-stream.go} +13 -13
  60. package/srpc/muxed-conn.go +31 -0
  61. package/srpc/observable-source.ts +40 -0
  62. package/srpc/packet.go +4 -3
  63. package/srpc/packet.ts +1 -1
  64. package/srpc/{rpcproto.ts → rpcproto.pb.ts} +0 -0
  65. package/srpc/server-pipe.go +1 -1
  66. package/srpc/server-rpc.go +3 -3
  67. package/srpc/server-rpc.ts +1 -1
  68. package/srpc/server.go +8 -1
  69. package/srpc/server.ts +8 -7
  70. package/srpc/stream.ts +1 -1
  71. package/srpc/websocket.go +1 -4
  72. package/srpc/websocket.ts +1 -1
  73. package/srpc/conn.go +0 -7
@@ -1,21 +1,7 @@
1
1
  import { pushable } from 'it-pushable';
2
- import { Packet } from './rpcproto.js';
2
+ import { Packet } from './rpcproto.pb.js';
3
3
  // CommonRPC is common logic between server and client RPCs.
4
4
  export class CommonRPC {
5
- // sink is the data sink for incoming messages.
6
- sink;
7
- // source is the packet source for outgoing Packets.
8
- source;
9
- // _source is used to write to the source.
10
- _source;
11
- // rpcDataSource emits incoming client RPC messages to the caller.
12
- rpcDataSource;
13
- // _rpcDataSource is used to write to the rpc message source.
14
- _rpcDataSource;
15
- // service is the rpc service
16
- service;
17
- // method is the rpc method
18
- method;
19
5
  constructor() {
20
6
  this.sink = this._createSink();
21
7
  const sourcev = this._createSource();
@@ -4,12 +4,10 @@ import { Conn } from './conn.js';
4
4
  //
5
5
  // expects Uint8Array objects
6
6
  export class DuplexConn extends Conn {
7
- // channel is the iterable
8
- channel;
9
7
  constructor(duplex, server, connParams) {
10
8
  super(server, connParams);
11
9
  this.channel = duplex;
12
- pipe(this, this.channel, this);
10
+ pipe(this.channel, this, this.channel);
13
11
  }
14
12
  // getChannelDuplex returns the Duplex channel.
15
13
  getChannelDuplex() {
@@ -7,7 +7,7 @@ export interface ConnParams {
7
7
  muxerFactory?: StreamMuxerFactory;
8
8
  }
9
9
  export interface StreamHandler {
10
- handleStream(strm: Duplex<Uint8Array>): Promise<void>;
10
+ handleStream(strm: Duplex<Uint8Array>): void;
11
11
  }
12
12
  export declare class Conn implements Duplex<Uint8Array> {
13
13
  private muxer;
package/dist/srpc/conn.js CHANGED
@@ -5,10 +5,6 @@ import { Client } from './client.js';
5
5
  // Implements the server by handling incoming streams.
6
6
  // If the server is unset, rejects any incoming streams.
7
7
  export class Conn {
8
- // muxer is the mplex stream muxer.
9
- muxer;
10
- // server is the server side, if set.
11
- server;
12
8
  constructor(server, connParams) {
13
9
  if (server) {
14
10
  this.server = server;
@@ -4,10 +4,6 @@ import { from as observableFrom } from 'rxjs';
4
4
  import { buildDecodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
5
5
  // StaticHandler is a handler with a definition and implementation.
6
6
  export class StaticHandler {
7
- // service is the service id
8
- service;
9
- // methods is the map of method to invoke fn
10
- methods;
11
7
  constructor(serviceID, methods) {
12
8
  this.service = serviceID;
13
9
  this.methods = methods;
@@ -38,23 +34,27 @@ export function createInvokeFn(methodInfo, methodProto) {
38
34
  objectMode: true,
39
35
  });
40
36
  // pipe responseSink to dataSink.
41
- const responsePipe = pipe(responseSink, buildEncodeMessageTransform(methodInfo.responseType), dataSink);
37
+ pipe(responseSink, buildEncodeMessageTransform(methodInfo.responseType), dataSink);
38
+ // requestSource is a Source of decoded request messages.
39
+ const requestSource = pipe(dataSource, requestDecode);
42
40
  // build the request argument.
43
41
  let requestArg;
44
42
  if (methodInfo.requestStream) {
45
- // requestSource is a Source of decoded request messages.
46
- const requestSource = pipe(dataSource, requestDecode);
47
43
  // convert the request data source into an Observable<T>
48
44
  requestArg = observableFrom(requestSource);
49
45
  }
50
46
  else {
51
47
  // receive a single message for the argument.
52
- const requestRx = requestDecode(dataSource);
53
- for await (const msg of requestRx) {
54
- requestArg = msg;
55
- break;
48
+ for await (const msg of requestSource) {
49
+ if (msg) {
50
+ requestArg = msg;
51
+ break;
52
+ }
56
53
  }
57
54
  }
55
+ if (!requestArg) {
56
+ throw new Error('request object was empty');
57
+ }
58
58
  // Call the implementation.
59
59
  try {
60
60
  const responseObj = methodProto(requestArg);
@@ -77,9 +77,7 @@ export function createInvokeFn(methodInfo, methodProto) {
77
77
  },
78
78
  complete: () => {
79
79
  responseSink.end();
80
- responsePipe.finally(() => {
81
- resolve();
82
- });
80
+ resolve();
83
81
  },
84
82
  });
85
83
  });
@@ -1,4 +1,4 @@
1
- export type { OpenStreamFunc } from './stream.js';
1
+ export type { PacketHandler, Stream, OpenStreamFunc } from './stream.js';
2
2
  export { Client } from './client.js';
3
3
  export { Server } from './server.js';
4
4
  export { Conn, ConnParams } from './conn.js';
@@ -6,3 +6,4 @@ export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js';
6
6
  export { Mux, createMux } from './mux.js';
7
7
  export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
8
8
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
9
+ export { ObservableSource } from './observable-source.js';
@@ -5,3 +5,4 @@ export { createHandler, createInvokeFn } from './handler.js';
5
5
  export { createMux } from './mux.js';
6
6
  export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
7
7
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
8
+ export { ObservableSource } from './observable-source.js';
@@ -2,14 +2,6 @@ import { EventIterator } from 'event-iterator';
2
2
  import { DuplexConn } from './conn-duplex.js';
3
3
  // MessagePortIterable is a AsyncIterable wrapper for MessagePort.
4
4
  export class MessagePortIterable {
5
- // port is the message port
6
- port;
7
- // sink is the sink for incoming messages.
8
- sink;
9
- // source is the source for outgoing messages.
10
- source;
11
- // _source is the EventIterator for source.
12
- _source;
13
5
  constructor(port) {
14
6
  this.port = port;
15
7
  this.sink = this._createSink();
@@ -51,8 +43,6 @@ export function newMessagePortIterable(port) {
51
43
  //
52
44
  // expects Uint8Array objects over the MessagePort.
53
45
  export class MessagePortConn extends DuplexConn {
54
- // messagePort is the message port iterable.
55
- messagePort;
56
46
  constructor(port, server, connParams) {
57
47
  const messagePort = new MessagePortIterable(port);
58
48
  super(messagePort, server, connParams);
package/dist/srpc/mux.js CHANGED
@@ -5,8 +5,10 @@ export function createMux() {
5
5
  // StaticMux contains a in-memory mapping between service ID and handlers.
6
6
  // implements Mux
7
7
  export class StaticMux {
8
- // services contains a mapping from service id to handlers.
9
- services = {};
8
+ constructor() {
9
+ // services contains a mapping from service id to handlers.
10
+ this.services = {};
11
+ }
10
12
  register(handler) {
11
13
  const serviceID = handler?.getServiceID();
12
14
  if (!serviceID) {
@@ -0,0 +1,9 @@
1
+ import { Source } from 'it-stream-types';
2
+ import { Observable } from 'rxjs';
3
+ export declare class ObservableSource<T> {
4
+ readonly source: Source<T>;
5
+ private readonly _source;
6
+ private readonly subscription;
7
+ constructor(observable: Observable<T>);
8
+ close(err?: Error): void;
9
+ }
@@ -0,0 +1,25 @@
1
+ import { pushable } from 'it-pushable';
2
+ // ObservableSource wraps an Observable into a Source.
3
+ export class ObservableSource {
4
+ constructor(observable) {
5
+ const source = pushable({ objectMode: true });
6
+ this.source = source;
7
+ this._source = source;
8
+ this.subscription = observable.subscribe({
9
+ next: (value) => {
10
+ this._source.push(value);
11
+ },
12
+ error: (err) => {
13
+ this._source.end(err);
14
+ },
15
+ complete: () => {
16
+ this._source.end();
17
+ },
18
+ });
19
+ }
20
+ // close closes the subscription.
21
+ close(err) {
22
+ this._source.end(err);
23
+ this.subscription.unsubscribe();
24
+ }
25
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Transform } from 'it-stream-types';
2
- import { Packet } from './rpcproto.js';
2
+ import { Packet } from './rpcproto.pb.js';
3
3
  export declare const decodePacketSource: (source: import("it-stream-types").Source<Uint8Array | Uint8Array[]>) => AsyncIterable<Packet>;
4
4
  export declare const encodePacketSource: (source: import("it-stream-types").Source<Packet | Packet[]>) => AsyncIterable<Uint8Array>;
5
5
  export declare function prependLengthPrefixTransform(): Transform<Uint8Array>;
@@ -1,5 +1,5 @@
1
1
  import { encode as lengthPrefixEncode, decode as lengthPrefixDecode, } from 'it-length-prefixed';
2
- import { Packet } from './rpcproto.js';
2
+ import { Packet } from './rpcproto.pb.js';
3
3
  import { buildDecodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
4
4
  // decodePacketSource decodes packets from a binary data stream.
5
5
  export const decodePacketSource = buildDecodeMessageTransform(Packet);
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- import type { CallData, CallStart } from './rpcproto.js';
1
+ import type { CallData, CallStart } from './rpcproto.pb.js';
2
2
  import { CommonRPC } from './common-rpc.js';
3
3
  import { Mux } from './mux.js';
4
4
  export declare class ServerRPC extends CommonRPC {
@@ -1,8 +1,6 @@
1
1
  import { CommonRPC } from './common-rpc.js';
2
2
  // ServerRPC is an ongoing RPC from the server side.
3
3
  export class ServerRPC extends CommonRPC {
4
- // mux is used to handle incoming rpcs.
5
- mux;
6
4
  constructor(mux) {
7
5
  super();
8
6
  this.mux = mux;
@@ -2,13 +2,13 @@ import { Stream } from '@libp2p/interface-connection';
2
2
  import { Duplex } from 'it-stream-types';
3
3
  import { Mux } from './mux.js';
4
4
  import { ServerRPC } from './server-rpc.js';
5
- import { Packet } from './rpcproto.js';
5
+ import { Packet } from './rpcproto.pb.js';
6
6
  import { StreamHandler } from './conn.js';
7
7
  export declare class Server implements StreamHandler {
8
8
  private mux;
9
9
  constructor(mux: Mux);
10
10
  startRpc(): ServerRPC;
11
- handleStream(stream: Stream): Promise<void>;
12
- handleDuplex(stream: Duplex<Uint8Array>): Promise<void>;
13
- handlePacketStream(stream: Duplex<Packet>): Promise<void>;
11
+ handleStream(stream: Stream): ServerRPC;
12
+ handleDuplex(stream: Duplex<Uint8Array>): ServerRPC;
13
+ handlePacketStream(stream: Duplex<Packet>): ServerRPC;
14
14
  }
@@ -3,8 +3,6 @@ import { ServerRPC } from './server-rpc.js';
3
3
  import { parseLengthPrefixTransform, prependLengthPrefixTransform, decodePacketSource, encodePacketSource, } from './packet.js';
4
4
  // Server implements the SRPC server in TypeScript with a Mux.
5
5
  export class Server {
6
- // mux is the mux used to handle requests.
7
- mux;
8
6
  constructor(mux) {
9
7
  this.mux = mux;
10
8
  }
@@ -14,18 +12,19 @@ export class Server {
14
12
  return new ServerRPC(this.mux);
15
13
  }
16
14
  // handleStream handles an incoming Uint8Array message duplex.
17
- // closes the stream when the rpc completes.
18
15
  handleStream(stream) {
19
16
  return this.handleDuplex(stream);
20
17
  }
21
18
  // handleDuplex handles an incoming message duplex.
22
- async handleDuplex(stream) {
19
+ handleDuplex(stream) {
23
20
  const rpc = this.startRpc();
24
- await pipe(stream, parseLengthPrefixTransform(), decodePacketSource, rpc, encodePacketSource, prependLengthPrefixTransform(), stream);
21
+ pipe(stream, parseLengthPrefixTransform(), decodePacketSource, rpc, encodePacketSource, prependLengthPrefixTransform(), stream);
22
+ return rpc;
25
23
  }
26
24
  // handlePacketStream handles an incoming Packet duplex.
27
- async handlePacketStream(stream) {
25
+ handlePacketStream(stream) {
28
26
  const rpc = this.startRpc();
29
- await pipe(stream, rpc, stream);
27
+ pipe(stream, rpc, stream);
28
+ return rpc;
30
29
  }
31
30
  }
@@ -1,4 +1,4 @@
1
- import type { Packet } from './rpcproto';
1
+ import type { Packet } from './rpcproto.pb.js';
2
2
  import type { Duplex } from 'it-stream-types';
3
3
  export declare type PacketHandler = (packet: Packet) => Promise<void>;
4
4
  export declare type Stream = Duplex<Uint8Array>;
@@ -4,15 +4,13 @@ import { Mplex } from '@libp2p/mplex';
4
4
  import { Conn } from './conn.js';
5
5
  // WebSocketConn implements a connection with a WebSocket and optional Server.
6
6
  export class WebSocketConn extends Conn {
7
- // socket is the web socket
8
- socket;
9
7
  constructor(socket, server) {
10
8
  super(server, {
11
9
  muxerFactory: new Mplex(),
12
10
  });
13
11
  this.socket = socket;
14
12
  const socketDuplex = duplex(socket);
15
- pipe(this.source, socketDuplex, this.sink);
13
+ pipe(socketDuplex, this, socketDuplex);
16
14
  }
17
15
  // getSocket returns the websocket.
18
16
  getSocket() {
package/e2e/debug.test ADDED
Binary file
package/e2e/e2e_test.go CHANGED
@@ -3,10 +3,13 @@ package e2e
3
3
  import (
4
4
  "context"
5
5
  "io"
6
+ "net"
6
7
  "testing"
7
8
 
8
9
  "github.com/aperturerobotics/starpc/echo"
9
10
  "github.com/aperturerobotics/starpc/srpc"
11
+ "github.com/libp2p/go-libp2p/p2p/muxer/mplex"
12
+ mp "github.com/libp2p/go-mplex"
10
13
  "github.com/pkg/errors"
11
14
  )
12
15
 
@@ -20,9 +23,24 @@ func RunE2E(t *testing.T, cb func(client echo.SRPCEchoerClient) error) {
20
23
  }
21
24
  server := srpc.NewServer(mux)
22
25
 
26
+ // note: also possible to do without mplex:
27
+ // openStream := srpc.NewServerPipe(server)
28
+ // client := srpc.NewClient(openStream)
29
+
23
30
  // construct the client
24
- openStream := srpc.NewServerPipe(server)
25
- client := srpc.NewClient(openStream)
31
+ clientPipe, serverPipe := net.Pipe()
32
+
33
+ ctx := context.Background()
34
+ go func() {
35
+ serverMp, _ := mp.NewMultiplex(serverPipe, false, nil)
36
+ _ = server.AcceptMuxedConn(ctx, mplex.NewMuxedConn(serverMp))
37
+ }()
38
+
39
+ clientMp, err := mp.NewMultiplex(clientPipe, true, nil)
40
+ if err != nil {
41
+ t.Fatal(err.Error())
42
+ }
43
+ client := srpc.NewClientWithMuxedConn(mplex.NewMuxedConn(clientMp))
26
44
 
27
45
  // construct the client rpc interface
28
46
  clientEcho := echo.NewSRPCEchoerClient(client)
@@ -1,5 +1,5 @@
1
1
  import { Client } from '../srpc/index.js'
2
- import { EchoerClientImpl, EchoMsg } from './echo.js'
2
+ import { EchoerClientImpl, EchoMsg } from './echo.pb.js'
3
3
  import { Observable } from 'rxjs'
4
4
 
5
5
  export async function runClientTest(client: Client) {
File without changes
package/echo/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from './echo.js'
1
+ export * from './echo.pb.js'
2
2
  export * from './server.js'
3
3
  export { runClientTest } from './client-test.js'
package/echo/server.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Observable, from as observableFrom } from 'rxjs'
2
- import { Echoer, EchoMsg } from './echo'
2
+ import { Echoer, EchoMsg } from './echo.pb.js'
3
3
  import { pushable, Pushable } from 'it-pushable'
4
4
 
5
5
  // EchoServer implements the Echoer server.
package/go.mod CHANGED
@@ -9,8 +9,9 @@ require (
9
9
  )
10
10
 
11
11
  require (
12
- github.com/libp2p/go-libp2p v0.20.1
12
+ github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c
13
13
  github.com/libp2p/go-libp2p-core v0.16.1
14
+ github.com/libp2p/go-mplex v0.7.0
14
15
  github.com/sirupsen/logrus v1.8.2-0.20220112234510-85981c045988
15
16
  )
16
17
 
@@ -25,7 +26,6 @@ require (
25
26
  github.com/klauspost/compress v1.15.1 // indirect
26
27
  github.com/klauspost/cpuid/v2 v2.0.12 // indirect
27
28
  github.com/libp2p/go-buffer-pool v0.0.2 // indirect
28
- github.com/libp2p/go-mplex v0.7.0 // indirect
29
29
  github.com/libp2p/go-openssl v0.0.7 // indirect
30
30
  github.com/mattn/go-isatty v0.0.14 // indirect
31
31
  github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
package/go.sum CHANGED
@@ -65,8 +65,8 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
65
65
  github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
66
66
  github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
67
67
  github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
68
- github.com/libp2p/go-libp2p v0.20.1 h1:tCgC8yXtleyOg/mp+ZoCcA+aryAhueCfFmAVXURT/PM=
69
- github.com/libp2p/go-libp2p v0.20.1/go.mod h1:XgJHsOhEBVBXp/2Sj9bm/yEyD94uunAaP6oaegdcKks=
68
+ github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c h1:BTR12zXVGQtdk9bT5GLOmdzJrhyFO6YUH3/e8lnkjQ0=
69
+ github.com/libp2p/go-libp2p v0.20.1-0.20220622205512-3cf611ad8c9c/go.mod h1:tqyre7jPbLfhzjQfpiFX6x7wj/sqt1074riPM0Xn/qI=
70
70
  github.com/libp2p/go-libp2p-core v0.16.1 h1:bWoiEBqVkpJ13hbv/f69tHODp86t6mvc4fBN4DkK73M=
71
71
  github.com/libp2p/go-libp2p-core v0.16.1/go.mod h1:O3i/7y+LqUb0N+qhzXjBjjpchgptWAVMG1Voegk7b4c=
72
72
  github.com/libp2p/go-libp2p-testing v0.9.2 h1:dCpODRtRaDZKF8HXT9qqqgON+OMEB423Knrgeod8j84=
@@ -1,5 +1,5 @@
1
- import { WebSocketConn } from '../srpc/websocket'
2
- import { runClientTest } from '../echo'
1
+ import { WebSocketConn } from '../srpc/websocket.js'
2
+ import { runClientTest } from '../echo/client-test.js'
3
3
  import WebSocket from 'isomorphic-ws'
4
4
 
5
5
  async function runRPC() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -22,6 +22,7 @@
22
22
  "Makefile",
23
23
  "dist/echo",
24
24
  "dist/srpc",
25
+ "dist/rpcstream",
25
26
  "e2e",
26
27
  "echo",
27
28
  "go.mod",
@@ -39,7 +40,7 @@
39
40
  "deps": "depcheck",
40
41
  "codegen": "npm run gen",
41
42
  "ci": "npm run build && npm run lint:js && npm run lint:go",
42
- "format": "prettier --write './{srpc,echo,e2e,integration}/**/(*.ts|*.tsx|*.html|*.css)'",
43
+ "format": "prettier --write './{srpc,echo,e2e,integration,rpcstream}/**/(*.ts|*.tsx|*.html|*.css)'",
43
44
  "gen": "make genproto",
44
45
  "test": "npm run test:js && npm run test:go",
45
46
  "test:go": "make test",
@@ -48,7 +49,7 @@
48
49
  "integration": "npm run test:integration",
49
50
  "lint": "npm run lint:go && npm run lint:js",
50
51
  "lint:go": "make lint",
51
- "lint:js": "eslint -c .eslintrc.js --ext .ts ./{srpc,echo}/**/*.ts",
52
+ "lint:js": "eslint -c .eslintrc.js --ext .ts ./{srpc,echo,rpcstream}/**/*.ts",
52
53
  "prepare": "patch-package",
53
54
  "precommit": "npm run format"
54
55
  },
@@ -46,8 +46,8 @@ export class BroadcastChannelIterable<T> implements Duplex<T> {
46
46
  queue.push(ev.data)
47
47
  }
48
48
  }
49
- this.readChannel.addEventListener('message', messageListener)
50
49
 
50
+ this.readChannel.addEventListener('message', messageListener)
51
51
  return () => {
52
52
  this.readChannel.removeEventListener('message', messageListener)
53
53
  }
@@ -46,9 +46,8 @@ func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
46
46
  }
47
47
 
48
48
  // Start sets the writer and writes the MsgSend message.
49
- // firstMsg can be nil, but is usually set if this is a non-streaming rpc.
50
49
  // must only be called once!
51
- func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
50
+ func (r *ClientRPC) Start(writer Writer, writeFirstMsg bool, firstMsg []byte) error {
52
51
  select {
53
52
  case <-r.ctx.Done():
54
53
  r.Close()
@@ -56,7 +55,13 @@ func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
56
55
  default:
57
56
  }
58
57
  r.writer = writer
59
- pkt := NewCallStartPacket(r.service, r.method, firstMsg)
58
+ var firstMsgEmpty bool
59
+ if writeFirstMsg {
60
+ firstMsgEmpty = len(firstMsg) == 0
61
+ } else {
62
+ firstMsg = nil
63
+ }
64
+ pkt := NewCallStartPacket(r.service, r.method, firstMsg, firstMsgEmpty)
60
65
  if err := writer.WritePacket(pkt); err != nil {
61
66
  r.Close()
62
67
  return err
@@ -1,4 +1,4 @@
1
- import type { CallStart } from './rpcproto.js'
1
+ import type { CallStart } from './rpcproto.pb.js'
2
2
  import { CommonRPC } from './common-rpc.js'
3
3
 
4
4
  // ClientRPC is an ongoing RPC from the client side.
package/srpc/client.go CHANGED
@@ -19,7 +19,7 @@ type Client interface {
19
19
 
20
20
  // OpenStreamFunc opens a stream with a remote.
21
21
  // msgHandler must not be called concurrently.
22
- type OpenStreamFunc = func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error)
22
+ type OpenStreamFunc = func(ctx context.Context, msgHandler PacketHandler) (Writer, error)
23
23
 
24
24
  // client implements Client with a transport.
25
25
  type client struct {
@@ -48,7 +48,7 @@ func (c *client) Invoke(rctx context.Context, service, method string, in, out Me
48
48
  if err != nil {
49
49
  return err
50
50
  }
51
- if err := clientRPC.Start(writer, firstMsg); err != nil {
51
+ if err := clientRPC.Start(writer, true, firstMsg); err != nil {
52
52
  return err
53
53
  }
54
54
  msgs, err := clientRPC.ReadAll()
@@ -85,11 +85,11 @@ func (c *client) NewStream(ctx context.Context, service, method string, firstMsg
85
85
  if err != nil {
86
86
  return nil, err
87
87
  }
88
- if err := clientRPC.Start(writer, firstMsgData); err != nil {
88
+ if err := clientRPC.Start(writer, firstMsg != nil, firstMsgData); err != nil {
89
89
  return nil, err
90
90
  }
91
91
 
92
- return NewRPCStream(ctx, clientRPC.writer, clientRPC.dataCh), nil
92
+ return NewMsgStream(ctx, clientRPC.writer, clientRPC.dataCh), nil
93
93
  }
94
94
 
95
95
  // _ is a type assertion
package/srpc/client.ts CHANGED
@@ -29,36 +29,38 @@ function writeClientStream(call: ClientRPC, data: Observable<Uint8Array>) {
29
29
 
30
30
  // Client implements the ts-proto Rpc interface with the drpcproto protocol.
31
31
  export class Client implements TsProtoRpc {
32
- // openConnFn is a promise which contains the OpenStreamFunc.
33
- private openConnFn: Promise<OpenStreamFunc>
34
- // _openConnFn resolves openConnFn.
35
- private _openConnFn?: (conn?: OpenStreamFunc, err?: Error) => void
32
+ // openStreamFn is a promise which contains the OpenStreamFunc.
33
+ private openStreamFn: Promise<OpenStreamFunc>
34
+ // _openStreamFn resolves openStreamFn.
35
+ private _openStreamFn?: (conn?: OpenStreamFunc, err?: Error) => void
36
36
 
37
- constructor(openConnFn?: OpenStreamFunc) {
38
- this.openConnFn = this.setOpenConnFn(openConnFn)
37
+ constructor(openStreamFn?: OpenStreamFunc) {
38
+ this.openStreamFn = this.setOpenStreamFn(openStreamFn)
39
39
  }
40
40
 
41
- // setOpenConnFn updates the openConnFn for the Client.
42
- public setOpenConnFn(openConnFn?: OpenStreamFunc): Promise<OpenStreamFunc> {
43
- if (this._openConnFn) {
44
- if (openConnFn) {
45
- this._openConnFn(openConnFn)
46
- this._openConnFn = undefined
41
+ // setOpenStreamFn updates the openStreamFn for the Client.
42
+ public setOpenStreamFn(
43
+ openStreamFn?: OpenStreamFunc
44
+ ): Promise<OpenStreamFunc> {
45
+ if (this._openStreamFn) {
46
+ if (openStreamFn) {
47
+ this._openStreamFn(openStreamFn)
48
+ this._openStreamFn = undefined
47
49
  }
48
50
  } else {
49
- if (openConnFn) {
50
- this.openConnFn = Promise.resolve(openConnFn)
51
+ if (openStreamFn) {
52
+ this.openStreamFn = Promise.resolve(openStreamFn)
51
53
  } else {
52
- this.initOpenConnFn()
54
+ this.initOpenStreamFn()
53
55
  }
54
56
  }
55
- return this.openConnFn
57
+ return this.openStreamFn
56
58
  }
57
59
 
58
- // initOpenConnFn creates the empty Promise for openConnFn.
59
- private initOpenConnFn(): Promise<OpenStreamFunc> {
60
+ // initOpenStreamFn creates the empty Promise for openStreamFn.
61
+ private initOpenStreamFn(): Promise<OpenStreamFunc> {
60
62
  const openPromise = new Promise<OpenStreamFunc>((resolve, reject) => {
61
- this._openConnFn = (conn?: OpenStreamFunc, err?: Error) => {
63
+ this._openStreamFn = (conn?: OpenStreamFunc, err?: Error) => {
62
64
  if (err) {
63
65
  reject(err)
64
66
  } else if (conn) {
@@ -66,8 +68,8 @@ export class Client implements TsProtoRpc {
66
68
  }
67
69
  }
68
70
  })
69
- this.openConnFn = openPromise
70
- return this.openConnFn
71
+ this.openStreamFn = openPromise
72
+ return this.openStreamFn
71
73
  }
72
74
 
73
75
  // request starts a non-streaming request.
@@ -109,7 +111,7 @@ export class Client implements TsProtoRpc {
109
111
  method: string,
110
112
  data: Uint8Array
111
113
  ): Observable<Uint8Array> {
112
- const pushServerData: Pushable<Uint8Array> = pushable()
114
+ const pushServerData: Pushable<Uint8Array> = pushable({ objectMode: true })
113
115
  const serverData = observableFrom(pushServerData)
114
116
  this.startRpc(service, method, data)
115
117
  .then(async (call) => {
@@ -132,7 +134,7 @@ export class Client implements TsProtoRpc {
132
134
  method: string,
133
135
  data: Observable<Uint8Array>
134
136
  ): Observable<Uint8Array> {
135
- const pushServerData: Pushable<Uint8Array> = pushable()
137
+ const pushServerData: Pushable<Uint8Array> = pushable({ objectMode: true })
136
138
  const serverData = observableFrom(pushServerData)
137
139
  this.startRpc(service, method, null)
138
140
  .then(async (call) => {
@@ -168,8 +170,8 @@ export class Client implements TsProtoRpc {
168
170
  rpcMethod: string,
169
171
  data: Uint8Array | null
170
172
  ): Promise<ClientRPC> {
171
- const openConnFn = await this.openConnFn
172
- const conn = await openConnFn()
173
+ const openStreamFn = await this.openStreamFn
174
+ const conn = await openStreamFn()
173
175
  const call = new ClientRPC(rpcService, rpcMethod)
174
176
  pipe(
175
177
  conn,