trpc-uwebsockets 11.0.4 → 11.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/___testHelpers.js +234 -0
- package/dist/___testHelpers.js.map +1 -0
- package/dist/websockets.js +2 -3
- package/dist/websockets.js.map +1 -1
- package/package.json +3 -3
- package/src/___testHelpers.ts +312 -0
- package/src/readme.spec.ts +1 -1
- package/src/server.spec.ts +279 -599
- package/src/websockets.ts +21 -21
- package/types/___testHelpers.d.ts +44 -0
- package/types/websockets.d.ts +18 -17
package/src/server.spec.ts
CHANGED
|
@@ -1,56 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
import type {
|
|
3
|
-
HTTPHeaders,
|
|
4
|
-
TRPCLink,
|
|
5
|
-
WebSocketClientOptions,
|
|
6
|
-
} from '@trpc/client';
|
|
7
|
-
import {
|
|
8
|
-
createTRPCClient,
|
|
9
|
-
createWSClient,
|
|
10
|
-
httpBatchLink,
|
|
11
|
-
TRPCClientError,
|
|
12
|
-
httpBatchStreamLink,
|
|
13
|
-
httpSubscriptionLink,
|
|
14
|
-
wsLink,
|
|
15
|
-
} from '@trpc/client';
|
|
1
|
+
import { TRPCClientError } from '@trpc/client';
|
|
16
2
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
17
3
|
import { observable } from '@trpc/server/observable';
|
|
18
|
-
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
19
4
|
import { EventEmitter } from 'events';
|
|
20
|
-
import uWs from 'uWebSockets.js';
|
|
21
|
-
import {
|
|
22
|
-
afterEach,
|
|
23
|
-
beforeEach,
|
|
24
|
-
describe,
|
|
25
|
-
expect,
|
|
26
|
-
expectTypeOf,
|
|
27
|
-
test,
|
|
28
|
-
vi,
|
|
29
|
-
} from 'vitest';
|
|
30
|
-
import { z } from 'zod';
|
|
31
5
|
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
CreateHandlerOptions,
|
|
36
|
-
} from './requestHandler';
|
|
37
|
-
import { applyWebsocketHandler } from './websockets';
|
|
6
|
+
import { afterEach, describe, expect, expectTypeOf, test, vi } from 'vitest';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { testFactory } from './___testHelpers';
|
|
38
9
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
10
|
+
import { type CreateContextOptions } from './requestHandler';
|
|
11
|
+
import { TRPCRequestInfo } from '@trpc/server/unstable-core-do-not-import';
|
|
42
12
|
|
|
43
13
|
async function createContext({ req, res, info, client }: CreateContextOptions) {
|
|
44
14
|
const user = { name: req.headers.get('username') || 'anonymous' };
|
|
45
15
|
|
|
46
16
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
47
17
|
|
|
48
|
-
if (
|
|
18
|
+
if (info.url?.searchParams.get('throw')) {
|
|
49
19
|
throw new TRPCError({
|
|
50
20
|
code: 'BAD_REQUEST',
|
|
51
|
-
message:
|
|
21
|
+
message: info.url!.searchParams.get('throw')!,
|
|
52
22
|
});
|
|
53
|
-
// throw new Error(req.headers.get('throw')!);
|
|
54
23
|
}
|
|
55
24
|
|
|
56
25
|
// for websocket context throwing after connection
|
|
@@ -61,15 +30,6 @@ async function createContext({ req, res, info, client }: CreateContextOptions) {
|
|
|
61
30
|
});
|
|
62
31
|
}
|
|
63
32
|
|
|
64
|
-
// for websocket context throwing during connection
|
|
65
|
-
const url = new URL(req.url);
|
|
66
|
-
if (url.searchParams.has('throw')) {
|
|
67
|
-
throw new TRPCError({
|
|
68
|
-
code: 'BAD_REQUEST',
|
|
69
|
-
message: url.searchParams.get('throw')!,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
33
|
// test writing headers inside the context
|
|
74
34
|
if (info.type === 'query') {
|
|
75
35
|
res.cork(() => {
|
|
@@ -79,8 +39,6 @@ async function createContext({ req, res, info, client }: CreateContextOptions) {
|
|
|
79
39
|
return { req, res, user, info, client };
|
|
80
40
|
}
|
|
81
41
|
|
|
82
|
-
type Context = Awaited<ReturnType<typeof createContext>>;
|
|
83
|
-
|
|
84
42
|
interface Message {
|
|
85
43
|
id: string;
|
|
86
44
|
}
|
|
@@ -90,7 +48,7 @@ function createAppRouter() {
|
|
|
90
48
|
const onNewMessageSubscription = vi.fn();
|
|
91
49
|
const onSubscriptionEnded = vi.fn();
|
|
92
50
|
|
|
93
|
-
const t = initTRPC.
|
|
51
|
+
const t = initTRPC.create();
|
|
94
52
|
const router = t.router;
|
|
95
53
|
const publicProcedure = t.procedure;
|
|
96
54
|
|
|
@@ -110,7 +68,7 @@ function createAppRouter() {
|
|
|
110
68
|
.nullish()
|
|
111
69
|
)
|
|
112
70
|
.query(({ input, ctx }) => ({
|
|
113
|
-
text: `hello ${input?.username ?? ctx.user?.name ?? 'world'}`,
|
|
71
|
+
text: `hello ${input?.username ?? (ctx as any).user?.name ?? 'world'}`,
|
|
114
72
|
})),
|
|
115
73
|
helloMutation: publicProcedure
|
|
116
74
|
.input(z.string())
|
|
@@ -126,7 +84,7 @@ function createAppRouter() {
|
|
|
126
84
|
})
|
|
127
85
|
)
|
|
128
86
|
.mutation(async ({ input, ctx }) => {
|
|
129
|
-
if (ctx.user.name === 'anonymous') {
|
|
87
|
+
if ((ctx as any).user.name === 'anonymous') {
|
|
130
88
|
return { error: 'Unauthorized user' };
|
|
131
89
|
}
|
|
132
90
|
const { id, data } = input;
|
|
@@ -148,10 +106,12 @@ function createAppRouter() {
|
|
|
148
106
|
return sub;
|
|
149
107
|
}),
|
|
150
108
|
pubSub: publicProcedure.input(z.null()).subscription(({ ctx }) => {
|
|
151
|
-
|
|
109
|
+
// TODO: fix all these
|
|
110
|
+
const anyContext = ctx as any;
|
|
111
|
+
if (!anyContext.client) {
|
|
152
112
|
throw new Error('assertion: client should never be null in websockets');
|
|
153
113
|
}
|
|
154
|
-
const client =
|
|
114
|
+
const client = anyContext.client!;
|
|
155
115
|
// for some reason client.publish does not work...
|
|
156
116
|
// currently okay is false, not sure why uWs behaves this way
|
|
157
117
|
// publishing from the server works though
|
|
@@ -186,7 +146,7 @@ function createAppRouter() {
|
|
|
186
146
|
}),
|
|
187
147
|
request: router({
|
|
188
148
|
info: publicProcedure.query(({ ctx }) => {
|
|
189
|
-
return ctx.info;
|
|
149
|
+
return (ctx as any).info as TRPCRequestInfo;
|
|
190
150
|
}),
|
|
191
151
|
}),
|
|
192
152
|
deferred: publicProcedure
|
|
@@ -222,7 +182,10 @@ function createAppRouter() {
|
|
|
222
182
|
return;
|
|
223
183
|
}
|
|
224
184
|
if (input.throw === i) {
|
|
225
|
-
throw new
|
|
185
|
+
throw new TRPCError({
|
|
186
|
+
code: 'BAD_REQUEST',
|
|
187
|
+
message: i.toString(),
|
|
188
|
+
});
|
|
226
189
|
}
|
|
227
190
|
yield i;
|
|
228
191
|
}
|
|
@@ -232,249 +195,35 @@ function createAppRouter() {
|
|
|
232
195
|
return { appRouter, ee, onNewMessageSubscription, onSubscriptionEnded };
|
|
233
196
|
}
|
|
234
197
|
|
|
235
|
-
|
|
236
|
-
type AppRouter = CreateAppRouter['appRouter'];
|
|
237
|
-
|
|
238
|
-
interface ServerOptions {
|
|
239
|
-
appRouter: AppRouter;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function createServer(opts: ServerOptions) {
|
|
243
|
-
const instance = uWs.App();
|
|
244
|
-
|
|
245
|
-
const router = opts.appRouter;
|
|
246
|
-
|
|
247
|
-
applyRequestHandler(instance, {
|
|
248
|
-
prefix: config.prefix,
|
|
249
|
-
ssl: false,
|
|
250
|
-
trpcOptions: {
|
|
251
|
-
router,
|
|
252
|
-
createContext,
|
|
253
|
-
onError(data) {
|
|
254
|
-
// console.error('trpc error', data);
|
|
255
|
-
},
|
|
256
|
-
responseMeta({ ctx, paths, type, errors }) {
|
|
257
|
-
return {
|
|
258
|
-
headers: {
|
|
259
|
-
'Access-Control-Allow-Origin': '*',
|
|
260
|
-
},
|
|
261
|
-
};
|
|
262
|
-
},
|
|
263
|
-
} satisfies CreateHandlerOptions<AppRouter>['trpcOptions'],
|
|
264
|
-
});
|
|
265
|
-
applyWebsocketHandler(instance, {
|
|
266
|
-
prefix: config.prefix,
|
|
267
|
-
router,
|
|
268
|
-
createContext,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
instance.get('/hello', async (res, req) => {
|
|
272
|
-
res.end('Hello world');
|
|
273
|
-
});
|
|
274
|
-
instance.post('/hello', async (res, req) => {
|
|
275
|
-
res.end(JSON.stringify({ hello: 'POST', body: 'TODO, why?' }));
|
|
276
|
-
});
|
|
198
|
+
let stopFn = () => {};
|
|
277
199
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
instance.ws('/pubsub', {
|
|
285
|
-
open: (client) => {
|
|
286
|
-
const ok = client.subscribe('topic');
|
|
287
|
-
|
|
288
|
-
if (!ok) {
|
|
289
|
-
throw new Error("assertion: failed to subscribe to 'topic'");
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
instance.any('/*', (res, req) => {
|
|
295
|
-
res.writeStatus('404 NOT FOUND');
|
|
296
|
-
res.end();
|
|
297
|
-
});
|
|
200
|
+
afterEach(() => {
|
|
201
|
+
stopFn();
|
|
202
|
+
});
|
|
298
203
|
|
|
299
|
-
|
|
204
|
+
function defaultFactory(config?: {
|
|
205
|
+
createContext?: (opts: CreateContextOptions) => Promise<any>;
|
|
206
|
+
}) {
|
|
207
|
+
const router = createAppRouter();
|
|
300
208
|
|
|
301
|
-
|
|
302
|
-
|
|
209
|
+
const factoryVal = testFactory({
|
|
210
|
+
appRouter: router.appRouter,
|
|
211
|
+
createContext: config?.createContext ?? createContext,
|
|
303
212
|
});
|
|
304
213
|
|
|
305
|
-
|
|
306
|
-
throw new Error('could not make a socket');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const port = uWs.us_socket_local_port(socket);
|
|
214
|
+
stopFn = factoryVal.stop;
|
|
310
215
|
|
|
311
216
|
return {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
throw new Error('could not close socket as socket is already closed');
|
|
315
|
-
}
|
|
316
|
-
uWs.us_listen_socket_close(socket);
|
|
317
|
-
socket = null;
|
|
318
|
-
},
|
|
319
|
-
port,
|
|
320
|
-
instance,
|
|
217
|
+
...router,
|
|
218
|
+
...factoryVal,
|
|
321
219
|
};
|
|
322
220
|
}
|
|
323
221
|
|
|
324
|
-
const orderedResults: number[] = [];
|
|
325
|
-
const linkSpy: TRPCLink<AppRouter> = () => {
|
|
326
|
-
// here we just got initialized in the app - this happens once per app
|
|
327
|
-
// useful for storing cache for instance
|
|
328
|
-
return ({ next, op }) => {
|
|
329
|
-
// this is when passing the result to the next link
|
|
330
|
-
// each link needs to return an observable which propagates results
|
|
331
|
-
return observable((observer) => {
|
|
332
|
-
const unsubscribe = next(op).subscribe({
|
|
333
|
-
next(value) {
|
|
334
|
-
orderedResults.push(value.result.data as number);
|
|
335
|
-
observer.next(value);
|
|
336
|
-
},
|
|
337
|
-
error: observer.error,
|
|
338
|
-
});
|
|
339
|
-
return unsubscribe;
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
interface ClientOptions {
|
|
345
|
-
wsClientOptions?: Omit<WebSocketClientOptions, 'url'> | undefined;
|
|
346
|
-
queryParams?: Record<string, string> | undefined;
|
|
347
|
-
headers?: HTTPHeaders | undefined;
|
|
348
|
-
port: number;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function toQueryString(queryParams: Record<string, string>) {
|
|
352
|
-
const raw = new URLSearchParams(queryParams).toString();
|
|
353
|
-
return raw.length == 0 ? '' : `?${raw}`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function createClientWs(opts: ClientOptions) {
|
|
357
|
-
const qs = toQueryString(opts.queryParams ?? {});
|
|
358
|
-
const host = `localhost:${opts.port}${config.prefix}${qs}`;
|
|
359
|
-
const wsClient = createWSClient({
|
|
360
|
-
retryDelayMs: () => 99999, // never retry by default
|
|
361
|
-
...opts.wsClientOptions,
|
|
362
|
-
url: `ws://${host}`,
|
|
363
|
-
});
|
|
364
|
-
const client = createTRPCClient<AppRouter>({
|
|
365
|
-
links: [linkSpy, wsLink({ client: wsClient })],
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
return { client, wsClient };
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function createClientBatchStream(opts: ClientOptions) {
|
|
372
|
-
const qs = toQueryString(opts.queryParams ?? {});
|
|
373
|
-
const host = `localhost:${opts.port}${config.prefix}${qs}`;
|
|
374
|
-
const client = createTRPCClient<AppRouter>({
|
|
375
|
-
links: [
|
|
376
|
-
linkSpy,
|
|
377
|
-
httpBatchStreamLink({
|
|
378
|
-
url: `http://${host}`,
|
|
379
|
-
headers: opts.headers,
|
|
380
|
-
}),
|
|
381
|
-
],
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
return { client };
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function createClientBatch(opts: ClientOptions) {
|
|
388
|
-
const qs = toQueryString(opts.queryParams ?? {});
|
|
389
|
-
const host = `localhost:${opts.port}${config.prefix}${qs}`;
|
|
390
|
-
const client = createTRPCClient<AppRouter>({
|
|
391
|
-
links: [
|
|
392
|
-
httpBatchLink({
|
|
393
|
-
url: `http://${host}`,
|
|
394
|
-
headers: opts.headers,
|
|
395
|
-
}),
|
|
396
|
-
],
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
return { client };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function createClientSSE(opts: ClientOptions) {
|
|
403
|
-
const qs = toQueryString(opts.queryParams ?? {});
|
|
404
|
-
const host = `localhost:${opts.port}${config.prefix}${qs}`;
|
|
405
|
-
const client = createTRPCClient<AppRouter>({
|
|
406
|
-
links: [
|
|
407
|
-
// loggerLink(),
|
|
408
|
-
httpSubscriptionLink({
|
|
409
|
-
url: `http://${host}`,
|
|
410
|
-
connectionParams: opts.wsClientOptions?.connectionParams,
|
|
411
|
-
// ponyfill EventSource
|
|
412
|
-
EventSource: EventSourcePolyfill as any,
|
|
413
|
-
}),
|
|
414
|
-
],
|
|
415
|
-
});
|
|
416
|
-
return { client };
|
|
417
|
-
}
|
|
418
|
-
type ClientType = 'batchStream' | 'batch' | 'sse';
|
|
419
|
-
async function createApp(serverOptions?: Partial<ServerOptions> | undefined) {
|
|
420
|
-
const { appRouter, ee } = createAppRouter();
|
|
421
|
-
const { instance, port, stop } = createServer({
|
|
422
|
-
...(serverOptions ?? {}),
|
|
423
|
-
appRouter,
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
return {
|
|
427
|
-
server: instance,
|
|
428
|
-
stop,
|
|
429
|
-
getClient(
|
|
430
|
-
clientType: ClientType,
|
|
431
|
-
clientOptions?: Partial<ClientOptions> | undefined
|
|
432
|
-
) {
|
|
433
|
-
switch (clientType) {
|
|
434
|
-
case 'batchStream':
|
|
435
|
-
return createClientBatchStream({
|
|
436
|
-
...clientOptions,
|
|
437
|
-
port,
|
|
438
|
-
});
|
|
439
|
-
case 'batch':
|
|
440
|
-
return createClientBatch({
|
|
441
|
-
...clientOptions,
|
|
442
|
-
port,
|
|
443
|
-
});
|
|
444
|
-
case 'sse':
|
|
445
|
-
return createClientSSE({
|
|
446
|
-
...clientOptions,
|
|
447
|
-
port,
|
|
448
|
-
});
|
|
449
|
-
default:
|
|
450
|
-
throw new Error('unknown client');
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
getClientWs(clientOptions?: Partial<ClientOptions> | undefined) {
|
|
454
|
-
return createClientWs({
|
|
455
|
-
...clientOptions,
|
|
456
|
-
port,
|
|
457
|
-
});
|
|
458
|
-
},
|
|
459
|
-
ee,
|
|
460
|
-
port,
|
|
461
|
-
opts: serverOptions,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
let app: Awaited<ReturnType<typeof createApp>>;
|
|
466
|
-
beforeEach(async () => {
|
|
467
|
-
orderedResults.length = 0;
|
|
468
|
-
app = await createApp();
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
afterEach(() => {
|
|
472
|
-
app.stop();
|
|
473
|
-
});
|
|
474
|
-
|
|
475
222
|
describe('server', () => {
|
|
476
223
|
test('fetch GET smoke', async () => {
|
|
477
|
-
const
|
|
224
|
+
const ctx = defaultFactory();
|
|
225
|
+
|
|
226
|
+
const res = await ctx.fetch(`/hello`, {
|
|
478
227
|
method: 'GET',
|
|
479
228
|
headers: {
|
|
480
229
|
Accept: 'application/json',
|
|
@@ -485,39 +234,23 @@ describe('server', () => {
|
|
|
485
234
|
expect(await res.text()).toEqual('Hello world');
|
|
486
235
|
});
|
|
487
236
|
|
|
488
|
-
test('response meta', async () => {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
);
|
|
237
|
+
test('forwards response meta', async () => {
|
|
238
|
+
const ctx = defaultFactory();
|
|
239
|
+
const res = await ctx.fetch(`/trpc/ping?input=${encodeURI('{}')}`, {});
|
|
492
240
|
expect(res.status).toEqual(200);
|
|
493
241
|
expect(res.headers.get('Access-Control-Allow-Origin')).toEqual('*'); // from the meta
|
|
494
242
|
expect(res.headers.get('x-test')).toEqual('true'); // from the context
|
|
495
243
|
});
|
|
496
244
|
|
|
497
245
|
test('query', async () => {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
`);
|
|
507
|
-
expect(
|
|
508
|
-
await client.hello.query({
|
|
509
|
-
username: 'test',
|
|
510
|
-
})
|
|
511
|
-
).toMatchInlineSnapshot(`
|
|
512
|
-
Object {
|
|
513
|
-
"text": "hello test",
|
|
514
|
-
}
|
|
515
|
-
`);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
{
|
|
519
|
-
// batch
|
|
520
|
-
const { client } = app.getClient('batch');
|
|
246
|
+
const ctx = defaultFactory();
|
|
247
|
+
const clients = [
|
|
248
|
+
ctx.clientBatchStream().client,
|
|
249
|
+
ctx.clientBatch().client,
|
|
250
|
+
ctx.clientWs().client,
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
for (const client of clients) {
|
|
521
254
|
expect(await client.ping.query()).toMatchInlineSnapshot(`"pong"`);
|
|
522
255
|
expect(await client.hello.query()).toMatchInlineSnapshot(`
|
|
523
256
|
Object {
|
|
@@ -537,23 +270,14 @@ describe('server', () => {
|
|
|
537
270
|
});
|
|
538
271
|
|
|
539
272
|
test('mutation', async () => {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
).toMatchInlineSnapshot(`
|
|
549
|
-
Object {
|
|
550
|
-
"error": "Unauthorized user",
|
|
551
|
-
}
|
|
552
|
-
`);
|
|
553
|
-
}
|
|
554
|
-
{
|
|
555
|
-
// batch
|
|
556
|
-
const { client } = app.getClient('batch');
|
|
273
|
+
const ctx = defaultFactory();
|
|
274
|
+
const clients = [
|
|
275
|
+
ctx.clientBatchStream().client,
|
|
276
|
+
ctx.clientBatch().client,
|
|
277
|
+
ctx.clientWs().client,
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
for (const client of clients) {
|
|
557
281
|
expect(
|
|
558
282
|
await client.editPost.mutate({
|
|
559
283
|
id: '42',
|
|
@@ -567,33 +291,102 @@ describe('server', () => {
|
|
|
567
291
|
}
|
|
568
292
|
});
|
|
569
293
|
|
|
570
|
-
test('
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
client.deferred.query({ wait: 1 }),
|
|
575
|
-
client.deferred.query({ wait: 2 }),
|
|
576
|
-
]);
|
|
577
|
-
expect(results).toEqual([3, 1, 2]);
|
|
578
|
-
expect(orderedResults).toEqual([1, 2, 3]);
|
|
579
|
-
});
|
|
294
|
+
test('subscription', { timeout: 5000 }, async () => {
|
|
295
|
+
const ctx = defaultFactory();
|
|
296
|
+
const ee = ctx.ee;
|
|
297
|
+
const clients = [ctx.clientSse().client, ctx.clientWs().client];
|
|
580
298
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
299
|
+
for (const client of clients) {
|
|
300
|
+
ee.once('subscription:created', () => {
|
|
301
|
+
setTimeout(() => {
|
|
302
|
+
ee.emit('server:msg', {
|
|
303
|
+
id: '1',
|
|
304
|
+
});
|
|
305
|
+
ee.emit('server:msg', {
|
|
306
|
+
id: '2',
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
const onStartedMock = vi.fn();
|
|
311
|
+
const onDataMock = vi.fn();
|
|
312
|
+
const sub = client.onMessage.subscribe('onMessage', {
|
|
313
|
+
onStarted: onStartedMock,
|
|
314
|
+
onData(data) {
|
|
315
|
+
expectTypeOf(data).not.toBeAny();
|
|
316
|
+
expectTypeOf(data).toMatchTypeOf<Message>();
|
|
317
|
+
onDataMock(data);
|
|
318
|
+
},
|
|
319
|
+
});
|
|
585
320
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
321
|
+
await vi.waitFor(
|
|
322
|
+
() => {
|
|
323
|
+
expect(onStartedMock).toHaveBeenCalledTimes(1);
|
|
324
|
+
expect(onDataMock).toHaveBeenCalledTimes(2);
|
|
325
|
+
},
|
|
326
|
+
{ timeout: 3000 }
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
ee.emit('server:msg', {
|
|
330
|
+
id: '3',
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await vi.waitFor(() => {
|
|
334
|
+
expect(onDataMock).toHaveBeenCalledTimes(3);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(onDataMock.mock.calls).toMatchInlineSnapshot(`
|
|
338
|
+
Array [
|
|
339
|
+
Array [
|
|
340
|
+
Object {
|
|
341
|
+
"id": "1",
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
Array [
|
|
345
|
+
Object {
|
|
346
|
+
"id": "2",
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
Array [
|
|
350
|
+
Object {
|
|
351
|
+
"id": "3",
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
]
|
|
355
|
+
`);
|
|
356
|
+
|
|
357
|
+
sub.unsubscribe();
|
|
358
|
+
|
|
359
|
+
await vi.waitFor(() => {
|
|
360
|
+
expect(ee.listenerCount('server:msg')).toBe(0);
|
|
361
|
+
expect(ee.listenerCount('server:error')).toBe(0);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('streaming', async () => {
|
|
367
|
+
const ctx = defaultFactory();
|
|
368
|
+
const tests = [ctx.clientBatchStream(), ctx.clientWs()];
|
|
369
|
+
|
|
370
|
+
for (const { client, orderedResults } of tests) {
|
|
371
|
+
const results = await Promise.all([
|
|
372
|
+
client.deferred.query({ wait: 3 }),
|
|
373
|
+
client.deferred.query({ wait: 1 }),
|
|
374
|
+
client.deferred.query({ wait: 2 }),
|
|
589
375
|
]);
|
|
590
|
-
expect(
|
|
376
|
+
expect(results).toEqual([3, 1, 2]);
|
|
377
|
+
expect(orderedResults).toEqual([1, 2, 3]);
|
|
591
378
|
}
|
|
379
|
+
});
|
|
592
380
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
381
|
+
test('batched requests', async () => {
|
|
382
|
+
const ctx = defaultFactory();
|
|
383
|
+
const clients = [
|
|
384
|
+
ctx.clientBatchStream().client,
|
|
385
|
+
ctx.clientBatch().client,
|
|
386
|
+
ctx.clientWs().client,
|
|
387
|
+
];
|
|
596
388
|
|
|
389
|
+
for (const client of clients) {
|
|
597
390
|
const res = await Promise.all([
|
|
598
391
|
client.helloMutation.mutate('world'),
|
|
599
392
|
client.helloMutation.mutate('KATT'),
|
|
@@ -602,105 +395,84 @@ describe('server', () => {
|
|
|
602
395
|
}
|
|
603
396
|
});
|
|
604
397
|
|
|
605
|
-
test('handles throwing procedure', async () => {
|
|
606
|
-
// batch stream
|
|
607
|
-
const { client } = app.getClient('batchStream');
|
|
608
|
-
await expect(
|
|
609
|
-
client.throw.query('expected_procedure_error')
|
|
610
|
-
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
611
|
-
`[TRPCClientError: expected_procedure_error]`
|
|
612
|
-
);
|
|
613
|
-
});
|
|
614
|
-
|
|
615
398
|
test('handles throwing context', async () => {
|
|
616
|
-
const
|
|
617
|
-
|
|
399
|
+
const ctx = defaultFactory();
|
|
400
|
+
const clientOpts = {
|
|
401
|
+
queryParams: {
|
|
618
402
|
throw: 'expected_context_error',
|
|
619
403
|
},
|
|
620
|
-
}
|
|
404
|
+
};
|
|
405
|
+
const clients = [
|
|
406
|
+
ctx.clientBatchStream(clientOpts).client,
|
|
407
|
+
ctx.clientBatch(clientOpts).client,
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
for (const client of clients) {
|
|
411
|
+
await expect(
|
|
412
|
+
client.echo.query('hii')
|
|
413
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
414
|
+
`[TRPCClientError: expected_context_error]`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// websocket context resolution failure hides the message
|
|
419
|
+
const wsClient = ctx.clientWs(clientOpts).client;
|
|
621
420
|
await expect(
|
|
622
|
-
|
|
421
|
+
wsClient.echo.query('hii')
|
|
623
422
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
624
|
-
`[TRPCClientError:
|
|
423
|
+
`[TRPCClientError: Unknown error]`
|
|
625
424
|
);
|
|
626
425
|
});
|
|
627
426
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const onDataMock = vi.fn();
|
|
646
|
-
const sub = client.onMessage.subscribe('onMessage', {
|
|
647
|
-
onStarted: onStartedMock,
|
|
648
|
-
onData(data) {
|
|
649
|
-
expectTypeOf(data).not.toBeAny();
|
|
650
|
-
expectTypeOf(data).toMatchTypeOf<Message>();
|
|
651
|
-
onDataMock(data);
|
|
652
|
-
},
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
await vi.waitFor(
|
|
656
|
-
() => {
|
|
657
|
-
expect(onStartedMock).toHaveBeenCalledTimes(1);
|
|
658
|
-
expect(onDataMock).toHaveBeenCalledTimes(2);
|
|
659
|
-
},
|
|
660
|
-
{ timeout: 3000 }
|
|
661
|
-
);
|
|
662
|
-
|
|
663
|
-
app.ee.emit('server:msg', {
|
|
664
|
-
id: '3',
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
await vi.waitFor(() => {
|
|
668
|
-
expect(onDataMock).toHaveBeenCalledTimes(3);
|
|
669
|
-
});
|
|
427
|
+
test('handles throwing procedure', async () => {
|
|
428
|
+
const ctx = defaultFactory();
|
|
429
|
+
const clients = [
|
|
430
|
+
ctx.clientBatchStream().client,
|
|
431
|
+
ctx.clientBatch().client,
|
|
432
|
+
ctx.clientWs().client,
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
for (const client of clients) {
|
|
436
|
+
await expect(
|
|
437
|
+
client.throw.query('expected_procedure_error')
|
|
438
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
439
|
+
`[TRPCClientError: expected_procedure_error]`
|
|
440
|
+
);
|
|
441
|
+
expect(ctx.onServerErrorSpy).toHaveBeenCalled();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
670
444
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
445
|
+
test('handles throwing subscription', async () => {
|
|
446
|
+
const ctx = defaultFactory();
|
|
447
|
+
const clients = [ctx.clientWs(), ctx.clientSse()];
|
|
448
|
+
|
|
449
|
+
for (const { client, orderedResults } of clients) {
|
|
450
|
+
let error: TRPCClientError<any> | null = null;
|
|
451
|
+
client.count.subscribe(
|
|
452
|
+
{
|
|
453
|
+
from: 0,
|
|
454
|
+
count: 10,
|
|
455
|
+
throw: 5,
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
onError(err) {
|
|
459
|
+
error = err;
|
|
686
460
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
expect(app.ee.listenerCount('server:error')).toBe(0);
|
|
696
|
-
});
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
await vi.waitFor(() => {
|
|
464
|
+
expect(orderedResults).toEqual([0, 1, 2, 3, 4]);
|
|
465
|
+
expect(ctx.onServerErrorSpy).toHaveBeenCalled();
|
|
466
|
+
expect(error!.message).toEqual(new TRPCClientError('5').message);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
697
469
|
});
|
|
698
470
|
});
|
|
699
471
|
|
|
700
472
|
describe('websocket', () => {
|
|
701
473
|
test('does not bind other websocket connection', async () => {
|
|
702
|
-
const
|
|
703
|
-
|
|
474
|
+
const ctx = defaultFactory();
|
|
475
|
+
const client = new WebSocket(`ws://localhost:${ctx.port}/ws`);
|
|
704
476
|
await new Promise<void>((resolve, reject) => {
|
|
705
477
|
client.onopen = () => {
|
|
706
478
|
client.send('hello');
|
|
@@ -708,113 +480,47 @@ describe('websocket', () => {
|
|
|
708
480
|
};
|
|
709
481
|
client.onerror = reject;
|
|
710
482
|
});
|
|
711
|
-
|
|
712
483
|
const promise = new Promise<string>((resolve) => {
|
|
713
484
|
client.onmessage = (msg) => {
|
|
714
485
|
return resolve(msg.data);
|
|
715
486
|
};
|
|
716
487
|
});
|
|
717
|
-
|
|
718
488
|
const message = await promise;
|
|
719
|
-
|
|
720
489
|
expect(message.toString()).toBe('hello');
|
|
721
|
-
|
|
722
490
|
client.close();
|
|
723
491
|
});
|
|
724
492
|
|
|
725
|
-
test('
|
|
726
|
-
const
|
|
493
|
+
test('properly passes context', async () => {
|
|
494
|
+
const contextDone = vi.fn();
|
|
495
|
+
const ctx = defaultFactory({
|
|
496
|
+
createContext: async ({ req, res, info, client }) => {
|
|
497
|
+
expect(client != null);
|
|
498
|
+
expect(res != null);
|
|
499
|
+
expect(client).equals(res);
|
|
727
500
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
});
|
|
733
|
-
app.ee.emit('server:msg', {
|
|
734
|
-
id: '2',
|
|
735
|
-
});
|
|
736
|
-
});
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
const onStartedMock = vi.fn();
|
|
740
|
-
const onDataMock = vi.fn();
|
|
741
|
-
const sub = client.onMessage.subscribe('onMessage', {
|
|
742
|
-
onStarted: onStartedMock,
|
|
743
|
-
onData(data) {
|
|
744
|
-
expectTypeOf(data).not.toBeAny();
|
|
745
|
-
expectTypeOf(data).toMatchTypeOf<Message>();
|
|
746
|
-
onDataMock(data);
|
|
747
|
-
},
|
|
748
|
-
});
|
|
501
|
+
// can still get the removeAddress from res
|
|
502
|
+
// hopefully this stays constant in ci?
|
|
503
|
+
const ip = new TextDecoder().decode(res.getRemoteAddressAsText());
|
|
504
|
+
expect(ip).equals('127.0.0.1');
|
|
749
505
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
expect(onDataMock).toHaveBeenCalledTimes(2);
|
|
753
|
-
});
|
|
506
|
+
// but other res-like features are not possible
|
|
507
|
+
expect('writeHeader' in res).equals(false);
|
|
754
508
|
|
|
755
|
-
|
|
756
|
-
id: '3',
|
|
757
|
-
});
|
|
509
|
+
contextDone();
|
|
758
510
|
|
|
759
|
-
|
|
760
|
-
expect(onDataMock).toHaveBeenCalledTimes(3);
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
expect(onDataMock.mock.calls).toMatchInlineSnapshot(`
|
|
764
|
-
Array [
|
|
765
|
-
Array [
|
|
766
|
-
Object {
|
|
767
|
-
"id": "1",
|
|
768
|
-
},
|
|
769
|
-
],
|
|
770
|
-
Array [
|
|
771
|
-
Object {
|
|
772
|
-
"id": "2",
|
|
773
|
-
},
|
|
774
|
-
],
|
|
775
|
-
Array [
|
|
776
|
-
Object {
|
|
777
|
-
"id": "3",
|
|
778
|
-
},
|
|
779
|
-
],
|
|
780
|
-
]
|
|
781
|
-
`);
|
|
782
|
-
|
|
783
|
-
sub.unsubscribe();
|
|
784
|
-
|
|
785
|
-
await vi.waitFor(() => {
|
|
786
|
-
expect(app.ee.listenerCount('server:msg')).toBe(0);
|
|
787
|
-
expect(app.ee.listenerCount('server:error')).toBe(0);
|
|
788
|
-
});
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
test('handles throwing procedure', async () => {
|
|
792
|
-
const { client } = app.getClientWs();
|
|
793
|
-
|
|
794
|
-
let error: any = null;
|
|
795
|
-
|
|
796
|
-
client.count.subscribe(
|
|
797
|
-
{
|
|
798
|
-
from: 0,
|
|
799
|
-
count: 10,
|
|
800
|
-
throw: 5,
|
|
511
|
+
return { req, res, info, client };
|
|
801
512
|
},
|
|
802
|
-
{
|
|
803
|
-
onError(err) {
|
|
804
|
-
error = err;
|
|
805
|
-
},
|
|
806
|
-
}
|
|
807
|
-
);
|
|
808
|
-
|
|
809
|
-
await vi.waitFor(() => {
|
|
810
|
-
expect(error.message).toEqual(new TRPCClientError('5').message);
|
|
811
513
|
});
|
|
514
|
+
const client = ctx.clientWs().client;
|
|
515
|
+
await client.echo.query('testing');
|
|
516
|
+
|
|
517
|
+
expect(contextDone).toHaveBeenCalledOnce();
|
|
812
518
|
});
|
|
813
519
|
|
|
814
520
|
test('handles throwing context with connection params', async () => {
|
|
521
|
+
const server = defaultFactory();
|
|
815
522
|
let closeCode: number | undefined = undefined;
|
|
816
|
-
|
|
817
|
-
const { client } = app.getClientWs({
|
|
523
|
+
const { client } = server.clientWs({
|
|
818
524
|
wsClientOptions: {
|
|
819
525
|
connectionParams: {
|
|
820
526
|
throw: 'expected_context_error',
|
|
@@ -828,28 +534,20 @@ describe('websocket', () => {
|
|
|
828
534
|
},
|
|
829
535
|
});
|
|
830
536
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
// client.count.subscribe(
|
|
837
|
-
// {
|
|
838
|
-
// from: 0,
|
|
839
|
-
// count: 10,
|
|
840
|
-
// },
|
|
841
|
-
// {}
|
|
842
|
-
// );
|
|
537
|
+
await expect(
|
|
538
|
+
client.echo.query('hi')
|
|
539
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
540
|
+
`[TRPCClientError: Unknown error]`
|
|
541
|
+
);
|
|
843
542
|
|
|
844
543
|
await vi.waitFor(() => {
|
|
845
544
|
expect(closeCode).toEqual(1008);
|
|
846
545
|
});
|
|
847
546
|
});
|
|
848
|
-
|
|
849
547
|
test('handles throwing context without connection params', async () => {
|
|
548
|
+
const server = defaultFactory();
|
|
850
549
|
let closeCode: number | undefined = undefined;
|
|
851
|
-
|
|
852
|
-
const { client } = app.getClientWs({
|
|
550
|
+
const { client } = server.clientWs({
|
|
853
551
|
queryParams: {
|
|
854
552
|
throw: 'expected_context_error',
|
|
855
553
|
},
|
|
@@ -860,12 +558,10 @@ describe('websocket', () => {
|
|
|
860
558
|
},
|
|
861
559
|
});
|
|
862
560
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
},
|
|
868
|
-
{}
|
|
561
|
+
await expect(
|
|
562
|
+
client.echo.query('hi')
|
|
563
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
564
|
+
`[TRPCClientError: Unknown error]`
|
|
869
565
|
);
|
|
870
566
|
|
|
871
567
|
await vi.waitFor(() => {
|
|
@@ -873,78 +569,31 @@ describe('websocket', () => {
|
|
|
873
569
|
});
|
|
874
570
|
});
|
|
875
571
|
|
|
876
|
-
// for some reason client.publish does not work, even though it should
|
|
877
|
-
test('uWebsockets pubsub', { skip: true }, async () => {
|
|
878
|
-
const clientWs = new WebSocket(`ws://localhost:${app.port}/pubsub`);
|
|
879
|
-
|
|
880
|
-
const messages: string[] = [];
|
|
881
|
-
let connected = false;
|
|
882
|
-
clientWs.onmessage = (ev) => {
|
|
883
|
-
console.log('triggered onmessage', ev.data);
|
|
884
|
-
messages.push(JSON.parse(ev.data));
|
|
885
|
-
};
|
|
886
|
-
clientWs.onerror = () => {
|
|
887
|
-
throw new Error('client had an error');
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
clientWs.onopen = () => {
|
|
891
|
-
connected = true;
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
await vi.waitFor(() => {
|
|
895
|
-
expect(connected).toBe(true);
|
|
896
|
-
});
|
|
897
|
-
|
|
898
|
-
expect(app.server.numSubscribers('topic')).toBe(1);
|
|
899
|
-
|
|
900
|
-
const { client } = app.getClientWs();
|
|
901
|
-
const sub = client.pubSub.subscribe(null, {
|
|
902
|
-
onData(data) {
|
|
903
|
-
console.log('trpc onMessage', data);
|
|
904
|
-
|
|
905
|
-
expectTypeOf(data).not.toBeAny();
|
|
906
|
-
expectTypeOf(data).toMatchTypeOf<Message>();
|
|
907
|
-
messages.push(data.id);
|
|
908
|
-
},
|
|
909
|
-
});
|
|
910
|
-
|
|
911
|
-
await vi.waitFor(() => {
|
|
912
|
-
expect(messages).toEqual(['created', 'message', 'ended']);
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
sub.unsubscribe();
|
|
916
|
-
clientWs.close();
|
|
917
|
-
});
|
|
918
|
-
|
|
919
572
|
test('keep alive', async () => {
|
|
573
|
+
const ctx = defaultFactory();
|
|
920
574
|
let closeCode: number | undefined = undefined;
|
|
921
|
-
|
|
922
|
-
const { client, wsClient } = app.getClientWs({
|
|
575
|
+
const { client, wsClient } = ctx.clientWs({
|
|
923
576
|
wsClientOptions: {
|
|
924
577
|
onClose(cause) {
|
|
925
578
|
closeCode = cause?.code;
|
|
926
579
|
},
|
|
927
580
|
keepAlive: {
|
|
928
581
|
enabled: true,
|
|
929
|
-
intervalMs:
|
|
930
|
-
pongTimeoutMs:
|
|
582
|
+
intervalMs: 100,
|
|
583
|
+
pongTimeoutMs: 200,
|
|
931
584
|
},
|
|
932
585
|
},
|
|
933
586
|
});
|
|
934
|
-
|
|
935
587
|
const { unsubscribe } = client.waitForMs.subscribe(2_000, {});
|
|
936
|
-
|
|
937
588
|
await vi.waitFor(() => {
|
|
938
589
|
expect(wsClient.connection!.state).toBe('open');
|
|
939
590
|
});
|
|
940
|
-
|
|
941
591
|
let pongCount = 0;
|
|
942
592
|
wsClient.connection!.ws!.addEventListener('message', (ev) => {
|
|
943
593
|
if (ev.data === 'PONG') {
|
|
944
594
|
pongCount++;
|
|
945
595
|
}
|
|
946
596
|
});
|
|
947
|
-
|
|
948
597
|
await vi.waitFor(
|
|
949
598
|
() => {
|
|
950
599
|
expect(pongCount).toEqual(4);
|
|
@@ -954,13 +603,44 @@ describe('websocket', () => {
|
|
|
954
603
|
timeout: 2_000,
|
|
955
604
|
}
|
|
956
605
|
);
|
|
957
|
-
|
|
958
606
|
unsubscribe();
|
|
959
|
-
|
|
960
607
|
wsClient.close();
|
|
961
|
-
|
|
962
608
|
await vi.waitFor(() => {
|
|
963
609
|
expect(closeCode).toEqual(1005);
|
|
964
610
|
});
|
|
965
611
|
});
|
|
612
|
+
// // for some reason client.publish does not work, even though it should
|
|
613
|
+
// test('uWebsockets pubsub', { skip: true }, async () => {
|
|
614
|
+
// const clientWs = new WebSocket(`ws://localhost:${app.port}/pubsub`);
|
|
615
|
+
// const messages: string[] = [];
|
|
616
|
+
// let connected = false;
|
|
617
|
+
// clientWs.onmessage = (ev) => {
|
|
618
|
+
// console.log('triggered onmessage', ev.data);
|
|
619
|
+
// messages.push(JSON.parse(ev.data));
|
|
620
|
+
// };
|
|
621
|
+
// clientWs.onerror = () => {
|
|
622
|
+
// throw new Error('client had an error');
|
|
623
|
+
// };
|
|
624
|
+
// clientWs.onopen = () => {
|
|
625
|
+
// connected = true;
|
|
626
|
+
// };
|
|
627
|
+
// await vi.waitFor(() => {
|
|
628
|
+
// expect(connected).toBe(true);
|
|
629
|
+
// });
|
|
630
|
+
// expect(app.server.numSubscribers('topic')).toBe(1);
|
|
631
|
+
// const { client } = app.clientWs();
|
|
632
|
+
// const sub = client.pubSub.subscribe(null, {
|
|
633
|
+
// onData(data) {
|
|
634
|
+
// console.log('trpc onMessage', data);
|
|
635
|
+
// expectTypeOf(data).not.toBeAny();
|
|
636
|
+
// expectTypeOf(data).toMatchTypeOf<Message>();
|
|
637
|
+
// messages.push(data.id);
|
|
638
|
+
// },
|
|
639
|
+
// });
|
|
640
|
+
// await vi.waitFor(() => {
|
|
641
|
+
// expect(messages).toEqual(['created', 'message', 'ended']);
|
|
642
|
+
// });
|
|
643
|
+
// sub.unsubscribe();
|
|
644
|
+
// clientWs.close();
|
|
645
|
+
// });
|
|
966
646
|
});
|