kkrpc
v0.0.12
Published
[![NPM Version](https://img.shields.io/npm/v/kkrpc)](https://www.npmjs.com/package/kkrpc) ![JSR Version](https://img.shields.io/jsr/v/kunkun/kkrpc) ![GitHub last commit](https://img.shields.io/github/last-commit/kunkunsh/kkrpc)
Downloads
1,287
Readme
kkrpc
A TypeScript-first RPC library that enables seamless bi-directional communication between processes. Call remote functions as if they were local, with full TypeScript type safety and autocompletion support.
Supported Environments
- stdio: RPC over stdio between any combinations of Node.js, Deno, Bun processes
- web: RPC over
postMessage
API and message channel between browser main thread and web workers, or main thread and iframe- Web Worker API (web standard) is also supported in Deno and Bun, the main thread can call functions in worker and vice versa.
- http: RPC over HTTP like tRPC
- supports any HTTP server (e.g. hono, bun, nodejs http, express, fastify, deno, etc.)
- WebSocket: RPC over WebSocket
The core of kkrpc design is in RPCChannel
and IoInterface
.
RPCChannel
is the bidirectional RPC channelLocalAPI
is the APIs to be exposed to the other side of the channelRemoteAPI
is the APIs exposed by the other side of the channel, and callable on the local siderpc.getAPI()
returns an object that isRemoteAPI
typed, and is callable on the local side like a normal local function call.IoInterface
is the interface for implementing the IO for different environments. The implementations are called adapters.- For example, for a Node process to communicate with a Deno process, we need
NodeIo
andDenoIo
adapters which implementsIoInterface
. They share the same stdio pipe (stdin/stdout
). - In web, we have
WorkerChildIO
andWorkerParentIO
adapters for web worker,IframeParentIO
andIframeChildIO
adapters for iframe.
- For example, for a Node process to communicate with a Deno process, we need
In browser, import from
kkrpc/browser
instead ofkkrpc
, Deno adapter uses node:buffer which doesn't work in browser.
interface IoInterface {
name: string
read(): Promise<Buffer | Uint8Array | string | null> // Reads input
write(data: string): Promise<void> // Writes output
}
class RPCChannel<
LocalAPI extends Record<string, any>,
RemoteAPI extends Record<string, any>,
Io extends IoInterface = IoInterface
> {}
Examplesr
Below are simple examples.
Stdio Example
import { NodeIo, RPCChannel } from "kkrpc"
import { apiMethods } from "./api.ts"
const stdio = new NodeIo(process.stdin, process.stdout)
const child = new RPCChannel(stdio, { expose: apiMethods })
import { spawn } from "child_process"
const worker = spawn("bun", ["scripts/node-api.ts"])
const io = new NodeIo(worker.stdout, worker.stdin)
const parent = new RPCChannel<{}, API>(io)
const api = parent.getAPI()
expect(await api.add(1, 2)).toBe(3)
Web Worker Example
import { RPCChannel, WorkerChildIO, type DestroyableIoInterface } from "kkrpc"
const worker = new Worker(new URL("./scripts/worker.ts", import.meta.url).href, { type: "module" })
const io = new WorkerChildIO(worker)
const rpc = new RPCChannel<API, API, DestroyableIoInterface>(io, { expose: apiMethods })
const api = rpc.getAPI()
expect(await api.add(1, 2)).toBe(3)
import { RPCChannel, WorkerParentIO, type DestroyableIoInterface } from "kkrpc"
const io: DestroyableIoInterface = new WorkerChildIO()
const rpc = new RPCChannel<API, API, DestroyableIoInterface>(io, { expose: apiMethods })
const api = rpc.getAPI()
const sum = await api.add(1, 2)
expect(sum).toBe(3)
HTTP Example
Codesandbox: https://codesandbox.io/p/live/4a349334-0b04-4352-89f9-cf1955553ae7
api.ts
Define API type and implementation.
export type API = {
echo: (message: string) => Promise<string>
add: (a: number, b: number) => Promise<number>
}
export const api: API = {
echo: (message) => {
return Promise.resolve(message)
},
add: (a, b) => {
return Promise.resolve(a + b)
}
}
server.ts
Server only requires a one-time setup, then it won't need to be touched again.
All the API implementation is in api.ts
.
import { HTTPServerIO, RPCChannel } from "kkrpc"
import { api, type API } from "./api"
const serverIO = new HTTPServerIO()
const serverRPC = new RPCChannel<API, API>(serverIO, { expose: api })
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url)
if (url.pathname === "/rpc") {
const res = await serverIO.handleRequest(await req.text())
return new Response(res, {
headers: { "Content-Type": "application/json" }
})
}
return new Response("Not found", { status: 404 })
}
})
console.log(`Start server on port: ${server.port}`)
client.ts
import { HTTPClientIO, RPCChannel } from "kkrpc"
import { api, type API } from "./api"
const clientIO = new HTTPClientIO({
url: "http://localhost:3000/rpc"
})
const clientRPC = new RPCChannel<{}, API>(clientIO, { expose: api })
const clientAPI = clientRPC.getAPI()
const echoResponse = await clientAPI.echo("hello")
console.log("echoResponse", echoResponse)
const sum = await clientAPI.add(2, 3)
console.log("Sum: ", sum)