Skip to content
e280Public

Repository files navigation

連絡
R·E·N·R·A·K·U

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@next

renraku 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/web imports are for the web.
  • @e280/renraku/node imports are for node.

⛩️ renraku provides composable primitives.

🍙 renraku is about async fns.

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()
      },
    },
  },
}

🍙 endpoints make fns json-callable.

import {makeEndpoint} from "@e280/renraku"

const aliceEndpoint = makeEndpoint(aliceFns)

await aliceEndpoint([["sum"], 1, 2])
  // {ok: true, value: 3}

🍙 remotes make endpoints beautiful 🌟

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

🍙 messengers enable bidirectionality.

  • 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"

🍙 fns can throw errors.

  • 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)
  • renraku will expose error details from ExposedError instances:
    import {ExposedError} from "@e280/renraku"
    
    throw new ExposedError("bingus")
      // clients would see "RemoteError: ExposedError: bingus"
  • makeEndpoint option exposeAllErrors exposes error info to the client.
    // danger mode, but great for debugging web workers.
    makeEndpoint(aliceFns, {exposeAllErrors: true})

⛩️ renraku over http.

🍵 serverside (node).

import {createServer} from "node:http"
import {makeEndpoint} from "@e280/renraku"
import {httpListener} from "@e280/renraku/node"

createServer(httpListener(makeEndpoint(aliceFns)))
  .listen(8080)

🍵 clientside (web, node).

import {httpRemote} from "@e280/renraku"

const remote = httpRemote<AliceFns>("http://localhost:8080")

await remote.hello()
  // "world"

⛩️ renraku over websockets.

🎏 serverside (node).

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)

🎏 clientside (web, node).

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"

⛩️ renraku portals.

portals bond messengers to message ports.

🌀 web workers.

  • 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.
    • autoTransfer from renraku-web, auto-transfers web transferables like ArrayBuffer, OffscreenCanvas, stuff like that. transferables on mdn. alternatively write your own transfer fn, or use noTransfer from 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"

🌀 node workers.

  • 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"

🌀 iframes.

  • 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"

🌀 popups.

  • 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"



🧑‍💻 https://e280.org/

Releases

Used by

Contributors

Languages