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

electron-messageport-trpc

v0.4.2

Published

MessagePort-based tRPC v11 transport for Electron

Readme

electron-messageport-trpc

MessagePort transport for tRPC v11 in Electron.

This package connects Electron MessagePort channels to normal tRPC v11 clients and routers. It keeps application code close to standard tRPC while using MessagePort connections that can be handed to different Electron processes.

Features

  • tRPC v11 over MessagePort: queries, mutations, subscriptions, inference, middleware, and errors across Electron processes
  • Main-to-utility and renderer-to-utility topologies for offloading work

Installation

pnpm add electron-messageport-trpc @trpc/server @trpc/client
npm install electron-messageport-trpc @trpc/server @trpc/client

Requirements

| Dependency | Version | |---|---| | Electron | >= 22 | | Node.js | >= 20 | | @trpc/server | ^11.17.0 | | @trpc/client | ^11.17.0 |

Quick Start: Renderer to Main

This is the standard setup: the renderer creates a tRPC client and calls a router running in the Electron main process.

1. Define a router in the main process

// electron/router.ts
import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

export const appRouter = t.router({
  greet: t.procedure.query(() => {
    return { message: 'Hello from main' };
  }),

  sendMessage: t.procedure.mutation(() => {
    return { ok: true };
  }),
});

export type AppRouter = typeof appRouter;

2. Attach the router to a BrowserWindow

// electron/main.ts
import path from 'node:path';
import { app, BrowserWindow } from 'electron';
import { createWindowMessagePortHandler } from 'electron-messageport-trpc/main';
import { appRouter } from './router';

let trpcHandler: { destroy(): void } | undefined;

async function createWindow() {
  const win = new BrowserWindow({
    width: 900,
    height: 700,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  trpcHandler = createWindowMessagePortHandler({
    router: appRouter,
    windows: [win],
    createContext: async ({ window }) => ({ window }),
  });

  await win.loadFile('index.html');
}

app.whenReady().then(createWindow);

app.on('before-quit', () => {
  trpcHandler?.destroy();
});

createWindowMessagePortHandler() creates a fresh MessagePort for each window load, transfers one side to the renderer, and attaches the other side to your router. Pass every window that should use this router, or create one handler per window when windows are created dynamically.

3. Expose the port receiver from preload

// preload/index.ts
import { exposePortReceiver } from 'electron-messageport-trpc/preload';

exposePortReceiver();

The preload script receives the transferred port from Electron and forwards it to the renderer main world. This is the Electron-specific handoff this package wraps so the renderer can use a normal tRPC client link.

4. Create the tRPC client in the renderer

// src/trpc.ts
import { createTRPCClient } from '@trpc/client';
import { getPort, portLink } from 'electron-messageport-trpc/renderer';
import type { AppRouter } from '../electron/router';

export const trpc = createTRPCClient<AppRouter>({
  links: [portLink({ port: getPort() })],
});
const greeting = await trpc.greet.query();
console.log(greeting.message);

await trpc.sendMessage.mutate();

Entry Points

| Entry point | Use it from | Purpose | |---|---|---| | electron-messageport-trpc/main | Electron main process | Attach routers, create main-side clients, broker ports | | electron-messageport-trpc/preload | Preload script | Receive and forward transferred renderer ports | | electron-messageport-trpc/renderer | Renderer process | Create a tRPC client over the received port | | electron-messageport-trpc/utility | Electron utility process | Attach a router to process.parentPort |

Common imports:

import {
  createPortBroker,
  createPortHandler,
  createWindowMessagePortHandler,
  mainPortLink,
} from 'electron-messageport-trpc/main';
import { exposePortReceiver } from 'electron-messageport-trpc/preload';
import { getPort, portLink } from 'electron-messageport-trpc/renderer';
import { createParentPortHandler } from 'electron-messageport-trpc/utility';

Which API Should I Use?

| API | Use when | |---|---| | createWindowMessagePortHandler | A renderer window calls procedures on a main-process router. This is the default choice. | | portLink | The renderer creates a tRPC client from the port received by getPort(). | | mainPortLink | The main process creates a tRPC client over a MessagePortMain, usually to call a utility process. | | createParentPortHandler | A utility process exposes a tRPC router on process.parentPort. | | createPortBroker | Main only brokers a port between renderer and utility, keeping main out of the request path. | | createPortHandler | Low-level helper for attaching a router to an existing protocol-dedicated port manually. |

Lifecycle

  • Call handler.destroy() when tearing down a custom handler or before app quit if you keep a long-lived handler reference.
  • Destroying a handler closes the port and aborts active subscriptions.
  • createWindowMessagePortHandler() also cleans up a window's active port when that window closes.

Current Constraints

  • Treat each MessagePort passed to this package as dedicated to the electron-messageport-trpc protocol.
  • Do not use the same MessagePort for app-defined postMessage() traffic.
  • Messages that do not match the electron-messageport-trpc protocol are discarded.
  • Inputs and results are sent through MessagePort.postMessage() after any configured tRPC transformer runs. Values that still cannot be cloned by the platform Structured Clone algorithm reject on the client side.
  • Blob support is not provided by the transformer path. Use ArrayBuffer or Uint8Array for binary payloads.

Examples and Docs

License

MIT