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 🙏

© 2025 – Pkg Stats / Ryan Hefner

json-rpc-mixin

v0.0.5

Published

Expose the public methods of your class as JSON-RPC methods over HTTP or WebSockets

Downloads

19

Readme

JSON-RPC Mixin (json-rpc-mixin)

This is a JSON-RPC mixin for Cloudflare Durable Objects (DO). It allows you to upgrade any DO to a JSON-RPC server over HTTP or WebSockets. It was originally designed to work with Cloudflare's Agent base class which manages WebSocket connections, but it can be used with any DO.

Features

  • Designed as a mixin for your existing Cloudflare DO classes with extra convenience if you are subclassing the Agent
  • Full JSON-RPC 2.0 spec compliance (batchs, notifications, errors, named/positional parameters, etc.)
  • Supports sending requests over HTTP and WebSockets to/from the browser, desktop, or any server
  • Handles JSON-RPC errors and responses acccording to the spec
  • High test coverage including all of the exmples from the spec
  • Support using Zod for method parameter validation as well as custom validation with correct error codes. If you throw a ZodError or a TypeError from inside a method, it will be returned as a JSON-RPC error message with code -32602 (invalid method params) and the message will include the list of ZodError issues in the case of ZodError.w2q saz
  • A convenience wrapper for the client that handles all boilerplate and round-trips over WebSockets using AgentClient

When to use this instead of the built-in Cloudflare RPC?

  • When you want to make RPC calls from anywhere: the browser, a desktop app, and/or some server not implemented in Cloudflare. The built-in Cloudflare RPC only supports RPC between Workers and DOs.
  • When you are using Agent and AgentClient for their WebSocket management functionality (reconnects, retries, etc.).
  • When you have an aversion to complexity and under-the-hood magic. The JSON-RPC spec is 2 pages printed with half that being examples and an error code table. The overview page alone of Cloudflare's RPC is 17 pages printed.
  • When you were going to create Zod or JSON schema anyway. Cloudflare's native RPC gives you effortless type feedback in your client-side code during development, but you need something like Zod or JSONSchema for runtime validation.
  • When you favor the human readability of on-the-wire JSON over the potential efficiency of a binary format.
  • When you want to use Postman or curl or other desktop tools to test your API.

When to use the Cloudflare RPC instead of this?

  • When you don't require RPC all the way to the browser/desktop.
  • When you know that you will never want to make RPC calls from a server that isn't implemented in Cloudflare.
  • When you want to send more data types over the wire. Clouflare's native RPC supports pretty much everything supported by the StructuredClone algorithm: Date, Set, Map, etc., and then some: ReadableStream, WritableStream, Request, Response, and...
  • When you want to send functions as method parameters (not sure why)... or at least appear to by magic.
  • When you want effortless TypeScript feedback during development in your client-side code. It uses higher-order types to generate client stubs. You can get the equivalent by exporting/importing types and/or Zod/JSON schema. That's a bit more work, but it has the advantage of also accomplishing runtime validation.
  • When you favor the the potential efficiency of a binary format over the human readability of on-the-wire JSON.

Installation

npm install json-rpc-mixin

Usage (server-side)

To use with the Cloudflare's Agent base class, you can do the following:

// In the file that defines your Worker and DO
import { JSONRPC } from 'json-rpc-mixin';
import { Agent, routeAgentRequest, Connection, ExecutionContext, WSMessage } from 'agents';

class MyClass extends Agent<Env> {

  // Optional, but if provided, it will be called for non-JSON-RPC requests (!url.pathname.endsWith('/jsonrpc'))
  async onRequest(request: Request): Promise<Response> {
    return new Response('Original onRequest was called', { status: 418 }); // 418 = I'm a teapot
  }
  
  // Optional, but if provided, it will be called for non-JSON-RPC messages (message not a string, 
  // message a string but not JSON, message is JSON but envelope doesn't have { type: 'jsonrpc' })
  async onMessage(connection: Connection, message: WSMessage): Promise<void> {
    connection.send('Original onMessage was called');
  }

  // subtract method for JSON-RPC spec examples
  subtract(...args: any): number {
    if (args.length === 1 && typeof args[0] === 'object') {
      // Named parameters
      const { minuend, subtrahend } = args[0];
      if (minuend && typeof minuend === 'number' && subtrahend && typeof subtrahend === 'number') {
        return minuend - subtrahend;
      }
    } else if (args.length === 2) {
      // Positional parameters
      if (typeof args[0] === 'number' && typeof args[1] === 'number') {
        return args[0] - args[1];
      }
    }
    // a TypeError thrown from inside a method triggers a JSON-RPC error code -32602 - Invalid method params
    throw new TypeError("Invalid parameters for subtract method");
    // Similarly, if you throw a ZodError from a method, it will be returned as a JSON-RPC error message
    // with code -32602 and the message will include the list of ZodError issues
  }

