bare-rpc 1.3.8 → 1.3.9

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
@@ -29,126 +29,7 @@ console.log(replyBuffer.toString()) // pong
29
29
 
30
30
  ## API
31
31
 
32
- ### `RPC`
33
-
34
- #### `const rpc = new RPC(stream[, onrequest])`
35
-
36
- Create an RPC instance using a duplex `stream`. `onrequest` is an optional callback run when a remote request is received. This is where processing and responding to an RPC request happens. `onrequest` receives a `RPCIncomingRequest` as an argument, for example:
37
-
38
- ```js
39
- const rpc = new RPC(stream, (req) => {
40
- if (req.command === 42) {
41
- req.reply('pong')
42
- }
43
- })
44
- ```
45
-
46
- See [`RPCIncomingRequest`](#rpcincomingrequest) for properties and methods for processing the request.
47
-
48
- `onrequest` can also be a [`RPCCommandRouter`](#rpccommandrouter).
49
-
50
- #### `const req = rpc.request(command)`
51
-
52
- Create a request for `command`. `command` is a unique number that should be used to differentiate different requests on the remote end. Returns a `RPCOutgoingRequest`.
53
-
54
- ### `RPCOutgoingRequest`
55
-
56
- #### `req.command`
57
-
58
- The command that the request was created with. A command is a unique number.
59
-
60
- #### `req.sent`
61
-
62
- A boolean for whether the request has been sent.
63
-
64
- #### `req.received`
65
-
66
- A boolean for whether the request has received a reply.
67
-
68
- #### `req.send([data[, encoding]])`
69
-
70
- Send the request with the provided `data`. `data` can be a buffer or a string which will be encoded using `encoding`.
71
-
72
- `.send()` can only be called once per request.
73
-
74
- #### `const data = await req.reply([encoding])`
75
-
76
- Await the reply from the remote end to the request. `encoding` can be defined for decoding the response `data` buffer back into a string.
77
-
78
- #### `const stream = req.createRequestStream([options])`
79
-
80
- Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending data with the request.
81
-
82
- #### `const stream = req.createResponseStream([options])`
83
-
84
- Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for receiving data in reply to the request.
85
-
86
- ### `RPCIncomingRequest`
87
-
88
- #### `req.command`
89
-
90
- The command that the request was sent as. A command is a unique number.
91
-
92
- #### `req.data`
93
-
94
- The data buffer sent with the request.
95
-
96
- #### `req.sent`
97
-
98
- A boolean for whether a reply has been sent.
99
-
100
- #### `req.received`
101
-
102
- A boolean for whether the request has been received as a stream. See [`req.createRequestStream()`](#const-stream--reqcreaterequeststream) for receiving requests as a stream.
103
-
104
- #### `req.reply([data, [encoding]])`
105
-
106
- Reply to the request with the provided `data`. `data` can be a buffer or a string which will be encoded using `encoding`.
107
-
108
- #### `const stream = req.createRequestStream([options])`
109
-
110
- Create a `Readable` stream for receiving data from the request.
111
-
112
- #### `const stream = req.createResponseStream([options])`
113
-
114
- Create a `Writable` stream for sending data in reply to the request.
115
-
116
- ### `RPCCommandRouter`
117
-
118
- An alternative way to define commands and handlers for receiving them.
119
-
120
- #### `const router = new RPC.CommandRouter()`
121
-
122
- Create a new command router. This router can then be used when creating an `rpc`. For example:
123
-
124
- ```js
125
- const router = new RPC.CommandRouter()
126
-
127
- router.respond(42, (req, data) => {
128
- console.log(data.toString()) // ping
129
-
130
- return Buffer.from('pong')
131
- })
132
-
133
- const rpc = new RPC(stream, router)
134
- const req = rpc.request(42)
135
- req.send('ping')
136
- ```
137
-
138
- #### `router.respond(command[, options], async (req, data) => {})`
139
-
140
- Define a command and the handler for it. The callback for a command receives both the request (`req`) and the `data` buffer and can return a value to respond. If the request is responded to in the callback, the return value is ignored.
141
-
142
- Options include:
143
-
144
- ```js
145
- options = {
146
- // Encoding for incoming request
147
- requestEncoding: c.raw,
148
- // Encoding for outgoing response
149
- responseEncoding: c.raw
150
- }
151
- ```
32
+ See the [`bare-rpc` reference](https://docs.pears.com/reference/bare/modules/bare-rpc).
152
33
 
153
34
  ## License
154
35
 
package/index.d.ts CHANGED
@@ -19,11 +19,18 @@ declare const constants: {
19
19
 
20
20
  interface RPCIncomingEvent {
21
21
  readonly rpc: RPC
22
+ /** The command that the request was created with. A command is a unique number. */
22
23
  readonly command: number
24
+ /** The data buffer sent with the request. */
23
25
  readonly data: Buffer | null
24
26
  }
25
27
 
26
28
  declare class RPCIncomingEvent {
29
+ /**
30
+ * @param rpc - The `RPC` instance the event arrived on.
31
+ * @param command - The command number the event was sent with.
32
+ * @param data - The payload buffer sent with the event.
33
+ */
27
34
  constructor(rpc: RPC, command: number, data: Buffer)
28
35
  }
29
36
 
@@ -32,16 +39,45 @@ interface RPCIncomingRequest {
32
39
  readonly id: number
33
40
  readonly command: number
34
41
  readonly data: Buffer | null
42
+ /** A boolean for whether the request has been sent. */
35
43
  readonly sent: boolean
44
+ /** A boolean for whether the request has received a reply. */
36
45
  readonly received: boolean
37
46
 
47
+ /**
48
+ * Send a reply to the request. `data` may be a buffer or a string encoded using `encoding`.
49
+ * @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
50
+ * send no data.
51
+ * @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
52
+ * @throws {ALREADY_SENT} a response has already been sent for this request.
53
+ */
38
54
  reply(data?: Buffer | string | null, encoding?: BufferEncoding): void
39
55
 
56
+ /**
57
+ * Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending
58
+ * the streamed reply to the request.
59
+ * @param opts - Options for the returned
60
+ * [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
61
+ * @throws {ALREADY_SENT} a response has already been sent for this request.
62
+ */
40
63
  createResponseStream(opts?: WritableOptions): RPCOutgoingStream
64
+ /**
65
+ * Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for
66
+ * receiving the request's streamed data.
67
+ * @param opts - Options for the returned
68
+ * [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
69
+ * @throws {ALREADY_RECEIVED} the request has already been received.
70
+ */
41
71
  createRequestStream(opts?: ReadableOptions): RPCIncomingStream
42
72
  }
43
73
 
44
74
  declare class RPCIncomingRequest {
75
+ /**
76
+ * @param rpc - The `RPC` instance the request arrived on.
77
+ * @param id - The request id, used to correlate the reply with the request.
78
+ * @param command - The command number the request was sent with.
79
+ * @param data - The payload buffer sent with the request.
80
+ */
45
81
  constructor(rpc: RPC, id: number, command: number, data: Buffer)
46
82
  }
47
83
 
@@ -50,10 +86,20 @@ interface RPCOutgoingEvent {
50
86
  readonly command: number
51
87
  readonly sent: boolean
52
88
 
89
+ /**
90
+ * @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
91
+ * send no data.
92
+ * @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
93
+ * @throws {ALREADY_SENT} the event has already been sent.
94
+ */
53
95
  send(data?: Buffer | string | null, encoding?: BufferEncoding): void
54
96
  }
55
97
 
56
98
  declare class RPCOutgoingEvent {
99
+ /**
100
+ * @param rpc - The `RPC` instance to send the event on.
101
+ * @param command - The command number to send.
102
+ */
57
103
  constructor(rpc: RPC, command: number)
58
104
  }
59
105
 
@@ -64,19 +110,61 @@ interface RPCOutgoingRequest {
64
110
  readonly sent: boolean
65
111
  readonly received: boolean
66
112
 
113
+ /**
114
+ * @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
115
+ * send no data.
116
+ * @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
117
+ * @throws {ALREADY_SENT} the request has already been sent.
118
+ */
67
119
  send(data?: Buffer | string | null, encoding?: BufferEncoding): void
68
120
 
121
+ /**
122
+ * Await the reply from the remote end to the request. `encoding` can be defined for decoding the
123
+ * response `data` buffer back into a string.
124
+ * @param encoding - If given, decodes the reply payload to a string using this encoding; omit to
125
+ * receive the raw `Buffer`.
126
+ * @returns a promise that resolves with the remote end's reply payload, or rejects with the
127
+ * channel's teardown error if it closes before a reply arrives.
128
+ * @throws {ALREADY_RECEIVED} a reply is already being received for this request.
129
+ */
69
130
  reply(encoding?: BufferEncoding): Promise<Buffer | string | null>
70
131
 
132
+ /**
133
+ * Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending
134
+ * data with the request.
135
+ * @param opts - Options for the returned
136
+ * [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
137
+ * @throws {ALREADY_SENT} the request has already been sent.
138
+ */
71
139
  createRequestStream(opts?: WritableOptions): RPCOutgoingStream
140
+ /**
141
+ * Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for
142
+ * receiving data in reply to the request.
143
+ * @param opts - Options for the returned
144
+ * [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
145
+ * @throws {ALREADY_RECEIVED} the response has already been received.
146
+ */
72
147
  createResponseStream(opts?: ReadableOptions): RPCIncomingStream
73
148
  }
74
149
 
75
150
  declare class RPCOutgoingRequest {
151
+ /**
152
+ * @param rpc - The `RPC` instance to send the request on.
153
+ * @param id - The request id, used to correlate the reply with the request.
154
+ * @param command - The command number to send.
155
+ */
76
156
  constructor(rpc: RPC, id: number, command: number)
77
157
  }
78
158
 
79
159
  declare class RPCIncomingStream extends Readable {
160
+ /**
161
+ * @param rpc - The `RPC` instance the stream belongs to.
162
+ * @param request - The request the stream carries data for.
163
+ * @param type - Whether the stream carries the request body (`constants.type.REQUEST`) or the
164
+ * response body (`constants.type.RESPONSE`).
165
+ * @param opts - Options for the underlying
166
+ * [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
167
+ */
80
168
  constructor(
81
169
  rpc: RPC,
82
170
  request: RPCIncomingRequest | RPCOutgoingRequest,
@@ -86,6 +174,14 @@ declare class RPCIncomingStream extends Readable {
86
174
  }
87
175
 
88
176
  declare class RPCOutgoingStream extends Writable {
177
+ /**
178
+ * @param rpc - The `RPC` instance the stream belongs to.
179
+ * @param request - The request the stream carries data for.
180
+ * @param type - Whether the stream carries the request body (`constants.type.REQUEST`) or the
181
+ * response body (`constants.type.RESPONSE`).
182
+ * @param opts - Options for the underlying
183
+ * [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
184
+ */
89
185
  constructor(
90
186
  rpc: RPC,
91
187
  request: RPCIncomingRequest | RPCOutgoingRequest,
@@ -94,17 +190,94 @@ declare class RPCOutgoingStream extends Writable {
94
190
  )
95
191
  }
96
192
 
193
+ /** A [`compact-encoding`](https://github.com/holepunchto/compact-encoding) encoding, such as `c.raw`. */
194
+ interface RPCValueEncoding {
195
+ preencode(state: unknown, value: unknown): void
196
+ encode(state: unknown, value: unknown): void
197
+ decode(state: unknown): unknown
198
+ }
199
+
200
+ interface RPCCommandRouterOptions {
201
+ /** The encoding of request and response data for commands that set none (defaults to `c.raw`). */
202
+ valueEncoding?: RPCValueEncoding
203
+ }
204
+
205
+ interface RPCRespondOptions {
206
+ /** The encoding of both the request and the response data. */
207
+ valueEncoding?: RPCValueEncoding
208
+ /** The encoding to decode the request data with, or `null` to pass the raw buffer. */
209
+ requestEncoding?: RPCValueEncoding | null
210
+ /** The encoding to encode the response data with, or `null` to reply with the value as is. */
211
+ responseEncoding?: RPCValueEncoding | null
212
+ }
213
+
214
+ /**
215
+ * @param req - The incoming request.
216
+ * @param data - The request data, decoded with the request encoding of the command.
217
+ * @returns the response data, encoded with the response encoding of the command and sent as the
218
+ * reply unless the handler has already replied.
219
+ */
220
+ type RPCRequestHandler = (req: RPCIncomingRequest, data: any) => unknown
221
+
222
+ interface RPCCommandRouter {
223
+ /**
224
+ * Answer requests for `command` with `onrequest`, in place of any handler registered before.
225
+ * @param command - The command number to answer.
226
+ * @param opts - The encodings of the command.
227
+ * @param onrequest - The handler of the requests.
228
+ */
229
+ respond(command: number, onrequest: RPCRequestHandler): void
230
+ respond(command: number, opts: RPCRespondOptions, onrequest: RPCRequestHandler): void
231
+ }
232
+
233
+ declare class RPCCommandRouter {
234
+ /**
235
+ * @param opts - The default encodings of the commands.
236
+ */
237
+ constructor(opts?: RPCCommandRouterOptions)
238
+ }
239
+
97
240
  interface RPC {
241
+ /**
242
+ * Whether there are no requests or responses currently in flight. Useful for determining when it
243
+ * is safe to tear down the underlying stream.
244
+ */
98
245
  readonly idle: boolean
99
246
 
247
+ /**
248
+ * @param command - A unique number identifying the event; the remote end differentiates events by
249
+ * it.
250
+ * @returns an `RPCOutgoingEvent`; call `send()` on it to dispatch the one-way event.
251
+ */
100
252
  event(command: number): RPCOutgoingEvent
253
+ /**
254
+ * @param command - A unique number identifying the request; the remote end differentiates
255
+ * requests by it.
256
+ * @returns an `RPCOutgoingRequest`; call `send()` on it to dispatch the request and `await
257
+ * reply()` for the response.
258
+ */
101
259
  request(command: number): RPCOutgoingRequest
102
260
  }
103
261
 
104
262
  declare class RPC {
105
- constructor(stream: Duplex, onrequest: (req: RPCIncomingRequest) => void | Promise<void>)
263
+ /**
264
+ * @param stream - The duplex stream to frame RPC messages over, such as a pipe or socket.
265
+ * @param onrequest - Callback run for each incoming request or event, receiving an
266
+ * `RPCIncomingRequest` (or `RPCIncomingEvent`) to inspect and `reply()` to, or an
267
+ * `RPCCommandRouter` that answers each request with the handler of its command. Defaults to a
268
+ * no-op.
269
+ */
270
+ constructor(
271
+ stream: Duplex,
272
+ onrequest?: ((req: RPCIncomingRequest) => void | Promise<void>) | RPCCommandRouter
273
+ )
106
274
  }
107
275
 
276
+ /**
277
+ * Create an RPC instance using a duplex `stream`. `onrequest` is an optional callback run when a
278
+ * remote request is received. This is where processing and responding to an RPC request happens.
279
+ * `onrequest` receives a `RPCIncomingRequest` as an argument.
280
+ */
108
281
  declare namespace RPC {
109
282
  export {
110
283
  type RPCIncomingEvent as IncomingEvent,
@@ -112,7 +285,12 @@ declare namespace RPC {
112
285
  type RPCIncomingRequest as IncomingRequest,
113
286
  type RPCOutgoingRequest as OutgoingRequest,
114
287
  type RPCIncomingStream as IncomingStream,
115
- type RPCOutgoingStream as OutgoingStream
288
+ type RPCOutgoingStream as OutgoingStream,
289
+ RPCCommandRouter as CommandRouter,
290
+ type RPCCommandRouterOptions as CommandRouterOptions,
291
+ type RPCRespondOptions as RespondOptions,
292
+ type RPCRequestHandler as RequestHandler,
293
+ type RPCValueEncoding as ValueEncoding
116
294
  }
117
295
  }
118
296
 
package/index.js CHANGED
@@ -85,7 +85,7 @@ module.exports = exports = class RPC {
85
85
 
86
86
  let flushed = this._stream.write(header)
87
87
 
88
- if (message.data) flushed = this._stream.write(message.data)
88
+ if (message.data && m.hasData(message)) flushed = this._stream.write(message.data)
89
89
 
90
90
  if (cb) {
91
91
  if (flushed) cb(null)
@@ -273,19 +273,26 @@ module.exports = exports = class RPC {
273
273
  }
274
274
 
275
275
  _onbeforeframe() {
276
- if (this._buffered < 4) return
276
+ while (this._buffered >= 4) {
277
+ const buffer = this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
277
278
 
278
- const buffer = this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
279
+ this._buffer = [buffer]
280
+ this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
279
281
 
280
- this._buffer = [buffer]
281
- this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
282
+ if (this._buffered < this._frame) return
282
283
 
283
- this._onafterframe()
284
+ this._onframe()
285
+ }
284
286
  }
285
287
 
286
288
  _onafterframe() {
287
289
  if (this._buffered < this._frame) return
288
290
 
291
+ this._onframe()
292
+ this._onbeforeframe()
293
+ }
294
+
295
+ _onframe() {
289
296
  const buffer = this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
290
297
 
291
298
  const frame = this._frame
@@ -295,7 +302,6 @@ module.exports = exports = class RPC {
295
302
  this._frame = -1
296
303
 
297
304
  this._onmessage(buffer.subarray(0, frame))
298
- this._onbeforeframe()
299
305
  }
300
306
 
301
307
  async _onmessage(buffer) {
package/lib/messages.js CHANGED
@@ -21,19 +21,31 @@ const error = {
21
21
  }
22
22
  }
23
23
 
24
+ function hasData(m) {
25
+ switch (m.type) {
26
+ case t.REQUEST:
27
+ return m.stream === 0
28
+ case t.RESPONSE:
29
+ return !m.error && m.stream === 0
30
+ case t.STREAM:
31
+ return (m.stream & s.ERROR) === 0 && (m.stream & s.DATA) !== 0
32
+ default:
33
+ return false
34
+ }
35
+ }
36
+
37
+ exports.hasData = hasData
38
+
24
39
  exports.header = {
25
40
  preencode(state, m) {
26
41
  c.uint32.preencode(state, 0) // Frame
27
42
  c.uint.preencode(state, m.type)
28
43
  c.uint.preencode(state, m.id)
29
44
 
30
- let hasData = false
31
-
32
45
  switch (m.type) {
33
46
  case t.REQUEST:
34
47
  c.uint.preencode(state, m.command)
35
48
  c.uint.preencode(state, m.stream)
36
- if (m.stream === 0) hasData = true
37
49
  break
38
50
 
39
51
  case t.RESPONSE:
@@ -41,18 +53,16 @@ exports.header = {
41
53
  c.uint.preencode(state, m.stream)
42
54
 
43
55
  if (m.error) error.preencode(state, m.error)
44
- else if (m.stream === 0) hasData = true
45
56
  break
46
57
 
47
58
  case t.STREAM:
48
59
  c.uint.preencode(state, m.stream)
49
60
 
50
61
  if (m.stream & s.ERROR) error.preencode(state, m.error)
51
- else if (m.stream & s.DATA) hasData = true
52
62
  break
53
63
  }
54
64
 
55
- if (hasData) c.uint.preencode(state, m.data ? m.data.byteLength : 0)
65
+ if (hasData(m)) c.uint.preencode(state, m.data ? m.data.byteLength : 0)
56
66
  },
57
67
  encode(state, m) {
58
68
  const frame = state.start
@@ -64,13 +74,10 @@ exports.header = {
64
74
  c.uint.encode(state, m.type)
65
75
  c.uint.encode(state, m.id)
66
76
 
67
- let hasData = false
68
-
69
77
  switch (m.type) {
70
78
  case t.REQUEST:
71
79
  c.uint.encode(state, m.command)
72
80
  c.uint.encode(state, m.stream)
73
- if (m.stream === 0) hasData = true
74
81
  break
75
82
 
76
83
  case t.RESPONSE:
@@ -78,24 +85,24 @@ exports.header = {
78
85
  c.uint.encode(state, m.stream)
79
86
 
80
87
  if (m.error) error.encode(state, m.error)
81
- else if (m.stream === 0) hasData = true
82
88
  break
83
89
 
84
90
  case t.STREAM:
85
91
  c.uint.encode(state, m.stream)
86
92
 
87
93
  if (m.stream & s.ERROR) error.encode(state, m.error)
88
- else if (m.stream & s.DATA) hasData = true
89
94
  break
90
95
  }
91
96
 
92
- if (hasData) c.uint.encode(state, m.data ? m.data.byteLength : 0)
97
+ const data = hasData(m)
98
+
99
+ if (data) c.uint.encode(state, m.data ? m.data.byteLength : 0)
93
100
 
94
101
  const end = state.start
95
102
 
96
103
  state.start = frame
97
104
 
98
- c.uint32.encode(state, end - start + (hasData && m.data ? m.data.byteLength : 0))
105
+ c.uint32.encode(state, end - start + (data && m.data ? m.data.byteLength : 0))
99
106
 
100
107
  state.start = end
101
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-rpc",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "librpc ABI compatible RPC for Bare",
5
5
  "exports": {
6
6
  ".": {
@@ -44,6 +44,7 @@
44
44
  "devDependencies": {
45
45
  "bare-buffer": "^3.0.1",
46
46
  "bare-ipc": "^1.1.0",
47
+ "bare-rpc-test": "^0.1.0",
47
48
  "brittle": "^3.2.1",
48
49
  "lunte": "^1.7.0",
49
50
  "prettier": "^3.4.2",