bare-rpc 0.1.4 → 0.2.1

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/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ReadableOptions,
5
5
  Writable,
6
6
  WritableOptions,
7
- PassThrough
7
+ Duplex
8
8
  } from 'bare-stream'
9
9
 
10
10
  declare const constants: {
@@ -26,9 +26,10 @@ declare const constants: {
26
26
  interface RPCIncomingRequest {
27
27
  readonly rpc: RPC
28
28
  readonly id: number
29
- readonly command: string
29
+ readonly command: number
30
30
  readonly data: Buffer
31
31
  readonly sent: boolean
32
+ readonly received: boolean
32
33
 
33
34
  reply(data: Buffer | string, encoding?: BufferEncoding): void
34
35
 
@@ -37,14 +38,15 @@ interface RPCIncomingRequest {
37
38
  }
38
39
 
39
40
  declare class RPCIncomingRequest {
40
- constructor(rpc: RPC, id: number, command: string, data: Buffer)
41
+ constructor(rpc: RPC, id: number, command: number, data: Buffer)
41
42
  }
42
43
 
43
44
  interface RPCOutgoingRequest {
44
45
  readonly rpc: RPC
45
46
  readonly id: number
46
- readonly command: string
47
+ readonly command: number
47
48
  readonly sent: boolean
49
+ readonly received: boolean
48
50
 
49
51
  send(data: Buffer | string, encoding?: BufferEncoding): void
50
52
 
@@ -55,7 +57,7 @@ interface RPCOutgoingRequest {
55
57
  }
56
58
 
57
59
  declare class RPCOutgoingRequest {
58
- constructor(rpc: RPC, command: string)
60
+ constructor(rpc: RPC, command: number)
59
61
  }
60
62
 
61
63
  declare class RPCIncomingStream extends Readable {
@@ -77,11 +79,11 @@ declare class RPCOutgoingStream extends Writable {
77
79
  }
78
80
 
79
81
  interface RPC {
80
- request(command: string): RPCOutgoingRequest
82
+ request(command: number): RPCOutgoingRequest
81
83
  }
82
84
 
83
85
  declare class RPC {
84
- constructor(stream: PassThrough, onrequest: (req: RPCIncomingRequest) => void)
86
+ constructor(stream: Duplex, onrequest: (req: RPCIncomingRequest) => void)
85
87
  }
86
88
 
87
89
  export = RPC
package/index.js CHANGED
@@ -7,8 +7,9 @@ const IncomingRequest = require('./lib/incoming-request')
7
7
  const IncomingStream = require('./lib/incoming-stream')
8
8
  const OutgoingRequest = require('./lib/outgoing-request')
9
9
  const OutgoingStream = require('./lib/outgoing-stream')
10
+ const CommandRouter = require('./lib/command-router')
10
11
 
11
- module.exports = class RPC {
12
+ module.exports = exports = class RPC {
12
13
  constructor(stream, onrequest) {
13
14
  this._stream = stream
14
15
 
@@ -21,9 +22,17 @@ module.exports = class RPC {
21
22
  this._pendingRequests = new Set()
22
23
  this._pendingResponses = new Set()
23
24
 
24
- this._buffer = null
25
+ this._buffer = []
26
+ this._buffered = 0
27
+ this._frame = -1
25
28
 
26
- this._onrequest = onrequest.bind(this)
29
+ if (typeof onrequest === 'function') {
30
+ onrequest = onrequest.bind(this)
31
+ } else {
32
+ onrequest = onrequest._onrequest.bind(onrequest)
33
+ }
34
+
35
+ this._onrequest = onrequest
27
36
  this._ondata = this._ondata.bind(this)
28
37
 
29
38
  this._stream.on('data', this._ondata)
@@ -122,58 +131,84 @@ module.exports = class RPC {
122
131
  }
123
132
 
124
133
  _ondata(data) {
125
- if (this._buffer === null) this._buffer = data
126
- else this._buffer = b4a.concat([this._buffer, data])
134
+ this._buffer.push(data)
135
+ this._buffered += data.byteLength
136
+
137
+ if (this._frame === -1) {
138
+ this._onbeforeframe()
139
+ } else {
140
+ this._onafterframe()
141
+ }
142
+ }
127
143
 
128
- while (this._buffer !== null) {
129
- const state = { start: 0, end: this._buffer.length, buffer: this._buffer }
144
+ _onbeforeframe() {
145
+ if (this._buffered < 4) return
130
146
 
131
- let message
132
- try {
133
- message = m.message.decode(state)
134
- } catch (err) {
135
- safetyCatch(err)
147
+ const buffer =
148
+ this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
136
149
 
137
- return this._stream.destroy(err)
138
- }
150
+ this._buffer = [buffer]
151
+ this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
139
152
 
140
- if (message === null) return
141
-
142
- switch (message.type) {
143
- case t.REQUEST: {
144
- const request = new IncomingRequest(
145
- this,
146
- message.id,
147
- message.command,
148
- message.data
149
- )
150
-
151
- try {
152
- this._onrequest(request)
153
- } catch (err) {
154
- safetyCatch(err)
155
-
156
- this._sendError(request, err)
157
- }
158
- break
159
- }
160
- case t.RESPONSE:
161
- try {
162
- this._onresponse(message)
163
- } catch (err) {
164
- safetyCatch(err)
165
- }
166
- break
167
- case t.STREAM:
168
- try {
169
- this._onstream(message)
170
- } catch (err) {
171
- safetyCatch(err)
172
- }
173
- }
153
+ this._onafterframe()
154
+ }
155
+
156
+ _onafterframe() {
157
+ while (this._frame >= 0 && this._frame <= this._buffered) {
158
+ const buffer =
159
+ this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
174
160
 
175
- this._buffer =
176
- state.start === state.end ? null : this._buffer.subarray(state.start)
161
+ const frame = this._frame
162
+
163
+ this._buffered -= frame
164
+ this._buffer = this._buffered > 0 ? [buffer.subarray(frame)] : []
165
+ this._frame = -1
166
+
167
+ this._onmessage(buffer.subarray(0, frame))
168
+ this._onbeforeframe()
169
+ }
170
+ }
171
+
172
+ _onmessage(buffer) {
173
+ let message
174
+ try {
175
+ message = m.message.decode(c.state(0, buffer.length, buffer))
176
+ } catch (err) {
177
+ safetyCatch(err)
178
+
179
+ return this._stream.destroy(err)
180
+ }
181
+
182
+ switch (message.type) {
183
+ case t.REQUEST:
184
+ const request = new IncomingRequest(
185
+ this,
186
+ message.id,
187
+ message.command,
188
+ message.data
189
+ )
190
+
191
+ try {
192
+ this._onrequest(request)
193
+ } catch (err) {
194
+ safetyCatch(err)
195
+
196
+ this._sendError(request, err)
197
+ }
198
+ break
199
+ case t.RESPONSE:
200
+ try {
201
+ this._onresponse(message)
202
+ } catch (err) {
203
+ safetyCatch(err)
204
+ }
205
+ break
206
+ case t.STREAM:
207
+ try {
208
+ this._onstream(message)
209
+ } catch (err) {
210
+ safetyCatch(err)
211
+ }
177
212
  }
178
213
  }
179
214
 
@@ -357,3 +392,5 @@ module.exports = class RPC {
357
392
  stream.destroy(message.error)
358
393
  }
359
394
  }
395
+
396
+ exports.CommandRouter = CommandRouter
@@ -0,0 +1,49 @@
1
+ const c = require('compact-encoding')
2
+
3
+ module.exports = class RPCCommandRouter {
4
+ constructor(opts = {}) {
5
+ const { valueEncoding = c.raw } = opts
6
+
7
+ this._responders = new Map()
8
+ this._defaultValueEncoding = valueEncoding
9
+ }
10
+
11
+ respond(command, opts = {}, onrequest) {
12
+ if (typeof opts === 'function') {
13
+ onrequest = opts
14
+ opts = {}
15
+ }
16
+
17
+ const {
18
+ valueEncoding = this._defaultValueEncoding,
19
+ requestEncoding = valueEncoding,
20
+ responseEncoding = valueEncoding
21
+ } = opts
22
+
23
+ this._responders.set(command, {
24
+ onrequest,
25
+ requestEncoding,
26
+ responseEncoding
27
+ })
28
+ }
29
+
30
+ async _onrequest(req) {
31
+ const responder = this._responders.get(req.command)
32
+
33
+ if (responder === undefined) return
34
+
35
+ const { onrequest, requestEncoding, responseEncoding } = responder
36
+
37
+ let data = req.data
38
+
39
+ if (requestEncoding) data = c.decode(requestEncoding, data)
40
+
41
+ data = await onrequest(req, req.data)
42
+
43
+ if (req.sent) return
44
+
45
+ if (responseEncoding) data = c.encode(responseEncoding, data)
46
+
47
+ req.reply(data)
48
+ }
49
+ }
package/lib/errors.js CHANGED
@@ -19,4 +19,8 @@ module.exports = class RPCError extends Error {
19
19
  static ALREADY_SENT(msg) {
20
20
  return new RPCError(msg, 'ALREADY_SENT', RPCError.ALREADY_SENT)
21
21
  }
22
+
23
+ static ALREADY_RECEIVED(msg) {
24
+ return new RPCError(msg, 'ALREADY_RECEIVED', RPCError.ALREADY_RECEIVED)
25
+ }
22
26
  }
@@ -8,13 +8,17 @@ module.exports = class RPCIncomingRequest {
8
8
  this.command = command
9
9
  this.data = data
10
10
  this.sent = false
11
+ this.received = false
11
12
 
12
13
  this._requestStream = null
13
14
  this._responseStream = null
14
15
  }
15
16
 
16
17
  reply(data, encoding) {
17
- if (this.sent) throw errors.ALREADY_SENT('Response has already been sent')
18
+ if (this.sent) {
19
+ throw errors.ALREADY_SENT('Response has already been sent')
20
+ }
21
+
18
22
  this.sent = true
19
23
 
20
24
  this.rpc._sendResponse(
@@ -24,15 +28,25 @@ module.exports = class RPCIncomingRequest {
24
28
  }
25
29
 
26
30
  createResponseStream(opts = {}) {
27
- if (this._responseStream === null)
28
- this.rpc._createResponseStream(this, true, opts)
31
+ if (this.sent) {
32
+ throw errors.ALREADY_SENT('Response has already been sent')
33
+ }
34
+
35
+ this.sent = true
36
+
37
+ this.rpc._createResponseStream(this, true, opts)
29
38
 
30
39
  return this._responseStream
31
40
  }
32
41
 
33
42
  createRequestStream(opts = {}) {
34
- if (this._requestStream === null)
35
- this.rpc._createRequestStream(this, false, opts)
43
+ if (this.received) {
44
+ throw errors.ALREADY_RECEIVED('Request has already been received')
45
+ }
46
+
47
+ this.received = true
48
+
49
+ this.rpc._createRequestStream(this, false, opts)
36
50
 
37
51
  return this._requestStream
38
52
  }
package/lib/messages.js CHANGED
@@ -4,17 +4,17 @@ const errors = require('./errors')
4
4
 
5
5
  const request = {
6
6
  preencode(state, m) {
7
- c.utf8.preencode(state, m.command)
7
+ c.uint.preencode(state, m.command)
8
8
  c.uint.preencode(state, m.stream)
9
9
  if (m.stream === 0) c.buffer.preencode(state, m.data)
10
10
  },
11
11
  encode(state, m) {
12
- c.utf8.encode(state, m.command)
12
+ c.uint.encode(state, m.command)
13
13
  c.uint.encode(state, m.stream)
14
14
  if (m.stream === 0) c.buffer.encode(state, m.data)
15
15
  },
16
16
  decode(state, id) {
17
- const command = c.utf8.decode(state)
17
+ const command = c.uint.decode(state)
18
18
  const stream = c.uint.decode(state)
19
19
  const data = stream === 0 ? c.buffer.decode(state) : null
20
20
 
@@ -142,11 +142,9 @@ exports.message = {
142
142
  state.start = end
143
143
  },
144
144
  decode(state) {
145
- if (state.end - state.start < 4) return null
146
-
147
145
  const frame = c.uint32.decode(state)
148
146
 
149
- if (state.end - state.start < frame) return null
147
+ if (state.end - state.start < frame) throw new RangeError('Out of bounds')
150
148
 
151
149
  const type = c.uint.decode(state)
152
150
  const id = c.uint.decode(state)
@@ -7,6 +7,7 @@ module.exports = class RPCOutgoingRequest {
7
7
  this.id = id
8
8
  this.command = command
9
9
  this.sent = false
10
+ this.received = false
10
11
 
11
12
  this._promise = new Promise((resolve, reject) => {
12
13
  this._resolve = resolve
@@ -18,7 +19,10 @@ module.exports = class RPCOutgoingRequest {
18
19
  }
19
20
 
20
21
  send(data, encoding) {
21
- if (this.sent) throw errors.ALREADY_SENT('Request has already been sent')
22
+ if (this.sent) {
23
+ throw errors.ALREADY_SENT('Request has already been sent')
24
+ }
25
+
22
26
  this.sent = true
23
27
 
24
28
  this.rpc._sendRequest(
@@ -28,24 +32,38 @@ module.exports = class RPCOutgoingRequest {
28
32
  }
29
33
 
30
34
  reply(encoding) {
35
+ if (this.received) {
36
+ throw errors.ALREADY_RECEIVED('Response is already being received')
37
+ }
38
+
39
+ this.received = true
40
+
31
41
  return encoding && encoding !== 'buffer'
32
42
  ? this._promise.then((data) => b4a.toString(data, encoding))
33
43
  : this._promise
34
44
  }
35
45
 
36
46
  createRequestStream(opts = {}) {
37
- if (this._requestStream === null) {
38
- this.rpc._createRequestStream(this, true, opts)
47
+ if (this.sent) {
48
+ throw errors.ALREADY_SENT('Request has already been sent')
39
49
  }
40
50
 
51
+ this.sent = true
52
+
53
+ this.rpc._createRequestStream(this, true, opts)
54
+
41
55
  return this._requestStream
42
56
  }
43
57
 
44
58
  createResponseStream(opts = {}) {
45
- if (this._requestStream === null) {
46
- this.rpc._createResponseStream(this, false, opts)
59
+ if (this.received) {
60
+ throw errors.ALREADY_RECEIVED('Response has already been received')
47
61
  }
48
62
 
63
+ this.received = true
64
+
65
+ this.rpc._createResponseStream(this, false, opts)
66
+
49
67
  return this._responseStream
50
68
  }
51
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-rpc",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "librpc ABI compatible RPC for Bare",
5
5
  "exports": {
6
6
  ".": {