Implement hrpc with streaming and wire into setup
- Add spec/hyperschema and spec/hrpc codegen (scripts/build-hrpc.js) with unary, request/response-stream, duplex, and send-only commands - Run build:hrpc and build:protomux from install.sh so setup is one-step - Native host: add attachHrpc (Protomux channel + generated HRPC), handlers for ping, streamSum, fetchStream, duplex, notify - Load HRPC lazily so missing spec does not crash host; use bare-stream for Duplex under Bare; add bare-stream, compact-encoding deps
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": 1,
|
||||
"schema": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "@bridgeswarm/ping",
|
||||
"request": {
|
||||
"name": "@bridgeswarm/ping-request",
|
||||
"stream": false
|
||||
},
|
||||
"response": {
|
||||
"name": "@bridgeswarm/ping-response",
|
||||
"stream": false
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "@bridgeswarm/streamSum",
|
||||
"request": {
|
||||
"name": "@bridgeswarm/stream-request-chunk",
|
||||
"stream": true
|
||||
},
|
||||
"response": {
|
||||
"name": "@bridgeswarm/stream-sum-response",
|
||||
"stream": false
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "@bridgeswarm/fetchStream",
|
||||
"request": {
|
||||
"name": "@bridgeswarm/fetch-request",
|
||||
"stream": false
|
||||
},
|
||||
"response": {
|
||||
"name": "@bridgeswarm/fetch-chunk",
|
||||
"stream": true
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "@bridgeswarm/duplex",
|
||||
"request": {
|
||||
"name": "@bridgeswarm/duplex-in",
|
||||
"stream": true
|
||||
},
|
||||
"response": {
|
||||
"name": "@bridgeswarm/duplex-out",
|
||||
"stream": true
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "@bridgeswarm/notify",
|
||||
"request": {
|
||||
"name": "@bridgeswarm/notify-request",
|
||||
"send": true
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// This file is autogenerated by the hrpc compiler
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable space-before-function-paren */
|
||||
|
||||
const { c, RPC, RPCStream, RPCRequestStream } = require('hrpc/runtime')
|
||||
const { getEncoding } = require('./messages.js')
|
||||
|
||||
const methods = new Map([
|
||||
['@bridgeswarm/ping', 0],
|
||||
[0, '@bridgeswarm/ping'],
|
||||
['@bridgeswarm/streamSum', 1],
|
||||
[1, '@bridgeswarm/streamSum'],
|
||||
['@bridgeswarm/fetchStream', 2],
|
||||
[2, '@bridgeswarm/fetchStream'],
|
||||
['@bridgeswarm/duplex', 3],
|
||||
[3, '@bridgeswarm/duplex'],
|
||||
['@bridgeswarm/notify', 4],
|
||||
[4, '@bridgeswarm/notify']
|
||||
])
|
||||
|
||||
class HRPC {
|
||||
constructor(stream) {
|
||||
this._stream = stream
|
||||
this._handlers = []
|
||||
this._requestEncodings = new Map([
|
||||
['@bridgeswarm/ping', getEncoding('@bridgeswarm/ping-request')],
|
||||
['@bridgeswarm/streamSum', getEncoding('@bridgeswarm/stream-request-chunk')],
|
||||
['@bridgeswarm/fetchStream', getEncoding('@bridgeswarm/fetch-request')],
|
||||
['@bridgeswarm/duplex', getEncoding('@bridgeswarm/duplex-in')],
|
||||
['@bridgeswarm/notify', getEncoding('@bridgeswarm/notify-request')]
|
||||
])
|
||||
this._responseEncodings = new Map([
|
||||
['@bridgeswarm/ping', getEncoding('@bridgeswarm/ping-response')],
|
||||
['@bridgeswarm/streamSum', getEncoding('@bridgeswarm/stream-sum-response')],
|
||||
['@bridgeswarm/fetchStream', getEncoding('@bridgeswarm/fetch-chunk')],
|
||||
['@bridgeswarm/duplex', getEncoding('@bridgeswarm/duplex-out')]
|
||||
])
|
||||
this._rpc = new RPC(stream, async (req) => {
|
||||
const command = methods.get(req.command)
|
||||
const responseEncoding = this._responseEncodings.get(command)
|
||||
const requestEncoding = this._requestEncodings.get(command)
|
||||
if (this._requestIsSend(command)) {
|
||||
const request = req.data ? c.decode(requestEncoding, req.data) : null
|
||||
await this._handlers[command](request)
|
||||
return
|
||||
}
|
||||
if (!this._requestIsStream(command) && !this._responseIsStream(command)) {
|
||||
const request = req.data ? c.decode(requestEncoding, req.data) : null
|
||||
const response = await this._handlers[command](request)
|
||||
req.reply(c.encode(responseEncoding, response))
|
||||
}
|
||||
if (!this._requestIsStream(command) && this._responseIsStream(command)) {
|
||||
const request = req.data ? c.decode(requestEncoding, req.data) : null
|
||||
const responseStream = new RPCStream(
|
||||
null,
|
||||
null,
|
||||
req.createResponseStream(),
|
||||
responseEncoding
|
||||
)
|
||||
responseStream.data = request
|
||||
await this._handlers[command](responseStream)
|
||||
}
|
||||
if (this._requestIsStream(command) && !this._responseIsStream(command)) {
|
||||
const requestStream = new RPCRequestStream(
|
||||
req,
|
||||
responseEncoding,
|
||||
req.createRequestStream(),
|
||||
requestEncoding
|
||||
)
|
||||
const response = await this._handlers[command](requestStream)
|
||||
req.reply(c.encode(responseEncoding, response))
|
||||
}
|
||||
if (this._requestIsStream(command) && this._responseIsStream(command)) {
|
||||
const requestStream = new RPCRequestStream(
|
||||
req,
|
||||
responseEncoding,
|
||||
req.createRequestStream(),
|
||||
requestEncoding,
|
||||
req.createResponseStream(),
|
||||
responseEncoding
|
||||
)
|
||||
await this._handlers[command](requestStream)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async _call(name, args) {
|
||||
const requestEncoding = this._requestEncodings.get(name)
|
||||
const responseEncoding = this._responseEncodings.get(name)
|
||||
const request = this._rpc.request(methods.get(name))
|
||||
const encoded = c.encode(requestEncoding, args)
|
||||
request.send(encoded)
|
||||
return c.decode(responseEncoding, await request.reply())
|
||||
}
|
||||
|
||||
_callSync(name, args) {
|
||||
const requestEncoding = this._requestEncodings.get(name)
|
||||
const responseEncoding = this._responseEncodings.get(name)
|
||||
if (this._requestIsSend(name)) {
|
||||
const encoded = c.encode(requestEncoding, args)
|
||||
const request = this._rpc.event(methods.get(name))
|
||||
request.send(encoded)
|
||||
}
|
||||
|
||||
const request = this._rpc.request(methods.get(name))
|
||||
|
||||
if (!this._requestIsStream(name) && this._responseIsStream(name)) {
|
||||
const encoded = c.encode(requestEncoding, args)
|
||||
request.send(encoded)
|
||||
return new RPCStream(request.createResponseStream(), responseEncoding)
|
||||
}
|
||||
if (this._requestIsStream(name) && !this._responseIsStream(name)) {
|
||||
return new RPCRequestStream(
|
||||
request,
|
||||
responseEncoding,
|
||||
null,
|
||||
null,
|
||||
request.createRequestStream(),
|
||||
requestEncoding
|
||||
)
|
||||
}
|
||||
if (this._requestIsStream(name) && this._responseIsStream(name)) {
|
||||
return new RPCRequestStream(
|
||||
request,
|
||||
responseEncoding,
|
||||
request.createResponseStream(),
|
||||
responseEncoding,
|
||||
request.createRequestStream(),
|
||||
requestEncoding
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async ping(args) {
|
||||
return this._call('@bridgeswarm/ping', args)
|
||||
}
|
||||
|
||||
streamSum(args) {
|
||||
return this._callSync('@bridgeswarm/streamSum', args)
|
||||
}
|
||||
|
||||
fetchStream(args) {
|
||||
return this._callSync('@bridgeswarm/fetchStream', args)
|
||||
}
|
||||
|
||||
duplex(args) {
|
||||
return this._callSync('@bridgeswarm/duplex', args)
|
||||
}
|
||||
|
||||
notify(args) {
|
||||
return this._callSync('@bridgeswarm/notify', args)
|
||||
}
|
||||
|
||||
onPing(responseFn) {
|
||||
this._handlers['@bridgeswarm/ping'] = responseFn
|
||||
}
|
||||
|
||||
onStreamSum(responseFn) {
|
||||
this._handlers['@bridgeswarm/streamSum'] = responseFn
|
||||
}
|
||||
|
||||
onFetchStream(responseFn) {
|
||||
this._handlers['@bridgeswarm/fetchStream'] = responseFn
|
||||
}
|
||||
|
||||
onDuplex(responseFn) {
|
||||
this._handlers['@bridgeswarm/duplex'] = responseFn
|
||||
}
|
||||
|
||||
onNotify(responseFn) {
|
||||
this._handlers['@bridgeswarm/notify'] = responseFn
|
||||
}
|
||||
|
||||
_requestIsStream(command) {
|
||||
return [
|
||||
'@bridgeswarm/streamSum',
|
||||
'@bridgeswarm/duplex'
|
||||
].includes(command)
|
||||
}
|
||||
|
||||
_responseIsStream(command) {
|
||||
return [
|
||||
'@bridgeswarm/fetchStream',
|
||||
'@bridgeswarm/duplex'
|
||||
].includes(command)
|
||||
}
|
||||
|
||||
// prettier-ignore-start
|
||||
_requestIsSend(command) {
|
||||
return [
|
||||
// prettier-ignore
|
||||
'@bridgeswarm/notify'
|
||||
].includes(command)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HRPC
|
||||
@@ -0,0 +1,314 @@
|
||||
// This file is autogenerated by the hyperschema compiler
|
||||
// Schema Version: 1
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable quotes */
|
||||
/* eslint-disable space-before-function-paren */
|
||||
|
||||
const { c } = require('hyperschema/runtime')
|
||||
|
||||
const VERSION = 1
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let version = VERSION
|
||||
|
||||
// @bridgeswarm/ping-request
|
||||
const encoding0 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 1 so always one byte
|
||||
|
||||
if (m.value) c.string.preencode(state, m.value)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = m.value ? 1 : 0
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.value) c.string.encode(state, m.value)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
value: (flags & 1) !== 0 ? c.string.decode(state) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/ping-response
|
||||
const encoding1 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 1 so always one byte
|
||||
|
||||
if (m.pong) c.string.preencode(state, m.pong)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = m.pong ? 1 : 0
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.pong) c.string.encode(state, m.pong)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
pong: (flags & 1) !== 0 ? c.string.decode(state) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/stream-request-chunk
|
||||
const encoding2 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.n) c.uint.preencode(state, m.n)
|
||||
if (m.label) c.string.preencode(state, m.label)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.n ? 1 : 0) | (m.label ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.n) c.uint.encode(state, m.n)
|
||||
if (m.label) c.string.encode(state, m.label)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
n: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
|
||||
label: (flags & 2) !== 0 ? c.string.decode(state) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/stream-sum-response
|
||||
const encoding3 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.sum) c.uint.preencode(state, m.sum)
|
||||
if (m.count) c.uint.preencode(state, m.count)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.sum ? 1 : 0) | (m.count ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.sum) c.uint.encode(state, m.sum)
|
||||
if (m.count) c.uint.encode(state, m.count)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
sum: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
|
||||
count: (flags & 2) !== 0 ? c.uint.decode(state) : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/fetch-request
|
||||
const encoding4 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 1 so always one byte
|
||||
|
||||
if (m.count) c.uint.preencode(state, m.count)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = m.count ? 1 : 0
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.count) c.uint.encode(state, m.count)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
count: (flags & 1) !== 0 ? c.uint.decode(state) : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/fetch-chunk
|
||||
const encoding5 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.i) c.uint.preencode(state, m.i)
|
||||
if (m.data) c.string.preencode(state, m.data)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.i ? 1 : 0) | (m.data ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.i) c.uint.encode(state, m.i)
|
||||
if (m.data) c.string.encode(state, m.data)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
i: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
|
||||
data: (flags & 2) !== 0 ? c.string.decode(state) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/duplex-in
|
||||
const encoding6 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.x) c.uint.preencode(state, m.x)
|
||||
if (m.y) c.string.preencode(state, m.y)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.x ? 1 : 0) | (m.y ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.x) c.uint.encode(state, m.x)
|
||||
if (m.y) c.string.encode(state, m.y)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
x: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
|
||||
y: (flags & 2) !== 0 ? c.string.decode(state) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/duplex-out
|
||||
const encoding7 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.result) c.string.preencode(state, m.result)
|
||||
if (m.n) c.uint.preencode(state, m.n)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.result ? 1 : 0) | (m.n ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.result) c.string.encode(state, m.result)
|
||||
if (m.n) c.uint.encode(state, m.n)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
result: (flags & 1) !== 0 ? c.string.decode(state) : null,
|
||||
n: (flags & 2) !== 0 ? c.uint.decode(state) : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @bridgeswarm/notify-request
|
||||
const encoding8 = {
|
||||
preencode(state, m) {
|
||||
state.end++ // max flag is 2 so always one byte
|
||||
|
||||
if (m.event) c.string.preencode(state, m.event)
|
||||
if (m.payload) c.uint.preencode(state, m.payload)
|
||||
},
|
||||
encode(state, m) {
|
||||
const flags = (m.event ? 1 : 0) | (m.payload ? 2 : 0)
|
||||
|
||||
c.uint.encode(state, flags)
|
||||
|
||||
if (m.event) c.string.encode(state, m.event)
|
||||
if (m.payload) c.uint.encode(state, m.payload)
|
||||
},
|
||||
decode(state) {
|
||||
const flags = c.uint.decode(state)
|
||||
|
||||
return {
|
||||
event: (flags & 1) !== 0 ? c.string.decode(state) : null,
|
||||
payload: (flags & 2) !== 0 ? c.uint.decode(state) : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setVersion(v) {
|
||||
version = v
|
||||
}
|
||||
|
||||
function encode(name, value, v = VERSION) {
|
||||
version = v
|
||||
return c.encode(getEncoding(name), value)
|
||||
}
|
||||
|
||||
function decode(name, buffer, v = VERSION) {
|
||||
version = v
|
||||
return c.decode(getEncoding(name), buffer)
|
||||
}
|
||||
|
||||
function getEnum(name) {
|
||||
switch (name) {
|
||||
default:
|
||||
throw new Error('Enum not found ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
function getEncoding(name) {
|
||||
switch (name) {
|
||||
case '@bridgeswarm/ping-request':
|
||||
return encoding0
|
||||
case '@bridgeswarm/ping-response':
|
||||
return encoding1
|
||||
case '@bridgeswarm/stream-request-chunk':
|
||||
return encoding2
|
||||
case '@bridgeswarm/stream-sum-response':
|
||||
return encoding3
|
||||
case '@bridgeswarm/fetch-request':
|
||||
return encoding4
|
||||
case '@bridgeswarm/fetch-chunk':
|
||||
return encoding5
|
||||
case '@bridgeswarm/duplex-in':
|
||||
return encoding6
|
||||
case '@bridgeswarm/duplex-out':
|
||||
return encoding7
|
||||
case '@bridgeswarm/notify-request':
|
||||
return encoding8
|
||||
default:
|
||||
throw new Error('Encoder not found ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
function getStruct(name, v = VERSION) {
|
||||
const enc = getEncoding(name)
|
||||
return {
|
||||
preencode(state, m) {
|
||||
version = v
|
||||
enc.preencode(state, m)
|
||||
},
|
||||
encode(state, m) {
|
||||
version = v
|
||||
enc.encode(state, m)
|
||||
},
|
||||
decode(state) {
|
||||
version = v
|
||||
return enc.decode(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resolveStruct = getStruct // compat
|
||||
|
||||
module.exports = {
|
||||
resolveStruct,
|
||||
getStruct,
|
||||
getEnum,
|
||||
getEncoding,
|
||||
encode,
|
||||
decode,
|
||||
setVersion,
|
||||
version
|
||||
}
|
||||
Reference in New Issue
Block a user