Skip to content

NeilTheFisher/orpc-json-diff

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

orpc-json-diff

npm version

Lightweight JSON patching for oRPC streaming responses. Only send deltas, not whole objects.

Motivation

When streaming large JSON objects that update frequently, re-sending the entire payload each time is wasteful. This library compresses event streams into incremental patches so only changes are transmitted, making it ideal for dashboards, state sync, and collaborative apps.

For example, instead of handling many per-field socket events:

socket.on("data-update", (newData) => updateData(newData));
socket.on("data-nested-field-update", (newField) => updateNested(newField));

You can stream the whole object once and let the client apply diffs:

for await (const newData of await orpc.data.live()) {
  doSomethingWith(newData);
}

Install

bun add orpc-json-diff
pnpm add orpc-json-diff
npm i orpc-json-diff

Server Usage

import { os, EventPublisher } from "@orpc/server";
import { RPCHandler } from "@orpc/server/fetch";
import { JsonDiffPlugin } from "orpc-json-diff/server";

interface ORPCMetadata {
  jsonDiff?: boolean;
}

const pub = new EventPublisher<{ update: object }>();

const router = os.$meta<ORPCMetadata>({}).router({
  live: os.meta({ jsonDiff: true }).handler(async function* ({ signal }) {
    yield await getInitial();
    for await (const update of pub.subscribe("update", { signal })) {
      yield update;
    }
  }),
});

// Add the plugin to your RPC handler, applying the JSON diffing to every event iterator.
const handler = new RPCHandler(router, {
  plugins: [new JsonDiffPlugin()],
});

Client Usage

import { RPCLink } from "@orpc/client/fetch";
import { JsonDiffPlugin } from "orpc-json-diff/client";

// Add the plugin to your RPC link
const rpcLink = new RPCLink({
  url: `${window.location.origin}/rpc`,
  plugins: [new JsonDiffPlugin()],
});

Behavior

  • First event: { patch: [], data: {...} } (full object)
  • Subsequent events: { patch: [...] } (incremental changes)
  • Client plugin reconstructs full object automatically

Enabling JSON Diff

By default, the plugin is disabled and requires opt-in per procedure using metadata:

interface ORPCMetadata {
  jsonDiff?: boolean;
}

const base = os.$meta<ORPCMetadata>({});

const router = base.router({
  myStream: base
    .meta({ jsonDiff: true }) // Enable JSON diff for this procedure
    .handler(async function* () {
      // ...
    }),
});

Alternatively, enable globally for all procedures:

const handler = new RPCHandler(router, {
  plugins: [
    new JsonDiffPlugin({
      include: true, // Enable for all procedures
    }),
  ],
});

You can also use a predicate to filter:

const handler = new RPCHandler(router, {
  plugins: [
    new JsonDiffPlugin({
      include: (options) => options.path.includes("stream"),
    }),
  ],
});

Procedure metadata takes priority over the plugin options.

Note

  • OpenAPI generation may not yet reflect streaming diffs in schemas.

About

Apply json patches for oRPC event iterators to save bandwidth. Inspired by [trpc-live's json diff recommendation](https://github.com/strblr/trpc-live#json-diff)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published