Message ports
Serve calls over a MessagePort, a web Worker or a worker_threads port.
handler.messagePort(port) serves calls over a MessagePort, a web Worker or a worker_threads port. Messages are structured-cloned objects in the same format as WebSocket messages, so subscriptions, deferred values, aborts and connectionParams work as they do over wsLink.
// worker.ts
import { const parentPort: MessagePort | nullparentPort } from 'node:worker_threads';
if (!const parentPort: MessagePort | nullparentPort) throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('worker.ts runs in a worker thread');
createFetchHandler<{
health: BuildProcedure<RootDef<{}, {}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}, undefined>(opts: FetchHandlerOptions<{
health: BuildProcedure<RootDef<{}, {}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}, undefined>): FetchHandler<undefined>
createFetchHandler({
BaseHandlerOptions<{ health: BuildProcedure<RootDef<{}, {}>, "query", { output: string; errors: never; services: never; unmapped: never; }>; }>.router: {
health: BuildProcedure<RootDef<{}, {}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}
router,
createContext?: ((opts: CreateContextOpts<undefined>) => MaybePromise<{}>) | undefinedcreateContext: (opts: CreateContextOpts<undefined>opts) => ({
token: unknowntoken: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.info: {
readonly calls: ReadonlyArray<{
path: string;
type: ProcedureType;
}>;
readonly signal: AbortSignal;
readonly transport: Transport;
readonly connectionParams: Record<string, unknown> | undefined;
}
info.connectionParams: Record<string, unknown> | undefinedconnectionParams?.['token'],
transport: Transporttransport: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.info: {
readonly calls: ReadonlyArray<{
path: string;
type: ProcedureType;
}>;
readonly signal: AbortSignal;
readonly transport: Transport;
readonly connectionParams: Record<string, unknown> | undefined;
}
info.transport: Transporttransport, // 'messagePort'
}),
}).FetchHandler<undefined>.messagePort(port: MessagePortLike, opts?: {
request?: Request;
platform?: undefined;
} | undefined): void
messagePort(const parentPort: MessagePortparentPort);On the other side, experimental_messagePortLink sends calls to the port:
import {
function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient,
function experimental_messagePortLink<TOpts extends MessagePortLinkOptions>(opts: TOpts): SocketLink<{
form: FormOfOptions<TOpts>;
}>
experimental_messagePortLink,
function routerType<TRouter>(): RouterType<TRouter>routerType,
} from '@trpc12/trpc/client';
const const worker: Workerworker = new var Worker: new (scriptURL: string | URL, options?: WorkerOptions) => WorkerWorker(new var URL: new (url: string | URL, base?: string | URL) => URLURL('./worker.ts', import.meta.ImportMeta.url: stringurl), {
WorkerOptions.type?: WorkerType | undefinedtype: 'module',
});
const const client: TrpcClient<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}, {}, undefined, LinksTypes<...>>
client = createTrpcClient<RouterType<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient({
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, readonly [...]>.router: RouterType<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}>
router: routerType<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}>(): RouterType<...>
routerType<type AppRouter = {
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}
AppRouter>(),
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, readonly [...]>.links: readonly [SocketLink<{
form: JsonForm;
}>]
links: [experimental_messagePortLink<{
port: Worker;
}>(opts: {
port: Worker;
}): SocketLink<{
form: JsonForm;
}>
experimental_messagePortLink({ port: Workerport: const worker: Workerworker })],
});createContextgets the request passed ashandler.messagePort(port, { request }), or a placeholderhttp://localhost/request.- Bytes in streamed outputs pass as they are, without base64. Files as inputs aren't sent; the call fails with a
BAD_REQUEST. - Closing the port is up to you. Electron's
MessagePortMainand iframe windows work too.