starpc 0.17.0 → 0.18.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.
@@ -4,7 +4,7 @@ import { Pushable } from 'it-pushable';
4
4
  import { Source, Sink } from 'it-stream-types';
5
5
  import { Uint8ArrayList } from 'uint8arraylist';
6
6
  export type RpcStreamCaller = (request: AsyncIterable<RpcStreamPacket>) => AsyncIterable<RpcStreamPacket>;
7
- export declare function openRpcStream(componentId: string, caller: RpcStreamCaller): Promise<Stream>;
7
+ export declare function openRpcStream(componentId: string, caller: RpcStreamCaller, waitAck?: boolean): Promise<Stream>;
8
8
  export declare function buildRpcStreamOpenStream(componentId: string, caller: RpcStreamCaller): OpenStreamFunc;
9
9
  export type RpcStreamHandler = (stream: Stream) => void;
10
10
  export type RpcStreamGetter = (componentId: string) => Promise<RpcStreamHandler | null>;
@@ -1,7 +1,7 @@
1
1
  import { pushable } from 'it-pushable';
2
2
  // openRpcStream attempts to open a stream over a RPC call.
3
- // waits for the remote to ack the stream before returning.
4
- export async function openRpcStream(componentId, caller) {
3
+ // if waitAck is set, waits for the remote to ack the stream before returning.
4
+ export async function openRpcStream(componentId, caller, waitAck) {
5
5
  const packetSink = pushable({ objectMode: true });
6
6
  const packetSource = caller(packetSink);
7
7
  // write the component id
@@ -11,21 +11,24 @@ export async function openRpcStream(componentId, caller) {
11
11
  init: { componentId },
12
12
  },
13
13
  });
14
- // wait for ack
14
+ // construct packet stream
15
15
  const packetIt = packetSource[Symbol.asyncIterator]();
16
- const ackPacketIt = await packetIt.next();
17
- if (ackPacketIt.done) {
18
- throw new Error(`rpcstream: closed before ack packet`);
19
- }
20
- const ackPacket = ackPacketIt.value;
21
- const ackBody = ackPacket?.body;
22
- if (!ackBody || ackBody.$case !== 'ack') {
23
- const msgType = ackBody?.$case || 'none';
24
- throw new Error(`rpcstream: expected ack packet but got ${msgType}`);
25
- }
26
- const errStr = ackBody.ack?.error;
27
- if (errStr) {
28
- throw new Error(`rpcstream: remote: ${errStr}`);
16
+ // wait for ack, if set.
17
+ if (waitAck) {
18
+ const ackPacketIt = await packetIt.next();
19
+ if (ackPacketIt.done) {
20
+ throw new Error(`rpcstream: closed before ack packet`);
21
+ }
22
+ const ackPacket = ackPacketIt.value;
23
+ const ackBody = ackPacket?.body;
24
+ if (!ackBody || ackBody.$case !== 'ack') {
25
+ const msgType = ackBody?.$case || 'none';
26
+ throw new Error(`rpcstream: expected ack packet but got ${msgType}`);
27
+ }
28
+ const errStr = ackBody.ack?.error;
29
+ if (errStr) {
30
+ throw new Error(`rpcstream: remote: ${errStr}`);
31
+ }
29
32
  }
30
33
  // build & return the data stream
31
34
  return new RpcStream(packetSink, packetIt); // packetSource)
@@ -136,10 +139,19 @@ export class RpcStream {
136
139
  }
137
140
  const value = msgIt.value;
138
141
  const body = value?.body;
139
- if (!body || body.$case !== 'data') {
142
+ if (!body) {
140
143
  continue;
141
144
  }
142
- yield* [body.data];
145
+ switch (body.$case) {
146
+ case 'ack':
147
+ if (body.ack?.error?.length) {
148
+ throw new Error(body.ack.error);
149
+ }
150
+ break;
151
+ case 'data':
152
+ yield* [body.data];
153
+ break;
154
+ }
143
155
  }
144
156
  })();
145
157
  }
@@ -8,7 +8,7 @@ export { Packet, CallStart, CallData } from './rpcproto.pb.js';
8
8
  export { Mux, StaticMux, createMux } from './mux.js';
9
9
  export { BroadcastChannelDuplex, newBroadcastChannelDuplex, BroadcastChannelConn, } from './broadcast-channel.js';
