npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codehz/ts-rpc

v0.1.0

Published

Transport-agnostic、双向对象能力的 TypeScript RPC 库。

Readme

ts-rpc

Transport-agnostic、双向对象能力的 TypeScript RPC 库。

  • 传输无关:只依赖一个 RpcTransport 接口(send / receive),可接驳 WebSocket、MessagePort、Worker、HTTP 或任意自定义通道
  • 双向对象引用:两端各自暴露 main 对象,可互相调用方法、传递函数、传递 RpcTarget 实例,引用自动回收
  • Promise pipelining:可直接链式调用尚未返回的远端 capability,多级调用只占用一个往返窗口
  • 流桥接ReadableStream / WritableStream 跨 RPC 透传,writer.write(chunk) 的 Promise 在对端实际完成时才 resolve
  • 构建发布:使用 tsdown 生成 ESM + .d.ts + sourcemap,运行时 import 时自动注入流桥接钩子
  • 运行时Bun

安装

bun install

如需生成发布产物:

bun run build

快速开始

import { RpcSession, RpcTarget, createTransportPair } from "ts-rpc";

// 1) 定义服务端 API:继承 RpcTarget 让对象按引用传递
class Calculator extends RpcTarget {
  add(a: number, b: number) { return a + b; }
  async slowAdd(a: number, b: number): Promise<number> { return a + b; }
}

// 2) 构造一对互连的 RpcTransport(这里用内存对,实际可换成 WebSocket 等)
const [clientTransport, serverTransport] = createTransportPair();

// 3) 各建一个 RpcSession,服务端传入 main 对象
const server = new RpcSession(serverTransport, new Calculator());
const client = new RpcSession(clientTransport);

// 4) 客户端拿到对端 main 的代理 stub,像本地一样调用
const remote = client.getRemoteMain<Calculator>();
console.log(await remote.add(2, 3));       // 5
console.log(await remote.slowAdd(40, 2));   // 42

Promise pipelining

远端方法返回 RpcTarget(或函数 capability)时,无需先等待中间结果:

class Profile extends RpcTarget {
  getName() { return "Ada"; }
}

class Users extends RpcTarget {
  async getProfile(): Promise<Profile> {
    return new Profile();
  }
}

const remote = client.getRemoteMain<Users>();
const name = await remote.getProfile().getName();

getName 调用会在 getProfile 返回前立即发送。每次调用结果都会成为一个隐式 export, 因此未决结果与普通 capability 共用同一套 import/export 引用计数和 release 回收机制。 当前原型只支持 capability 方法调用;普通值属性投影、stream pipelining,以及把 未 awaitRpcPromise 直接作为参数传递均不支持。

RpcPromise 支持显式资源管理。若不再需要通过某个未决结果继续 pipeline,可使用 using 或调用其 [Symbol.dispose]();这只释放未来寻址能力,不会取消已发送的调用, 也不会阻止该 Promise 自身最终 resolve/reject。

核心概念

RpcTransport — 唯一的传输抽象

interface RpcTransport {
  send(message: unknown): void | Promise<void>;
  receive(): Promise<unknown>;
  abort?(reason: unknown): void;
}

消息是不透明对象(内部为 expression tree,可被 JSON.stringifystructuredClone)。实现 RpcTransport 时自由选择底层编码:

RpcTransport 必须可靠且保持 FIFO:多次 send() 的消息需按调用顺序被对端 receive() 取得。Session 不会等待前一次异步 send() 完成后再发送下一条消息。

  • WebSocket 文本帧:用 createJsonStringTransport 包装 ws.send / ws.onmessage
  • MessagePort / Worker:直接 postMessage / event.data,structuredClone 透传
  • 自定义二进制:CBOR / MessagePack 等由用户加持

RpcTarget — 按引用传递

继承 RpcTarget 的实例、任意函数、内置 ReadableStream / WritableStream 才按引用传递;DateErrorUint8Array 等走值拷贝。Map / Set 不支持,需先转为普通对象/数组。

双向调用与回调

两端都能向对端传递对象和函数,对端可反向调用:

class ServerAPI extends RpcTarget {
  // 接收客户端传入的函数 stub,在服务端反向调用
  async callFn(fn: (x: number) => number, x: number): Promise<number> {
    return fn(x);
  }
}

const server = new RpcSession(serverTransport, new ServerAPI());
const client = new RpcSession(clientTransport);
const remote = client.getRemoteMain<ServerAPI>();

// 客户端把本地函数传过去,服务端调用它
const result = await remote.callFn((x: number) => x * 10, 5);
console.log(result); // 50

class FileReceiver extends RpcTarget {
  async save(stream: WritableStream<string>): Promise<void> {
    const writer = stream.getWriter();
    await writer.write("hello");
    await writer.write("world");
    await writer.close();
  }
}

// 客户端创建本地 WritableStream,传给服务端写入
const localStream = new WritableStream<string>({
  write(chunk) { console.log("收到:", chunk); },
});
const remote = client.getRemoteMain<FileReceiver>();
await remote.save(localStream);
// 输出:收到: hello / 收到: world

writer.write(chunk) 的 Promise 在对端实际写入/读取完成时才 resolve——真正的端到端 ack。chunk 中可包含 RpcTarget 对象或函数,引用由 FinalizationRegistry 在 GC 时自动回收。

API 一览

| 导出 | 用途 | |------|------| | RpcTransport | 传输接口(用户实现) | | RpcSession | 在 RpcTransport 上建立双向 RPC 会话 | | RpcTarget | 标记基类,继承后按引用传递 | | RpcStub | 对象代理,new RpcStub(obj) 包装本地对象 | | RpcPromise | 可 await、同时可继续调用未决 capability 的 Promise-like 类型 | | serialize / deserialize | 纯值版本(无会话时往返简单数据) | | encodeMessage / decodeMessage / createJsonStringTransport | 接驳字符串通道 | | createTransportPair | 测试用内存 RpcTransport 对 | | RpcAbortError | 会话中止错误 |

运行与测试

bun run build                  # 生成 dist/ 发布产物(tsdown)
bun run build:watch            # 监听并持续构建
bun test                       # 运行全部测试
bun test test/index.test.ts    # 运行指定测试文件
bun run src/index.ts           # 运行入口
bunx tsc --noEmit              # 类型检查

发布产物

构建后会生成:

  • dist/index.js:ESM 入口
  • dist/index.d.ts:类型声明
  • dist/index.js.map:源码映射

package.jsonexports / module / types 已指向 dist,发布时只包含 dist/

许可

MIT