starpc 0.23.2 → 0.24.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.
Files changed (47) hide show
  1. package/dist/e2e/e2e.js +6 -5
  2. package/dist/rpcstream/rpcstream.d.ts +5 -5
  3. package/dist/rpcstream/rpcstream.js +1 -1
  4. package/dist/srpc/broadcast-channel.d.ts +9 -13
  5. package/dist/srpc/broadcast-channel.js +64 -30
  6. package/dist/srpc/client.js +11 -4
  7. package/dist/srpc/common-rpc.d.ts +4 -2
  8. package/dist/srpc/common-rpc.js +19 -7
  9. package/dist/srpc/conn.d.ts +16 -12
  10. package/dist/srpc/conn.js +35 -34
  11. package/dist/srpc/handler.d.ts +1 -4
  12. package/dist/srpc/handler.js +1 -67
  13. package/dist/srpc/index.d.ts +7 -7
  14. package/dist/srpc/index.js +5 -5
  15. package/dist/srpc/invoker.d.ts +4 -0
  16. package/dist/srpc/invoker.js +66 -0
  17. package/dist/srpc/message-port.d.ts +7 -9
  18. package/dist/srpc/message-port.js +56 -13
  19. package/dist/srpc/pushable.d.ts +1 -1
  20. package/dist/srpc/pushable.js +2 -14
  21. package/dist/srpc/server-rpc.js +1 -0
  22. package/dist/srpc/server.d.ts +2 -6
  23. package/dist/srpc/server.js +4 -17
  24. package/dist/srpc/stream.d.ts +5 -3
  25. package/dist/srpc/stream.js +16 -1
  26. package/dist/srpc/websocket.d.ts +2 -2
  27. package/dist/srpc/websocket.js +2 -2
  28. package/e2e/e2e.ts +8 -5
  29. package/package.json +1 -1
  30. package/srpc/broadcast-channel.ts +78 -47
  31. package/srpc/client.ts +10 -3
  32. package/srpc/common-rpc.go +1 -10
  33. package/srpc/common-rpc.ts +23 -8
  34. package/srpc/conn.ts +54 -58
  35. package/srpc/handler.ts +2 -92
  36. package/srpc/index.ts +18 -7
  37. package/srpc/invoker.ts +92 -0
  38. package/srpc/message-port.ts +71 -21
  39. package/srpc/open-stream-ctr.ts +2 -2
  40. package/srpc/pushable.ts +5 -17
  41. package/srpc/server-rpc.ts +1 -0
  42. package/srpc/server.ts +5 -37
  43. package/srpc/stream.ts +32 -7
  44. package/srpc/websocket.ts +2 -2
  45. package/dist/srpc/conn-stream.d.ts +0 -8
  46. package/dist/srpc/conn-stream.js +0 -16
  47. package/srpc/conn-stream.ts +0 -24
@@ -3,13 +3,14 @@ import { pushable } from 'it-pushable'
3
3
 
4
4
  import type { CallData, CallStart } from './rpcproto.pb.js'
5
5
  import { Packet } from './rpcproto.pb.js'
6
+ import { ERR_RPC_ABORT } from './errors.js'
6
7
 
7
8
  // CommonRPC is common logic between server and client RPCs.