10
10
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
11
- export { MessageDefinition, DecodeMessageTransform, buildDecodeMessageTransform, EncodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
11
+ export { MessageDefinition, DecodeMessageTransform, buildDecodeMessageTransform, EncodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
12
12
  export { ValueCtr } from './value-ctr.js';
13
13
  export { OpenStreamCtr } from './open-stream-ctr.js';
14
14
  export { writeToPushable } from './pushable';
@@ -7,7 +7,7 @@ export { Packet, CallStart, CallData } from './rpcproto.pb.js';
7
7
  export { StaticMux, createMux } from './mux.js';
8
8
  export { BroadcastChannelDuplex, newBroadcastChannelDuplex, BroadcastChannelConn, } from './broadcast-channel.js';
9
9
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
10
- export { buildDecodeMessageTransform, buildEncodeMessageTransform, } from './message.js';
10
+ export { buildDecodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
11
11
  export { ValueCtr } from './value-ctr.js';
12
12
  export { OpenStreamCtr } from './open-stream-ctr.js';
13
13
  export { writeToPushable } from './pushable';
@@ -1,10 +1,13 @@
1
1
  import * as pbjs from 'protobufjs/minimal';
2
2
  import type { Source } from 'it-stream-types';
3
3
  export interface MessageDefinition<T> {
4
+ create(msg?: Partial<T>): T;
4
5
  encode(message: T, writer?: pbjs.Writer): pbjs.Writer;
5
6
  decode(input: pbjs.Reader | Uint8Array, length?: number): T;
6
7
  }
8
+ export declare function memoProto<T>(def: MessageDefinition<T>): (msg: Partial<T>) => Uint8Array;
9
+ export declare function memoProtoDecode<T>(def: MessageDefinition<T>): (msg: Uint8Array) => T;
7
10
  export type DecodeMessageTransform<T> = (source: Source<Uint8Array | Uint8Array[]>) => AsyncIterable<T>;
8
- export declare function buildDecodeMessageTransform<T>(def: MessageDefinition<T>): DecodeMessageTransform<T>;
11
+ export declare function buildDecodeMessageTransform<T>(def: MessageDefinition<T>, memoize?: boolean): DecodeMessageTransform<T>;
9
12
  export type EncodeMessageTransform<T> = (source: Source<T | T[]>) => AsyncIterable<Uint8Array>;
10
- export declare function buildEncodeMessageTransform<T>(def: MessageDefinition<T>): EncodeMessageTransform<T>;
13
+ export declare function buildEncodeMessageTransform<T>(def: MessageDefinition<T>, memoize?: boolean): EncodeMessageTransform<T>;
@@ -1,31 +1,53 @@
1
+ import memoize from 'memoize-one';
2
+ // memoProto returns a function that encodes the message and caches the result.
3
+ export function memoProto(def) {
4
+ return memoize((msg) => {
5
+ return def.encode(def.create(msg)).finish();
6
+ });
7
+ }
8
+ // memoProtoDecode returns a function that decodes the message and caches the result.
9
+ export function memoProtoDecode(def) {
10
+ return memoize((msg) => {
11
+ return def.decode(msg);
12
+ });
13
+ }
1
14
  // buildDecodeMessageTransform builds a source of decoded messages.
2
- export function buildDecodeMessageTransform(def) {
15
+ //
16
+ // set memoize if you expect to repeatedly see the same message.
17
+ export function buildDecodeMessageTransform(def, memoize) {
18
+ const decode = memoize ? memoProtoDecode(def) : def.decode.bind(def);
3
19
  // decodeMessageSource unmarshals and async yields encoded Messages.
4
20
  return async function* decodeMessageSource(source) {
5
21
  for await (const pkt of source) {
6
22
  if (Array.isArray(pkt)) {
7
23
  for (const p of pkt) {
8
- yield* [def.decode(p)];
24
+ yield* [decode(p)];
9
25
  }
10
26
  }
11
27
  else {
12
- yield* [def.decode(pkt)];
28
+ yield* [decode(pkt)];
13
29
  }
14
30
  }
15
31
  };
16
32
  }
17
33
  // buildEncodeMessageTransform builds a source of decoded messages.
18
- export function buildEncodeMessageTransform(def) {
34
+ // set memoize if you expect to repeatedly encode the same message.
35
+ export function buildEncodeMessageTransform(def, memoize) {
36
+ const encode = memoize
37
+ ? memoProto(def)
38
+ : (msg) => {
39
+ return def.encode(msg).finish();
40
+ };
19
41
  // encodeMessageSource encodes messages to byte arrays.
20
42
  return async function* encodeMessageSource(source) {
21
43
  for await (const pkt of source) {
22
44
  if (Array.isArray(pkt)) {
23
45
  for (const p of pkt) {
24
- yield* [def.encode(p).finish()];
46
+ yield* [encode(p)];
25
47
  }
26
48
  }
27
49
  else {
28
- yield* [def.encode(pkt).finish()];
50
+ yield* [encode(pkt)];
29
51
  }
30
52
  }
31
53
  };
package/e2e/e2e_test.go CHANGED
@@ -227,7 +227,7 @@ func TestE2E_RpcStream(t *testing.T) {
227
227
  RunE2E(t, func(client echo.SRPCEchoerClient) error {
228
228
  openStreamFn := rpcstream.NewRpcStreamOpenStream(func(ctx context.Context) (rpcstream.RpcStream, error) {
229
229
  return client.RpcStream(ctx)
230
- }, "test")
230
+ }, "test", false)
231
231
  proxiedClient := srpc.NewClient(openStreamFn)
232
232
  proxiedSvc := echo.NewSRPCEchoerClient(proxiedClient)
233
233
 
package/go.mod CHANGED
@@ -9,7 +9,7 @@ require (
9
9
  )
10
10
 
11
11
  require (
12
- github.com/aperturerobotics/util v0.0.0-20230105015752-3e6dd02b20c6
12
+ github.com/aperturerobotics/util v1.0.3-0.20230130061818-dab10b56858b
13
13
  github.com/libp2p/go-libp2p v0.24.2
14
14
  github.com/libp2p/go-yamux/v4 v4.0.1-0.20220919134236-1c09f2ab3ec1
15
15
  github.com/sirupsen/logrus v1.9.0
package/go.sum CHANGED
@@ -1,5 +1,5 @@
1
- github.com/aperturerobotics/util v0.0.0-20230105015752-3e6dd02b20c6 h1:YCg1Eyh5M5SvWCpJmdM62iInd0yfZXSGFld10GQIIPA=
2
- github.com/aperturerobotics/util v0.0.0-20230105015752-3e6dd02b20c6/go.mod h1:up2AYcp62UgmFVTm7QhM4USXAKGv73gpb5dHraKmzxQ=
1
+ github.com/aperturerobotics/util v1.0.3-0.20230130061818-dab10b56858b h1:E+riBVCoYAZL2ATHTlDZYZRZj/6FPb+UGHVTQdU03UU=
2
+ github.com/aperturerobotics/util v1.0.3-0.20230130061818-dab10b56858b/go.mod h1:gFLdglkzXYEWP/iYrOGeJEKzfjarlMDPrLNLgwDHC1o=
3
3
  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4
4
  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5
5
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -36,7 +36,7 @@
36
36
  "build": "rimraf ./dist && tsc --project tsconfig.build.json --outDir ./dist/",
37
37
  "check": "npm run typecheck",
38
38
  "typecheck": "tsc --noEmit",
39
- "deps": "depcheck --ignores 'bufferutil,utf-8-validate,ts-proto,@aperturerobotics/ts-common'",
39
+ "deps": "depcheck --ignores 'bufferutil,utf-8-validate,ts-proto,rimraf,@aperturerobotics/ts-common'",
40
40
  "codegen": "npm run gen",
41
41
  "ci": "npm run build && npm run lint:js && npm run lint:go",
42
42
  "format": "prettier --write './{srpc,echo,e2e,integration,rpcstream}/**/(*.ts|*.tsx|*.html|*.css)'",
@@ -71,7 +71,7 @@
71
71
  "prettier": "^2.8.3",
72
72
  "rimraf": "^4.1.0",
73
73
  "ts-proto": "^1.138.0",
74
- "typescript": "^4.9.4",
74
+ "typescript": "^4.9.5",
75
75
  "utf-8-validate": "^6.0.0"
76
76
  },
77
77
  "dependencies": {
@@ -88,6 +88,7 @@
88
88
  "it-stream-types": "^1.0.5",
89
89
  "it-ws": "^5.0.6",
90
90
  "long": "^5.2.1",
91
+ "memoize-one": "^6.0.0",
91
92
  "patch-package": "^6.5.1",
92
93
  "protobufjs": "^7.1.2",
93
94
  "uint8arraylist": "^2.4.3",
package/srpc/index.ts CHANGED
@@ -22,6 +22,8 @@ export {
22
22
  buildDecodeMessageTransform,
23
23
  EncodeMessageTransform,
24
24
  buildEncodeMessageTransform,
25
+ memoProto,
26
+ memoProtoDecode,
25
27
  } from './message.js'
26
28
  export { ValueCtr } from './value-ctr.js'
27
29
  export { OpenStreamCtr } from './open-stream-ctr.js'
package/srpc/message.ts CHANGED
@@ -1,23 +1,49 @@
1
1
  import * as pbjs from 'protobufjs/minimal'
2
2
  import type { Source } from 'it-stream-types'
3
+ import memoize from 'memoize-one'
3
4
 
4
5
  // MessageDefinition represents a ts-proto message definition.
5
6
  export interface MessageDefinition<T> {
7
+ // create creates a full message from a partial and/or no message.
8
+ create(msg?: Partial<T>): T
6
9
  // encode encodes the message and returns writer.
7
10
  encode(message: T, writer?: pbjs.Writer): pbjs.Writer
8
11
  // decode decodes the message from the reader
9
12
  decode(input: pbjs.Reader | Uint8Array, length?: number): T
10
13
  }
11
14
 
15
+ // memoProto returns a function that encodes the message and caches the result.
16
+ export function memoProto<T>(
17
+ def: MessageDefinition<T>
18
+ ): (msg: Partial<T>) => Uint8Array {
19
+ return memoize((msg: Partial<T>): Uint8Array => {
20
+ return def.encode(def.create(msg)).finish()
21
+ })
22
+ }
23
+
24
+ // memoProtoDecode returns a function that decodes the message and caches the result.
25
+ export function memoProtoDecode<T>(
26
+ def: MessageDefinition<T>
27
+ ): (msg: Uint8Array) => T {
28
+ return memoize((msg: Uint8Array): T => {
29
+ return def.decode(msg)
30
+ })
31
+ }
32
+
12
33
  // DecodeMessageTransform decodes messages to objects.
13
34
  export type DecodeMessageTransform<T> = (
14
35
  source: Source<Uint8Array | Uint8Array[]>
15
36
  ) => AsyncIterable<T>
16
37
 
17
38
  // buildDecodeMessageTransform builds a source of decoded messages.
39
+ //
40
+ // set memoize if you expect to repeatedly see the same message.
18
41
  export function buildDecodeMessageTransform<T>(
19
- def: MessageDefinition<T>
42
+ def: MessageDefinition<T>,
43
+ memoize?: boolean
20
44
  ): DecodeMessageTransform<T> {
45
+ const decode = memoize ? memoProtoDecode(def) : def.decode.bind(def)
46
+
21
47
  // decodeMessageSource unmarshals and async yields encoded Messages.
22
48
  return async function* decodeMessageSource(
23
49
  source: Source<Uint8Array | Uint8Array[]>
@@ -25,10 +51,10 @@ export function buildDecodeMessageTransform<T>(
25
51
  for await (const pkt of source) {
26
52
  if (Array.isArray(pkt)) {
27
53
  for (const p of pkt) {
28
- yield* [def.decode(p)]
54
+ yield* [decode(p)]
29
55
  }
30
56
  } else {
31
- yield* [def.decode(pkt)]
57
+ yield* [decode(pkt)]
32
58
  }
33
59
  }
34
60
  }
@@ -40,9 +66,17 @@ export type EncodeMessageTransform<T> = (
40
66
  ) => AsyncIterable<Uint8Array>
41
67
 
42
68
  // buildEncodeMessageTransform builds a source of decoded messages.
69
+ // set memoize if you expect to repeatedly encode the same message.
43
70
  export function buildEncodeMessageTransform<T>(
44
- def: MessageDefinition<T>
71
+ def: MessageDefinition<T>,
72
+ memoize?: boolean
45
73
  ): EncodeMessageTransform<T> {
74
+ const encode = memoize
75
+ ? memoProto(def)
76
+ : (msg: T): Uint8Array => {
77
+ return def.encode(msg).finish()
78
+ }
79
+
46
80
  // encodeMessageSource encodes messages to byte arrays.
47
81
  return async function* encodeMessageSource(
48
82
  source: Source<T | T[]>
@@ -50,10 +84,10 @@ export function buildEncodeMessageTransform<T>(
50
84
  for await (const pkt of source) {
51
85
  if (Array.isArray(pkt)) {
52
86
  for (const p of pkt) {
53
- yield* [def.encode(p).finish()]
87
+ yield* [encode(p)]
54
88
  }
55
89
  } else {
56
- yield* [def.encode(pkt).finish()]
90
+ yield* [encode(pkt)]
57
91
  }
58
92
  }
59
93
  }