Subscriptions
tRPC v11 uses async iterables for subscriptions. This library natively supports them over MessagePort, giving you real-time data streaming between Electron processes.
Defining a Subscription
Section titled “Defining a Subscription”Use t.procedure.subscription() to define a subscription that yields values over time:
import { initTRPC } from '@trpc/server';import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({ onTimer: t.procedure .input(z.object({ intervalMs: z.number().min(100) })) .subscription(async function* ({ input, signal }) { let count = 0; while (!signal.aborted) { yield { count: count++, timestamp: new Date() }; await new Promise((resolve) => setTimeout(resolve, input.intervalMs)); } }),});Subscribing from the Renderer
Section titled “Subscribing from the Renderer”import { createTRPCClient } from '@trpc/client';import { portLink, getPort } from 'electron-messageport-trpc/renderer';import type { AppRouter } from '../main/router';
const trpc = createTRPCClient<AppRouter>({ links: [portLink({ port: getPort() })],});
// Subscribe and iterate over valuesconst subscription = trpc.onTimer.subscribe( { intervalMs: 1000 }, { onStarted() { console.log('Subscription started'); }, onData(value) { // value is typed as { count: number; timestamp: Date } console.log(`Tick #${value.count} at ${value.timestamp}`); }, onError(err) { console.error('Subscription error:', err); }, onStopped() { console.log('Subscription stopped by the server'); }, onComplete() { console.log('Subscription ended'); }, },);
// Later, unsubscribe to stop the streamsubscription.unsubscribe();How It Works Under the Hood
Section titled “How It Works Under the Hood”- The client sends a
{ kind: 'request', method: 'subscription', ... }message. - The server calls the procedure and receives an async iterable.
- The server sends
{ kind: 'result', type: 'started' }. - For each yielded value, the server sends
{ kind: 'result', type: 'data', data }. - When the client calls
unsubscribe(), it sends{ kind: 'subscription.stop', id }. - The server aborts the
AbortController, which signals the async generator to stop. - If the iterable completes naturally, the server sends
{ kind: 'result', type: 'stopped' }.
Error Handling
Section titled “Error Handling”If the subscription procedure throws an error, the server sends a { kind: 'error' } message with the tRPC error shape. The client’s onError callback receives a TRPCClientError instance.
Lifecycle and Cleanup
Section titled “Lifecycle and Cleanup”- Client unsubscribes: sends
subscription.stop, server aborts theAbortController - Port closes: the server’s
closeevent handler aborts all active subscriptions destroy()called: same as port close — all subscriptions are aborted and the port is closed