starpc 0.4.0 → 0.4.3

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/Makefile CHANGED
@@ -1,13 +1,18 @@
1
+ # https://github.com/aperturerobotics/protobuf-project
2
+
1
3
  PROTOWRAP=hack/bin/protowrap
2
4
  PROTOC_GEN_GO=hack/bin/protoc-gen-go
5
+ PROTOC_GEN_GO_STARPC=hack/bin/protoc-gen-go-starpc
3
6
  PROTOC_GEN_VTPROTO=hack/bin/protoc-gen-go-vtproto
4
- PROTOC_GEN_STARPC=hack/bin/protoc-gen-go-starpc
5
7
  GOIMPORTS=hack/bin/goimports
6
8
  GOLANGCI_LINT=hack/bin/golangci-lint
7
9
  GO_MOD_OUTDATED=hack/bin/go-mod-outdated
8
- export GO111MODULE=on
9
10
  GOLIST=go list -f "{{ .Dir }}" -m
10
11
 
12
+ export GO111MODULE=on
13
+ undefine GOARCH
14
+ undefine GOOS
15
+
11
16
  all:
12
17
 
13
18
  vendor:
@@ -17,19 +22,19 @@ $(PROTOC_GEN_GO):
17
22
  cd ./hack; \
18
23
  go build -v \
19
24
  -o ./bin/protoc-gen-go \
20
- github.com/golang/protobuf/protoc-gen-go
25
+ google.golang.org/protobuf/cmd/protoc-gen-go
21
26
 
22
- $(PROTOC_GEN_VTPROTO):
27
+ $(PROTOC_GEN_GO_STARPC):
23
28
  cd ./hack; \
24
29
  go build -v \
25
- -o ./bin/protoc-gen-go-vtproto \
26
- github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
30
+ -o ./bin/protoc-gen-go-starpc \
31
+ github.com/aperturerobotics/starpc/cmd/protoc-gen-go-starpc
27
32
 
28
- $(PROTOC_GEN_STARPC):
33
+ $(PROTOC_GEN_VTPROTO):
29
34
  cd ./hack; \
30
35
  go build -v \
31
- -o ./bin/protoc-gen-go-starpc \
32
- github.com/aperturerobotics/starpc/cmd/protoc-gen-go-starpc
36
+ -o ./bin/protoc-gen-go-vtproto \
37
+ github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
33
38
 
34
39
  $(GOIMPORTS):
35
40
  cd ./hack; \
@@ -56,7 +61,7 @@ $(GO_MOD_OUTDATED):
56
61
  github.com/psampaz/go-mod-outdated
57
62
 
58
63
  .PHONY: gengo
59
- gengo: $(GOIMPORTS) $(PROTOWRAP) $(PROTOC_GEN_GO) $(PROTOC_GEN_VTPROTO) $(PROTOC_GEN_STARPC)
64
+ gengo: $(GOIMPORTS) $(PROTOWRAP) $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_STARPC) $(PROTOC_GEN_VTPROTO)
60
65
  go mod vendor
61
66
  shopt -s globstar; \
62
67
  set -eo pipefail; \
@@ -120,15 +125,19 @@ genproto: gengo gents
120
125
  .PHONY: gen
121
126
  gen: genproto
122
127
 
128
+ .PHONY: outdated
123
129
  outdated: $(GO_MOD_OUTDATED)
124
130
  go list -mod=mod -u -m -json all | $(GO_MOD_OUTDATED) -update -direct
125
131
 
132
+ .PHONY: list
126
133
  list: $(GO_MOD_OUTDATED)
127
134
  go list -mod=mod -u -m -json all | $(GO_MOD_OUTDATED)
128
135
 
136
+ .PHONY: lint
129
137
  lint: $(GOLANGCI_LINT)
130
138
  $(GOLANGCI_LINT) run
131
139
 
140
+ .PHONY: fix
132
141
  fix: $(GOLANGCI_LINT)
133
142
  $(GOLANGCI_LINT) run --fix
134
143
 
@@ -138,5 +147,4 @@ test:
138
147
 
139
148
  .PHONY: integration