8
9
  export class CommonRPC {
9
10
  // sink is the data sink for incoming messages.
10
- public sink: Sink<Source<Packet>>
11
+ public readonly sink: Sink<Source<Packet>>
11
12
  // source is the packet source for outgoing Packets.
12
- public source: AsyncIterable<Packet>
13
+ public readonly source: AsyncIterable<Packet>
13
14
  // rpcDataSource is the source for rpc packets.
14
15
  public readonly rpcDataSource: AsyncIterable<Uint8Array>
15
16
 
@@ -95,6 +96,7 @@ export class CommonRPC {
95
96
  //
96
97
  // note: closes the stream if any error is thrown.
97
98
  public async handlePacket(packet: Partial<Packet>) {
99
+ // console.log('handlePacket', packet)
98
100
  try {
99
101
  switch (packet?.body?.$case) {
100
102
  case 'callStart':
@@ -103,6 +105,11 @@ export class CommonRPC {
103
105
  case 'callData':
104
106
  await this.handleCallData(packet.body.callData)
105
107
  break
108
+ case 'callCancel':
109
+ if (packet.body.callCancel) {
110
+ await this.handleCallCancel()
111
+ }
112
+ break
106
113
  }
107
114
  } catch (err) {
108
115
  let asError = err as Error
@@ -151,19 +158,28 @@ export class CommonRPC {
151
158
  }
152
159
  }
153
160
 
161
+ // handleCallCancel handles a CallCancel packet.
162
+ public async handleCallCancel() {
163
+ this.close(new Error(ERR_RPC_ABORT))
164
+ }
165
+
154
166
  // close closes the call, optionally with an error.
155
167
  public async close(err?: Error) {
156
168
  if (this.closed) {
157
169
  return
158
170
  }
159
171
  this.closed = true
160
- this._rpcDataSource.end(err)
161
- try {
172
+ // note: don't pass error to _source here.
173
+ if (err) {
162
174
  await this.writeCallCancel()
163
- } finally {
164
- // note: don't pass error to _source here.
165
- this._source.end()
166
175
  }
176
+ this._source.end()
177
+ this._rpcDataSource.end(err)
178
+ }
179
+
180
+ // closeWrite closes the call for writing.
181
+ public closeWrite() {
182
+ this._source.end()
167
183
  }
168
184
 
169
185
  // _createSink returns a value for the sink field.
@@ -181,7 +197,6 @@ export class CommonRPC {
181
197
  await this.handlePacket(msg)
182
198
  }
183
199
  }
184
- this._rpcDataSource.end()
185
200
  } catch (err) {
186
201
  this.close(err as Error)
187
202
  }
package/srpc/conn.ts CHANGED
@@ -1,33 +1,31 @@
1
- import { yamux } from '@chainsafe/libp2p-yamux'
1
+ import { YamuxMuxerInit, yamux } from '@chainsafe/libp2p-yamux'
2
2
  import type {
3
3
  Direction,
4
4
  Stream,
5
5
  StreamMuxer,
6
6
  StreamMuxerFactory,
7
7
  } from '@libp2p/interface'
8
- import { pipe } from 'it-pipe'
9
- import type { Duplex, Source } from 'it-stream-types'
8
+ import type { Duplex } from 'it-stream-types'
10
9
  import { Uint8ArrayList } from 'uint8arraylist'
11
- import isPromise from 'is-promise'
12
- import { pushable, Pushable } from 'it-pushable'
13
10
  import { defaultLogger } from '@libp2p/logger'
14
11
 
15
- import type { OpenStreamFunc, Stream as SRPCStream } from './stream.js'
16
- import { Client } from './client.js'
17
- import { combineUint8ArrayListTransform } from './array-list.js'
18
12
  import {
19
- parseLengthPrefixTransform,
20
- prependLengthPrefixTransform,
21
- } from './packet.js'
22
- import { buildPushableSink } from './pushable.js'
13
+ streamToPacketStream,
14
+ type OpenStreamFunc,
15
+ type PacketStream,
16
+ } from './stream.js'
17
+ import { Client } from './client.js'
23
18
 
24
- // ConnParams are parameters that can be passed to the Conn constructor.
25
- export interface ConnParams {
19
+ // ConnParams are parameters that can be passed to the StreamConn constructor.
20
+ export interface StreamConnParams {
26
21
  // muxerFactory overrides using the default yamux factory.
27
22
  muxerFactory?: StreamMuxerFactory
28
23
  // direction is the muxer connection direction.
29
24
  // defaults to outbound (client).
30
25
  direction?: Direction
26
+ // yamuxParams are parameters to pass to yamux.
27
+ // only used if muxerFactory is unset
28
+ yamuxParams?: YamuxMuxerInit
31
29
  }
32
30
 
33
31
  // StreamHandler handles incoming streams.
@@ -35,52 +33,35 @@ export interface ConnParams {
35
33
  export interface StreamHandler {
36
34
  // handlePacketStream handles an incoming Uint8Array duplex.
37
35
  // the stream has one Uint8Array per packet w/o length prefix.
38
- handlePacketStream(strm: SRPCStream): void
39
- }
40
-
41
- // streamToSRPCStream converts a Stream to a SRPCStream.
42
- // uses length-prefix for packet framing
43
- export function streamToSRPCStream(
44
- stream: Duplex<
45
- AsyncIterable<Uint8ArrayList>,
46
- Source<Uint8ArrayList | Uint8Array>,
47
- Promise<void>
48
- >,
49
- ): SRPCStream {
50
- const pushSink: Pushable<Uint8Array> = pushable({ objectMode: true })
51
- pipe(pushSink, prependLengthPrefixTransform(), stream.sink)
52
- return {
53
- source: pipe(
54
- stream,
55
- parseLengthPrefixTransform(),
56
- combineUint8ArrayListTransform(),
57
- ),
58
- sink: buildPushableSink(pushSink),
59
- }
36
+ handlePacketStream(strm: PacketStream): void
60
37
  }
61
38
 
62
- // Conn implements a generic connection with a two-way stream.
39
+ // StreamConn implements a generic connection with a two-way stream.
40
+ // The stream is not expected to manage packet boundaries.
41
+ // Packets will be sent with uint32le length prefixes.
42
+ // Uses Yamux to manage streams over the connection.
43
+ //
63
44
  // Implements the client by opening streams with the remote.
64
45
  // Implements the server by handling incoming streams.
65
46
  // If the server is unset, rejects any incoming streams.
66
- export class Conn
47
+ export class StreamConn
67
48
  implements Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>>
68
49
  {
69
50
  // muxer is the stream muxer.
70
- private muxer: StreamMuxer
51
+ private _muxer: StreamMuxer
71
52
  // server is the server side, if set.
72
- private server?: StreamHandler
53
+ private _server?: StreamHandler
73
54
 
74
- constructor(server?: StreamHandler, connParams?: ConnParams) {
55
+ constructor(server?: StreamHandler, connParams?: StreamConnParams) {
75
56
  if (server) {
76
- this.server = server
57
+ this._server = server
77
58
  }
78
59
  const muxerFactory =
79
60
  connParams?.muxerFactory ??
80
- yamux({ enableKeepAlive: false })({
61
+ yamux({ enableKeepAlive: false, ...connParams?.yamuxParams })({
81
62
  logger: defaultLogger(),
82
63
  })
83
- this.muxer = muxerFactory.createStreamMuxer({
64
+ this._muxer = muxerFactory.createStreamMuxer({
84
65
  onIncomingStream: this.handleIncomingStream.bind(this),
85
66
  direction: connParams?.direction || 'outbound',
86
67
  })
@@ -88,17 +69,27 @@ export class Conn
88
69
 
89
70
  // sink returns the message sink.
90
71
  get sink() {
91
- return this.muxer.sink
72
+ return this._muxer.sink
92
73
  }
93
74
 
94
75
  // source returns the outgoing message source.
95
76
  get source() {
96
- return this.muxer.source
77
+ return this._muxer.source
97
78
  }
98
79
 
99
80
  // streams returns the set of all ongoing streams.
100
81
  get streams() {
101
- return this.muxer.streams
82
+ return this._muxer.streams
83
+ }
84
+
85
+ // muxer returns the muxer
86
+ get muxer() {
87
+ return this._muxer
88
+ }
89
+
90
+ // server returns the server, if any.
91
+ get server() {
92
+ return this._server
102
93
  }
103
94
 
104
95
  // buildClient builds a new client from the connection.
@@ -107,15 +98,9 @@ export class Conn
107
98
  }
108
99
 
109
100
  // openStream implements the client open stream function.
110
- public async openStream(): Promise<SRPCStream> {
111
- const streamPromise = this.muxer.newStream()
112
- let stream: Stream
113
- if (isPromise(streamPromise)) {
114
- stream = await streamPromise
115
- } else {
116
- stream = streamPromise
117
- }
118
- return streamToSRPCStream(stream)
101
+ public async openStream(): Promise<PacketStream> {
102
+ const strm = await this.muxer.newStream()
103
+ return streamToPacketStream(strm)
119
104
  }
120
105
 
121
106
  // buildOpenStreamFunc returns openStream bound to this conn.
@@ -124,11 +109,22 @@ export class Conn
124
109
  }
125
110
 
126
111
  // handleIncomingStream handles an incoming stream.
127
- private handleIncomingStream(strm: Stream) {
112
+ //
113
+ // this is usually called by the muxer when streams arrive.
114
+ public handleIncomingStream(strm: Stream) {
128
115
  const server = this.server
129
116
  if (!server) {
130
117
  return strm.abort(new Error('server not implemented'))
131
118
  }
132
- server.handlePacketStream(streamToSRPCStream(strm))
119
+ server.handlePacketStream(streamToPacketStream(strm))
120
+ }
121
+
122
+ // close closes or aborts the muxer with an optional error.
123
+ public close(err?: Error) {
124
+ if (err) {
125
+ this.muxer.abort(err)
126
+ } else {
127
+ this.muxer.close()
128
+ }
133
129
  }
134
130
  }
package/srpc/handler.ts CHANGED
@@ -1,13 +1,6 @@
1
1
  import type { Sink, Source } from 'it-stream-types'
2
- import { pipe } from 'it-pipe'
3
- import { pushable } from 'it-pushable'
4
-
5
- import { Definition, MethodDefinition } from './definition.js'
6
- import {
7
- buildDecodeMessageTransform,
8
- buildEncodeMessageTransform,
9
- } from './message.js'
10
- import { writeToPushable } from './pushable.js'
2
+ import { Definition } from './definition.js'
3
+ import { MethodProto, createInvokeFn } from './invoker.js'
11
4
 
12
5
  // InvokeFn describes an SRPC call method invoke function.
13
6
  export type InvokeFn = (
@@ -64,89 +57,6 @@ export class StaticHandler implements Handler {
64
57
  }
65
58
  }
66
59
 
67
- // MethodProto is a function which matches one of the RPC signatures.
68
- type MethodProto<R, O> =
69
- | ((request: R) => Promise<O>)
70
- | ((request: R) => AsyncIterable<O>)
71
- | ((request: AsyncIterable<R>) => Promise<O>)
72
- | ((request: AsyncIterable<R>) => AsyncIterable<O>)
73
-
74
- // createInvokeFn builds an InvokeFn from a method definition and a function prototype.
75
- export function createInvokeFn<R, O>(
76
- methodInfo: MethodDefinition<R, O>,
77
- methodProto: MethodProto<R, O>,
78
- ): InvokeFn {
79
- const requestDecode = buildDecodeMessageTransform<R>(methodInfo.requestType)
80
- return async (
81
- dataSource: Source<Uint8Array>,
82
- dataSink: Sink<Source<Uint8Array>>,
83
- ) => {
84
- // responseSink is a Sink for response messages.
85
- const responseSink = pushable<O>({
86
- objectMode: true,
87
- })
88
-
89
- // pipe responseSink to dataSink.
90
- pipe(
91
- responseSink,
92
- buildEncodeMessageTransform(methodInfo.responseType),
93
- dataSink,
94
- )
95
-
96
- // requestSource is a Source of decoded request messages.
97
- const requestSource = pipe(dataSource, requestDecode)
98
-
99
- // build the request argument.
100
- let requestArg: any
101
- if (methodInfo.requestStream) {
102
- // use the request source as the argument.
103
- requestArg = requestSource
104
- } else {
105
- // receive a single message for the argument.
106
- for await (const msg of requestSource) {
107
- if (msg) {
108
- requestArg = msg
109
- break
110
- }
111
- }
112
- }
113
-
114
- if (!requestArg) {
115
- throw new Error('request object was empty')
116
- }
117
-
118
- // Call the implementation.
119
- try {
120
- const responseObj = methodProto(requestArg)
121
- if (!responseObj) {
122
- throw new Error('return value was undefined')
123
- }
124
- if (methodInfo.responseStream) {
125
- const response = responseObj as AsyncIterable<O>
126
- return writeToPushable(response, responseSink)
127
- } else {
128
- const responsePromise = responseObj as Promise<O>
129
- if (!responsePromise.then) {
130
- throw new Error('expected return value to be a Promise')
131
- }
132
- const responseMsg = await responsePromise
133
- if (!responseMsg) {
134
- throw new Error('expected non-empty response object')
135
- }
136
- responseSink.push(responseMsg)
137
- responseSink.end()
138
- }
139
- } catch (err) {
140
- let asError = err as Error
141
- if (!asError?.message) {
142
- asError = new Error('error calling implementation: ' + err)
143
- }
144
- // mux will return the error to the rpc caller.
145
- throw asError
146
- }
147
- }
148
- }
149
-
150
60
  // createHandler creates a handler from a definition and an implementation.
151
61
  // if serviceID is not set, uses the fullName of the service as the identifier.
152
62
  export function createHandler(
package/srpc/index.ts CHANGED
@@ -1,22 +1,33 @@
1
1
  export { ERR_RPC_ABORT, isAbortError, castToError } from './errors.js'
2
2
  export { Client } from './client.js'
3
3
  export { Server } from './server.js'
4
- export { Conn, ConnParams } from './conn.js'
4
+ export { StreamConn, StreamConnParams, StreamHandler } from './conn.js'
5
5
  export { WebSocketConn } from './websocket.js'
6
- export { StreamConn } from './conn-stream.js'
7
- export type { PacketHandler, Stream, OpenStreamFunc } from './stream.js'
8
- export { Handler, InvokeFn, createHandler, createInvokeFn } from './handler.js'
6
+ export type {
7
+ PacketHandler,
8
+ OpenStreamFunc,
9
+ PacketStream,
10
+ streamToPacketStream,
11
+ } from './stream.js'
12
+ export {
13
+ Handler,
14
+ InvokeFn,
15
+ MethodMap,
16
+ StaticHandler,
17
+ createHandler,
18
+ } from './handler.js'
19
+ export { MethodProto, createInvokeFn } from './invoker.js'
9
20
  export { Packet, CallStart, CallData } from './rpcproto.pb.js'
10
- export { Mux, StaticMux, createMux } from './mux.js'
21
+ export { Mux, StaticMux, LookupMethod, createMux } from './mux.js'
11
22
  export {
12
23
  BroadcastChannelDuplex,
13
- newBroadcastChannelDuplex,
14
24
  BroadcastChannelConn,
25
+ newBroadcastChannelDuplex,
15
26
  } from './broadcast-channel.js'
16
27
  export {
17
28
  MessagePortDuplex,
18
- newMessagePortDuplex,
19
29
  MessagePortConn,
30
+ newMessagePortDuplex,
20
31
  } from './message-port.js'
21
32
  export {
22
33
  MessageDefinition,
@@ -0,0 +1,92 @@
1
+ import { Sink, Source } from 'it-stream-types'
2
+ import { pushable } from 'it-pushable'
3
+ import { pipe } from 'it-pipe'
4
+ import { MethodDefinition } from './definition.js'
5
+ import { InvokeFn } from './handler.js'
6
+ import {
7
+ buildDecodeMessageTransform,
8
+ buildEncodeMessageTransform,
9
+ } from './message.js'
10
+ import { writeToPushable } from './pushable.js'
11
+
12
+ // MethodProto is a function which matches one of the RPC signatures.
13
+ export type MethodProto<R, O> =
14
+ | ((request: R) => Promise<O>)
15
+ | ((request: R) => AsyncIterable<O>)
16
+ | ((request: AsyncIterable<R>) => Promise<O>)
17
+ | ((request: AsyncIterable<R>) => AsyncIterable<O>)
18
+
19
+ // createInvokeFn builds an InvokeFn from a method definition and a function prototype.
20
+ export function createInvokeFn<R, O>(
21
+ methodInfo: MethodDefinition<R, O>,
22
+ methodProto: MethodProto<R, O>,
23
+ ): InvokeFn {
24
+ const requestDecode = buildDecodeMessageTransform<R>(methodInfo.requestType)
25
+ return async (
26
+ dataSource: Source<Uint8Array>,
27
+ dataSink: Sink<Source<Uint8Array>>,
28
+ ) => {
29
+ // responseSink is a Sink for response messages.
30
+ const responseSink = pushable<O>({
31
+ objectMode: true,
32
+ })
33
+
34
+ // pipe responseSink to dataSink.
35
+ pipe(
36
+ responseSink,
37
+ buildEncodeMessageTransform(methodInfo.responseType),
38
+ dataSink,
39
+ )
40
+
41
+ // requestSource is a Source of decoded request messages.
42
+ const requestSource = pipe(dataSource, requestDecode)
43
+
44
+ // build the request argument.
45
+ let requestArg: any
46
+ if (methodInfo.requestStream) {
47
+ // use the request source as the argument.
48
+ requestArg = requestSource
49
+ } else {
50
+ // receive a single message for the argument.
51
+ for await (const msg of requestSource) {
52
+ requestArg = msg
53
+ break
54
+ }
55
+ }
56
+
57
+ if (!requestArg) {
58
+ throw new Error('request object was empty')
59
+ }
60
+
61
+ // Call the implementation.
62
+ try {
63
+ const responseObj = methodProto(requestArg)
64
+ if (!responseObj) {
65
+ throw new Error('return value was undefined')
66
+ }
67
+ if (methodInfo.responseStream) {
68
+ const response = responseObj as AsyncIterable<O>
69
+ return writeToPushable(response, responseSink)
70
+ } else {
71
+ const responsePromise = responseObj as Promise<O>
72
+ if (!responsePromise.then) {
73
+ throw new Error('expected return value to be a Promise')
74
+ }
75
+ const responseMsg = await responsePromise
76
+ if (!responseMsg) {
77
+ throw new Error('expected non-empty response object')
78
+ }
79
+ responseSink.push(responseMsg)
80
+ responseSink.end()
81
+ }
82
+ } catch (err) {
83
+ let asError = err as Error
84
+ if (!asError?.message) {
85
+ asError = new Error('error calling implementation: ' + err)
86
+ }
87
+ // mux will return the error to the rpc caller.
88
+ responseSink.end()
89
+ throw asError
90
+ }
91
+ }
92
+ }
@@ -1,13 +1,19 @@
1
- import type { Source } from 'it-stream-types'
1
+ import type { Duplex, Source } from 'it-stream-types'
2
2
  import { EventIterator } from 'event-iterator'
3
+ import { pipe } from 'it-pipe'
3
4
 
4
- import { ConnParams } from './conn.js'
5
+ import { StreamConn, StreamConnParams } from './conn.js'
5
6
  import { Server } from './server'
6
- import { Stream } from './stream.js'
7
- import { StreamConn } from './conn-stream.js'
7
+ import { combineUint8ArrayListTransform } from './array-list.js'
8
8
 
9
9
  // MessagePortDuplex is a AsyncIterable wrapper for MessagePort.
10
- export class MessagePortDuplex<T> implements Stream<T> {
10
+ //
11
+ // When the sink is closed, the message port will also be closed.
12
+ // Note: there is no way to know when a MessagePort is closed!
13
+ // You will need an additional keep-alive on top of MessagePortDuplex.
14
+ export class MessagePortDuplex<T>
15
+ implements Duplex<AsyncGenerator<T>, Source<T>, Promise<void>>
16
+ {
11
17
  // port is the message port
12
18
  public readonly port: MessagePort
13
19
  // sink is the sink for incoming messages.
@@ -23,24 +29,35 @@ export class MessagePortDuplex<T> implements Stream<T> {
23
29
 
24
30
  // close closes the message port.
25
31
  public close() {
32
+ this.port.postMessage(null)
26
33
  this.port.close()
27
34
  }
28
35
 
29
36
  // _createSink initializes the sink field.
30
37
  private _createSink(): (source: Source<T>) => Promise<void> {
31
38
  return async (source) => {
32
- for await (const msg of source) {
33
- this.port.postMessage(msg)
39
+ try {
40
+ for await (const msg of source) {
41
+ this.port.postMessage(msg)
42
+ }
43
+ } catch (err: unknown) {
44
+ this.close()
45
+ throw err
34
46
  }
47
+
48
+ this.close()
35
49
  }
36
50
  }
37
51
 
38
52
  // _createSource initializes the source field.
39
53
  private async *_createSource(): AsyncGenerator<T> {
40
54
  const iterator = new EventIterator<T>((queue) => {
41
- const messageListener = (ev: MessageEvent<T>) => {
42
- if (ev.data) {
43
- queue.push(ev.data)
55
+ const messageListener = (ev: MessageEvent<T | null>) => {
56
+ const data = ev.data
57
+ if (data !== null) {
58
+ queue.push(data)
59
+ } else {
60
+ queue.stop()
44
61
  }
45
62
  }
46
63
 
@@ -50,9 +67,16 @@ export class MessagePortDuplex<T> implements Stream<T> {
50
67
  }
51
68
  })
52
69
 
53
- for await (const value of iterator) {
54
- yield value
70
+ try {
71
+ for await (const value of iterator) {
72
+ yield value
73
+ }
74
+ } catch (err) {
75
+ this.close()
76
+ throw err
55
77
  }
78
+
79
+ this.close()
56
80
  }
57
81
  }
58
82
 
@@ -66,23 +90,49 @@ export function newMessagePortDuplex<T>(
66
90
  // MessagePortConn implements a connection with a MessagePort.
67
91
  //
68
92
  // expects Uint8Array objects over the MessagePort.
93
+ // uses Yamux to mux streams over the port.
69
94
  export class MessagePortConn extends StreamConn {
70
- // messagePort is the message port iterable.
71
- private messagePort: MessagePortDuplex<Uint8Array>
95
+ // _messagePort is the message port iterable.
96
+ private _messagePort: MessagePortDuplex<Uint8Array>
72
97
 
73
- constructor(port: MessagePort, server?: Server, connParams?: ConnParams) {
98
+ constructor(
99
+ port: MessagePort,
100
+ server?: Server,
101
+ connParams?: StreamConnParams,
102
+ ) {
74
103
  const messagePort = new MessagePortDuplex<Uint8Array>(port)
75
- super(messagePort, server, connParams)
76
- this.messagePort = messagePort
104
+ super(server, {
105
+ ...connParams,
106
+ yamuxParams: {
107
+ // There is no way to tell when a MessagePort is closed.
108
+ // We will send an undefined object through the MessagePort to indicate closed.
109
+ // We still need a way to detect when the connection is not cleanly terminated.
110
+ // Enable keep-alive to detect this on the other end.
111
+ enableKeepAlive: true,
112
+ keepAliveInterval: 1500,
113
+ ...connParams?.yamuxParams,
114
+ },
115
+ })
116
+ this._messagePort = messagePort
117
+ pipe(
118
+ messagePort,
119
+ this,
120
+ // Uint8ArrayList usually cannot be sent over MessagePort, so we combine to a Uint8Array as part of the pipe.
121
+ combineUint8ArrayListTransform(),
122
+ messagePort,
123
+ )
124
+ .catch((err) => this.close(err))
125
+ .then(() => this.close())
77
126
  }
78
127
 
79
- // getMessagePort returns the MessagePort.
80
- public getMessagePort(): MessagePort {
81
- return this.messagePort.port
128
+ // messagePort returns the MessagePort.
129
+ get messagePort(): MessagePort {
130
+ return this._messagePort.port
82
131
  }
83
132
 
84
133
  // close closes the message port.
85
- public close() {
134
+ public override close(err?: Error) {
135
+ super.close(err)
86
136
  this.messagePort.close()
87
137
  }
88
138
  }
@@ -1,4 +1,4 @@
1
- import { OpenStreamFunc, Stream } from './stream.js'
1
+ import { OpenStreamFunc } from './stream.js'
2
2
  import { ValueCtr } from './value-ctr.js'
3
3
 
4
4
  // OpenStreamCtr contains an OpenStream func which can be awaited.
@@ -9,7 +9,7 @@ export class OpenStreamCtr extends ValueCtr<OpenStreamFunc> {
9
9
 
10
10
  // openStreamFunc returns an OpenStreamFunc which waits for the underlying OpenStreamFunc.
11
11
  get openStreamFunc(): OpenStreamFunc {
12
- return async (): Promise<Stream> => {
12
+ return async () => {
13
13
  let openFn = this.value
14
14
  if (!openFn) {
15
15
  openFn = await this.wait()