  // ... any other RPC methods you want to define
}

export const TestJsonRpcAgent = JSONRPC(MyClass);  // TestJsonRpcAgent is your DO class_name
// export const TestJsonRpcAgent = JSONRPC(MyClass, {
//   route: 'rpc',  // change the route from default '/jsonrpc' to '/rpc'
//   disallowedMethods: [ dontCallMe, dontCallMeEither ],  // change from default to your own
// });  

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return await routeAgentRequest(request, env)
  }
} satisfies ExportedHandler<Env>;

Options

  • route: The route to use for the JSON-RPC endpoint. Default is /jsonrpc.
  • disallowedMethods: An array of method names that are not allowed to be called. This is useful for preventing access to Typescript private/protected methods. The default list mirrors those disallowed by Cloudflare's RPC:
    [
      "fetch",
      "connect",
      "dup",
      "constructor",
      "alarm",
      "webSocketMessage",
      "webSocketClose",
      "webSocketError"
    ]

Usage (client-side)

Over HTTP

To call your subtract method over HTTP:

const response = await fetch("http://example.com/agents/testjsonrpcagent/specific-instance-name/jsonrpc", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "subtract",
    params: [42, 23],
    id: 1
  })
});
const response = await response.json();
console.log(response); // { jsonrpc: '2.0', result: 19, id: 1 }

Over WebSockets using AgentClient directly (not recommended)

To call your subtract method over WebSockets using AgentClient, you could do the following, but the recommended way is to use the JSONRPCAgentClient wrapper which handles all the boilerplate for you. That's shown in the next section.

import { AgentClient } from "agents/client";

// Connect to an Agent instance
const agentClient = new AgentClient({
  agent: "testjsonrpcagent",  // Name of your Agent class in kebab-case
  name: "specific-instance-name",  // Specific instance name
  host: window.location.host,  // Use the current host
});

// Wait until connection opens, then call the subtract method
agentClient.onopen = () => {
  agentClient.send(JSON.stringify({
    type: "jsonrpc",
    payload: {
      jsonrpc: "2.0",
      method: "subtract",
      params: [42, 23],
      id: 1,
    },
  }));
};

// Wait for response from the server
agentClient.onmessage = (event) => {
  console.log(event.data);  
  // { type: 'jsonrpc', payload: { jsonrpc: '2.0', result: 19, id: 1 }}
};

Over WebSockets using JSONRPCAgentClient (recommended)

From the client side, we provide a convenience wrapper, JSONRPCAgentClient, around AgentClient to handle request/response round trips and add the JSON-RPC wrapper as well as the expected WebSocket message envelope.

import { JSONRPCAgentClient } from "json-rpc-mixin";

const jsonRPCAgentClient = new JSONRPCAgentClient({
  agent: "testjsonrpcagent",
  name: "specific-instance-name",
  host: window.location.host,
  timeout: 1000,  // default 5000 = 5 seconds
  route: "jsonrpc",  // default 'jsonrpc'
});

// With it, you will be able to call subtract in any of the following ways:
const result = await jsonRPCAgentClient.call("subtract", [42, 23]);
const result = await jsonRPCAgentClient.subtract(42, 23);
const result = await jsonRPCAgentClient.subtract({ minuend: 42, subtrahend: 23 });
const result = await jsonRPCAgentClient.subtract({ subtrahend: 23, minuend: 42 });
// all return the same result
console.log(result);  // 19

Potential Gotchas

  • Typescript private/protected methods may be accessible. Depending upon your TypeScript version and compiler settings, private/protected methods may or may not be accessible at runtime via this[method] lookup. I strongly recommend that you use JavaScript native private methods (starting with #) for private methods.

    If you'd rather not do that, you can use the disallowedMethods option to explicitly disallow private/protected methods.

    There are two tests in the test script that are currently marked as skip because they fail sometimes when using TypeScript private/protected methods. Unmark those as skip if you want to test this behavior for your TypeScript version and compiler settings.

To-do

  • [ ] Confirm that Cloudflare native RPC still works when the JSONRPC mixin is applied. I'm pretty sure it will but I don't use it so I haven't tested it.
  • [ ] Document how to use it without Agent over HTTP
  • [ ] Document how to use it without Agent over WebSockets
  • [ ] Document how to use it with Zod
  • [ ] Show how to use it from JavaScript
  • [ ] Figure out and document how to use it with hono, express, and/or other frameworks outside of Cloudflare