140
149
  integration: node_modules vendor
141
- cd ./integration && \
142
- bash ./integration.bash
150
+ cd ./integration && bash ./integration.bash
package/README.md CHANGED
@@ -13,6 +13,10 @@ 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
+ [rpcstream] supports sub-streams for per-component sub-services.
17
+
18
+ [rpcstream]: ./rpcstream
19
+
16
20
  # Usage
17
21
 
18
22
  Starting with the [protobuf-project] repository on the "starpc" branch.
@@ -0,0 +1,19 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Packet } from './rpcstream.pb.js';
3
+ import { Server } from '../srpc/server.js';
4
+ import { OpenStreamFunc, Stream } from '../srpc/stream.js';
5
+ import { Pushable } from 'it-pushable';
6
+ import { Source, Sink } from 'it-stream-types';
7
+ export declare type RpcStreamCaller = (request: Observable<Packet>) => Observable<Packet>;
8
+ export declare function buildRpcStreamOpenStream(componentId: string, caller: RpcStreamCaller): OpenStreamFunc;
9
+ export declare type RpcStreamGetter = (componentId: string) => Promise<Server>;
10
+ export declare function handleRpcStream(stream: Observable<Packet>, getter: RpcStreamGetter): AsyncIterable<Packet>;
11
+ export declare class RpcStream implements Stream {
12
+ readonly source: Source<Uint8Array>;
13
+ readonly sink: Sink<Uint8Array>;
14
+ private readonly _packetSink;
15
+ private readonly _source;
16
+ constructor(packetSink: Pushable<Packet>, packetSource: Observable<Packet>);
17
+ private _createSink;
18
+ private _subscribePacketSource;
19
+ }
@@ -0,0 +1,96 @@
1
+ import { from as obsFrom } from 'rxjs';
2
+ import { pushable } from 'it-pushable';
3
+ // buildRpcStreamOpenStream builds a OpenStream func with a RpcStream.
4
+ export function buildRpcStreamOpenStream(componentId, caller) {
5
+ return async () => {
6
+ const packetSink = pushable({ objectMode: true });
7
+ const packetObs = obsFrom(packetSink);
8
+ const packetSource = caller(packetObs);
9
+ // write the component id
10
+ packetSink.push({
11
+ body: {
12
+ $case: 'init',
13
+ init: { componentId },
14
+ }
15
+ });
16
+ // build & return the stream
17
+ return new RpcStream(packetSink, packetSource);
18
+ };
19
+ }
20
+ // handleRpcStream handles an incoming RPC stream (remote is the initiator).
21
+ export async function* handleRpcStream(stream, getter) {
22
+ // read the component id
23
+ const initPromise = new Promise((resolve, reject) => {
24
+ const subscription = stream.subscribe({
25
+ next(value) {
26
+ resolve(value);
27
+ subscription.unsubscribe();
28
+ },
29
+ error(err) {
30
+ reject(err);
31
+ },
32
+ complete() {
33
+ reject(new Error('no packet received'));
34
+ },
35
+ });
36
+ });
37
+ // read the init packet
38
+ const initPacket = await initPromise;
39
+ if (initPacket?.body?.$case !== 'init') {
40
+ throw new Error('expected init packet');
41
+ }
42
+ // lookup the server for the component id.
43
+ const server = await getter(initPacket.body.init.componentId);
44
+ // build the outgoing packet sink & the packet source
45
+ const packetSink = pushable({ objectMode: true });
46
+ // handle the stream
47
+ const rpcStream = new RpcStream(packetSink, stream);
48
+ server.handleDuplex(rpcStream);
49
+ // process packets
50
+ for await (const packet of packetSink) {
51
+ yield* [packet];
52
+ }
53
+ }
54
+ // RpcStream implements the Stream on top of a RPC call.
55
+ export class RpcStream {
56
+ constructor(packetSink, packetSource) {
57
+ this._packetSink = packetSink;
58
+ this.sink = this._createSink();
59
+ const source = pushable({ objectMode: true });
60
+ this.source = source;
61
+ this._source = source;
62
+ this._subscribePacketSource(packetSource);
63
+ }
64
+ // _createSink initializes the sink field.
65
+ _createSink() {
66
+ return async (source) => {
67
+ try {
68
+ for await (const msg of source) {
69
+ this._packetSink.push({
70
+ body: { $case: 'data', data: msg }
71
+ });
72
+ }
73
+ this._packetSink.end();
74
+ }
75
+ catch (err) {
76
+ this._packetSink.end(err);
77
+ }
78
+ };
79
+ }
80
+ // _subscribePacketSource starts the subscription to the response data.
81
+ _subscribePacketSource(packetSource) {
82
+ packetSource.subscribe({
83
+ next: (value) => {
84
+ if (value?.body?.$case === 'data') {
85
+ this._source.push(value.body.data);
86
+ }
87
+ },
88
+ error: (err) => {
89
+ this._source.end(err);
90
+ },
91
+ complete: () => {
92
+ this._source.end();
93
+ },
94
+ });
95
+ }
96
+ }
@@ -0,0 +1,85 @@
1
+ import Long from 'long';
2
+ import * as _m0 from 'protobufjs/minimal';
3
+ export declare const protobufPackage = "rpcstream";
4
+ /** Packet is a packet encapsulating data for a RPC stream. */
5
+ export interface Packet {
6
+ body?: {
7
+ $case: 'init';
8
+ init: RpcStreamInit;
9
+ } | {
10
+ $case: 'data';
11
+ data: Uint8Array;
12
+ };
13
+ }
14
+ /** RpcStreamInit is the first message in a RPC stream. */
15
+ export interface RpcStreamInit {
16
+ /** ComponentId is the identifier of the component making the request. */
17
+ componentId: string;
18
+ }
19
+ export declare const Packet: {
20
+ encode(message: Packet, writer?: _m0.Writer): _m0.Writer;
21
+ decode(input: _m0.Reader | Uint8Array, length?: number): Packet;
22
+ fromJSON(object: any): Packet;
23
+ toJSON(message: Packet): unknown;
24
+ fromPartial<I extends {
25
+ body?: ({
26
+ init?: {
27
+ componentId?: string | undefined;
28
+ } | undefined;
29
+ } & {
30
+ $case: "init";
31
+ }) | ({
32
+ data?: Uint8Array | undefined;
33
+ } & {
34
+ $case: "data";
35
+ }) | undefined;
36
+ } & {
37
+ body?: ({
38
+ init?: {
39
+ componentId?: string | undefined;
40
+ } | undefined;
41
+ } & {
42
+ $case: "init";
43
+ } & {
44
+ init?: ({
45
+ componentId?: string | undefined;
46
+ } & {
47
+ componentId?: string | undefined;
48
+ } & Record<Exclude<keyof I["body"]["init"], "componentId">, never>) | undefined;
49
+ $case: "init";
50
+ } & Record<Exclude<keyof I["body"], "$case" | "init">, never>) | ({
51
+ data?: Uint8Array | undefined;
52
+ } & {
53
+ $case: "data";
54
+ } & {
55
+ data?: Uint8Array | undefined;
56
+ $case: "data";
57
+ } & Record<Exclude<keyof I["body"], "$case" | "data">, never>) | undefined;
58
+ } & Record<Exclude<keyof I, "body">, never>>(object: I): Packet;
59
+ };
60
+ export declare const RpcStreamInit: {
61
+ encode(message: RpcStreamInit, writer?: _m0.Writer): _m0.Writer;
62
+ decode(input: _m0.Reader | Uint8Array, length?: number): RpcStreamInit;
63
+ fromJSON(object: any): RpcStreamInit;
64
+ toJSON(message: RpcStreamInit): unknown;
65
+ fromPartial<I extends {
66
+ componentId?: string | undefined;
67
+ } & {
68
+ componentId?: string | undefined;
69
+ } & Record<Exclude<keyof I, "componentId">, never>>(object: I): RpcStreamInit;
70
+ };
71
+ declare type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
72
+ export declare type DeepPartial<T> = T extends Builtin ? T : T extends Long ? string | number | Long : T extends Array<infer U> ? Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {
73
+ $case: string;
74
+ } ? {
75
+ [K in keyof Omit<T, '$case'>]?: DeepPartial<T[K]>;
76
+ } & {
77
+ $case: T['$case'];
78
+ } : T extends {} ? {
79
+ [K in keyof T]?: DeepPartial<T[K]>;
80
+ } : Partial<T>;
81
+ declare type KeysOfUnion<T> = T extends T ? keyof T : never;
82
+ export declare type Exact<P, I extends P> = P extends Builtin ? P : P & {
83
+ [K in keyof P]: Exact<P[K], I[K]>;
84
+ } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;
85
+ export {};
@@ -0,0 +1,160 @@
1
+ /* eslint-disable */
2
+ import Long from 'long';
3
+ import * as _m0 from 'protobufjs/minimal';
4
+ export const protobufPackage = 'rpcstream';
5
+ function createBasePacket() {
6
+ return { body: undefined };
7
+ }
8
+ export const Packet = {
9
+ encode(message, writer = _m0.Writer.create()) {
10
+ if (message.body?.$case === 'init') {
11
+ RpcStreamInit.encode(message.body.init, writer.uint32(10).fork()).ldelim();
12
+ }
13
+ if (message.body?.$case === 'data') {
14
+ writer.uint32(18).bytes(message.body.data);
15
+ }
16
+ return writer;
17
+ },
18
+ decode(input, length) {
19
+ const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
20
+ let end = length === undefined ? reader.len : reader.pos + length;
21
+ const message = createBasePacket();
22
+ while (reader.pos < end) {
23
+ const tag = reader.uint32();
24
+ switch (tag >>> 3) {
25
+ case 1:
26
+ message.body = {
27
+ $case: 'init',
28
+ init: RpcStreamInit.decode(reader, reader.uint32()),
29
+ };
30
+ break;
31
+ case 2:
32
+ message.body = { $case: 'data', data: reader.bytes() };
33
+ break;
34
+ default:
35
+ reader.skipType(tag & 7);
36
+ break;
37
+ }
38
+ }
39
+ return message;
40
+ },
41
+ fromJSON(object) {
42
+ return {
43
+ body: isSet(object.init)
44
+ ? { $case: 'init', init: RpcStreamInit.fromJSON(object.init) }
45
+ : isSet(object.data)
46
+ ? { $case: 'data', data: bytesFromBase64(object.data) }
47
+ : undefined,
48
+ };
49
+ },
50
+ toJSON(message) {
51
+ const obj = {};
52
+ message.body?.$case === 'init' &&
53
+ (obj.init = message.body?.init
54
+ ? RpcStreamInit.toJSON(message.body?.init)
55
+ : undefined);
56
+ message.body?.$case === 'data' &&
57
+ (obj.data =
58
+ message.body?.data !== undefined
59
+ ? base64FromBytes(message.body?.data)
60
+ : undefined);
61
+ return obj;
62
+ },
63
+ fromPartial(object) {
64
+ const message = createBasePacket();
65
+ if (object.body?.$case === 'init' &&
66
+ object.body?.init !== undefined &&
67
+ object.body?.init !== null) {
68
+ message.body = {
69
+ $case: 'init',
70
+ init: RpcStreamInit.fromPartial(object.body.init),
71
+ };
72
+ }
73
+ if (object.body?.$case === 'data' &&
74
+ object.body?.data !== undefined &&
75
+ object.body?.data !== null) {
76
+ message.body = { $case: 'data', data: object.body.data };
77
+ }
78
+ return message;
79
+ },
80
+ };
81
+ function createBaseRpcStreamInit() {
82
+ return { componentId: '' };
83
+ }
84
+ export const RpcStreamInit = {
85
+ encode(message, writer = _m0.Writer.create()) {
86
+ if (message.componentId !== '') {
87
+ writer.uint32(10).string(message.componentId);
88
+ }
89
+ return writer;
90
+ },
91
+ decode(input, length) {
92
+ const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
93
+ let end = length === undefined ? reader.len : reader.pos + length;
94
+ const message = createBaseRpcStreamInit();
95
+ while (reader.pos < end) {
96
+ const tag = reader.uint32();
97
+ switch (tag >>> 3) {
98
+ case 1:
99
+ message.componentId = reader.string();
100
+ break;
101
+ default:
102
+ reader.skipType(tag & 7);
103
+ break;
104
+ }
105
+ }
106
+ return message;
107
+ },
108
+ fromJSON(object) {
109
+ return {
110
+ componentId: isSet(object.componentId) ? String(object.componentId) : '',
111
+ };
112
+ },
113
+ toJSON(message) {
114
+ const obj = {};
115
+ message.componentId !== undefined && (obj.componentId = message.componentId);
116
+ return obj;
117
+ },
118
+ fromPartial(object) {
119
+ const message = createBaseRpcStreamInit();
120
+ message.componentId = object.componentId ?? '';
121
+ return message;
122
+ },
123
+ };
124
+ var globalThis = (() => {
125
+ if (typeof globalThis !== 'undefined')
126
+ return globalThis;
127
+ if (typeof self !== 'undefined')
128
+ return self;
129
+ if (typeof window !== 'undefined')
130
+ return window;
131
+ if (typeof global !== 'undefined')
132
+ return global;
133
+ throw 'Unable to locate global object';
134
+ })();
135
+ const atob = globalThis.atob ||
136
+ ((b64) => globalThis.Buffer.from(b64, 'base64').toString('binary'));
137
+ function bytesFromBase64(b64) {
138
+ const bin = atob(b64);
139
+ const arr = new Uint8Array(bin.length);
140
+ for (let i = 0; i < bin.length; ++i) {
141
+ arr[i] = bin.charCodeAt(i);
142
+ }
143
+ return arr;
144
+ }
145
+ const btoa = globalThis.btoa ||
146
+ ((bin) => globalThis.Buffer.from(bin, 'binary').toString('base64'));
147
+ function base64FromBytes(arr) {
148
+ const bin = [];
149
+ arr.forEach((byte) => {
150
+ bin.push(String.fromCharCode(byte));
151
+ });
152
+ return btoa(bin.join(''));
153
+ }
154
+ if (_m0.util.Long !== Long) {
155
+ _m0.util.Long = Long;
156
+ _m0.configure();
157
+ }
158
+ function isSet(value) {
159
+ return value !== null && value !== undefined;
160
+ }
@@ -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;
@@ -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';
@@ -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
+ }
@@ -8,7 +8,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
  }
