@trpc/client 11.0.0-rc.772 → 11.0.0-rc.781

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.
Files changed (51) hide show
  1. package/dist/bundle-analysis.json +132 -39
  2. package/dist/index.js +3 -2
  3. package/dist/index.mjs +2 -1
  4. package/dist/links/wsLink/createWsClient.d.ts +6 -0
  5. package/dist/links/wsLink/createWsClient.d.ts.map +1 -0
  6. package/dist/links/wsLink/createWsClient.js +9 -0
  7. package/dist/links/wsLink/createWsClient.mjs +7 -0
  8. package/dist/links/wsLink/wsClient/options.d.ts +79 -0
  9. package/dist/links/wsLink/wsClient/options.d.ts.map +1 -0
  10. package/dist/links/wsLink/wsClient/options.js +22 -0
  11. package/dist/links/wsLink/wsClient/options.mjs +18 -0
  12. package/dist/links/wsLink/wsClient/requestManager.d.ts +102 -0
  13. package/dist/links/wsLink/wsClient/requestManager.d.ts.map +1 -0
  14. package/dist/links/wsLink/wsClient/requestManager.js +138 -0
  15. package/dist/links/wsLink/wsClient/requestManager.mjs +136 -0
  16. package/dist/links/wsLink/wsClient/utils.d.ts +38 -0
  17. package/dist/links/wsLink/wsClient/utils.d.ts.map +1 -0
  18. package/dist/links/wsLink/wsClient/utils.js +94 -0
  19. package/dist/links/wsLink/wsClient/utils.mjs +88 -0
  20. package/dist/links/wsLink/wsClient/wsClient.d.ts +85 -0
  21. package/dist/links/wsLink/wsClient/wsClient.d.ts.map +1 -0
  22. package/dist/links/wsLink/wsClient/wsClient.js +331 -0
  23. package/dist/links/wsLink/wsClient/wsClient.mjs +329 -0
  24. package/dist/links/wsLink/wsClient/wsConnection.d.ts +79 -0
  25. package/dist/links/wsLink/wsClient/wsConnection.d.ts.map +1 -0
  26. package/dist/links/wsLink/wsClient/wsConnection.js +181 -0
  27. package/dist/links/wsLink/wsClient/wsConnection.mjs +178 -0
  28. package/dist/links/wsLink/wsLink.d.ts +11 -0
  29. package/dist/links/wsLink/wsLink.d.ts.map +1 -0
  30. package/dist/links/wsLink/wsLink.js +35 -0
  31. package/dist/links/wsLink/wsLink.mjs +32 -0
  32. package/dist/links.d.ts +1 -1
  33. package/dist/links.d.ts.map +1 -1
  34. package/links/wsLink/wsLink/index.d.ts +1 -0
  35. package/links/wsLink/wsLink/index.js +1 -0
  36. package/package.json +8 -8
  37. package/src/links/wsLink/createWsClient.ts +10 -0
  38. package/src/links/wsLink/wsClient/options.ts +91 -0
  39. package/src/links/wsLink/wsClient/requestManager.ts +174 -0
  40. package/src/links/wsLink/wsClient/utils.ts +94 -0
  41. package/src/links/wsLink/wsClient/wsClient.ts +441 -0
  42. package/src/links/wsLink/wsClient/wsConnection.ts +230 -0
  43. package/src/links/wsLink/wsLink.ts +55 -0
  44. package/src/links.ts +1 -1
  45. package/dist/links/wsLink.d.ts +0 -125
  46. package/dist/links/wsLink.d.ts.map +0 -1
  47. package/dist/links/wsLink.js +0 -498
  48. package/dist/links/wsLink.mjs +0 -495
  49. package/links/wsLink/index.d.ts +0 -1
  50. package/links/wsLink/index.js +0 -1
  51. package/src/links/wsLink.ts +0 -737
