"an api should just be a bunch of async functions, damn it"
— Chase Moskal, many years ago
💡 elegantly expose async functions as an api
🔌 http, websockets, postmessage, and more
🏛️ json-rpc 2.0
🌐 node and browser
🚚 transport agnostic toolkit
🛡️ handy little auth helpers
💻 an https://e280.org/ project
i've been using and sharpening this typescript implementation for many years.
⚠️ IMPORTANT
renraku is in the middle of an ongoing fat refactor!
[email protected]
is the most recent stable version, which has more features.
@e280/[email protected]
is the first of an unstable new line of wip development.
- install renraku into your project
npm i @e280/renraku
example.ts
— so you've got some async functionsexport const exampleFns = { async now() { return Date.now() }, async add(a: number, b: number) { return a + b }, nesty: { is: { besty: { async mul(a: number, b: number) { return a * b }, }, }, }, }
server.ts
— expose 'em via httpimport {exampleFns} from "./example.js" import {httpServer} from "@e280/renraku/node" await httpServer({ port: 8000, expose: () => exampleFns, })
client.ts
— make a clientside remote🪄 now you can magically call the functions on the clientsideimport {httpRemote} from "@e280/renraku" import type {exampleFns} from "./example.js" // ↑ // we actually only need the *type* here const example = httpRemote<typeof exampleFns>({ url: "http://localhost:8000/", })
await example.now() // 1723701145176 await example.add(2, 2) // 4 await example.nesty.is.besty.mul(2, 3) // 6
Note
for input validation, you should use zod or something.
- renraku provides the http stuff you need
await httpServer({ port: 8000, // 🆒 🆒 🆒 // ↓ ↓ ↓ expose: ({req, ip, headers}) => exampleFns, })
- you can stick a
LoggerTap
into many thingsimport {LoggerTap} from "@e280/renraku"
- use it to enable logging on the serverside
await httpServer({ port: 8000, expose: () => exampleFns, // ✅ logging enabled tap: new LoggerTap(), })
- use it to enable logging on the clientside
const example = httpRemote<typeof exampleFns>({ url: "http://localhost:8000/", // ✅ logging enabled tap: new LoggerTap(), })
maxRequestBytes
prevents gigantic requests from dumping on you10_000_000
(10 megabytes) is the default
timeout
kills a request if it goes stale60_000
(60 seconds) is the default
- you could set these to
Infinity
if you've lost your mind
secure
andauthorize
do not support arbitrary nesting, so you have to pass them a flat object of async functions- use the
secure
function to section off parts of your api that require authimport {secure} from "renraku" export const exampleFns = { // declaring this area requires auth // | // | auth can be any type you want // ↓ ↓ math: secure(async(auth: string) => { // here you can do any auth work you need if (auth !== "hello") throw new Error("auth error: did not receive warm greeting") return { async sum(a: number, b: number) { return a + b }, } }), }
- you see,
secure
merely adds your initial auth parameter as a required argument to each function// auth param // ↓ await example.math.sum("hello", 1, 2)
- you see,
- use the
authorize
function on the clientside to provide the auth param upfrontimport {authorize} from "renraku" // (the secured area) (async getter for auth param) // ↓ ↓ const math = authorize(example.math, async() => "hello") // it's an async function so you could refresh // tokens or whatever // now the auth is magically provided for each call await math.sum(1, 2)
- but why an async getter function?
ah, well that's because it's a perfect opportunity for you to refresh your tokens or what-have-you.
the getter is called for each api call.
- but why an async getter function?
- do you need renraku to operate over another medium, like carrier pigeons?
- well, you're in luck, because it's easy to setup your own transport medium
- so let's assume you have a group of async functions called
myFunctions
- first, let's do your "serverside":
import {endpoint} from "@e280/renraku" import {myFunctions} from "./my-functions.js" // create a renraku endpoint for your functions const myEndpoint = endpoint({fns: myFunctions}) // create your wacky carrier pigeon server const pigeons = new CarrierPigeonServer({ handleIncomingPigeon: async incoming => { // you parse your incoming string as json const request = JSON.parse(incoming) // execute the api call on your renraku endpoint const response = await myEndpoint(request) // you send back the json response as a string pigeons.send(JSON.stringify(response)) }, })
- second, let's do your "clientside":
import {remote} from "@e280/renraku" import type {myFunctions} from "./my-functions.js" // create your wacky carrier pigeon client const pigeons = new CarrierPigeonClient() // create a remote with the type of your async functions const myRemote = remote<typeof myFunctions>({ // your carrier pigeon implementation needs only to // transmit the json request object, and return then json response object endpoint: async request => await carrierPigeon.send(request) }) // usage await myRemote.math.sum(1, 2) // 3
💖 free and open source just for you
🌟 reward us with github stars
💻 join us at e280 if you're a real one