Skip to content

Commit a02a567

Browse files
authored
Feature: context support (#5)
* refactor(test): extract stack creation to external factory, recreate one for each test for better isolation * refactor(test): wrap tests in `withFactory` wrapper, cleans up after itself in case of exceptions * feat: add `createContext` support & associated test
1 parent 8c12465 commit a02a567

File tree

4 files changed

+124
-68
lines changed

4 files changed

+124
-68
lines changed

src/adapter/index.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ export type CreateMQTTHandlerOptions<TRouter extends AnyRouter> = {
2424
router: TRouter;
2525
onError?: OnErrorFunction<TRouter, ConsumeMessage>;
2626
verbose?: boolean;
27+
createContext?: () => Promise<inferRouterContext<TRouter>>;
2728
};
2829

2930
export const createMQTTHandler = <TRouter extends AnyRouter>(
3031
opts: CreateMQTTHandlerOptions<TRouter>
3132
) => {
32-
const { client, requestTopic: requestTopic, router, onError, verbose } = opts;
33+
const { client, requestTopic: requestTopic, router, onError, verbose, createContext } = opts;
3334

3435
const protocolVersion = client.options.protocolVersion ?? 4;
3536
client.subscribe(requestTopic);
@@ -43,7 +44,7 @@ export const createMQTTHandler = <TRouter extends AnyRouter>(
4344
const correlationId = packet.properties?.correlationData?.toString();
4445
const responseTopic = packet.properties?.responseTopic?.toString();
4546
if (!correlationId || !responseTopic) return;
46-
const res = await handleMessage(router, msg, onError);
47+
const res = await handleMessage(router, msg, onError, createContext);
4748
if (!res) return;
4849
client.publish(responseTopic, Buffer.from(JSON.stringify({ trpc: res })), {
4950
properties: {
@@ -62,7 +63,7 @@ export const createMQTTHandler = <TRouter extends AnyRouter>(
6263
return;
6364
}
6465
if (!correlationId || !responseTopic) return;
65-
const res = await handleMessage(router, msg, onError);
66+
const res = await handleMessage(router, msg, onError, createContext);
6667
if (!res) return;
6768
client.publish(responseTopic, Buffer.from(JSON.stringify({ trpc: res, correlationId })));
6869
}
@@ -72,7 +73,8 @@ export const createMQTTHandler = <TRouter extends AnyRouter>(
7273
async function handleMessage<TRouter extends AnyRouter>(
7374
router: TRouter,
7475
msg: ConsumeMessage,
75-
onError?: OnErrorFunction<TRouter, ConsumeMessage>
76+
onError?: OnErrorFunction<TRouter, ConsumeMessage>,
77+
createContext?: () => Promise<inferRouterContext<TRouter>>
7678
) {
7779
const { transformer } = router._def._config;
7880

@@ -85,7 +87,7 @@ async function handleMessage<TRouter extends AnyRouter>(
8587

8688
const { id, params } = trpc;
8789
const type = MQTT_METHOD_PROCEDURE_TYPE_MAP[trpc.method] ?? ('query' as const);
88-
const ctx: inferRouterContext<TRouter> | undefined = undefined;
90+
const ctx: inferRouterContext<TRouter> | undefined = await createContext?.();
8991

9092
try {
9193
const path = params.path;

test/appRouter.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { initTRPC } from '@trpc/server';
22

33
export type AppRouter = typeof appRouter;
44

5-
const t = initTRPC.create();
5+
export async function createContext() {
6+
return { hello: 'world' };
7+
}
8+
9+
export type Context = Awaited<ReturnType<typeof createContext>>;
10+
11+
const t = initTRPC.context<Context>().create();
612

713
const publicProcedure = t.procedure;
814
const router = t.router;
@@ -28,5 +34,8 @@ export const appRouter = router({
2834
slow: publicProcedure.query(async () => {
2935
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
3036
return 'done';
37+
}),
38+
getContext: publicProcedure.query(({ ctx }) => {
39+
return ctx;
3140
})
3241
});

test/factory.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { createTRPCProxyClient } from '@trpc/client';
2+
import Aedes from 'aedes';
3+
import { once } from 'events';
4+
import mqtt from 'mqtt';
5+
import { createServer } from 'net';
6+
7+
import { createMQTTHandler } from '../src/adapter';
8+
import { mqttLink } from '../src/link';
9+
import { type AppRouter, appRouter, createContext } from './appRouter';
10+
11+
export function factory() {
12+
const requestTopic = 'rpc/request';
13+
14+
const aedes = new Aedes();
15+
// aedes.on('publish', (packet, client) => console.log(packet.topic, packet.payload.toString()));
16+
const broker = createServer(aedes.handle);
17+
broker.listen(1883);
18+
const mqttClient = mqtt.connect('mqtt://localhost');
19+
20+
createMQTTHandler({
21+
client: mqttClient,
22+
requestTopic,
23+
router: appRouter,
24+
createContext
25+
});
26+
27+
const client = createTRPCProxyClient<AppRouter>({
28+
links: [
29+
mqttLink({
30+
client: mqttClient,
31+
requestTopic
32+
})
33+
]
34+
});
35+
36+
return {
37+
client,
38+
broker,
39+
mqttClient,
40+
async ready() {
41+
await once(broker, 'listening');
42+
await once(mqttClient, 'connect');
43+
},
44+
close() {
45+
mqttClient.end();
46+
broker.close();
47+
aedes.close();
48+
}
49+
};
50+
}
51+
52+
export async function withFactory(fn: (f: ReturnType<typeof factory>) => Promise<void>) {
53+
const f = factory();
54+
await f.ready();
55+
try {
56+
await fn(f);
57+
} finally {
58+
f.close();
59+
}
60+
}

test/index.test.ts

Lines changed: 47 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,59 @@
1-
import { createTRPCProxyClient } from '@trpc/client';
2-
import Aedes from 'aedes';
3-
import { once } from 'events';
4-
import mqtt from 'mqtt';
5-
import { createServer } from 'net';
1+
import { withFactory } from './factory';
62

7-
import { createMQTTHandler } from '../src/adapter';
8-
import { mqttLink } from '../src/link';
9-
import { AppRouter, appRouter } from './appRouter';
10-
11-
const requestTopic = 'rpc/request';
12-
13-
const aedes = new Aedes();
14-
// aedes.on('publish', (packet, client) => console.log(packet.topic, packet.payload.toString()));
15-
const broker = createServer(aedes.handle);
16-
broker.listen(1883);
17-
const mqttClient = mqtt.connect('mqtt://localhost');
18-
19-
createMQTTHandler({
20-
client: mqttClient,
21-
requestTopic,
22-
router: appRouter
23-
});
24-
25-
const client = createTRPCProxyClient<AppRouter>({
26-
links: [
27-
mqttLink({
28-
client: mqttClient,
29-
requestTopic
30-
})
31-
]
32-
});
33-
34-
beforeAll(async () => {
35-
await once(broker, 'listening');
36-
await once(mqttClient, 'connect');
37-
});
38-
39-
test('broker is listening', () => {
40-
expect(broker.listening).toBe(true);
41-
});
42-
43-
test('mqtt client is connected', () => {
44-
expect(mqttClient.connected).toBe(true);
3+
describe('broker', () => {
4+
test('is listening', async () => {
5+
await withFactory(async ({ broker }) => {
6+
expect(broker.listening).toBe(true);
7+
});
8+
});
459
});
4610

47-
test('greet query', async () => {
48-
const greeting = await client.greet.query('world');
49-
expect(greeting).toEqual({ greeting: 'hello, world!' });
11+
describe('mqtt client', () => {
12+
test('is connected', async () => {
13+
await withFactory(async ({ mqttClient }) => {
14+
expect(mqttClient.connected).toBe(true);
15+
});
16+
});
5017
});
5118

52-
test('countUp mutation', async () => {
53-
const addOne = await client.countUp.mutate(1);
54-
expect(addOne).toBe(1);
19+
describe('procedures', () => {
20+
test('greet query', async () => {
21+
await withFactory(async ({ client }) => {
22+
const greeting = await client.greet.query('world');
23+
expect(greeting).toEqual({ greeting: 'hello, world!' });
24+
});
25+
});
5526

56-
const addTwo = await client.countUp.mutate(2);
57-
expect(addTwo).toBe(3);
58-
});
27+
test('countUp mutation', async () => {
28+
await withFactory(async ({ client }) => {
29+
const addOne = await client.countUp.mutate(1);
30+
expect(addOne).toBe(1);
5931

60-
test('abortSignal is handled', async () => {
61-
const controller = new AbortController();
62-
const promise = client.slow.query(undefined, {
63-
signal: controller.signal
32+
const addTwo = await client.countUp.mutate(2);
33+
expect(addTwo).toBe(3);
34+
});
6435
});
6536

66-
controller.abort();
67-
await expect(promise).rejects.toThrow('aborted');
37+
describe('abort signal', () => {
38+
test('is handled', async () => {
39+
await withFactory(async ({ client }) => {
40+
const controller = new AbortController();
41+
const promise = client.slow.query(undefined, {
42+
signal: controller.signal
43+
});
44+
45+
controller.abort();
46+
await expect(promise).rejects.toThrow('aborted');
47+
});
48+
});
49+
});
6850
});
6951

70-
afterAll(async () => {
71-
mqttClient.end();
72-
broker.close();
73-
aedes.close();
52+
describe('context', () => {
53+
test('getContext query', async () => {
54+
await withFactory(async ({ client }) => {
55+
const ctx = await client.getContext.query();
56+
expect(ctx).toEqual({ hello: 'world' });
57+
});
58+
});
7459
});

0 commit comments

Comments
 (0)