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 +9 -7
- package/index.js +87 -50
- package/lib/command-router.js +49 -0
- package/lib/errors.js +4 -0
- package/lib/incoming-request.js +19 -5
- package/lib/messages.js +4 -6
- package/lib/outgoing-request.js +23 -5
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
ReadableOptions,
|
|
5
5
|
Writable,
|
|
6
6
|
WritableOptions,
|
|
7
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
82
|
+
request(command: number): RPCOutgoingRequest
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
declare class RPC {
|
|
84
|
-
constructor(stream:
|
|
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 =
|
|
25
|
+
this._buffer = []
|
|
26
|
+
this._buffered = 0
|
|
27
|
+
this._frame = -1
|
|
25
28
|
|
|
26
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
144
|
+
_onbeforeframe() {
|
|
145
|
+
if (this._buffered < 4) return
|
|
130
146
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
138
|
-
|
|
150
|
+
this._buffer = [buffer]
|
|
151
|
+
this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
|
|
139
152
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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.
|
|
176
|
-
|
|
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
|
}
|
package/lib/incoming-request.js
CHANGED
|
@@ -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)
|
|
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.
|
|
28
|
-
|
|
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.
|
|
35
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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)
|
|
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)
|
package/lib/outgoing-request.js
CHANGED
|
@@ -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)
|
|
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.
|
|
38
|
-
|
|
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.
|
|
46
|
-
|
|
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
|
}
|