import {RPC_Error} from './rpc_error.jsy' export const rx_rpc = /[\$\s]/ export class RPC :: static create(rpc_api) :: return new this(rpc_api) constructor(rpc_api) :: this.rpc_api = rpc_api // overlay using rpc_api for let k of ['lookup', 'log', 'error', 'dnu'] :: k = 'rpc_'+k let fn = rpc_api[k] this[k] = fn ? fn.bind(rpc_api) : this[k].bind(this) with(rpc_api, kw) :: return @{} __proto__: this, rpc_api, ...kw async rpc_stream(xtgt) :: for await let [pkt, pktctx] of xtgt.stream || xtgt :: await this.rpc(pkt.body, pktctx) rpc_target() :: return (pkt, pktctx) => this.rpc(pkt.body, pktctx) as_rpc_record(rpc_call) :: if rpc_call :: if ! rpc_call.is_valid_rpc :: rpc_call = this._rpc_record.from(rpc_call) if rpc_call.is_valid_rpc() :: return rpc_call async rpc(rpc_call, pktctx) :: rpc_call = this.as_rpc_record(rpc_call) if ! rpc_call :: return let reply, rpc_fn = this.bind_rpc(rpc_call, pktctx) if false === rpc_fn :: this.rpc_log(rpc_call, 'dnu') await this.rpc_dnu(rpc_call) if reply = rpc_call.id_reply :: pktctx.send @ reply, @{} err: 'DNU', dnu: true return false if rpc_fn :: this.rpc_log(rpc_call, 'call') try :: let r = await rpc_fn() this.rpc_log(rpc_call, 'done', r) if reply = pktctx.reply :: reply @: ok: r catch err :: this.rpc_log(rpc_call, 'error', err) if reply = pktctx.reply :: if err.is_rpc :: reply @: err: `${err.message || err}`, rpc: err.rpc return true // error sent -- avoid rpc_error noise reply @: err: `${err.message || err}` this.rpc_error(err, rpc_call) return true await this.rpc_invalid(rpc_call, pktctx) bind_rpc(rpc_call, pktctx) :: if rpc_call.is_valid_rpc() :: let rpc_fn = this.rpc_lookup(rpc_call) return rpc_fn ? this._bind_rpc(rpc_fn, rpc_call, pktctx) : false // otherwise, invalid rpc_call _bind_rpc(rpc_fn, rpc_call, pktctx) :: // rpc_call: @[] '!', id_reply, method, ... args let {id_reply, args} = rpc_call if id_reply :: // id_reply pktctx.with_reply(id_reply) pktctx.RPC_Error = RPC_Error return rpc_fn.bind(this.rpc_api, pktctx, ... args) rpc_lookup(rpc_call) :: return this.rpc_api[rpc_call.method] rpc_log(rpc_call, step, ...info) :: rpc_error(err, rpc_call) :: console.warn @ `RPC ERROR [%o]`, rpc_call.method, err rpc_invalid(pkt, pktctx) :: console.warn @ `Non-RPC pkt: %o`, pkt.body rpc_dnu(rpc_call) :: // DNU is "does not understand" convention from Smalltalk. console.warn @ `RPC DNU [%o]`, rpc_call.method class rpc_record extends Array :: // rpc_call: @[] '!', id_reply, method, ... args is_valid_rpc() :: return '!' === this[0] && rx_rpc.test(this[2]) get id_reply() :: return this[1] get method() :: return this[2] get args() :: return this.slice(3) RPC.prototype._rpc_record = rpc_record