@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.
- package/dist/TRPCClientError.d.ts +1 -1
- package/dist/TRPCClientError.d.ts.map +1 -1
- package/dist/bundle-analysis.json +58 -46
- package/dist/index.js +2 -0
- package/dist/index.mjs +1 -0
- package/dist/internals/TRPCUntypedClient.d.ts +3 -3
- package/dist/internals/TRPCUntypedClient.d.ts.map +1 -1
- package/dist/internals/TRPCUntypedClient.js +24 -8
- package/dist/internals/TRPCUntypedClient.mjs +24 -8
- package/dist/links/httpSubscriptionLink.d.ts +7 -7
- package/dist/links/httpSubscriptionLink.d.ts.map +1 -1
- package/dist/links/httpSubscriptionLink.js +61 -1
- package/dist/links/httpSubscriptionLink.mjs +62 -2
- package/dist/links/internals/retryLink.d.ts +26 -6
- package/dist/links/internals/retryLink.d.ts.map +1 -1
- package/dist/links/internals/retryLink.js +43 -0
- package/dist/links/internals/retryLink.mjs +41 -0
- package/dist/links/internals/subscriptions.d.ts +20 -0
- package/dist/links/internals/subscriptions.d.ts.map +1 -0
- package/dist/links/loggerLink.d.ts +4 -4
- package/dist/links/loggerLink.d.ts.map +1 -1
- package/dist/links/loggerLink.js +1 -1
- package/dist/links/loggerLink.mjs +1 -1
- package/dist/links/types.d.ts +5 -4
- package/dist/links/types.d.ts.map +1 -1
- package/dist/links/wsLink.d.ts +24 -1
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/links/wsLink.js +125 -54
- package/dist/links/wsLink.mjs +126 -55
- package/dist/links.d.ts +1 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/unstable-internals.d.ts +1 -0
- package/dist/unstable-internals.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/TRPCClientError.ts +1 -1
- package/src/internals/TRPCUntypedClient.ts +23 -10
- package/src/links/httpSubscriptionLink.ts +88 -17
- package/src/links/internals/retryLink.ts +42 -24
- package/src/links/internals/subscriptions.ts +26 -0
- package/src/links/loggerLink.ts +16 -6
- package/src/links/types.ts +12 -4
- package/src/links/wsLink.ts +163 -56
- package/src/links.ts +1 -1
- package/src/unstable-internals.ts +1 -0
package/dist/links/wsLink.js
CHANGED
|
@@ -12,7 +12,12 @@ const lazyDefaults = {
|
|
|
12
12
|
enabled: false,
|
|
13
13
|
closeMs: 0
|
|
14
14
|
};
|
|
15
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
200
|
-
|
|
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
|
|
228
|
-
|
|
269
|
+
3000);
|
|
270
|
+
onCloseOrError(new TRPCWebSocketClosedError({
|
|
271
|
+
message: 'Initialization error',
|
|
272
|
+
cause
|
|
273
|
+
}));
|
|
229
274
|
});
|
|
230
|
-
}
|
|
231
|
-
ws.
|
|
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.
|
|
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.
|
|
335
|
+
};
|
|
336
|
+
ws.onclose = (event)=>{
|
|
289
337
|
const wasOpen = self.state === 'open';
|
|
290
|
-
|
|
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
|
-
}
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
461
|
+
unsubscribeRequest();
|
|
392
462
|
},
|
|
393
463
|
complete () {
|
|
394
464
|
observer.complete();
|
|
395
465
|
},
|
|
396
|
-
next (
|
|
397
|
-
const transformed = unstableCoreDoNotImport.transformResult(
|
|
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
|
-
|
|
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
|
-
|
|
485
|
+
unsubscribeRequest();
|
|
486
|
+
connState?.unsubscribe();
|
|
416
487
|
};
|
|
417
488
|
});
|
|
418
489
|
};
|