@trpc/client 11.0.0-rc.592 → 11.0.0-rc.593

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 (44) hide show
  1. package/dist/TRPCClientError.d.ts +1 -1
  2. package/dist/TRPCClientError.d.ts.map +1 -1
  3. package/dist/bundle-analysis.json +58 -46
  4. package/dist/index.js +2 -0
  5. package/dist/index.mjs +1 -0
  6. package/dist/internals/TRPCUntypedClient.d.ts +3 -3
  7. package/dist/internals/TRPCUntypedClient.d.ts.map +1 -1
  8. package/dist/internals/TRPCUntypedClient.js +24 -8
  9. package/dist/internals/TRPCUntypedClient.mjs +24 -8
  10. package/dist/links/httpSubscriptionLink.d.ts +7 -7
  11. package/dist/links/httpSubscriptionLink.d.ts.map +1 -1
  12. package/dist/links/httpSubscriptionLink.js +61 -1
  13. package/dist/links/httpSubscriptionLink.mjs +62 -2
  14. package/dist/links/internals/retryLink.d.ts +26 -6
  15. package/dist/links/internals/retryLink.d.ts.map +1 -1
  16. package/dist/links/internals/retryLink.js +43 -0
  17. package/dist/links/internals/retryLink.mjs +41 -0
  18. package/dist/links/internals/subscriptions.d.ts +20 -0
  19. package/dist/links/internals/subscriptions.d.ts.map +1 -0
  20. package/dist/links/loggerLink.d.ts +4 -4
  21. package/dist/links/loggerLink.d.ts.map +1 -1
  22. package/dist/links/loggerLink.js +1 -1
  23. package/dist/links/loggerLink.mjs +1 -1
  24. package/dist/links/types.d.ts +5 -4
  25. package/dist/links/types.d.ts.map +1 -1
  26. package/dist/links/wsLink.d.ts +24 -1
  27. package/dist/links/wsLink.d.ts.map +1 -1
  28. package/dist/links/wsLink.js +125 -54
  29. package/dist/links/wsLink.mjs +126 -55
  30. package/dist/links.d.ts +1 -0
  31. package/dist/links.d.ts.map +1 -1
  32. package/dist/unstable-internals.d.ts +1 -0
  33. package/dist/unstable-internals.d.ts.map +1 -1
  34. package/package.json +4 -4
  35. package/src/TRPCClientError.ts +1 -1
  36. package/src/internals/TRPCUntypedClient.ts +23 -10
  37. package/src/links/httpSubscriptionLink.ts +88 -17
  38. package/src/links/internals/retryLink.ts +42 -24
  39. package/src/links/internals/subscriptions.ts +26 -0
  40. package/src/links/loggerLink.ts +16 -6
  41. package/src/links/types.ts +12 -4
  42. package/src/links/wsLink.ts +163 -56
  43. package/src/links.ts +1 -1
  44. package/src/unstable-internals.ts +1 -0
@@ -2,50 +2,68 @@
2
2
  // We're not actually exporting this link
3
3
  import type { Unsubscribable } from '@trpc/server/observable';
4
4
  import { observable } from '@trpc/server/observable';
5
- import type { AnyRouter } from '@trpc/server/unstable-core-do-not-import';
6
- import type { TRPCLink } from '../types';
5
+ import type { InferrableClientTypes } from '@trpc/server/unstable-core-do-not-import';
6
+ import type { TRPCClientError } from '../../TRPCClientError';
7
+ import type { Operation, TRPCLink } from '../types';
8
+
9
+ interface RetryLinkOptions<TInferrable extends InferrableClientTypes> {
10
+ /**
11
+ * The retry function
12
+ */
13
+ retry: (opts: RetryFnOptions<TInferrable>) => boolean;
14
+ }
15
+
16
+ interface RetryFnOptions<TInferrable extends InferrableClientTypes> {
17
+ /**
18
+ * The operation that failed
19
+ */
20
+ op: Operation;
21
+ /**
22
+ * The error that occurred
23
+ */
24
+ error: TRPCClientError<TInferrable>;
25
+ /**
26
+ * The number of attempts that have been made (including the first call)
27
+ */
28
+ attempts: number;
29
+ }
7
30
 
