starpc 0.3.2 → 0.3.5

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Stream RPC
2
2
 
3
- **starpc** implements [Proto3 services] in both TypeScript and Go.
3
+ **starpc** implements [Proto3 services] (server & client) in both TypeScript and Go.
4
4
 
5
5
  [Proto3 services]: https://developers.google.com/protocol-buffers/docs/proto3#services
6
6
 
@@ -13,8 +13,6 @@ Can use any Stream multiplexer: defaults to [libp2p-mplex] over a WebSocket.
13
13
 
14
14
  [libp2p-mplex]: https://github.com/libp2p/js-libp2p-mplex
15
15
 
16
- Note: the server has not yet been implemented in TypeScript.
17
-
18
16
  # Usage
19
17
 
20
18
  Starting with the [protobuf-project] repository on the "starpc" branch.
@@ -30,6 +28,8 @@ The demo/boilerplate project implements the Echo example below.
30
28
 
31
29
  [protobuf-project]: https://github.com/aperturerobotics/protobuf-project/tree/starpc
32
30
 
31
+ This repository uses protowrap, see the [Makefile](./Makefile).
32
+
33
33
  ## Protobuf
34
34
 
35
35
  The following examples use the [echo](./echo/echo.proto) protobuf sample.
@@ -56,9 +56,9 @@ message EchoMsg {
56
56
  }
57
57
  ```
58
58
 
59
- ## Go: Server & Client
59
+ ## Go
60
60
 
61
- A basic example can be found in the [e2e test]:
61
+ This example demonstrates both the server and client:
62
62
 
63
63
  ```go
64
64
  // construct the server
