starpc 0.40.0 → 0.41.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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Stream RPC (starpc)
1
+ # starpc
2
2
 
3
3
  [![GoDoc Widget]][GoDoc] [![Go Report Card Widget]][Go Report Card]
4
4
 
5
- > A high-performance Protobuf 3 RPC framework supporting bidirectional streaming over any multiplexer.
5
+ > Streaming Protobuf RPC with bidirectional streaming over any multiplexed transport.
6
6
 
7
7
  [GoDoc]: https://godoc.org/github.com/aperturerobotics/starpc
8
8
  [GoDoc Widget]: https://godoc.org/github.com/aperturerobotics/starpc?status.svg
@@ -18,20 +18,22 @@
18
18
  - [Protobuf Definition](#protobuf)
19
19
  - [Go Implementation](#go)
20
20
  - [TypeScript Implementation](#typescript)
21
+ - [Debugging](#debugging)
21
22
  - [Development Setup](#development-setup)
22
23
  - [Support](#support)
23
24
 
24
25
  ## Features
25
26
 
26
- - Full [Proto3 services] implementation for both TypeScript and Go
27
- - Bidirectional streaming support in web browsers
28
- - Built on libp2p streams with `@chainsafe/libp2p-yamux`
29
- - Efficient RPC multiplexing over single connections
30
- - Zero-reflection Go code via [protobuf-go-lite]
31
- - TypeScript interfaces via [protobuf-es-lite]
32
- - Sub-streams support through [rpcstream]
27
+ - Full [Proto3 services] support for TypeScript and Go
28
+ - Bidirectional streaming in browsers via WebSocket or WebRTC
29
+ - Built on libp2p streams with [@chainsafe/libp2p-yamux]
30
+ - Efficient multiplexing of RPCs over single connections
31
+ - Zero-reflection Go via [protobuf-go-lite]
32
+ - Lightweight TypeScript via [protobuf-es-lite]
33
+ - Sub-stream support via [rpcstream]
33
34
 
34
35
  [Proto3 services]: https://developers.google.com/protocol-buffers/docs/proto3#services
36
+ [@chainsafe/libp2p-yamux]: https://github.com/ChainSafe/js-libp2p-yamux
35
37
  [protobuf-go-lite]: https://github.com/aperturerobotics/protobuf-go-lite
36
38
  [protobuf-es-lite]: https://github.com/aperturerobotics/protobuf-es-lite
37
39
  [rpcstream]: ./rpcstream
@@ -119,118 +121,115 @@ if out.GetBody() != bodyTxt {
119
121
  }
120
122
  ```
121
123
 
122
- [e2e test]: ./e2e/e2e_test.go
123
-
124
124
  ### TypeScript
125
125
 
126
- See the ts-proto README to generate the TypeScript for your protobufs.
127
-
128
- For an example of Go <-> TypeScript interop, see the [integration] test. For an
129
- example of TypeScript <-> TypeScript interop, see the [e2e] test.
126
+ For Go <-> TypeScript interop, see the [integration] test.
127
+ For TypeScript <-> TypeScript, see the [e2e] test.
130
128
 
131
129
  [e2e]: ./e2e/e2e.ts
132
130
  [integration]: ./integration/integration.ts
133
131
 
134
- Supports any AsyncIterable communication channel.
135
-
136
132
  #### WebSocket Example
137
133
 
138
- This examples demonstrates connecting to a WebSocket server:
134
+ Connect to a WebSocket server:
139
135
 
140
136
  ```typescript
141
- import { WebSocketConn } from 'srpc'
142
- import { EchoerClient } from 'srpc/echo'
137
+ import { WebSocketConn } from 'starpc'
138
+ import { EchoerClient } from './echo/index.js'
143
139
 
144
- const ws = new WebSocket('ws://localhost:1347/demo')
145
- const channel = new WebSocketConn(ws)
146
- const client = channel.buildClient()
147
- const demoServiceClient = new EchoerClient(client)
140
+ const ws = new WebSocket('ws://localhost:8080/api')
141
+ const conn = new WebSocketConn(ws)
142
+ const client = conn.buildClient()
143
+ const echoer = new EchoerClient(client)
148
144
 
149
- const result = await demoServiceClient.Echo({
150
- body: "Hello world!"
151
- })
152
- console.log('output', result.body)
145
+ const result = await echoer.Echo({ body: 'Hello world!' })
146
+ console.log('result:', result.body)
153
147
  ```
154
148
 
155
- #### In-memory Demo with TypeScript Server and Client
149
+ #### In-Memory Example
156
150
 
157
- This example demonstrates both the server and client with an in-memory pipe:
151
+ Server and client with an in-memory pipe:
158
152
 
159
153
  ```typescript
160
154
  import { pipe } from 'it-pipe'
161
- import { createHandler, createMux, Server, Client, Conn } from 'srpc'
162
- import { EchoerDefinition, EchoerServer, runClientTest } from 'srpc/echo'
163
- import { pushable } from 'it-pushable'
155
+ import { createHandler, createMux, Server, StreamConn } from 'starpc'
156
+ import { EchoerDefinition, EchoerServer } from './echo/index.js'
164
157
 
165
- // Create the server and register the handlers.
158
+ // Create server with registered handlers
166
159
  const mux = createMux()
167
160
  const echoer = new EchoerServer()
168
161
  mux.register(createHandler(EchoerDefinition, echoer))
169
162
  const server = new Server(mux.lookupMethod)
170
163
 
171
- // Create the client connection to the server with an in-memory pipe.
172
- const clientConn = new Conn()
173
- const serverConn = new Conn(server)
164
+ // Create client and server connections, pipe together
165
+ const clientConn = new StreamConn()
166
+ const serverConn = new StreamConn(server)
174
167
  pipe(clientConn, serverConn, clientConn)
175
- const client = new Client(clientConn.buildOpenStreamFunc())
176
168
 
177
- // Examples of different types of RPC calls:
169
+ // Build client and make RPC calls
170
+ const client = clientConn.buildClient()
171
+ const echoerClient = new EchoerClient(client)
178
172
 
179
- // One-shot request/response (unary):
180
- console.log('Calling Echo: unary call...')
181
- let result = await demoServiceClient.Echo({
182
- body: 'Hello world!',
183
- })
184
- console.log('success: output', result.body)
185
-
186
- // Streaming from client->server with a single server response:
187
- const clientRequestStream = pushable<EchoMsg>({objectMode: true})
188
- clientRequestStream.push({body: 'Hello world from streaming request.'})
189
- clientRequestStream.end()
190
- console.log('Calling EchoClientStream: client -> server...')
191
- result = await demoServiceClient.EchoClientStream(clientRequestStream)
192
- console.log('success: output', result.body)
193
-
194
- // Streaming from server -> client with a single client message.
195
- console.log('Calling EchoServerStream: server -> client...')
196
- const serverStream = demoServiceClient.EchoServerStream({
197
- body: 'Hello world from server to client streaming request.',
198
- })
199
- for await (const msg of serverStream) {
200
- console.log('server: output', msg.body)
173
+ // Unary call
174
+ const result = await echoerClient.Echo({ body: 'Hello world!' })
175
+ console.log('result:', result.body)
176
+
177
+ // Client streaming
178
+ import { pushable } from 'it-pushable'
179
+ const stream = pushable({ objectMode: true })
180
+ stream.push({ body: 'Message 1' })
181
+ stream.push({ body: 'Message 2' })
182
+ stream.end()
183
+ const response = await echoerClient.EchoClientStream(stream)
184
+ console.log('response:', response.body)
185
+
186
+ // Server streaming
187
+ for await (const msg of echoerClient.EchoServerStream({ body: 'Hello' })) {
188
+ console.log('server msg:', msg.body)
201
189
  }
202
190
  ```
203
191
 
192
+ ## Debugging
193
+
194
+ Enable debug logging in TypeScript using the `DEBUG` environment variable:
195
+
196
+ ```bash
197
+ # Enable all starpc logs
198
+ DEBUG=starpc:* node app.js
199
+
200
+ # Enable specific component logs
201
+ DEBUG=starpc:stream-conn node app.js
202
+ ```
203
+
204
204
  ## Attribution
205
205
 
206
206
  `protoc-gen-go-starpc` is a heavily modified version of `protoc-gen-go-drpc`.
207
-
208
- Be sure to check out [drpc] as well: it's compatible with grpc, twirp, and more.
207
+ Check out [drpc] as well - it's compatible with grpc, twirp, and more.
209
208
 
210
209
  [drpc]: https://github.com/storj/drpc
211
210
 
212
- Uses [vtprotobuf] to generate Go Protobuf marshal / unmarshal code.
211
+ Uses [vtprotobuf] to generate Protobuf marshal/unmarshal code for Go.
213
212
 
214
213
  [vtprotobuf]: https://github.com/planetscale/vtprotobuf
215
214
 
216
- Uses [protobuf-es-lite] (fork of [protobuf-es]) to generate TypeScript Protobuf marshal / unmarshal code.
215
+ `protoc-gen-es-starpc` is a modified version of `protoc-gen-connect-es`.
216
+ Uses [protobuf-es-lite] (fork of [protobuf-es]) for TypeScript.
217
217
 
218
218
  [protobuf-es]: https://github.com/bufbuild/protobuf-es
219
- [protobuf-es-lite]: https://github.com/aperturerobotics/protobuf-es-lite
220
-
221
- `protoc-gen-es-starpc` is a heavily modified version of `protoc-gen-connect-es`.
222
219
 
223
220
  ## Development Setup
224
221
 
225
- ### MacOS Requirements
222
+ ### MacOS
223
+
224
+ Install required packages:
226
225
 
227
- 1. Install required packages:
228
226
  ```bash
229
227
  brew install bash make coreutils gnu-sed findutils protobuf
230
228
  brew link --overwrite protobuf
231
229
  ```
232
230
 
233
- 2. Add to your .bashrc or .zshrc:
231
+ Add to your shell rc file (.bashrc, .zshrc):
232
+
234
233
  ```bash
235
234
  export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
236
235
  export PATH="/opt/homebrew/opt/gnu-sed/libexec/gnubin:$PATH"
@@ -238,23 +237,12 @@ export PATH="/opt/homebrew/opt/findutils/libexec/gnubin:$PATH"
238
237
  export PATH="/opt/homebrew/opt/make/libexec/gnubin:$PATH"
239
238
  ```
240
239
 
241
- ## Attribution
242
-
243
- - `protoc-gen-go-starpc`: Modified version of `protoc-gen-go-drpc`
244
- - `protoc-gen-es-starpc`: Modified version of `protoc-gen-connect-es`
245
- - Uses [vtprotobuf] for Go Protobuf marshaling
246
- - Uses [protobuf-es-lite] for TypeScript Protobuf interfaces
247
-
248
- [vtprotobuf]: https://github.com/planetscale/vtprotobuf
249
-
250
240
  ## Support
251
241
 
252
- Need help? We're here:
253
-
254
- - [File a GitHub Issue][GitHub issue]
255
- - [Join our Discord][Join Discord]
256
- - [Matrix Chat][Matrix Chat]
242
+ - [GitHub Issues][issues]
243
+ - [Discord][discord]
244
+ - [Matrix][matrix]
257
245
 
258
- [GitHub issue]: https://github.com/aperturerobotics/starpc/issues/new
259
- [Join Discord]: https://discord.gg/KJutMESRsT
260
- [Matrix Chat]: https://matrix.to/#/#aperturerobotics:matrix.org
246
+ [issues]: https://github.com/aperturerobotics/starpc/issues/new
247
+ [discord]: https://discord.gg/KJutMESRsT
248
+ [matrix]: https://matrix.to/#/#aperturerobotics:matrix.org
@@ -1,27 +1,26 @@
1
1
  import { YamuxMuxerInit } from '@chainsafe/libp2p-yamux';
2
- import type { MessageStreamDirection, Stream, StreamMuxer, StreamMuxerFactory } from '@libp2p/interface';
3
- import type { Duplex, Source } from 'it-stream-types';
2
+ import type { ComponentLogger, Direction, Stream, StreamMuxer, StreamMuxerFactory } from '@libp2p/interface';
3
+ import type { Duplex } from 'it-stream-types';
4
4
  import { Uint8ArrayList } from 'uint8arraylist';
5
5
  import { type OpenStreamFunc, type PacketStream } from './stream.js';
6
6
  import { Client } from './client.js';
7
7
  export interface StreamConnParams {
8
- loggerName?: string;
8
+ logger?: ComponentLogger;
9
9
  muxerFactory?: StreamMuxerFactory;
10
- direction?: MessageStreamDirection;
10
+ direction?: Direction;
11
11
  yamuxParams?: YamuxMuxerInit;
12
12
  }
13
13
  export interface StreamHandler {
14
14
  handlePacketStream(strm: PacketStream): void;
15
15
  }
16
- export declare class StreamConn implements Duplex<AsyncIterable<Uint8Array | Uint8ArrayList>, Source<Uint8Array | Uint8ArrayList>, Promise<void>> {
16
+ export declare class StreamConn implements Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> {
17
17
  private _muxer;
18
- private _messageStream;
19
18
  private _server?;
20
19
  constructor(server?: StreamHandler, connParams?: StreamConnParams);
21
- get sink(): (source: Source<Uint8Array | Uint8ArrayList>) => Promise<void>;
22
- get source(): AsyncIterable<Uint8Array | Uint8ArrayList>;
20
+ get sink(): import("it-stream-types").Sink<AsyncGenerator<Uint8Array<ArrayBufferLike> | Uint8ArrayList, any, any>, unknown>;
21
+ get source(): AsyncGenerator<Uint8Array<ArrayBufferLike> | Uint8ArrayList, any, any>;
23
22
  get streams(): Stream[];
24
- get muxer(): StreamMuxer<Stream>;
23
+ get muxer(): StreamMuxer;
25
24
  get server(): StreamHandler | undefined;
26
25
  buildClient(): Client;
27
26
  openStream(): Promise<PacketStream>;
package/dist/srpc/conn.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { yamux } from '@chainsafe/libp2p-yamux';
2
2
  import { streamToPacketStream, } from './stream.js';
3
3
  import { Client } from './client.js';
4
- import { createDuplexMessageStream, } from './duplex-message-stream.js';
4
+ import { createDisabledComponentLogger } from './log.js';
5
5
  // StreamConn implements a generic connection with a two-way stream.
6
6
  // The stream is not expected to manage packet boundaries.
7
7
  // Packets will be sent with uint32le length prefixes.
@@ -13,37 +13,28 @@ import { createDuplexMessageStream, } from './duplex-message-stream.js';
13
13
  export class StreamConn {
14
14
  // muxer is the stream muxer.
15
15
  _muxer;
16
- // messageStream wraps the duplex as a MessageStream for the muxer.
17
- _messageStream;
18
16
  // server is the server side, if set.
19
17
  _server;
20
18
  constructor(server, connParams) {
21
19
  if (server) {
22
20
  this._server = server;
23
21
  }
24
- // Create the MessageStream adapter
25
- const direction = connParams?.direction || 'outbound';
26
- this._messageStream = createDuplexMessageStream({
27
- loggerName: connParams?.loggerName,
28
- direction,
29
- });
30
- // Create the muxer factory - yamux(init)() returns a StreamMuxerFactory
31
22
  const muxerFactory = connParams?.muxerFactory ??
32
- yamux({ enableKeepAlive: false, ...connParams?.yamuxParams })();
33
- // Create the muxer with the MessageStream
34
- this._muxer = muxerFactory.createStreamMuxer(this._messageStream);
35
- // Listen for incoming streams
36
- this._muxer.addEventListener('stream', (evt) => {
37
- this.handleIncomingStream(evt.detail);
23
+ yamux({ enableKeepAlive: false, ...connParams?.yamuxParams })({
24
+ logger: connParams?.logger ?? createDisabledComponentLogger(),
25
+ });
26
+ this._muxer = muxerFactory.createStreamMuxer({
27
+ onIncomingStream: this.handleIncomingStream.bind(this),
28
+ direction: connParams?.direction || 'outbound',
38
29
  });
39
30
  }
40
31
  // sink returns the message sink.
41
32
  get sink() {
42
- return this._messageStream.sink;
33
+ return this._muxer.sink;
43
34
  }
44
35
  // source returns the outgoing message source.
45
36
  get source() {
46
- return this._messageStream.source;
37
+ return this._muxer.source;
47
38
  }
48
39
  // streams returns the set of all ongoing streams.
49
40
  get streams() {
@@ -63,7 +54,7 @@ export class StreamConn {
63
54
  }
64
55
  // openStream implements the client open stream function.
65
56
  async openStream() {
66
- const strm = await this.muxer.createStream();
57
+ const strm = await this.muxer.newStream();
67
58
  return streamToPacketStream(strm);
68
59
  }
69
60
  // buildOpenStreamFunc returns openStream bound to this conn.
@@ -20,4 +20,3 @@ export { HandleStreamCtr } from './handle-stream-ctr.js';
20
20
  export { writeToPushable, buildPushableSink, messagePushable, } from './pushable.js';
21
21
  export { Watchdog } from './watchdog.js';
22
22
  export { ProtoRpc } from './proto-rpc.js';
23
- export { DuplexMessageStream, DuplexMessageStreamInit, createDuplexMessageStream, } from './duplex-message-stream.js';
@@ -18,4 +18,3 @@ export { OpenStreamCtr } from './open-stream-ctr.js';
18
18
  export { HandleStreamCtr } from './handle-stream-ctr.js';
19
19
  export { writeToPushable, buildPushableSink, messagePushable, } from './pushable.js';
20
20
  export { Watchdog } from './watchdog.js';
21
- export { DuplexMessageStream, createDuplexMessageStream, } from './duplex-message-stream.js';
@@ -0,0 +1,3 @@
1
+ import type { ComponentLogger, Logger } from '@libp2p/interface';
2
+ export declare function createDisabledLogger(namespace: string): Logger;
3
+ export declare function createDisabledComponentLogger(): ComponentLogger;
@@ -0,0 +1,22 @@
1
+ // https://github.com/libp2p/js-libp2p/issues/2276
2
+ // https://github.com/libp2p/js-libp2p/blob/bca8d6e689b47d85dda74082ed72e671139391de/packages/logger/src/index.ts#L86
3
+ // https://github.com/libp2p/js-libp2p/issues/2275
4
+ // https://github.com/ChainSafe/js-libp2p-yamux/issues/69
5
+ export function createDisabledLogger(namespace) {
6
+ const logger = () => { };
7
+ logger.enabled = false;
8
+ logger.color = '';
9
+ logger.diff = 0;
10
+ logger.log = () => { };
11
+ logger.namespace = namespace;
12
+ logger.destroy = () => true;
13
+ logger.extend = () => logger;
14
+ logger.debug = logger;
15
+ logger.error = logger;
16
+ logger.trace = logger;
17
+ logger.newScope = () => logger;
18
+ return logger;
19
+ }
20
+ export function createDisabledComponentLogger() {
21
+ return { forComponent: createDisabledLogger };
22
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Duplex, Source } from 'it-stream-types';
2
- import type { Stream } from '@libp2p/interface';
2
+ import { Stream } from '@libp2p/interface';
3
3
  import type { Packet } from './rpcproto.pb.js';
4
4
  export type PacketHandler = (packet: Packet) => Promise<void>;
5
5
  export type PacketStream = Duplex<AsyncGenerator<Uint8Array>, Source<Uint8Array>, Promise<void>>;
@@ -8,17 +8,9 @@ export function streamToPacketStream(stream) {
8
8
  return {
9
9
  source: pipe(stream, parseLengthPrefixTransform(), combineUint8ArrayListTransform()),
10
10
  sink: async (source) => {
11
- try {
12
- for await (const data of pipe(source, prependLengthPrefixTransform())) {
13
- stream.send(data);
14
- }
15
- await stream.close();
16
- }
17
- catch (err) {
18
- await stream
19
- .close({ signal: AbortSignal.timeout(1000) })
20
- .catch(() => { });
21
- }
11
+ await pipe(source, prependLengthPrefixTransform(), stream)
12
+ .catch((err) => stream.close(err))
13
+ .then(() => stream.close());
22
14
  },
23
15
  };
24
16
  }
@@ -1,9 +1,9 @@
1
- import type { MessageStreamDirection } from '@libp2p/interface';
1
+ import { Direction } from '@libp2p/interface';
2
2
  import type WebSocket from '@aptre/it-ws/web-socket';
3
3
  import { StreamConn } from './conn.js';
4
4
  import { Server } from './server.js';
5
5
  export declare class WebSocketConn extends StreamConn {
6
6
  private socket;
7
- constructor(socket: WebSocket, direction: MessageStreamDirection, server?: Server);
7
+ constructor(socket: WebSocket, direction: Direction, server?: Server);
8
8
  getSocket(): WebSocket;
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -85,27 +85,26 @@
85
85
  "./{srpc,echo,e2e,integration,rpcstream,cmd}/**/(*.ts|*.tsx|*.html|*.css)": "prettier --config .prettierrc.yaml --write"
86
86
  },
87
87
  "devDependencies": {
88
- "@typescript-eslint/eslint-plugin": "^8.39.1",
89
- "@typescript-eslint/parser": "^8.39.1",
88
+ "@typescript-eslint/eslint-plugin": "^8.48.0",
89
+ "@typescript-eslint/parser": "^8.48.0",
90
90
  "depcheck": "^1.4.6",
91
91
  "esbuild": "^0.27.0",
92
- "eslint": "^9.33.0",
92
+ "eslint": "^9.39.1",
93
93
  "eslint-config-prettier": "^10.0.2",
94
+ "happy-dom": "^20.0.10",
94
95
  "husky": "^9.1.7",
95
- "lint-staged": "^16.1.5",
96
+ "lint-staged": "^16.2.7",
96
97
  "prettier": "^3.5.3",
97
- "rimraf": "^6.0.1",
98
+ "rimraf": "^6.1.2",
98
99
  "tsx": "^4.20.4",
99
100
  "typescript": "^5.8.2",
100
- "vitest": "^4.0.0"
101
+ "vitest": "^4.0.14"
101
102
  },
102
103
  "dependencies": {
103
104
  "@aptre/it-ws": "^1.1.2",
104
105
  "@aptre/protobuf-es-lite": "^0.5.2",
105
- "@chainsafe/libp2p-yamux": "^8.0.0",
106
- "@libp2p/interface": "^3.0.0",
107
- "@libp2p/logger": "^6.2.2",
108
- "@libp2p/utils": "^7.0.9",
106
+ "@chainsafe/libp2p-yamux": "^7.0.1",
107
+ "@libp2p/interface": "^2.6.1",
109
108
  "event-iterator": "^2.0.0",
110
109
  "isomorphic-ws": "^5.0.0",
111
110
  "it-first": "^3.0.6",
package/srpc/conn.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { YamuxMuxerInit, yamux } from '@chainsafe/libp2p-yamux'
2
2
  import type {
3
- MessageStreamDirection,
3
+ ComponentLogger,
4
+ Direction,
4
5
  Stream,
5
6
  StreamMuxer,
6
7
  StreamMuxerFactory,
7
8
  } from '@libp2p/interface'
8
- import type { Duplex, Source } from 'it-stream-types'
9
+ import type { Duplex } from 'it-stream-types'
9
10
  import { Uint8ArrayList } from 'uint8arraylist'
10
11
 
11
12
  import {
@@ -14,20 +15,17 @@ import {
14
15
  type PacketStream,
15
16
  } from './stream.js'
16
17
  import { Client } from './client.js'
17
- import {
18
- DuplexMessageStream,
19
- createDuplexMessageStream,
20
- } from './duplex-message-stream.js'
18
+ import { createDisabledComponentLogger } from './log.js'
21
19
 
22
20
  // ConnParams are parameters that can be passed to the StreamConn constructor.
23
21
  export interface StreamConnParams {
24
- // loggerName is the debug-style logger name (e.g. 'starpc:conn').
25
- loggerName?: string
22
+ // logger is the logger to use, defaults to disabled logger.
23
+ logger?: ComponentLogger
26
24
  // muxerFactory overrides using the default yamux factory.
27
25
  muxerFactory?: StreamMuxerFactory
28
26
  // direction is the muxer connection direction.
29
27
  // defaults to outbound (client).
30
- direction?: MessageStreamDirection
28
+ direction?: Direction
31
29
  // yamuxParams are parameters to pass to yamux.
32
30
  // only used if muxerFactory is unset
33
31
  yamuxParams?: YamuxMuxerInit
@@ -50,17 +48,10 @@ export interface StreamHandler {
50
48
  // Implements the server by handling incoming streams.
51
49
  // If the server is unset, rejects any incoming streams.
52
50
  export class StreamConn
53
- implements
54
- Duplex<
55
- AsyncIterable<Uint8Array | Uint8ArrayList>,
56
- Source<Uint8Array | Uint8ArrayList>,
57
- Promise<void>
58
- >
51
+ implements Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>>
59
52
  {
60
53
  // muxer is the stream muxer.
61
54
  private _muxer: StreamMuxer
62
- // messageStream wraps the duplex as a MessageStream for the muxer.
63
- private _messageStream: DuplexMessageStream
64
55
  // server is the server side, if set.
65
56
  private _server?: StreamHandler
66
57
 
@@ -68,36 +59,25 @@ export class StreamConn
68
59
  if (server) {
69
60
  this._server = server
70
61
  }
71
-
72
- // Create the MessageStream adapter
73
- const direction = connParams?.direction || 'outbound'
74
- this._messageStream = createDuplexMessageStream({
75
- loggerName: connParams?.loggerName,
76
- direction,
77
- })
78
-
79
- // Create the muxer factory - yamux(init)() returns a StreamMuxerFactory
80
62
  const muxerFactory =
81
63
  connParams?.muxerFactory ??
82
- yamux({ enableKeepAlive: false, ...connParams?.yamuxParams })()
83
-
84
- // Create the muxer with the MessageStream
85
- this._muxer = muxerFactory.createStreamMuxer(this._messageStream)
86
-
87
- // Listen for incoming streams
88
- this._muxer.addEventListener('stream', (evt) => {
89
- this.handleIncomingStream(evt.detail)
64
+ yamux({ enableKeepAlive: false, ...connParams?.yamuxParams })({
65
+ logger: connParams?.logger ?? createDisabledComponentLogger(),
66
+ })
67
+ this._muxer = muxerFactory.createStreamMuxer({
68
+ onIncomingStream: this.handleIncomingStream.bind(this),
69
+ direction: connParams?.direction || 'outbound',
90
70
  })
91
71
  }
92
72
 
93
73
  // sink returns the message sink.
94
- get sink(): (source: Source<Uint8Array | Uint8ArrayList>) => Promise<void> {
95
- return this._messageStream.sink
74
+ get sink() {
75
+ return this._muxer.sink
96
76
  }
97
77
 
98
78
  // source returns the outgoing message source.
99
- get source(): AsyncIterable<Uint8Array | Uint8ArrayList> {
100
- return this._messageStream.source
79
+ get source() {
80
+ return this._muxer.source
101
81
  }
102
82
 
103
83
  // streams returns the set of all ongoing streams.
@@ -122,7 +102,7 @@ export class StreamConn
122
102
 
123
103
  // openStream implements the client open stream function.
124
104
  public async openStream(): Promise<PacketStream> {
125
- const strm = await this.muxer.createStream()
105
+ const strm = await this.muxer.newStream()
126
106
  return streamToPacketStream(strm)
127
107
  }
128
108
 
package/srpc/index.ts CHANGED
@@ -81,8 +81,3 @@ export {
81
81
  } from './pushable.js'
82
82
  export { Watchdog } from './watchdog.js'
83
83
  export { ProtoRpc } from './proto-rpc.js'
84
- export {
85
- DuplexMessageStream,
86
- DuplexMessageStreamInit,
87
- createDuplexMessageStream,
88
- } from './duplex-message-stream.js'
package/srpc/log.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
2
+
3
+ // https://github.com/libp2p/js-libp2p/issues/2276
4
+ // https://github.com/libp2p/js-libp2p/blob/bca8d6e689b47d85dda74082ed72e671139391de/packages/logger/src/index.ts#L86
5
+ // https://github.com/libp2p/js-libp2p/issues/2275
6
+ // https://github.com/ChainSafe/js-libp2p-yamux/issues/69
7
+ export function createDisabledLogger(namespace: string): Logger {
8
+ const logger = (): void => {}
9
+ logger.enabled = false
10
+ logger.color = ''
11
+ logger.diff = 0
12
+ logger.log = (): void => {}
13
+ logger.namespace = namespace
14
+ logger.destroy = () => true
15
+ logger.extend = () => logger
16
+ logger.debug = logger
17
+ logger.error = logger
18
+ logger.trace = logger
19
+ logger.newScope = () => logger
20
+
21
+ return logger
22
+ }
23
+
24
+ export function createDisabledComponentLogger(): ComponentLogger {
25
+ return { forComponent: createDisabledLogger }
26
+ }
package/srpc/stream.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Duplex, Source } from 'it-stream-types'
2
2
  import { pipe } from 'it-pipe'
3
- import type { Stream } from '@libp2p/interface'
3
+ import { Stream } from '@libp2p/interface'
4
4
 
5
5
  import type { Packet } from './rpcproto.pb.js'
6
6
  import { combineUint8ArrayListTransform } from './array-list.js'
@@ -38,16 +38,9 @@ export function streamToPacketStream(stream: Stream): PacketStream {
38
38
  combineUint8ArrayListTransform(),
39
39
  ),
40
40
  sink: async (source: Source<Uint8Array>): Promise<void> => {
41
- try {
42
- for await (const data of pipe(source, prependLengthPrefixTransform())) {
43
- stream.send(data)
44
- }
45
- await stream.close()
46
- } catch (err: unknown) {
47
- await stream
48
- .close({ signal: AbortSignal.timeout(1000) })
49
- .catch(() => {})
50
- }
41
+ await pipe(source, prependLengthPrefixTransform(), stream)
42
+ .catch((err) => stream.close(err))
43
+ .then(() => stream.close())
51
44
  },
52
45
  }
53
46
  }
package/srpc/websocket.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { pipe } from 'it-pipe'
2
- import type { MessageStreamDirection } from '@libp2p/interface'
2
+ import { Direction } from '@libp2p/interface'
3
3
 
4
4
  import duplex from '@aptre/it-ws/duplex'
5
5
  import type WebSocket from '@aptre/it-ws/web-socket'
@@ -13,11 +13,7 @@ export class WebSocketConn extends StreamConn {
13
13
  // socket is the web socket
14
14
  private socket: WebSocket
15
15
 
16
- constructor(
17
- socket: WebSocket,
18
- direction: MessageStreamDirection,
19
- server?: Server,
20
- ) {
16
+ constructor(socket: WebSocket, direction: Direction, server?: Server) {
21
17
  super(server, { direction })
22
18
  this.socket = socket
23
19
  const socketDuplex = duplex(socket)
@@ -1,22 +0,0 @@
1
- import type { AbortOptions, MessageStreamDirection } from '@libp2p/interface';
2
- import { AbstractMessageStream, type SendResult } from '@libp2p/utils';
3
- import type { Duplex, Source } from 'it-stream-types';
4
- import { Uint8ArrayList } from 'uint8arraylist';
5
- export interface DuplexMessageStreamInit {
6
- direction?: MessageStreamDirection;
7
- loggerName?: string;
8
- inactivityTimeout?: number;
9
- maxReadBufferLength?: number;
10
- }
11
- export declare class DuplexMessageStream extends AbstractMessageStream {
12
- private readonly _outgoing;
13
- constructor(init?: DuplexMessageStreamInit);
14
- get source(): AsyncIterable<Uint8Array | Uint8ArrayList>;
15
- get sink(): (source: Source<Uint8Array | Uint8ArrayList>) => Promise<void>;
16
- sendData(data: Uint8ArrayList): SendResult;
17
- sendReset(_err: Error): void;
18
- sendPause(): void;
19
- sendResume(): void;
20
- close(_options?: AbortOptions): Promise<void>;
21
- }
22
- export declare function createDuplexMessageStream(init?: DuplexMessageStreamInit): DuplexMessageStream & Duplex<AsyncIterable<Uint8Array | Uint8ArrayList>>;
@@ -1,95 +0,0 @@
1
- import { logger } from '@libp2p/logger';
2
- import { AbstractMessageStream, } from '@libp2p/utils';
3
- import { pushable } from 'it-pushable';
4
- // DuplexMessageStream wraps a Duplex stream as a MessageStream.
5
- // This allows using duplex streams with the new libp2p StreamMuxer API.
6
- //
7
- // Extends AbstractMessageStream to get proper read/write buffer management,
8
- // backpressure handling, and event semantics from libp2p.
9
- export class DuplexMessageStream extends AbstractMessageStream {
10
- // _outgoing is a pushable that collects data to be sent out.
11
- _outgoing;
12
- constructor(init) {
13
- // Create the MessageStreamInit required by AbstractMessageStream
14
- const streamInit = {
15
- log: logger(init?.loggerName ?? 'starpc:duplex-message-stream'),
16
- direction: init?.direction ?? 'outbound',
17
- inactivityTimeout: init?.inactivityTimeout,
18
- maxReadBufferLength: init?.maxReadBufferLength,
19
- };
20
- super(streamInit);
21
- this._outgoing = pushable();
22
- }
23
- // source returns an async iterable that yields data to be sent to the remote.
24
- get source() {
25
- return this._outgoing;
26
- }
27
- // sink consumes data from the remote and feeds it into the stream.
28
- // This is the receiving end of the duplex.
29
- get sink() {
30
- return async (source) => {
31
- try {
32
- for await (const data of source) {
33
- if (this.status === 'closed' ||
34
- this.status === 'aborted' ||
35
- this.status === 'reset') {
36
- break;
37
- }
38
- // Use the parent's onData method which handles buffering and events
39
- this.onData(data);
40
- }
41
- // Remote closed their write side
42
- this.onRemoteCloseWrite();
43
- }
44
- catch (err) {
45
- this.abort(err);
46
- }
47
- };
48
- }
49
- // sendData implements AbstractMessageStream.sendData
50
- // Called by the parent class when processing the write queue.
51
- sendData(data) {
52
- // Push data to the outgoing pushable
53
- this._outgoing.push(data);
54
- return {
55
- sentBytes: data.byteLength,
56
- canSendMore: true, // Our pushable can always accept more
57
- };
58
- }
59
- // sendReset implements AbstractMessageStream.sendReset
60
- // Called when the stream is aborted locally.
61
- sendReset(_err) {
62
- // End the outgoing pushable - we can't send a reset over a generic duplex
63
- this._outgoing.end();
64
- }
65
- // sendPause implements AbstractMessageStream.sendPause
66
- // Called when the stream is paused.
67
- sendPause() {
68
- // No-op: generic duplex streams don't support pause signaling
69
- this.log.trace('pause requested (no-op for duplex stream)');
70
- }
71
- // sendResume implements AbstractMessageStream.sendResume
72
- // Called when the stream is resumed.
73
- sendResume() {
74
- // No-op: generic duplex streams don't support resume signaling
75
- this.log.trace('resume requested (no-op for duplex stream)');
76
- }
77
- // close gracefully closes the stream.
78
- async close(_options) {
79
- if (this.status === 'closed' || this.status === 'closing') {
80
- return;
81
- }
82
- this.status = 'closing';
83
- this.writeStatus = 'closing';
84
- // End the outgoing pushable to signal we're done writing
85
- this._outgoing.end();
86
- this.writeStatus = 'closed';
87
- if (this.readStatus === 'closed') {
88
- this.onTransportClosed();
89
- }
90
- }
91
- }
92
- // createDuplexMessageStream creates a new DuplexMessageStream.
93
- export function createDuplexMessageStream(init) {
94
- return new DuplexMessageStream(init);
95
- }
@@ -1,132 +0,0 @@
1
- import type { AbortOptions, MessageStreamDirection } from '@libp2p/interface'
2
- import { logger } from '@libp2p/logger'
3
- import {
4
- AbstractMessageStream,
5
- type MessageStreamInit,
6
- type SendResult,
7
- } from '@libp2p/utils'
8
- import type { Duplex, Source } from 'it-stream-types'
9
- import { pushable, type Pushable } from 'it-pushable'
10
- import { Uint8ArrayList } from 'uint8arraylist'
11
-
12
- // DuplexMessageStreamInit are parameters for DuplexMessageStream.
13
- export interface DuplexMessageStreamInit {
14
- // direction is the stream direction.
15
- direction?: MessageStreamDirection
16
- // loggerName is the name to use for the logger.
17
- loggerName?: string
18
- // inactivityTimeout is the inactivity timeout in ms.
19
- inactivityTimeout?: number
20
- // maxReadBufferLength is the max read buffer length.
21
- maxReadBufferLength?: number
22
- }
23
-
24
- // DuplexMessageStream wraps a Duplex stream as a MessageStream.
25
- // This allows using duplex streams with the new libp2p StreamMuxer API.
26
- //
27
- // Extends AbstractMessageStream to get proper read/write buffer management,
28
- // backpressure handling, and event semantics from libp2p.
29
- export class DuplexMessageStream extends AbstractMessageStream {
30
- // _outgoing is a pushable that collects data to be sent out.
31
- private readonly _outgoing: Pushable<Uint8Array | Uint8ArrayList>
32
-
33
- constructor(init?: DuplexMessageStreamInit) {
34
- // Create the MessageStreamInit required by AbstractMessageStream
35
- const streamInit: MessageStreamInit = {
36
- log: logger(init?.loggerName ?? 'starpc:duplex-message-stream'),
37
- direction: init?.direction ?? 'outbound',
38
- inactivityTimeout: init?.inactivityTimeout,
39
- maxReadBufferLength: init?.maxReadBufferLength,
40
- }
41
- super(streamInit)
42
- this._outgoing = pushable<Uint8Array | Uint8ArrayList>()
43
- }
44
-
45
- // source returns an async iterable that yields data to be sent to the remote.
46
- get source(): AsyncIterable<Uint8Array | Uint8ArrayList> {
47
- return this._outgoing
48
- }
49
-
50
- // sink consumes data from the remote and feeds it into the stream.
51
- // This is the receiving end of the duplex.
52
- get sink(): (source: Source<Uint8Array | Uint8ArrayList>) => Promise<void> {
53
- return async (
54
- source: Source<Uint8Array | Uint8ArrayList>,
55
- ): Promise<void> => {
56
- try {
57
- for await (const data of source) {
58
- if (
59
- this.status === 'closed' ||
60
- this.status === 'aborted' ||
61
- this.status === 'reset'
62
- ) {
63
- break
64
- }
65
- // Use the parent's onData method which handles buffering and events
66
- this.onData(data)
67
- }
68
- // Remote closed their write side
69
- this.onRemoteCloseWrite()
70
- } catch (err) {
71
- this.abort(err as Error)
72
- }
73
- }
74
- }
75
-
76
- // sendData implements AbstractMessageStream.sendData
77
- // Called by the parent class when processing the write queue.
78
- sendData(data: Uint8ArrayList): SendResult {
79
- // Push data to the outgoing pushable
80
- this._outgoing.push(data)
81
- return {
82
- sentBytes: data.byteLength,
83
- canSendMore: true, // Our pushable can always accept more
84
- }
85
- }
86
-
87
- // sendReset implements AbstractMessageStream.sendReset
88
- // Called when the stream is aborted locally.
89
- sendReset(_err: Error): void {
90
- // End the outgoing pushable - we can't send a reset over a generic duplex
91
- this._outgoing.end()
92
- }
93
-
94
- // sendPause implements AbstractMessageStream.sendPause
95
- // Called when the stream is paused.
96
- sendPause(): void {
97
- // No-op: generic duplex streams don't support pause signaling
98
- this.log.trace('pause requested (no-op for duplex stream)')
99
- }
100
-
101
- // sendResume implements AbstractMessageStream.sendResume
102
- // Called when the stream is resumed.
103
- sendResume(): void {
104
- // No-op: generic duplex streams don't support resume signaling
105
- this.log.trace('resume requested (no-op for duplex stream)')
106
- }
107
-
108
- // close gracefully closes the stream.
109
- async close(_options?: AbortOptions): Promise<void> {
110
- if (this.status === 'closed' || this.status === 'closing') {
111
- return
112
- }
113
- this.status = 'closing'
114
- this.writeStatus = 'closing'
115
-
116
- // End the outgoing pushable to signal we're done writing
117
- this._outgoing.end()
118
-
119
- this.writeStatus = 'closed'
120
- if (this.readStatus === 'closed') {
121
- this.onTransportClosed()
122
- }
123
- }
124
- }
125
-
126
- // createDuplexMessageStream creates a new DuplexMessageStream.
127
- export function createDuplexMessageStream(
128
- init?: DuplexMessageStreamInit,
129
- ): DuplexMessageStream & Duplex<AsyncIterable<Uint8Array | Uint8ArrayList>> {
130
- return new DuplexMessageStream(init) as DuplexMessageStream &
131
- Duplex<AsyncIterable<Uint8Array | Uint8ArrayList>>
132
- }