@trpc/client 11.0.0-rc.591 → 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
@@ -12,7 +12,12 @@ const lazyDefaults = {
12
12
  enabled: false,
13
13
  closeMs: 0
14
14
  };
15
- function createWSClient(opts) {
15
+ /**
16
+ * @see https://trpc.io/docs/v11/client/links/wsLink
17
+ * @deprecated
18
+ * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
19
+ * See https://github.com/trpc/trpc/issues/6109
20
+ */ function createWSClient(opts) {
16
21
  const { WebSocket: WebSocketImpl = WebSocket , retryDelayMs: retryDelayFn = exponentialBackoff , } = opts;
17
22
  const lazyOpts = {
18
23
  ...lazyDefaults,
@@ -30,11 +35,21 @@ function createWSClient(opts) {
30
35
  let connectionIndex = 0;
31
36
  let lazyDisconnectTimer = undefined;
32
37
  let activeConnection = lazyOpts.enabled ? null : createConnection();
38
+ const initState = activeConnection ? {
39
+ type: 'state',
40
+ state: 'connecting',
41
+ error: null
42
+ } : {
43
+ type: 'state',
44
+ state: 'idle',
45
+ error: null
46
+ };
47
+ const connectionState = observable.behaviorSubject(initState);
33
48
  /**
34
49
  * tries to send the list of messages
35
50
  */ function dispatch() {
36
51
  if (!activeConnection) {
37
- activeConnection = createConnection();
52
+ reconnect(null);
38
53
  return;
39
54
  }
40
55
  // using a timeout to batch messages
@@ -59,12 +74,12 @@ function createWSClient(opts) {
59
74
  startLazyDisconnectTimer();
60
75
  });
61
76
  }
62
- function tryReconnect() {
77
+ function tryReconnect(cause) {
63
78
  if (!!connectTimer) {
64
79
  return;
65
80
  }
66
81
  const timeout = retryDelayFn(connectAttempt++);
67
- reconnectInMs(timeout);
82
+ reconnectInMs(timeout, cause);
68
83
  }
69
84
  function hasPendingRequests(conn) {
70
85
  const requests = Object.values(pendingRequests);
@@ -73,20 +88,30 @@ function createWSClient(opts) {
73
88
  }
74
89
  return requests.some((req)=>req.connection === conn);
75
90
  }
76
- function reconnect() {
91
+ function reconnect(cause) {
77
92
  if (lazyOpts.enabled && !hasPendingRequests()) {
78
- // Skip reconnecting if there are pending requests and we're in lazy mode
93
+ // Skip reconnecting if there aren't pending requests and we're in lazy mode
79
94
  return;
80
95
  }
81
96
  const oldConnection = activeConnection;
82
97
  activeConnection = createConnection();
83
98
  oldConnection && closeIfNoPending(oldConnection);
99
+ const currentState = connectionState.get();
100
+ if (currentState.state !== 'connecting') {
101
+ connectionState.next({
102
+ type: 'state',
103
+ state: 'connecting',
104
+ error: cause ? TRPCClientError.TRPCClientError.from(cause) : null
105
+ });
106
+ }
84
107
  }
85
- function reconnectInMs(ms) {
108
+ function reconnectInMs(ms, cause) {
86
109
  if (connectTimer) {
87
110
  return;
88
111
  }
89
- connectTimer = setTimeout(reconnect, ms);
112
+ connectTimer = setTimeout(()=>{
113
+ reconnect(cause);
114
+ }, ms);
90
115
  }
91
116
  function closeIfNoPending(conn) {
92
117
  // disconnect as soon as there are are no pending requests
@@ -113,9 +138,14 @@ function createWSClient(opts) {
113
138
  if (!activeConnection) {
114
139
  return;
115
140
  }
116
- if (!hasPendingRequests(activeConnection)) {
141
+ if (!hasPendingRequests()) {
117
142
  activeConnection.ws?.close();
118
143
  activeConnection = null;
144
+ connectionState.next({
145
+ type: 'state',
146
+ state: 'idle',
147
+ error: null
148
+ });
119
149
  }
120
150
  }, lazyOpts.closeMs);
121
151
  };
@@ -127,16 +157,27 @@ function createWSClient(opts) {
127
157
  state: 'connecting'
128
158
  };
129
159
  clearTimeout(lazyDisconnectTimer);
130
- const onCloseOrError = ()=>{
160
+ function destroy() {
161
+ const noop = ()=>{
162
+ // no-op
163
+ };
164
+ const { ws } = self;
165
+ if (ws) {
166
+ ws.onclose = noop;
167
+ ws.onerror = noop;
168
+ ws.onmessage = noop;
169
+ ws.onopen = noop;
170
+ ws.close();
171
+ }
172
+ self.state = 'closed';
173
+ }
174
+ const onCloseOrError = (cause)=>{
131
175
  clearTimeout(pingTimeout);
132
176
  clearTimeout(pongTimeout);
133
- if (self.state === 'closed') {
134
- return;
135
- }
136
177
  self.state = 'closed';
137
178
  if (activeConnection === self) {
138
179
  // connection might have been replaced already
139
- tryReconnect();
180
+ tryReconnect(cause);
140
181
  }
141
182
  for (const [key, req] of Object.entries(pendingRequests)){
142
183
  if (req.connection !== self) {
@@ -149,25 +190,17 @@ function createWSClient(opts) {
149
190
  } else {
150
191
  // Queries and mutations will error if interrupted
151
192
  delete pendingRequests[key];
152
- req.callbacks.error?.(TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError('WebSocket closed prematurely')));
193
+ req.callbacks.error?.(TRPCClientError.TRPCClientError.from(cause ?? new TRPCWebSocketClosedError()));
153
194
  }
154
195
  }
155
196
  };
156
- const onClose = (code)=>{
157
- const wasOpen = self.state === 'open';
158
- onCloseOrError();
159
- if (wasOpen) {
160
- opts.onClose?.({
161
- code
162
- });
163
- }
164
- };
165
197
  const onError = (evt)=>{
166
- onCloseOrError();
198
+ onCloseOrError(new TRPCWebSocketClosedError({
199
+ cause: evt
200
+ }));
167
201
  opts.onError?.(evt);
168
202
  };
169
- run(async ()=>{
170
- let url = await urlWithConnectionParams.resultOf(opts.url);
203
+ function connect(url) {
171
204
  if (opts.connectionParams) {
172
205
  // append `?connectionParams=1` when connection params are used
173
206
  const prefix = url.includes('?') ? '&' : '?';
@@ -177,7 +210,7 @@ function createWSClient(opts) {
177
210
  self.ws = ws;
178
211
  clearTimeout(connectTimer);
179
212
  connectTimer = undefined;
180
- ws.addEventListener('open', ()=>{
213
+ ws.onopen = ()=>{
181
214
  async function sendConnectionParams() {
182
215
  if (!opts.connectionParams) {
183
216
  return;
@@ -196,8 +229,11 @@ function createWSClient(opts) {
196
229
  const schedulePing = ()=>{
197
230
  const schedulePongTimeout = ()=>{
198
231
  pongTimeout = setTimeout(()=>{
199
- ws.close(3001);
200
- onClose(3001);
232
+ const wasOpen = self.state === 'open';
233
+ destroy();
234
+ if (wasOpen) {
235
+ opts.onClose?.();
236
+ }
201
237
  }, pongTimeoutMs);
202
238
  };
203
239
  pingTimeout = setTimeout(()=>{
@@ -220,21 +256,32 @@ function createWSClient(opts) {
220
256
  await sendConnectionParams();
221
257
  connectAttempt = 0;
222
258
  self.state = 'open';
259
+ // Update connection state
260
+ connectionState.next({
261
+ type: 'state',
262
+ state: 'pending',
263
+ error: null
264
+ });
223
265
  opts.onOpen?.();
224
266
  dispatch();
225
267
  }).catch((cause)=>{
226
268
  ws.close(// "Status codes in the range 3000-3999 are reserved for use by libraries, frameworks, and applications"
227
- 3000, cause);
228
- onError();
269
+ 3000);
270
+ onCloseOrError(new TRPCWebSocketClosedError({
271
+ message: 'Initialization error',
272
+ cause
273
+ }));
229
274
  });
230
- });
231
- ws.addEventListener('error', onError);
275
+ };
276
+ ws.onerror = onError;
232
277
  const handleIncomingRequest = (req)=>{
233
278
  if (self !== activeConnection) {
234
279
  return;
235
280
  }
236
281
  if (req.method === 'reconnect') {
237
- reconnect();
282
+ reconnect(new TRPCWebSocketClosedError({
283
+ message: 'Server requested reconnect'
284
+ }));
238
285
  // notify subscribers
239
286
  for (const pendingReq of Object.values(pendingRequests)){
240
287
  if (pendingReq.type === 'subscription') {
@@ -265,7 +312,8 @@ function createWSClient(opts) {
265
312
  req.callbacks.complete();
266
313
  }
267
314
  };
268
- ws.addEventListener('message', ({ data })=>{
315
+ ws.onmessage = (event)=>{
316
+ const { data } = event;
269
317
  if (data === 'PONG') {
270
318
  return;
271
319
  }
@@ -284,17 +332,21 @@ function createWSClient(opts) {
284
332
  // when receiving a message, we close old connection that has no pending requests
285
333
  closeIfNoPending(self);
286
334
  }
287
- });
288
- ws.addEventListener('close', ({ code })=>{
335
+ };
336
+ ws.onclose = (event)=>{
289
337
  const wasOpen = self.state === 'open';
290
- onCloseOrError();
338
+ destroy();
339
+ onCloseOrError(new TRPCWebSocketClosedError({
340
+ cause: event
341
+ }));
291
342
  if (wasOpen) {
292
- opts.onClose?.({
293
- code
294
- });
343
+ opts.onClose?.(event);
295
344
  }
296
- });
297
- }).catch(onError);
345
+ };
346
+ }
347
+ Promise.resolve(urlWithConnectionParams.resultOf(opts.url)).then(connect).catch(()=>{
348
+ onCloseOrError(new Error('Failed to resolve url'));
349
+ });
298
350
  return self;
299
351
  }
300
352
  function request(opts) {
@@ -342,7 +394,9 @@ function createWSClient(opts) {
342
394
  req.callbacks.complete();
343
395
  } else if (!req.connection) {
344
396
  // close pending requests that aren't attached to a connection yet
345
- req.callbacks.error(TRPCClientError.TRPCClientError.from(new Error('Closed before connection was established')));
397
+ req.callbacks.error(TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError({
398
+ message: 'Closed before connection was established'
399
+ })));
346
400
  }
347
401
  }
348
402
  activeConnection && closeIfNoPending(activeConnection);
@@ -356,18 +410,26 @@ function createWSClient(opts) {
356
410
  },
357
411
  /**
358
412
  * Reconnect to the WebSocket server
359
- */ reconnect
413
+ */ reconnect,
414
+ connectionState: connectionState
360
415
  };
361
416
  }
362
417
  class TRPCWebSocketClosedError extends Error {
363
- constructor(message){
364
- super(message);
418
+ constructor(opts){
419
+ super(opts?.message ?? 'WebSocket closed', // eslint-disable-next-line @typescript-eslint/ban-ts-comment
420
+ // @ts-ignore https://github.com/tc39/proposal-error-cause
421
+ {
422
+ cause: opts?.cause
423
+ });
365
424
  this.name = 'TRPCWebSocketClosedError';
366
425
  Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);
367
426
  }
368
427
  }
369
428
  /**
370
429
  * @see https://trpc.io/docs/v11/client/links/wsLink
430
+ * @deprecated
431
+ * 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
432
+ * See https://github.com/trpc/trpc/issues/6109
371
433
  */ function wsLink(opts) {
372
434
  const transformer$1 = transformer.getTransformer(opts.transformer);
373
435
  return ()=>{
@@ -376,7 +438,15 @@ class TRPCWebSocketClosedError extends Error {
376
438
  return observable.observable((observer)=>{
377
439
  const { type , path , id , context } = op;
378
440
  const input = transformer$1.input.serialize(op.input);
379
- const unsub = client.request({
441
+ const connState = type === 'subscription' ? client.connectionState.subscribe({
442
+ next (result) {
443
+ observer.next({
444
+ result,
445
+ context
446
+ });
447
+ }
448
+ }) : null;
449
+ const unsubscribeRequest = client.request({
380
450
  op: {
381
451
  type,
382
452
  path,
@@ -388,13 +458,13 @@ class TRPCWebSocketClosedError extends Error {
388
458
  callbacks: {
389
459
  error (err) {
390
460
  observer.error(err);
391
- unsub();
461
+ unsubscribeRequest();
392
462
  },
393
463
  complete () {
394
464
  observer.complete();
395
465
  },
396
- next (message) {
397
- const transformed = unstableCoreDoNotImport.transformResult(message, transformer$1.output);
466
+ next (event) {
467
+ const transformed = unstableCoreDoNotImport.transformResult(event, transformer$1.output);
398
468
  if (!transformed.ok) {
399
469
  observer.error(TRPCClientError.TRPCClientError.from(transformed.error));
400
470
  return;
@@ -404,7 +474,7 @@ class TRPCWebSocketClosedError extends Error {
404
474
  });
405
475
  if (op.type !== 'subscription') {
406
476
  // if it isn't a subscription we don't care about next response
407
- unsub();
477
+ unsubscribeRequest();
408
478
  observer.complete();
409
479
  }
410
480
  }
@@ -412,7 +482,8 @@ class TRPCWebSocketClosedError extends Error {
412
482
  lastEventId: undefined
413
483
  });
414
484
  return ()=>{
415
- unsub();
485
+ unsubscribeRequest();
486
+ connState?.unsubscribe();
416
487
  };
417
488
  });
418
489
  };