elegant weapons, for a more civilized age.
Tip
you are looking at wip docs for renraku@next v0.6 — you may instead like to see the v0.5 readme.
npm install @e280/renraku@nextrenraku makes typescript functions callable across boundaries.
make a beautiful api. servers, clients. http, websockets. node, or web workers. popups, or iframes.
@e280/renraku"core" imports are universal.@e280/renraku/webimports are for the web.@e280/renraku/nodeimports are for node.
type AliceFns = typeof aliceFns
const aliceFns = {
async hello() {
return "world"
},
async sum(a: number, b: number) {
return a + b
},
nesty: {
is: {
async besty() {
return Math.random()
},
},
},
}import {makeEndpoint} from "@e280/renraku"
const aliceEndpoint = makeEndpoint(aliceFns)
await aliceEndpoint([["sum"], 1, 2])
// {ok: true, value: 3}import {makeRemote} from "@e280/renraku"
const remote = makeRemote<AliceFns>(aliceEndpoint)
await remote.hello()
// "world"
await remote.sum(1, 2)
// 3
await remote.nesty.is.besty()
// 0.103639826821733- introducing bob.
type BobFns = typeof bobFns const bobFns = { async bingus() { return 123 } } const bobEndpoint = makeEndpoint(bobFns)
- alice and bob each get their own messenger.
import {Messenger} from "@e280/renraku" const alice = new Messenger<BobFns>(aliceEndpoint) const bob = new Messenger<AliceFns>(bobEndpoint) alice.onSend(bob.recv) bob.onSend(alice.recv)
- alice and bob can call each other's fns.
// alice talks to bob. await alice.remote.bingus() // 123 // bob talks to alice. await bob.remote.hello() // "world"
- security precaution: by default, renraku endpoints conceal error info from clients.
- they see a generic
RemoteError: an error occurred (details not exposed, renraku security precaution)
- they see a generic
- renraku will expose error details from
ExposedErrorinstances:import {ExposedError} from "@e280/renraku" throw new ExposedError("bingus") // clients would see "RemoteError: ExposedError: bingus"
makeEndpointoptionexposeAllErrorsexposes error info to the client.// danger mode, but great for debugging web workers. makeEndpoint(aliceFns, {exposeAllErrors: true})
import {createServer} from "node:http"
import {makeEndpoint} from "@e280/renraku"
import {httpListener} from "@e280/renraku/node"
createServer(httpListener(makeEndpoint(aliceFns)))
.listen(8080)import {httpRemote} from "@e280/renraku"
const remote = httpRemote<AliceFns>("http://localhost:8080")
await remote.hello()
// "world"import {createServer} from "node:http"
import {websockets} from "@e280/renraku/node"
import {wire, connect, Messenger} from "@e280/renraku"
createServer()
.on("upgrade", websockets(async websocket => {
const {connection, messenger} = wire({
connection: await connect(websocket),
messenger: new Messenger<BobFns>(aliceEndpoint),
})
await messenger.remote.bingus()
// 123
// automatic ping time stats
connection.rtt.latest // 81
connection.rtt.average // 84
// handle connection closed
connection.onClose(() => console.log("closed"))
// close the connection yourself
connection.close()
}))
.listen(8080)import {wire, connect, Messenger} from "@e280/renraku"
const {connection, messenger} = wire({
messenger: new Messenger<AliceFns>(bobEndpoint),
connection: await connect(new WebSocket("ws://localhost:8080")),
})
await messenger.remote.hello()
// "world"portals bond messengers to message ports.
- hostside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, acceptWorkerPort} from "@e280/renraku/web" const {remote} = makePortal({ autoTransfer, messenger: new Messenger<BobFns>(aliceEndpoint), port: await acceptWorkerPort( new Worker("./my-worker.bundle.min.js", {type: "module"}) ), }) await remote.bingus() // 123
acceptWorkerPort's job is to do a postMessage connection handshake with the worker, and obtain a MessagePort.autoTransferfrom renraku-web, auto-transfers web transferables like ArrayBuffer, OffscreenCanvas, stuff like that. transferables on mdn. alternatively write your own transfer fn, or usenoTransferfrom renraku-core to opt-out of transfers.
- workerside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, offerWorkerPort} from "@e280/renraku/web" const {remote} = makePortal({ autoTransfer, port: await offerWorkerPort(), messenger: new Messenger<AliceFns>(bobEndpoint), }) await remote.hello() // "world"
- hostside.
import {Worker} from "node:worker_threads" import {makePortal, Messenger} from "@e280/renraku" import {nodeAutoTransfer, nodeAcceptWorkerPort} from "@e280/renraku/node" const {remote} = makePortal({ autoTransfer: nodeAutoTransfer, messenger: new Messenger<BobFns>(aliceEndpoint), port: await nodeAcceptWorkerPort(new Worker("./my-worker.js")), }) await remote.bingus() // 123
- workerside.
import {makePortal, Messenger} from "@e280/renraku" import {nodeAutoTransfer, nodeOfferWorkerPort} from "@e280/renraku/node" const {remote} = makePortal({ autoTransfer: nodeAutoTransfer, port: await nodeOfferWorkerPort(), messenger: new Messenger<AliceFns>(bobEndpoint), }) await remote.hello() // "world"
- parentside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, acceptWindowPort} from "@e280/renraku/web" const iframe = document.createElement("iframe") iframe.src = "http://localhost:8080/iframe" document.body.append(iframe) const {remote} = makePortal({ autoTransfer, messenger: new Messenger<BobFns>(aliceEndpoint), port: await acceptWindowPort({ topic: "example", from: iframe.contentWindow!, origin: "http://localhost:8080", }), }) await remote.bingus() // 123
- iframeside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, offerWindowPort} from "@e280/renraku/web" const {remote} = makePortal({ autoTransfer, messenger: new Messenger<AliceFns>(bobEndpoint), port: await offerWindowPort({ topic: "example", to: window.parent, origin: "http://localhost:8080", }), }) await remote.hello() // "world"
- openerside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, acceptWindowPort} from "@e280/renraku/web" const popup = window.open("http://localhost:8080/popup") if (!popup) throw new Error("popup blocked") const {remote} = makePortal({ autoTransfer, messenger: new Messenger<BobFns>(aliceEndpoint), port: await acceptWindowPort({ topic: "example", from: popup, origin: "http://localhost:8080", }), }) await remote.bingus() // 123
- popupside.
import {makePortal, Messenger} from "@e280/renraku" import {autoTransfer, offerWindowPort} from "@e280/renraku/web" const {remote} = makePortal({ autoTransfer, messenger: new Messenger<AliceFns>(bobEndpoint), port: await offerWindowPort({ topic: "example", to: window.opener!, origin: "http://localhost:8080", }), }) await remote.hello() // "world"