@@ -1,737 +0,0 @@
1
- import type { Observer, UnsubscribeFn } from '@trpc/server/observable';
2
- import { behaviorSubject, observable } from '@trpc/server/observable';
3
- import type { TRPCConnectionParamsMessage } from '@trpc/server/rpc';
4
- import type {
5
- AnyRouter,
6
- inferClientTypes,
7
- inferRouterError,
8
- ProcedureType,
9
- TRPCClientIncomingMessage,
10
- TRPCClientIncomingRequest,
11
- TRPCClientOutgoingMessage,
12
- TRPCRequestMessage,
13
- TRPCResponseMessage,
14
- } from '@trpc/server/unstable-core-do-not-import';
15
- import { transformResult } from '@trpc/server/unstable-core-do-not-import';
16
- import { TRPCClientError } from '../TRPCClientError';
17
- import type { TransformerOptions } from '../unstable-internals';
18
- import { getTransformer } from '../unstable-internals';
19
- import type { TRPCConnectionState } from './internals/subscriptions';
20
- import {
21
- resultOf,
22
- type UrlOptionsWithConnectionParams,
23
- } from './internals/urlWithConnectionParams';
24
- import type { Operation, TRPCLink } from './types';
25
-
26
- const run = <TResult>(fn: () => TResult): TResult => fn();
27
-
28
- type WSCallbackResult<TRouter extends AnyRouter, TOutput> = TRPCResponseMessage<
29
- TOutput,
30
- inferRouterError<TRouter>
31
- >;
32
-
33
- type WSCallbackObserver<TRouter extends AnyRouter, TOutput> = Observer<
34
- WSCallbackResult<TRouter, TOutput>,
35
- TRPCClientError<TRouter>
36
- >;
37
-
38
- const exponentialBackoff = (attemptIndex: number) =>
39
- attemptIndex === 0 ? 0 : Math.min(1000 * 2 ** attemptIndex, 30000);
40
-
41
- export interface WebSocketClientOptions extends UrlOptionsWithConnectionParams {
42
- /**
43
- * Ponyfill which WebSocket implementation to use
44
- */
45
- WebSocket?: typeof WebSocket;
46
- /**
47
- * The number of milliseconds before a reconnect is attempted.
48
- * @default {@link exponentialBackoff}
49
- */
50
- retryDelayMs?: typeof exponentialBackoff;
51
- /**
52
- * Triggered when a WebSocket connection is established
53
- */
54
- onOpen?: () => void;
55
- /**
56
- * Triggered when a WebSocket connection encounters an error
57
- */
58
- onError?: (evt?: Event) => void;
59
- /**
60
- * Triggered when a WebSocket connection is closed
61
- */
62
- onClose?: (cause?: { code?: number }) => void;
63
- /**
64
- * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests)
65
- */
66
- lazy?: {
67
- /**
68
- * Enable lazy mode
69
- * @default false
70
- */
71
- enabled: boolean;
72
- /**
73
- * Close the WebSocket after this many milliseconds
74
- * @default 0
75
- */
76
- closeMs: number;
77
- };
78
- /**
79
- * Send ping messages to the server and kill the connection if no pong message is returned
80
- */
81
- keepAlive?: {
82
- /**
83
- * @default false
84
- */
85
- enabled: boolean;
86
- /**
87
- * Send a ping message every this many milliseconds
88
- * @default 5_000
89
- */
90
- intervalMs?: number;
91
- /**
92
- * Close the WebSocket after this many milliseconds if the server does not respond
93
- * @default 1_000
94
- */
95
- pongTimeoutMs?: number;
96
- };
97
- }
98
-
99
- type LazyOptions = Required<NonNullable<WebSocketClientOptions['lazy']>>;
100
- const lazyDefaults: LazyOptions = {
101
- enabled: false,
102
- closeMs: 0,
103
- };
104
-
105
- /**
106
- * @see https://trpc.io/docs/v11/client/links/wsLink
107
- * @deprecated
108
- * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
109
- * See https://github.com/trpc/trpc/issues/6109
110
- */
111
- export function createWSClient(opts: WebSocketClientOptions) {
112
- const {
113
- WebSocket: WebSocketImpl = WebSocket,
114
- retryDelayMs: retryDelayFn = exponentialBackoff,
115
- } = opts;
116
- const lazyOpts: LazyOptions = {
117
- ...lazyDefaults,
118
- ...opts.lazy,
119
- };
120
-
121
- /* istanbul ignore next -- @preserve */
122
- if (!WebSocketImpl) {
123
- throw new Error(
124
- "No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill",
125
- );
126
- }
127
- /**
128
- * outgoing messages buffer whilst not open
129
- */
130
- let outgoing: TRPCClientOutgoingMessage[] = [];
131
- /**
132
- * pending outgoing requests that are awaiting callback
133
- */
134
- type TCallbacks = WSCallbackObserver<AnyRouter, unknown>;
135
- type WsRequest = {
136
- /**
137
- * Reference to the WebSocket instance this request was made to
138
- */
139
- connection: Connection | null;
140
- type: ProcedureType;
141
- callbacks: TCallbacks;
142
- op: Operation;
143
- /**
144
- * The last event id that the client has received
145
- */
146
- lastEventId: string | undefined;
147
- };
148
- const pendingRequests: Record<number | string, WsRequest> =
149
- Object.create(null);
150
- let connectAttempt = 0;
151
- let connectTimer: ReturnType<typeof setTimeout> | undefined = undefined;
152
- let connectionIndex = 0;
153
- let lazyDisconnectTimer: ReturnType<typeof setTimeout> | undefined =
154
- undefined;
155
- let activeConnection: null | Connection = lazyOpts.enabled
156
- ? null
157
- : createConnection();
158
-
159
- type Connection = {
160
- id: number;
161
- } & (
162
- | {
163
- state: 'open';
164
- ws: WebSocket;
165
- }
166
- | {
167
- state: 'closed';
168
- ws: WebSocket;
169
- }
170
- | {
171
- state: 'connecting';
172
- ws?: WebSocket;
173
- }
174
- );
175
-
176
- const initState: TRPCConnectionState<TRPCClientError<AnyRouter>> =
177
- activeConnection
178
- ? {
179
- type: 'state',
180
- state: 'connecting',
181
- error: null,
182
- }
183
- : {
184
- type: 'state',
185
- state: 'idle',
186
- error: null,
187
- };
188
- const connectionState =
189
- behaviorSubject<TRPCConnectionState<TRPCClientError<AnyRouter>>>(initState);
190
-
191
- /**
192
- * tries to send the list of messages
193
- */
194
- function dispatch() {
195
- if (!activeConnection) {
196
- reconnect(null);
197
- return;
198
- }
199
- // using a timeout to batch messages
200
- setTimeout(() => {
201
- if (activeConnection?.state !== 'open') {
202
- return;
203
- }
204
- for (const pending of Object.values(pendingRequests)) {
205
- if (!pending.connection) {
206
- pending.connection = activeConnection;
207
- }
208
- }
209
- if (outgoing.length === 1) {
210
- // single send
211
- activeConnection.ws.send(JSON.stringify(outgoing.pop()));
212
- } else {
213
- // batch send
214
- activeConnection.ws.send(JSON.stringify(outgoing));
215
- }
216
- // clear
217
- outgoing = [];
218
-
219
- startLazyDisconnectTimer();
220
- });
221
- }
222
- function tryReconnect(cause: Error | null) {
223
- if (!!connectTimer) {
224
- return;
225
- }
226
-
227
- const timeout = retryDelayFn(connectAttempt++);
228
- reconnectInMs(timeout, cause);
229
- }
230
- function hasPendingRequests(conn?: Connection) {
231
- const requests = Object.values(pendingRequests);
232
- if (!conn) {
233
- return requests.length > 0;
234
- }
235
- return requests.some((req) => req.connection === conn);
236
- }
237
-
238
- function reconnect(cause: Error | null) {
239
- if (lazyOpts.enabled && !hasPendingRequests()) {
240
- // Skip reconnecting if there aren't pending requests and we're in lazy mode
241
- return;
242
- }
243
- const oldConnection = activeConnection;
244
- activeConnection = createConnection();
245
- if (oldConnection) {
246
- closeIfNoPending(oldConnection);
247
- }
248
-
249
- const currentState = connectionState.get();
250
- if (currentState.state !== 'connecting') {
251
- connectionState.next({
252
- type: 'state',
253
- state: 'connecting',
254
- error: cause ? TRPCClientError.from(cause) : null,
255
- });
256
- }
257
- }
258
- function reconnectInMs(ms: number, cause: Error | null) {
259
- if (connectTimer) {
260
- return;
261
- }
262
- connectTimer = setTimeout(() => {
263
- reconnect(cause);
264
- }, ms);
265
- }
266
-
267
- function closeIfNoPending(conn: Connection) {
268
- // disconnect as soon as there are are no pending requests
269
- if (!hasPendingRequests(conn)) {
270
- conn.ws?.close();
271
- }
272
- }
273
- function resumeSubscriptionOnReconnect(req: WsRequest) {
274
- if (outgoing.some((r) => r.id === req.op.id)) {
275
- return;
276
- }
277
- request({
278
- op: req.op,
279
- callbacks: req.callbacks,
280
- lastEventId: req.lastEventId,
281
- });
282
- }
283
-
284
- const startLazyDisconnectTimer = () => {
285
- if (!lazyOpts.enabled) {
286
- return;
287
- }
288
-
289
- clearTimeout(lazyDisconnectTimer);
290
- lazyDisconnectTimer = setTimeout(() => {
291
- if (!activeConnection) {
292
- return;
293
- }
294
-
295
- if (!hasPendingRequests()) {
296
- activeConnection.ws?.close();
297
- activeConnection = null;
298
- connectionState.next({
299
- type: 'state',
300
- state: 'idle',
301
- error: null,
302
- });
303
- }
304
- }, lazyOpts.closeMs);
305
- };
306
-
307
- function createConnection(): Connection {
308
- let pingTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
309
- let pongTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
310
- const self: Connection = {
311
- id: ++connectionIndex,
312
- state: 'connecting',
313
- } as Connection;
314
-
315
- clearTimeout(lazyDisconnectTimer);
316
-
317
- function destroy() {
318
- const noop = () => {
319
- // no-op
320
- };
321
- const { ws } = self;
322
- if (ws) {
323
- ws.onclose = noop;
324
- ws.onerror = noop;
325
- ws.onmessage = noop;
326
- ws.onopen = noop;
327
-
328
- ws.close();
329
- }
330
-
331
- self.state = 'closed';
332
- }
333
-
334
- const onCloseOrError = (cause: Error | null) => {
335
- clearTimeout(pingTimeout);
336
- clearTimeout(pongTimeout);
337
-
338
- self.state = 'closed';
339
- if (activeConnection === self) {
340
- // connection might have been replaced already
341
- tryReconnect(cause);
342
- }
343
-
344
- for (const [key, req] of Object.entries(pendingRequests)) {
345
- if (req.connection !== self) {
346
- continue;
347
- }
348
-
349
- // The connection was closed either unexpectedly or because of a reconnect
350
- if (req.type === 'subscription') {
351
- // Subscriptions will resume after we've reconnected
352
- resumeSubscriptionOnReconnect(req);
353
- } else {
354
- // Queries and mutations will error if interrupted
355
- delete pendingRequests[key];
356
- req.callbacks.error?.(
357
- TRPCClientError.from(cause ?? new TRPCWebSocketClosedError()),
358
- );
359
- }
360
- }
361
- };
362
-
363
- const onError = (evt?: Event) => {
364
- onCloseOrError(new TRPCWebSocketClosedError({ cause: evt }));
365
- opts.onError?.(evt);
366
- };
367
-
368
- function connect(url: string) {
369
- if (opts.connectionParams) {
370
- // append `?connectionParams=1` when connection params are used
371
- const prefix = url.includes('?') ? '&' : '?';
372
- url += prefix + 'connectionParams=1';
373
- }
374
-
375
- const ws = new WebSocketImpl(url);
376
- self.ws = ws;
377
-
378
- clearTimeout(connectTimer);
379
- connectTimer = undefined;
380
-
381
- ws.onopen = () => {
382
- async function sendConnectionParams() {
383
- if (!opts.connectionParams) {
384
- return;
385
- }
386
-
387
- const connectMsg: TRPCConnectionParamsMessage = {
388
- method: 'connectionParams',
389
- data: await resultOf(opts.connectionParams),
390
- };
391
-
392
- ws.send(JSON.stringify(connectMsg));
393
- }
394
- function handleKeepAlive() {
395
- if (!opts.keepAlive?.enabled) {
396
- return;
397
- }
398
- const { pongTimeoutMs = 1_000, intervalMs = 5_000 } = opts.keepAlive;
399
-
400
- const schedulePing = () => {
401
- const schedulePongTimeout = () => {
402
- pongTimeout = setTimeout(() => {
403
- const wasOpen = self.state === 'open';
404
- destroy();
405
- if (wasOpen) {
406
- opts.onClose?.();
407
- }
408
- }, pongTimeoutMs);
409
- };
410
- pingTimeout = setTimeout(() => {
411
- ws.send('PING');
412
- schedulePongTimeout();
413
- }, intervalMs);
414
- };
415
- ws.addEventListener('message', () => {
416
- clearTimeout(pingTimeout);
417
- clearTimeout(pongTimeout);
418
-
419
- schedulePing();
420
- });
421
- schedulePing();
422
- }
423
- run(async () => {
424
- /* istanbul ignore next -- @preserve */
425
- if (activeConnection?.ws !== ws) {
426
- return;
427
- }
428
- handleKeepAlive();
429
-
430
- await sendConnectionParams();
431
-
432
- connectAttempt = 0;
433
- self.state = 'open';
434
-
435
- // Update connection state
436
- connectionState.next({
437
- type: 'state',
438
- state: 'pending',
439
- error: null,
440
- });
441
-
442
- opts.onOpen?.();
443
- dispatch();
444
- }).catch((cause: unknown) => {
445
- ws.close(
446
- // "Status codes in the range 3000-3999 are reserved for use by libraries, frameworks, and applications"
447
- 3000,
448
- );
449
- onCloseOrError(
450
- new TRPCWebSocketClosedError({
451
- message: 'Initialization error',
452
- cause,
453
- }),
454
- );
455
- });
456
- };
457
- ws.onerror = onError;
458
- const handleIncomingRequest = (req: TRPCClientIncomingRequest) => {
459
- if (self !== activeConnection) {
460
- return;
461
- }
462
-
463
- if (req.method === 'reconnect') {
464
- reconnect(
465
- new TRPCWebSocketClosedError({
466
- message: 'Server requested reconnect',
467
- }),
468
- );
469
- // notify subscribers
470
- for (const pendingReq of Object.values(pendingRequests)) {
471
- if (pendingReq.type === 'subscription') {
472
- resumeSubscriptionOnReconnect(pendingReq);
473
- }
474
- }
475
- }
476
- };
477
- const handleIncomingResponse = (data: TRPCResponseMessage) => {
478
- const req = data.id !== null && pendingRequests[data.id];
479
- if (!req) {
480
- // do something?
481
- return;
482
- }
483
-
484
- req.callbacks.next?.(data);
485
- if (self === activeConnection && req.connection !== activeConnection) {
486
- // gracefully replace old connection with a new connection
487
- req.connection = self;
488
- }
489
- if (req.connection !== self) {
490
- // the connection has been replaced
491
- return;
492
- }
493
-
494
- if (
495
- 'result' in data &&
496
- data.result.type === 'data' &&
497
- typeof data.result.id === 'string'
498
- ) {
499
- req.lastEventId = data.result.id;
500
- }
501
- if (
502
- 'result' in data &&
503
- data.result.type === 'stopped' &&
504
- activeConnection === self
505
- ) {
506
- req.callbacks.complete();
507
- }
508
- };
509
-
510
- ws.onmessage = (event) => {
511
- const { data } = event;
512
- if (data === 'PONG') {
513
- return;
514
- }
515
- if (data === 'PING') {
516
- ws.send('PONG');
517
- return;
518
- }
519
- startLazyDisconnectTimer();
520
-
521
- const msg = JSON.parse(data) as TRPCClientIncomingMessage;
522
-
523
- if ('method' in msg) {
524
- handleIncomingRequest(msg);
525
- } else {
526
- handleIncomingResponse(msg);
527
- }
528
- if (self !== activeConnection) {
529
- // when receiving a message, we close old connection that has no pending requests
530
- closeIfNoPending(self);
531
- }
532
- };
533
-
534
- ws.onclose = (event) => {
535
- const wasOpen = self.state === 'open';
536
-
537
- destroy();
538
- onCloseOrError(new TRPCWebSocketClosedError({ cause: event }));
539
-
540
- if (wasOpen) {
541
- opts.onClose?.(event);
542
- }
543
- };
544
- }
545
-
546
- Promise.resolve(resultOf(opts.url))
547
- .then(connect)
548
- .catch(() => {
549
- onCloseOrError(new Error('Failed to resolve url'));
550
- });
551
- return self;
552
- }
553
-
554
- function request(opts: {
555
- op: Operation;
556
- callbacks: TCallbacks;
557
- lastEventId: string | undefined;
558
- }): UnsubscribeFn {
559
- const { op, callbacks, lastEventId } = opts;
560
- const { type, input, path, id } = op;
561
- const envelope: TRPCRequestMessage = {
562
- id,
563
- method: type,
564
- params: {
565
- input,
566
- path,
567
- lastEventId,
568
- },
569
- };
570
-
571
- pendingRequests[id] = {
572
- connection: null,
573
- type,
574
- callbacks,
575
- op,
576
- lastEventId,
577
- };
578
-
579
- // enqueue message
580
- outgoing.push(envelope);
581
-
582
- dispatch();
583
-
584
- return () => {
585
- const callbacks = pendingRequests[id]?.callbacks;
586
- delete pendingRequests[id];
587
- outgoing = outgoing.filter((msg) => msg.id !== id);
588
-
589
- callbacks?.complete?.();
590
- if (activeConnection?.state === 'open' && op.type === 'subscription') {
591
- outgoing.push({
592
- id,
593
- method: 'subscription.stop',
594
- });
595
- dispatch();
596
- }
597
- startLazyDisconnectTimer();
598
- };
599
- }
600
-
601
- return {
602
- close: () => {
603
- connectAttempt = 0;
604
-
605
- for (const req of Object.values(pendingRequests)) {
606
- if (req.type === 'subscription') {
607
- req.callbacks.complete();
608
- } else if (!req.connection) {
609
- // close pending requests that aren't attached to a connection yet
610
- req.callbacks.error(
611
- TRPCClientError.from(
612
- new TRPCWebSocketClosedError({
613
- message: 'Closed before connection was established',
614
- }),
615
- ),
616
- );
617
- }
618
- }
619
- if (activeConnection) {
620
- closeIfNoPending(activeConnection);
621
- }
622
- clearTimeout(connectTimer);
623
- connectTimer = undefined;
624
- activeConnection = null;
625
- },
626
- request,
627
- get connection() {
628
- return activeConnection;
629
- },
630
- /**
631
- * Reconnect to the WebSocket server
632
- */
633
- reconnect,
634
- connectionState: connectionState,
635
- };
636
- }
637
-
638
- /**
639
- * @see https://trpc.io/docs/v11/client/links/wsLink
640
- * @deprecated
641
- * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
642
- * See https://github.com/trpc/trpc/issues/6109
643
- */
644
- export type TRPCWebSocketClient = ReturnType<typeof createWSClient>;
645
-
646
- /**
647
- * @see https://trpc.io/docs/v11/client/links/wsLink
648
- * @deprecated
649
- * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
650
- * See https://github.com/trpc/trpc/issues/6109
651
- */
652
- export type WebSocketLinkOptions<TRouter extends AnyRouter> = {
653
- client: TRPCWebSocketClient;
654
- } & TransformerOptions<inferClientTypes<TRouter>>;
655
- class TRPCWebSocketClosedError extends Error {
656
- constructor(opts?: { cause?: unknown; message?: string }) {
657
- super(
658
- opts?.message ?? 'WebSocket closed',
659
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
660
- // @ts-ignore https://github.com/tc39/proposal-error-cause
661
- {
662
- cause: opts?.cause,
663
- },
664
- );
665
- this.name = 'TRPCWebSocketClosedError';
666
- Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);
667
- }
668
- }
669
-
670
- /**
671
- * @see https://trpc.io/docs/v11/client/links/wsLink
672
- * @deprecated
673
- * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
674
- * See https://github.com/trpc/trpc/issues/6109
675
- */
676
- export function wsLink<TRouter extends AnyRouter>(
677
- opts: WebSocketLinkOptions<TRouter>,
678
- ): TRPCLink<TRouter> {
679
- const transformer = getTransformer(opts.transformer);
680
- return () => {
681
- const { client } = opts;
682
- return ({ op }) => {
683
- return observable((observer) => {
684
- const { type, path, id, context } = op;
685
-
686
- const input = transformer.input.serialize(op.input);
687
-
688
- const connState =
689
- type === 'subscription'
690
- ? client.connectionState.subscribe({
691
- next(result) {
692
- observer.next({
693
- result,
694
- context,
695
- });
696
- },
697
- })
698
- : null;
699
- const unsubscribeRequest = client.request({
700
- op: { type, path, input, id, context, signal: null },
701
- callbacks: {
702
- error(err) {
703
- observer.error(err);
704
- unsubscribeRequest();
705
- },
706
- complete() {
707
- observer.complete();
708
- },
709
- next(event) {
710
- const transformed = transformResult(event, transformer.output);
711
-
712
- if (!transformed.ok) {
713
- observer.error(TRPCClientError.from(transformed.error));
714
- return;
715
- }
716
- observer.next({
717
- result: transformed.result,
718
- });
719
-
720
- if (op.type !== 'subscription') {
721
- // if it isn't a subscription we don't care about next response
722
-
723
- unsubscribeRequest();
724
- observer.complete();
725
- }
726
- },
727
- },
728
- lastEventId: undefined,
729
- });
730
- return () => {
731
- unsubscribeRequest();
732
- connState?.unsubscribe();
733
- };
734
- });
735
- };
736
- };
737
- }