Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: trpc 11.x support (resolves #184) #194

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/electron-trpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
},
"devDependencies": {
"@tanstack/react-query": "^5.32.1",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"@trpc/client": "next",
"@trpc/server": "next",
"@types/debug": "^4.1.12",
"@types/node": "^20.12.8",
"@vitest/coverage-v8": "^1.6.0",
Expand All @@ -51,8 +51,8 @@
"zod": "^3.23.6"
},
"peerDependencies": {
"@trpc/client": ">10.0.0",
"@trpc/server": ">10.0.0",
"@trpc/client": ">=11.0.0",
"@trpc/server": ">=11.0.0",
"electron": ">19.0.0"
},
"dependencies": {
Expand Down
62 changes: 0 additions & 62 deletions packages/electron-trpc/src/main/__tests__/utils.test.ts

This file was deleted.

17 changes: 9 additions & 8 deletions packages/electron-trpc/src/main/createIPCHandler.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import type { AnyRouter, inferRouterContext } from '@trpc/server';
import type { AnyTRPCRouter, inferRouterContext } from '@trpc/server';
import { Unsubscribable } from '@trpc/server/observable';
import { ipcMain } from 'electron';
import type { BrowserWindow, IpcMainEvent } from 'electron';
import { handleIPCMessage } from './handleIPCMessage';
import { CreateContextOptions } from './types';

import { ELECTRON_TRPC_CHANNEL } from '../constants';
import { ETRPCRequest } from '../types';
import { Unsubscribable } from '@trpc/server/observable';
import { handleIPCMessage } from './handleIPCMessage';
import { CreateContextOptions } from './types';
import debugFactory from 'debug';

const debug = debugFactory('electron-trpc:main:IPCHandler');

type Awaitable<T> = T | Promise<T>;
type MaybePromise<TType> = Promise<TType> | TType;

const getInternalId = (event: IpcMainEvent, request: ETRPCRequest) => {
const messageId = request.method === 'request' ? request.operation.id : request.id;
return `${event.sender.id}-${event.senderFrame.routingId}:${messageId}`;
};

class IPCHandler<TRouter extends AnyRouter> {
class IPCHandler<TRouter extends AnyTRPCRouter> {
#windows: BrowserWindow[] = [];
#subscriptions: Map<string, Unsubscribable> = new Map();

Expand All @@ -26,7 +27,7 @@ class IPCHandler<TRouter extends AnyRouter> {
router,
windows = [],
}: {
createContext?: (opts: CreateContextOptions) => Awaitable<inferRouterContext<TRouter>>;
createContext?: (opts: CreateContextOptions) => MaybePromise<inferRouterContext<TRouter>>;
router: TRouter;
windows?: BrowserWindow[];
}) {
Expand Down Expand Up @@ -97,7 +98,7 @@ class IPCHandler<TRouter extends AnyRouter> {
}
}

export const createIPCHandler = <TRouter extends AnyRouter>({
export const createIPCHandler = <TRouter extends AnyTRPCRouter>({
createContext,
router,
windows = [],
Expand Down
19 changes: 10 additions & 9 deletions packages/electron-trpc/src/main/handleIPCMessage.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { callProcedure, TRPCError } from '@trpc/server';
import type { AnyRouter, inferRouterContext } from '@trpc/server';
import { callTRPCProcedure, TRPCError, getErrorShape, getTRPCErrorFromUnknown } from '@trpc/server';
import type { AnyTRPCRouter, inferRouterContext } from '@trpc/server';
import type { TRPCResponseMessage } from '@trpc/server/rpc';
import type { IpcMainEvent } from 'electron';
import { isObservable, Unsubscribable } from '@trpc/server/observable';
import { transformTRPCResponse } from '@trpc/server/shared';
import { getTRPCErrorFromUnknown } from './utils';
import { transformTRPCResponse } from '@trpc/server';
import { CreateContextOptions } from './types';
import { ELECTRON_TRPC_CHANNEL } from '../constants';
import { ETRPCRequest } from '../types';
import debugFactory from 'debug';

const debug = debugFactory('electron-trpc:main:handleIPCMessage');

export async function handleIPCMessage<TRouter extends AnyRouter>({
export async function handleIPCMessage<TRouter extends AnyTRPCRouter>({
router,
createContext,
internalId,
Expand Down Expand Up @@ -51,11 +50,11 @@ export async function handleIPCMessage<TRouter extends AnyRouter>({
};

try {
const result = await callProcedure({
const result = await callTRPCProcedure({
ctx,
path,
procedures: router._def.procedures,
rawInput: input,
getRawInput: async () => input,
type,
});

Expand Down Expand Up @@ -91,7 +90,8 @@ export async function handleIPCMessage<TRouter extends AnyRouter>({
const error = getTRPCErrorFromUnknown(err);
respond({
id,
error: router.getErrorShape({
error: getErrorShape({
config: router._def._config,
error,
type,
path,
Expand All @@ -117,7 +117,8 @@ export async function handleIPCMessage<TRouter extends AnyRouter>({

return respond({
id,
error: router.getErrorShape({
error: getErrorShape({
config: router._def._config,
error,
type,
path,
Expand Down
33 changes: 0 additions & 33 deletions packages/electron-trpc/src/main/utils.ts

This file was deleted.

38 changes: 23 additions & 15 deletions packages/electron-trpc/src/renderer/__tests__/ipcLink.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { createTRPCProxyClient } from '@trpc/client';
import { createTRPCClient } from '@trpc/client';
import { initTRPC } from '@trpc/server';
import type { TRPCResponseMessage } from '@trpc/server/rpc';
import z from 'zod';
import type { RendererGlobalElectronTRPC } from '../../types';
import { ipcLink } from '../ipcLink';
import superjson from 'superjson';

const t = initTRPC.create();
const t = initTRPC.create({ transformer: superjson });
const router = t.router({
testQuery: t.procedure.query(() => 'query success'),
testMutation: t.procedure.input(z.string()).mutation(() => 'mutation success'),
Expand All @@ -18,7 +18,13 @@ const router = t.router({
};
}),
testInputs: t.procedure
.input(z.object({ str: z.string(), date: z.date(), bigint: z.bigint() }))
.input(
z.object({
str: z.string(),
date: z.date(),
bigint: z.bigint().optional(),
})
)
.query((input) => {
return input;
}),
Expand All @@ -40,15 +46,15 @@ vi.stubGlobal('electronTRPC', electronTRPC);

describe('ipcLink', () => {
test('can create ipcLink', () => {
expect(() => createTRPCProxyClient({ links: [ipcLink()] })).not.toThrow();
expect(() => createTRPCClient({ links: [ipcLink()] })).not.toThrow();
});

describe('operations', () => {
let client: ReturnType<typeof createTRPCProxyClient<Router>>;
let client: ReturnType<typeof createTRPCClient<Router>>;
const mock = vi.mocked(electronTRPC);

beforeEach(() => {
client = createTRPCProxyClient({ links: [ipcLink()] });
client = createTRPCClient({ links: [ipcLink()] });
});

test('routes query to/from', async () => {
Expand Down Expand Up @@ -225,9 +231,8 @@ describe('ipcLink', () => {
});

test('serializes inputs/outputs', async () => {
const client = createTRPCProxyClient<Router>({
transformer: superjson,
links: [ipcLink()],
const client = createTRPCClient<Router>({
links: [ipcLink({ transformer: superjson })],
});

const mock = vi.mocked(electronTRPC);
Expand Down Expand Up @@ -270,12 +275,15 @@ describe('ipcLink', () => {
});

test('serializes inputs with custom transformer', async () => {
const client = createTRPCProxyClient<Router>({
transformer: {
serialize: (input) => JSON.stringify(input),
deserialize: (input) => JSON.parse(input),
},
links: [ipcLink()],
const client = createTRPCClient<Router>({
links: [
ipcLink({
transformer: {
serialize: (input) => JSON.stringify(input),
deserialize: (input) => JSON.parse(input),
},
}),
],
});

const mock = vi.mocked(electronTRPC);
Expand Down
33 changes: 23 additions & 10 deletions packages/electron-trpc/src/renderer/ipcLink.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { Operation, TRPCClientError, TRPCLink } from '@trpc/client';
import type { AnyRouter, inferRouterContext, ProcedureType } from '@trpc/server';
import type {
AnyTRPCRouter,
inferRouterContext,
TRPCProcedureType,
inferTRPCClientTypes,
} from '@trpc/server';
import type { TRPCResponseMessage } from '@trpc/server/rpc';
import type { RendererGlobalElectronTRPC } from '../types';
import { observable, Observer } from '@trpc/server/observable';
import { transformResult } from './utils';
import debugFactory from 'debug';
import { type TransformerOptions, getTransformer } from '@trpc/client/unstable-internals';
import type { RendererGlobalElectronTRPC } from '../types';
import { transformResult } from './utils';

const debug = debugFactory('electron-trpc:renderer:ipcLink');

type IPCCallbackResult<TRouter extends AnyRouter = AnyRouter> = TRPCResponseMessage<
type IPCCallbackResult<TRouter extends AnyTRPCRouter = AnyTRPCRouter> = TRPCResponseMessage<
unknown,
inferRouterContext<TRouter>
>;

type IPCCallbacks<TRouter extends AnyRouter = AnyRouter> = Observer<
type IPCCallbacks<TRouter extends AnyTRPCRouter = AnyTRPCRouter> = Observer<
IPCCallbackResult<TRouter>,
TRPCClientError<TRouter>
>;

type IPCRequest = {
type: ProcedureType;
type: TRPCProcedureType;
callbacks: IPCCallbacks;
op: Operation;
};
Expand Down Expand Up @@ -88,13 +94,20 @@ class IPCClient {
}
}

export function ipcLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
return (runtime) => {
export type IPCLinkOptions<TRouter extends AnyTRPCRouter> = TransformerOptions<
inferTRPCClientTypes<TRouter>
>;

export function ipcLink<TRouter extends AnyTRPCRouter>(
opts?: IPCLinkOptions<TRouter>
): TRPCLink<TRouter> {
return () => {
const client = new IPCClient();
const transformer = getTransformer(opts?.transformer);

return ({ op }) => {
return observable((observer) => {
op.input = runtime.transformer.serialize(op.input);
op.input = transformer.input.serialize(op.input);

const unsubscribe = client.request(op, {
error(err) {
Expand All @@ -105,7 +118,7 @@ export function ipcLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
observer.complete();
},
next(response) {
const transformed = transformResult(response, runtime);
const transformed = transformResult(response, transformer.output);

if (!transformed.ok) {
observer.error(TRPCClientError.from(transformed.error));
Expand Down
Loading