@@ -12,18 +12,19 @@ export class Server {
12
12
  return new ServerRPC(this.mux);
13
13
  }
14
14
  // handleStream handles an incoming Uint8Array message duplex.
15
- // closes the stream when the rpc completes.
16
15
  handleStream(stream) {
17
16
  return this.handleDuplex(stream);
18
17
  }
19
18
  // handleDuplex handles an incoming message duplex.
20
- async handleDuplex(stream) {
19
+ handleDuplex(stream) {
21
20
  const rpc = this.startRpc();
22
- await pipe(stream, parseLengthPrefixTransform(), decodePacketSource, rpc, encodePacketSource, prependLengthPrefixTransform(), stream);
21
+ pipe(stream, parseLengthPrefixTransform(), decodePacketSource, rpc, encodePacketSource, prependLengthPrefixTransform(), stream);
22
+ return rpc;
23
23
  }
24
24
  // handlePacketStream handles an incoming Packet duplex.
25
- async handlePacketStream(stream) {
25
+ handlePacketStream(stream) {
26
26
  const rpc = this.startRpc();
27
- await pipe(stream, rpc, stream);
27
+ pipe(stream, rpc, stream);
28
+ return rpc;
28
29
  }
29
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
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
  },
package/srpc/client.ts CHANGED
@@ -39,7 +39,9 @@ export class Client implements TsProtoRpc {
39
39
  }
40
40
 
41
41
  // setOpenStreamFn updates the openStreamFn for the Client.
42
- public setOpenStreamFn(openStreamFn?: OpenStreamFunc): Promise<OpenStreamFunc> {
42
+ public setOpenStreamFn(
43
+ openStreamFn?: OpenStreamFunc
44
+ ): Promise<OpenStreamFunc> {
43
45
  if (this._openStreamFn) {
44
46
  if (openStreamFn) {
45
47
  this._openStreamFn(openStreamFn)
package/srpc/conn.ts CHANGED
@@ -19,7 +19,7 @@ export interface ConnParams {
19
19
  // Implemented by Server.
20
20
  export interface StreamHandler {
21
21
  // handleStream handles an incoming stream.
22
- handleStream(strm: Duplex<Uint8Array>): Promise<void>
22
+ handleStream(strm: Duplex<Uint8Array>): void
23
23
  }
24
24
 
25
25
  // Conn implements a generic connection with a two-way stream.
package/srpc/index.ts CHANGED
@@ -9,9 +9,9 @@ export {
9
9
  newBroadcastChannelIterable,
10
10
  BroadcastChannelConn,
11
11
  } from './broadcast-channel.js'
12
-
13
12
  export {
14
13
  MessagePortIterable,
15
14
  newMessagePortIterable,
16
15
  MessagePortConn,
17
16
  } from './message-port.js'
17
+ export { ObservableSource } from './observable-source.js'
@@ -0,0 +1,40 @@
1
+ import { Source } from 'it-stream-types'
2
+ import { pushable, Pushable } from 'it-pushable'
3
+ import { Observable, Subscription } from 'rxjs'
4
+
5
+ // ObservableSource wraps an Observable into a Source.
6
+ export class ObservableSource<T> {
7
+ // source is the source for observable objects.
8
+ public readonly source: Source<T>
9
+ // _source emits incoming data to the source.
10
+ private readonly _source: {
11
+ push: (val: T) => void
12
+ end: (err?: Error) => void
13
+ }
14
+ // subscription is the observable subscription
15
+ private readonly subscription: Subscription
16
+
17
+ constructor(observable: Observable<T>) {
18
+ const source: Pushable<T> = pushable({ objectMode: true })
19
+ this.source = source
20
+ this._source = source
21
+
22
+ this.subscription = observable.subscribe({
23
+ next: (value: T) => {
24
+ this._source.push(value)
25
+ },
26
+ error: (err) => {
27
+ this._source.end(err)
28
+ },
29
+ complete: () => {
30
+ this._source.end()
31
+ },
32
+ })
33
+ }
34
+
35
+ // close closes the subscription.
36
+ public close(err?: Error) {
37
+ this._source.end(err)
38
+ this.subscription.unsubscribe()
39
+ }
40
+ }
package/srpc/server.ts CHANGED
@@ -29,15 +29,14 @@ export class Server implements StreamHandler {
29
29
  }
30
30
 
31
31
  // handleStream handles an incoming Uint8Array message duplex.
32
- // closes the stream when the rpc completes.
33
- public handleStream(stream: Stream): Promise<void> {
32
+ public handleStream(stream: Stream): ServerRPC {
34
33
  return this.handleDuplex(stream)
35
34
  }
36
35
 
37
36
  // handleDuplex handles an incoming message duplex.
38
- public async handleDuplex(stream: Duplex<Uint8Array>): Promise<void> {
37
+ public handleDuplex(stream: Duplex<Uint8Array>): ServerRPC {
39
38
  const rpc = this.startRpc()
40
- await pipe(
39
+ pipe(
41
40
  stream,
42
41
  parseLengthPrefixTransform(),
43
42
  decodePacketSource,
@@ -46,11 +45,13 @@ export class Server implements StreamHandler {
46
45
  prependLengthPrefixTransform(),
47
46
  stream
48
47
  )
48
+ return rpc
49
49
  }
50
50
 
51
51
  // handlePacketStream handles an incoming Packet duplex.
52
- public async handlePacketStream(stream: Duplex<Packet>): Promise<void> {
52
+ public handlePacketStream(stream: Duplex<Packet>): ServerRPC {
53
53
  const rpc = this.startRpc()
54
- await pipe(stream, rpc, stream)
54
+ pipe(stream, rpc, stream)
55
+ return rpc
55
56
  }
56
57
  }
package/e2e/debug.test DELETED
Binary file