@@ -96,39 +96,58 @@ if out.GetBody() != bodyTxt {
96
96
 
97
97
  See the ts-proto README to generate the TypeScript for your protobufs.
98
98
 
99
- Also check out the [integration](./integration/integration.ts) test.
99
+ For an example of Go <-> TypeScript interop, see the [integration] test. For an
100
+ example of TypeScript <-> TypeScript interop, see the [e2e] test.
100
101
 
101
- Supports any AsyncIterable communication channel with an included implementation
102
- for WebSockets.
102
+ [e2e]: ./e2e/e2e.ts
103
+ [integration]: ./integration/integration.ts
103
104
 
104
- This repository uses protowrap, see the [Makefile](./Makefile).
105
-
106
- `WebSocketConn` uses [js-libp2p-mplex] to multiplex streams over the WebSocket.
105
+ Supports any AsyncIterable communication channel. `DuplexConn`,
106
+ `MessagePortConn`, and `WebSocketConn` use [js-libp2p-mplex] to multiplex
107
+ streams, but any multiplexer can be used.
107
108
 
108
109
  [js-libp2p-mplex]: https://github.com/libp2p/js-libp2p-mplex
109
110
 
110
- ### Server
111
+ This example demonstrates both the server and client:
111
112
 
112
113
  ```typescript
113
- import { WebSocketConn, Server, createMux } from '../srpc'
114
- import { EchoerClientImpl } from '../echo/echo'
114
+ import { pipe } from 'it-pipe'
115
+ import { createHandler, createMux, Server, Client, Conn } from 'srpc'
116
+ import { EchoerDefinition, EchoerServer, runClientTest } from 'srpc/echo'
115
117
 
116
118
  const mux = createMux()
117
- mux.register(TODO)
119
+ const echoer = new EchoerServer()
120
+ mux.register(createHandler(EchoerDefinition, echoer))
118
121
  const server = new Server(mux)
119
122
 
120
- // TODO: accept a WebSocket-like object.
121
- const ws = TODO
122
- const channel = new WebSocketConn(ws, server)
123
- // incoming streams will be handled by server.
123
+ const clientConn = new Conn()
124
+ const serverConn = new Conn(server)
125
+ pipe(clientConn, serverConn, clientConn)
126
+ const client = new Client(clientConn.buildOpenStreamFunc())
127
+
128
+ console.log('Calling Echo: unary call...')
129
+ let result = await demoServiceClient.Echo({
130
+ body: 'Hello world!',
131
+ })
132
+ console.log('success: output', result.body)
133
+
134
+ const clientRequestStream = new Observable<EchoMsg>(subscriber => {
135
+ subscriber.next({body: 'Hello world from streaming request.'})
136
+ subscriber.complete()
137
+ })
138
+
139
+ console.log('Calling EchoClientStream: client -> server...')
140
+ result = await demoServiceClient.EchoClientStream(clientRequestStream)
141
+ console.log('success: output', result.body)
124
142
  ```
125
143
 
144
+ ## WebSocket
126
145
 
127
- ### Client
146
+ One way to integrate Go and TypeScript is over a WebSocket:
128
147
 
129
148
  ```typescript
130
- import { WebSocketConn } from '../srpc'
131
- import { EchoerClientImpl } from '../echo/echo'
149
+ import { WebSocketConn } from 'srpc'
150
+ import { EchoerClientImpl } from 'srpc/echo'
132
151
 
133
152
  const ws = new WebSocket('ws://localhost:5000/demo')
134
153
  const channel = new WebSocketConn(ws)
@@ -139,15 +158,6 @@ const result = await demoServiceClient.Echo({
139
158
  body: "Hello world!"
140
159
  })
141
160
  console.log('output', result.body)
142
-
143
- const clientRequestStream = new Observable<EchoMsg>(subscriber => {
144
- subscriber.next({body: 'Hello world from streaming request.'})
145
- subscriber.complete()
146
- })
147
-
148
- console.log('Calling EchoClientStream: client -> server...')
149
- result = await demoServiceClient.EchoClientStream(clientRequestStream)
150
- console.log('success: output', result.body)
151
161
  ```
152
162
 
153
163
  # Attribution
@@ -3,8 +3,10 @@ import type { TsProtoRpc } from './ts-proto-rpc.js';
3
3
  import type { OpenStreamFunc } from './stream.js';
4
4
  export declare class Client implements TsProtoRpc {
5
5
  private openConnFn;
6
- constructor(openConnFn: OpenStreamFunc);
7
- setOpenConnFn(openConnFn: OpenStreamFunc): void;
6
+ private _openConnFn?;
7
+ constructor(openConnFn?: OpenStreamFunc);
8
+ setOpenConnFn(openConnFn?: OpenStreamFunc): Promise<OpenStreamFunc>;
9
+ private initOpenConnFn;
8
10
  request(service: string, method: string, data: Uint8Array): Promise<Uint8Array>;
9
11
  clientStreamingRequest(service: string, method: string, data: Observable<Uint8Array>): Promise<Uint8Array>;
10
12
  serverStreamingRequest(service: string, method: string, data: Uint8Array): Observable<Uint8Array>;
@@ -19,15 +19,45 @@ function writeClientStream(call, data) {
19
19
  }
20
20
  // Client implements the ts-proto Rpc interface with the drpcproto protocol.
21
21
  export class Client {
22
- // openConnFn is the open connection function.
23
- // called when starting RPC.
22
+ // openConnFn is a promise which contains the OpenStreamFunc.
24
23
  openConnFn;
24
+ // _openConnFn resolves openConnFn.
25
+ _openConnFn;
25
26
  constructor(openConnFn) {
26
- this.openConnFn = openConnFn;
27
+ this.openConnFn = this.setOpenConnFn(openConnFn);
27
28
  }
28
29
  // setOpenConnFn updates the openConnFn for the Client.
29
30
  setOpenConnFn(openConnFn) {
30
- this.openConnFn = openConnFn;
31
+ if (this._openConnFn) {
32
+ if (openConnFn) {
33
+ this._openConnFn(openConnFn);
34
+ this._openConnFn = undefined;
35
+ }
36
+ }
37
+ else {
38
+ if (openConnFn) {
39
+ this.openConnFn = Promise.resolve(openConnFn);
40
+ }
41
+ else {
42
+ this.initOpenConnFn();
43
+ }
44
+ }
45
+ return this.openConnFn;
46
+ }
47
+ // initOpenConnFn creates the empty Promise for openConnFn.
48
+ initOpenConnFn() {
49
+ const openPromise = new Promise((resolve, reject) => {
50
+ this._openConnFn = (conn, err) => {
51
+ if (err) {
52
+ reject(err);
53
+ }
54
+ else if (conn) {
55
+ resolve(conn);
56
+ }
57
+ };
58
+ });
59
+ this.openConnFn = openPromise;
60
+ return this.openConnFn;
31
61
  }
32
62
  // request starts a non-streaming request.
33
63
  async request(service, method, data) {
@@ -105,7 +135,8 @@ export class Client {
105
135
  // throws any error starting the rpc call
106
136
  // if data == null and data.length == 0, sends a separate data packet.
107
137
  async startRpc(rpcService, rpcMethod, data) {
108
- const conn = await this.openConnFn();
138
+ const openConnFn = await this.openConnFn;
139
+ const conn = await openConnFn();
109
140
  const call = new ClientRPC(rpcService, rpcMethod);
110
141
  pipe(conn, parseLengthPrefixTransform(), decodePacketSource, call, encodePacketSource, prependLengthPrefixTransform(), conn);
111
142
  await call.writeCallStart(data || undefined);
@@ -4,6 +4,5 @@ export { Server } from './server.js';
4
4
  export { Conn, ConnParams } from './conn.js';
5
5
  export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js';
6
6
  export { Mux, createMux } from './mux.js';
7
- export { WebSocketConn } from './websocket.js';
8
7
  export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
9
8
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
@@ -3,6 +3,5 @@ export { Server } from './server.js';
3
3
  export { Conn } from './conn.js';
4
4
  export { createHandler, createInvokeFn } from './handler.js';
5
5
  export { createMux } from './mux.js';
6
- export { WebSocketConn } from './websocket.js';
7
6
  export { BroadcastChannelIterable, newBroadcastChannelIterable, BroadcastChannelConn, } from './broadcast-channel.js';
8
7
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
@@ -1,6 +1,9 @@
1
1
  #!/bin/bash
2
2
  set -eo pipefail
3
3
 
4
+ unset GOOS
5
+ unset GOARCH
6
+
4
7
  echo "Compiling ts..."
5
8
  # ../node_modules/.bin/tsc --out integration.js --project tsconfig.json
6
9
  ../node_modules/.bin/esbuild integration.ts --bundle --platform=node --outfile=integration.js
@@ -1,4 +1,4 @@
1
- import { WebSocketConn } from '../srpc'
1
+ import { WebSocketConn } from '../srpc/websocket'
2
2
  import { runClientTest } from '../echo'
3
3
  import WebSocket from 'isomorphic-ws'
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
package/srpc/client.ts CHANGED
@@ -29,17 +29,45 @@ 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 the open connection function.
33
- // called when starting RPC.
34
- private openConnFn: OpenStreamFunc
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
35
36
 
36
- constructor(openConnFn: OpenStreamFunc) {
37
- this.openConnFn = openConnFn
37
+ constructor(openConnFn?: OpenStreamFunc) {
38
+ this.openConnFn = this.setOpenConnFn(openConnFn)
38
39
  }
39
40
 
40
41
  // setOpenConnFn updates the openConnFn for the Client.
41
- public setOpenConnFn(openConnFn: OpenStreamFunc) {
42
- this.openConnFn = openConnFn
42
+ public setOpenConnFn(openConnFn?: OpenStreamFunc): Promise<OpenStreamFunc> {
43
+ if (this._openConnFn) {
44
+ if (openConnFn) {
45
+ this._openConnFn(openConnFn)
46
+ this._openConnFn = undefined
47
+ }
48
+ } else {
49
+ if (openConnFn) {
50
+ this.openConnFn = Promise.resolve(openConnFn)
51
+ } else {
52
+ this.initOpenConnFn()
53
+ }
54
+ }
55
+ return this.openConnFn
56
+ }
57
+
58
+ // initOpenConnFn creates the empty Promise for openConnFn.
59
+ private initOpenConnFn(): Promise<OpenStreamFunc> {
60
+ const openPromise = new Promise<OpenStreamFunc>((resolve, reject) => {
61
+ this._openConnFn = (conn?: OpenStreamFunc, err?: Error) => {
62
+ if (err) {
63
+ reject(err)
64
+ } else if (conn) {
65
+ resolve(conn)
66
+ }
67
+ }
68
+ })
69
+ this.openConnFn = openPromise
70
+ return this.openConnFn
43
71
  }
44
72
 
45
73
  // request starts a non-streaming request.
@@ -140,7 +168,8 @@ export class Client implements TsProtoRpc {
140
168
  rpcMethod: string,
141
169
  data: Uint8Array | null
142
170
  ): Promise<ClientRPC> {
143
- const conn = await this.openConnFn()
171
+ const openConnFn = await this.openConnFn
172
+ const conn = await openConnFn()
144
173
  const call = new ClientRPC(rpcService, rpcMethod)
145
174
  pipe(
146
175
  conn,
package/srpc/index.ts CHANGED
@@ -4,7 +4,6 @@ export { Server } from './server.js'
4
4
  export { Conn, ConnParams } from './conn.js'
5
5
  export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js'
6
6
  export { Mux, createMux } from './mux.js'
7
- export { WebSocketConn } from './websocket.js'
8
7
  export {
9
8
  BroadcastChannelIterable,
10
9
  newBroadcastChannelIterable,
@@ -57,7 +57,7 @@ func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
57
57
  return
58
58
  }
59
59
  go func() {
60
- err := s.srpc.HandleConn(ctx, strm)
60
+ err := s.srpc.HandleStream(ctx, strm)
61
61
  _ = err
62
62
  // TODO: handle / log error?
63
63
  // err != nil && err != io.EOF && err != context.Canceled
@@ -7,12 +7,12 @@ import (
7
7
 
8
8
  // NewServerPipe constructs a open stream func which creates an in-memory Pipe
9
9
  // Stream with the given Server. Starts read pumps for both. Starts the
10
- // HandleConn function on the server in a separate goroutine.
10
+ // HandleStream function on the server in a separate goroutine.
11
11
  func NewServerPipe(server *Server) OpenStreamFunc {
12
12
  return func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error) {
13
13
  srvPipe, clientPipe := net.Pipe()
14
14
  go func() {
15
- _ = server.HandleConn(ctx, srvPipe)
15
+ _ = server.HandleStream(ctx, srvPipe)
16
16
  }()
17
17
  clientPrw := NewPacketReadWriter(clientPipe, msgHandler)
18
18
  go func() {
package/srpc/server.go CHANGED
@@ -3,6 +3,8 @@ package srpc
3
3
  import (
4
4
  "context"
5
5
  "io"
6
+
7
+ "github.com/libp2p/go-libp2p-core/network"
6
8
  )
7
9
 
8
10
  // Server handles incoming RPC streams with a mux.
@@ -18,8 +20,8 @@ func NewServer(mux Mux) *Server {
18
20
  }
19
21
  }
20
22
 
21
- // HandleConn handles an incoming ReadWriteCloser.
22
- func (s *Server) HandleConn(ctx context.Context, rwc io.ReadWriteCloser) error {
23
+ // HandleStream handles an incoming ReadWriteCloser stream.
24
+ func (s *Server) HandleStream(ctx context.Context, rwc io.ReadWriteCloser) error {
23
25
  subCtx, subCtxCancel := context.WithCancel(ctx)
24
26
  defer subCtxCancel()
25
27
  serverRPC := NewServerRPC(subCtx, s.mux)
@@ -27,3 +29,28 @@ func (s *Server) HandleConn(ctx context.Context, rwc io.ReadWriteCloser) error {
27
29
  serverRPC.SetWriter(prw)
28
30
  return prw.ReadPump()
29
31
  }
32
+
33
+ // AcceptMuxedConn runs a loop which calls Accept on a muxer to handle streams.
34
+ //
35
+ // Starts HandleStream in a separate goroutine to handle the stream.
36
+ // Returns context.Canceled or io.EOF when the loop is complete / closed.
37
+ func (s *Server) AcceptMuxedConn(ctx context.Context, mplex network.MuxedConn) error {
38
+ for {
39
+ select {
40
+ case <-ctx.Done():
41
+ return context.Canceled
42
+ default:
43
+ if mplex.IsClosed() {
44
+ return io.EOF
45
+ }
46
+ }
47
+
48
+ muxedStream, err := mplex.AcceptStream()
49
+ if err != nil {
50
+ return err
51
+ }
52
+ go func() {
53
+ _ = s.HandleStream(ctx, muxedStream)
54
+ }()
55
+ }
56
+ }