8
31
  /**
9
- * @internal used for testing
32
+ * @see https://trpc.io/docs/v11/client/links/retryLink
10
33
  */
11
- export function retryLink<TRouter extends AnyRouter = AnyRouter>(opts: {
12
- attempts: number;
13
- }): TRPCLink<TRouter> {
34
+ export function retryLink<TInferrable extends InferrableClientTypes>(
35
+ opts: RetryLinkOptions<TInferrable>,
36
+ ): TRPCLink<TInferrable> {
14
37
  // initialized config
15
38
  return () => {
16
39
  // initialized in app
17
40
  return ({ op, next }) => {
18
41
  // initialized for request
19
42
  return observable((observer) => {
20
- let next$: Unsubscribable | null = null;
21
- let attempts = 0;
22
- let isDone = false;
23
- function attempt() {
24
- attempts++;
25
- next$?.unsubscribe();
43
+ let next$: Unsubscribable;
44
+
45
+ attempt(1);
46
+
47
+ function attempt(attempts: number) {
26
48
  next$ = next(op).subscribe({
27
49
  error(error) {
28
- /* istanbul ignore if -- @preserve */
29
- if (attempts >= opts.attempts) {
30
- observer.error(error);
31
- return;
32
- }
33
- attempt();
50
+ const shouldRetry = opts.retry({
51
+ op,
52
+ attempts,
53
+ error,
54
+ });
55
+ shouldRetry ? attempt(attempts + 1) : observer.error(error);
34
56
  },
35
57
  next(result) {
36
58
  observer.next(result);
37
59
  },
38
60
  complete() {
39
- if (isDone) {
40
- observer.complete();
41
- }
61
+ observer.complete();
42
62
  },
43
63
  });
44
64
  }
45
- attempt();
46
65
  return () => {
47
- isDone = true;
48
- next$?.unsubscribe();
66
+ next$.unsubscribe();
49
67
  };
50
68
  });
51
69
  };
@@ -0,0 +1,26 @@
1
+ interface ConnectionStateBase<TError> {
2
+ type: 'state';
3
+ data?: never;
4
+ error: TError | null;
5
+ }
6
+
7
+ interface ConnectionIdleState<TError> extends ConnectionStateBase<TError> {
8
+ state: 'idle';
9
+ error: null;
10
+ }
11
+
12
+ interface ConnectionConnectingState<TError>
13
+ extends ConnectionStateBase<TError> {
14
+ state: 'connecting';
15
+ error: TError | null;
16
+ }
17
+
18
+ interface ConnectionPendingState extends ConnectionStateBase<never> {
19
+ state: 'pending';
20
+ error: null;
21
+ }
22
+
23
+ export type TRPCConnectionState<TError> =
24
+ | ConnectionIdleState<TError>
25
+ | ConnectionConnectingState<TError>
26
+ | ConnectionPendingState;
@@ -6,7 +6,10 @@
6
6
  // even if end-user `tsconfig.json` omits it in the `lib` array.
7
7
 
8
8
  import { observable, tap } from '@trpc/server/observable';
9
- import type { AnyRouter } from '@trpc/server/unstable-core-do-not-import';
9
+ import type {
10
+ AnyRouter,
11
+ InferrableClientTypes,
12
+ } from '@trpc/server/unstable-core-do-not-import';
10
13
  import type { TRPCClientError } from '../TRPCClientError';
11
14
  import type { Operation, OperationResultEnvelope, TRPCLink } from './types';
12
15
 
@@ -15,10 +18,12 @@ type ConsoleEsque = {
15
18
  error: (...args: any[]) => void;
16
19
  };
17
20
 
18
- type EnableFnOptions<TRouter extends AnyRouter> =
21
+ type EnableFnOptions<TRouter extends InferrableClientTypes> =
19
22
  | {
20
23
  direction: 'down';
21
- result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
24
+ result:
25
+ | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
26
+ | TRPCClientError<TRouter>;
22
27
  }
23
28
  | (Operation & {
24
29
  direction: 'up';
@@ -34,7 +39,9 @@ type LoggerLinkFnOptions<TRouter extends AnyRouter> = Operation &
34
39
  * Request result
35
40
  */
36
41
  direction: 'down';
37
- result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
42
+ result:
43
+ | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
44
+ | TRPCClientError<TRouter>;
38
45
  elapsedMs: number;
39
46
  }
40
47
  | {
@@ -193,7 +200,8 @@ const defaultLogger =
193
200
  const fn: 'error' | 'log' =
194
201
  props.direction === 'down' &&
195
202
  props.result &&
196
- (props.result instanceof Error || 'error' in props.result.result)
203
+ (props.result instanceof Error ||
204
+ ('error' in props.result.result && props.result.result.error))
197
205
  ? 'error'
198
206
  : 'log';
199
207
 
@@ -226,7 +234,9 @@ export function loggerLink<TRouter extends AnyRouter = AnyRouter>(
226
234
  });
227
235
  const requestStartTime = Date.now();
228
236
  function logResult(
229
- result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>,
237
+ result:
238
+ | OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
239
+ | TRPCClientError<TRouter>,
230
240
  ) {
231
241
  const elapsedMs = Date.now() - requestStartTime;
232
242
 
@@ -7,6 +7,7 @@ import type {
7
7
  } from '@trpc/server/unstable-core-do-not-import';
8
8
  import type { ResponseEsque } from '../internals/types';
9
9
  import type { TRPCClientError } from '../TRPCClientError';
10
+ import type { TRPCConnectionState } from './internals/subscriptions';
10
11
 
11
12
  export {
12
13
  isNonJsonSerializable,
@@ -58,10 +59,11 @@ export interface TRPCClientRuntime {
58
59
  /**
59
60
  * @internal
60
61
  */
61
- export interface OperationResultEnvelope<TOutput> {
62
+ export interface OperationResultEnvelope<TOutput, TError> {
62
63
  result:
63
64
  | TRPCResultMessage<TOutput>['result']
64
- | TRPCSuccessResponse<TOutput>['result'];
65
+ | TRPCSuccessResponse<TOutput>['result']
66
+ | TRPCConnectionState<TError>;
65
67
  context?: OperationContext;
66
68
  }
67
69
 
@@ -71,7 +73,10 @@ export interface OperationResultEnvelope<TOutput> {
71
73
  export type OperationResultObservable<
72
74
  TInferrable extends InferrableClientTypes,
73
75
  TOutput,
74
- > = Observable<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
76
+ > = Observable<
77
+ OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>,
78
+ TRPCClientError<TInferrable>
79
+ >;
75
80
 
76
81
  /**
77
82
  * @internal
@@ -79,7 +84,10 @@ export type OperationResultObservable<
79
84
  export type OperationResultObserver<
80
85
  TInferrable extends InferrableClientTypes,
81
86
  TOutput,
82
- > = Observer<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
87
+ > = Observer<
88
+ OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>,
89
+ TRPCClientError<TInferrable>
90
+ >;
83
91
 
84
92
  /**
85
93
  * @internal
@@ -1,5 +1,5 @@
1
1
  import type { Observer, UnsubscribeFn } from '@trpc/server/observable';
2
- import { observable } from '@trpc/server/observable';
2
+ import { behaviorSubject, observable } from '@trpc/server/observable';
3
3
  import type { TRPCConnectionParamsMessage } from '@trpc/server/rpc';
4
4
  import type {
5
5
  AnyRouter,
@@ -16,6 +16,7 @@ import { transformResult } from '@trpc/server/unstable-core-do-not-import';
16
16
  import { TRPCClientError } from '../TRPCClientError';
17
17
  import type { TransformerOptions } from '../unstable-internals';
18
18
  import { getTransformer } from '../unstable-internals';
19
+ import type { TRPCConnectionState } from './internals/subscriptions';
19
20
  import {
20
21
  resultOf,
21
22
  type UrlOptionsWithConnectionParams,
@@ -100,6 +101,13 @@ const lazyDefaults: LazyOptions = {
100
101
  enabled: false,
101
102
  closeMs: 0,
102
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
+ */
103
111
  export function createWSClient(opts: WebSocketClientOptions) {
104
112
  const {
105
113
  WebSocket: WebSocketImpl = WebSocket,
@@ -165,12 +173,27 @@ export function createWSClient(opts: WebSocketClientOptions) {
165
173
  }
166
174
  );
167
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
+
168
191
  /**
169
192
  * tries to send the list of messages
170
193
  */
171
194
  function dispatch() {
172
195
  if (!activeConnection) {
173
- activeConnection = createConnection();
196
+ reconnect(null);
174
197
  return;
175
198
  }
176
199
  // using a timeout to batch messages
@@ -196,12 +219,13 @@ export function createWSClient(opts: WebSocketClientOptions) {
196
219
  startLazyDisconnectTimer();
197
220
  });
198
221
  }
199
- function tryReconnect() {
222
+ function tryReconnect(cause: Error | null) {
200
223
  if (!!connectTimer) {
201
224
  return;
202
225
  }
226
+
203
227
  const timeout = retryDelayFn(connectAttempt++);
204
- reconnectInMs(timeout);
228
+ reconnectInMs(timeout, cause);
205
229
  }
206
230
  function hasPendingRequests(conn?: Connection) {
207
231
  const requests = Object.values(pendingRequests);
@@ -211,20 +235,31 @@ export function createWSClient(opts: WebSocketClientOptions) {
211
235
  return requests.some((req) => req.connection === conn);
212
236
  }
213
237
 
214
- function reconnect() {
238
+ function reconnect(cause: Error | null) {
215
239
  if (lazyOpts.enabled && !hasPendingRequests()) {
216
- // Skip reconnecting if there are pending requests and we're in lazy mode
240
+ // Skip reconnecting if there aren't pending requests and we're in lazy mode
217
241
  return;
218
242
  }
219
243
  const oldConnection = activeConnection;
220
244
  activeConnection = createConnection();
221
245
  oldConnection && closeIfNoPending(oldConnection);
246
+
247
+ const currentState = connectionState.get();
248
+ if (currentState.state !== 'connecting') {
249
+ connectionState.next({
250
+ type: 'state',
251
+ state: 'connecting',
252
+ error: cause ? TRPCClientError.from(cause) : null,
253
+ });
254
+ }
222
255
  }
223
- function reconnectInMs(ms: number) {
256
+ function reconnectInMs(ms: number, cause: Error | null) {
224
257
  if (connectTimer) {
225
258
  return;
226
259
  }
227
- connectTimer = setTimeout(reconnect, ms);
260
+ connectTimer = setTimeout(() => {
261
+ reconnect(cause);
262
+ }, ms);
228
263
  }
229
264
 
230
265
  function closeIfNoPending(conn: Connection) {
@@ -255,9 +290,14 @@ export function createWSClient(opts: WebSocketClientOptions) {
255
290
  return;
256
291
  }
257
292
 
258
- if (!hasPendingRequests(activeConnection)) {
293
+ if (!hasPendingRequests()) {
259
294
  activeConnection.ws?.close();
260
295
  activeConnection = null;
296
+ connectionState.next({
297
+ type: 'state',
298
+ state: 'idle',
299
+ error: null,
300
+ });
261
301
  }
262
302
  }, lazyOpts.closeMs);
263
303
  };
@@ -272,18 +312,31 @@ export function createWSClient(opts: WebSocketClientOptions) {
272
312
 
273
313
  clearTimeout(lazyDisconnectTimer);
274
314
 
275
- const onCloseOrError = () => {
315
+ function destroy() {
316
+ const noop = () => {
317
+ // no-op
318
+ };
319
+ const { ws } = self;
320
+ if (ws) {
321
+ ws.onclose = noop;
322
+ ws.onerror = noop;
323
+ ws.onmessage = noop;
324
+ ws.onopen = noop;
325
+
326
+ ws.close();
327
+ }
328
+
329
+ self.state = 'closed';
330
+ }
331
+
332
+ const onCloseOrError = (cause: Error | null) => {
276
333
  clearTimeout(pingTimeout);
277
334
  clearTimeout(pongTimeout);
278
335
 
279
- if (self.state === 'closed') {
280
- return;
281
- }
282
-
283
- (self as Connection).state = 'closed';
336
+ self.state = 'closed';
284
337
  if (activeConnection === self) {
285
338
  // connection might have been replaced already
286
- tryReconnect();
339
+ tryReconnect(cause);
287
340
  }
288
341
 
289
342
  for (const [key, req] of Object.entries(pendingRequests)) {
@@ -299,29 +352,18 @@ export function createWSClient(opts: WebSocketClientOptions) {
299
352
  // Queries and mutations will error if interrupted
300
353
  delete pendingRequests[key];
301
354
  req.callbacks.error?.(
302
- TRPCClientError.from(
303
- new TRPCWebSocketClosedError('WebSocket closed prematurely'),
304
- ),
355
+ TRPCClientError.from(cause ?? new TRPCWebSocketClosedError()),
305
356
  );
306
357
  }
307
358
  }
308
359
  };
309
360
 
310
- const onClose = (code: number) => {
311
- const wasOpen = self.state === 'open';
312
- onCloseOrError();
313
-
314
- if (wasOpen) {
315
- opts.onClose?.({ code });
316
- }
317
- };
318
-
319
361
  const onError = (evt?: Event) => {
320
- onCloseOrError();
362
+ onCloseOrError(new TRPCWebSocketClosedError({ cause: evt }));
321
363
  opts.onError?.(evt);
322
364
  };
323
- run(async () => {
324
- let url = await resultOf(opts.url);
365
+
366
+ function connect(url: string) {
325
367
  if (opts.connectionParams) {
326
368
  // append `?connectionParams=1` when connection params are used
327
369
  const prefix = url.includes('?') ? '&' : '?';
@@ -334,7 +376,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
334
376
  clearTimeout(connectTimer);
335
377
  connectTimer = undefined;
336
378
 
337
- ws.addEventListener('open', () => {
379
+ ws.onopen = () => {
338
380
  async function sendConnectionParams() {
339
381
  if (!opts.connectionParams) {
340
382
  return;
@@ -356,8 +398,11 @@ export function createWSClient(opts: WebSocketClientOptions) {
356
398
  const schedulePing = () => {
357
399
  const schedulePongTimeout = () => {
358
400
  pongTimeout = setTimeout(() => {
359
- ws.close(3001);
360
- onClose(3001);
401
+ const wasOpen = self.state === 'open';
402
+ destroy();
403
+ if (wasOpen) {
404
+ opts.onClose?.();
405
+ }
361
406
  }, pongTimeoutMs);
362
407
  };
363
408
  pingTimeout = setTimeout(() => {
@@ -385,25 +430,40 @@ export function createWSClient(opts: WebSocketClientOptions) {
385
430
  connectAttempt = 0;
386
431
  self.state = 'open';
387
432
 
433
+ // Update connection state
434
+ connectionState.next({
435
+ type: 'state',
436
+ state: 'pending',
437
+ error: null,
438
+ });
439
+
388
440
  opts.onOpen?.();
389
441
  dispatch();
390
- }).catch((cause) => {
442
+ }).catch((cause: unknown) => {
391
443
  ws.close(
392
444
  // "Status codes in the range 3000-3999 are reserved for use by libraries, frameworks, and applications"
393
445
  3000,
394
- cause,
395
446
  );
396
- onError();
447
+ onCloseOrError(
448
+ new TRPCWebSocketClosedError({
449
+ message: 'Initialization error',
450
+ cause,
451
+ }),
452
+ );
397
453
  });
398
- });
399
- ws.addEventListener('error', onError);
454
+ };
455
+ ws.onerror = onError;
400
456
  const handleIncomingRequest = (req: TRPCClientIncomingRequest) => {
401
457
  if (self !== activeConnection) {
402
458
  return;
403
459
  }
404
460
 
405
461
  if (req.method === 'reconnect') {
406
- reconnect();
462
+ reconnect(
463
+ new TRPCWebSocketClosedError({
464
+ message: 'Server requested reconnect',
465
+ }),
466
+ );
407
467
  // notify subscribers
408
468
  for (const pendingReq of Object.values(pendingRequests)) {
409
469
  if (pendingReq.type === 'subscription') {
@@ -445,7 +505,8 @@ export function createWSClient(opts: WebSocketClientOptions) {
445
505
  }
446
506
  };
447
507
 
448
- ws.addEventListener('message', ({ data }) => {
508
+ ws.onmessage = (event) => {
509
+ const { data } = event;
449
510
  if (data === 'PONG') {
450
511
  return;
451
512
  }
@@ -466,18 +527,25 @@ export function createWSClient(opts: WebSocketClientOptions) {
466
527
  // when receiving a message, we close old connection that has no pending requests
467
528
  closeIfNoPending(self);
468
529
  }
469
- });
530
+ };
470
531
 
471
- ws.addEventListener('close', ({ code }) => {
532
+ ws.onclose = (event) => {
472
533
  const wasOpen = self.state === 'open';
473
534
 
474
- onCloseOrError();
535
+ destroy();
536
+ onCloseOrError(new TRPCWebSocketClosedError({ cause: event }));
475
537
 
476
538
  if (wasOpen) {
477
- opts.onClose?.({ code });
539
+ opts.onClose?.(event);
478
540
  }
541
+ };
542
+ }
543
+
544
+ Promise.resolve(resultOf(opts.url))
545
+ .then(connect)
546
+ .catch(() => {
547
+ onCloseOrError(new Error('Failed to resolve url'));
479
548
  });
480
- }).catch(onError);
481
549
  return self;
482
550
  }
483
551
 
@@ -527,6 +595,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
527
595
  startLazyDisconnectTimer();
528
596
  };
529
597
  }
598
+
530
599
  return {
531
600
  close: () => {
532
601
  connectAttempt = 0;
@@ -538,7 +607,9 @@ export function createWSClient(opts: WebSocketClientOptions) {
538
607
  // close pending requests that aren't attached to a connection yet
539
608
  req.callbacks.error(
540
609
  TRPCClientError.from(
541
- new Error('Closed before connection was established'),
610
+ new TRPCWebSocketClosedError({
611
+ message: 'Closed before connection was established',
612
+ }),
542
613
  ),
543
614
  );
544
615
  }
@@ -556,16 +627,37 @@ export function createWSClient(opts: WebSocketClientOptions) {
556
627
  * Reconnect to the WebSocket server
557
628
  */
558
629
  reconnect,
630
+ connectionState: connectionState,
559
631
  };
560
632
  }
633
+
634
+ /**
635
+ * @see https://trpc.io/docs/v11/client/links/wsLink
636
+ * @deprecated
637
+ * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
638
+ * See https://github.com/trpc/trpc/issues/6109
639
+ */
561
640
  export type TRPCWebSocketClient = ReturnType<typeof createWSClient>;
562
641
 
642
+ /**
643
+ * @see https://trpc.io/docs/v11/client/links/wsLink
644
+ * @deprecated
645
+ * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
646
+ * See https://github.com/trpc/trpc/issues/6109
647
+ */
563
648
  export type WebSocketLinkOptions<TRouter extends AnyRouter> = {
564
649
  client: TRPCWebSocketClient;
565
650
  } & TransformerOptions<inferClientTypes<TRouter>>;
566
651
  class TRPCWebSocketClosedError extends Error {
567
- constructor(message: string) {
568
- super(message);
652
+ constructor(opts?: { cause?: unknown; message?: string }) {
653
+ super(
654
+ opts?.message ?? 'WebSocket closed',
655
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
656
+ // @ts-ignore https://github.com/tc39/proposal-error-cause
657
+ {
658
+ cause: opts?.cause,
659
+ },
660
+ );
569
661
  this.name = 'TRPCWebSocketClosedError';
570
662
  Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);
571
663
  }
@@ -573,6 +665,9 @@ class TRPCWebSocketClosedError extends Error {
573
665
 
574
666
  /**
575
667
  * @see https://trpc.io/docs/v11/client/links/wsLink
668
+ * @deprecated
669
+ * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
670
+ * See https://github.com/trpc/trpc/issues/6109
576
671
  */
577
672
  export function wsLink<TRouter extends AnyRouter>(
578
673
  opts: WebSocketLinkOptions<TRouter>,
@@ -586,18 +681,29 @@ export function wsLink<TRouter extends AnyRouter>(
586
681
 
587
682
  const input = transformer.input.serialize(op.input);
588
683
 
589
- const unsub = client.request({
684
+ const connState =
685
+ type === 'subscription'
686
+ ? client.connectionState.subscribe({
687
+ next(result) {
688
+ observer.next({
689
+ result,
690
+ context,
691
+ });
692
+ },
693
+ })
694
+ : null;
695
+ const unsubscribeRequest = client.request({
590
696
  op: { type, path, input, id, context, signal: null },
591
697
  callbacks: {
592
698
  error(err) {
593
- observer.error(err as TRPCClientError<any>);
594
- unsub();
699
+ observer.error(err);
700
+ unsubscribeRequest();
595
701
  },
596
702
  complete() {
597
703
  observer.complete();
598
704
  },
599
- next(message) {
600
- const transformed = transformResult(message, transformer.output);
705
+ next(event) {
706
+ const transformed = transformResult(event, transformer.output);
601
707
 
602
708
  if (!transformed.ok) {
603
709
  observer.error(TRPCClientError.from(transformed.error));
@@ -610,7 +716,7 @@ export function wsLink<TRouter extends AnyRouter>(
610
716
  if (op.type !== 'subscription') {
611
717
  // if it isn't a subscription we don't care about next response
612
718
 
613
- unsub();
719
+ unsubscribeRequest();
614
720
  observer.complete();
615
721
  }
616
722
  },
@@ -618,7 +724,8 @@ export function wsLink<TRouter extends AnyRouter>(
618
724
  lastEventId: undefined,
619
725
  });
620
726
  return () => {
621
- unsub();
727
+ unsubscribeRequest();
728
+ connState?.unsubscribe();
622
729
  };
623
730
  });
624
731
  };
package/src/links.ts CHANGED
@@ -8,7 +8,7 @@ export * from './links/loggerLink';
8
8
  export * from './links/splitLink';
9
9
  export * from './links/wsLink';
10
10
  export * from './links/httpSubscriptionLink';
11
+ export * from './links/internals/retryLink';
11
12
 
12
13
  // These are not public (yet) as we get this functionality from tanstack query
13
- // export * from './links/internals/retryLink';
14
14
  // export * from './links/internals/dedupeLink';
@@ -1 +1,2 @@
1
1
  export * from './internals/transformer';
2
+ export * from './links/internals/subscriptions';