@trpc/client 11.0.0-alpha-tmp-subscription-connection-state.488 → 11.0.0-alpha-tmp-12-06-react.665
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/TRPCClientError.js +19 -1
- package/dist/TRPCClientError.mjs +19 -1
- package/dist/bundle-analysis.json +124 -98
- package/dist/createTRPCClient.d.ts +3 -2
- package/dist/createTRPCClient.d.ts.map +1 -1
- package/dist/createTRPCClient.js +1 -1
- package/dist/createTRPCClient.mjs +1 -1
- package/dist/index.js +6 -6
- package/dist/index.mjs +2 -2
- package/dist/internals/TRPCUntypedClient.d.ts +5 -4
- package/dist/internals/TRPCUntypedClient.d.ts.map +1 -1
- package/dist/internals/TRPCUntypedClient.js +42 -12
- package/dist/internals/TRPCUntypedClient.mjs +42 -12
- package/dist/internals/inputWithTrackedEventId.d.ts +2 -0
- package/dist/internals/inputWithTrackedEventId.d.ts.map +1 -0
- package/dist/internals/inputWithTrackedEventId.js +16 -0
- package/dist/internals/inputWithTrackedEventId.mjs +14 -0
- package/dist/internals/signals.d.ts +15 -0
- package/dist/internals/signals.d.ts.map +1 -0
- package/dist/internals/signals.js +47 -0
- package/dist/internals/signals.mjs +44 -0
- package/dist/internals/transformer.d.ts +2 -2
- package/dist/internals/types.d.ts +1 -1
- package/dist/internals/types.d.ts.map +1 -1
- package/dist/links/HTTPBatchLinkOptions.d.ts +1 -1
- package/dist/links/httpBatchLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.js +4 -3
- package/dist/links/httpBatchLink.mjs +5 -4
- package/dist/links/httpBatchStreamLink.d.ts.map +1 -1
- package/dist/links/httpBatchStreamLink.js +6 -4
- package/dist/links/httpBatchStreamLink.mjs +7 -5
- package/dist/links/httpLink.d.ts +2 -2
- package/dist/links/httpLink.js +3 -3
- package/dist/links/httpLink.mjs +3 -3
- package/dist/links/httpSubscriptionLink.d.ts +11 -6
- package/dist/links/httpSubscriptionLink.d.ts.map +1 -1
- package/dist/links/httpSubscriptionLink.js +130 -94
- package/dist/links/httpSubscriptionLink.mjs +132 -96
- package/dist/links/internals/contentTypes.d.ts +2 -2
- package/dist/links/internals/contentTypes.d.ts.map +1 -1
- package/dist/links/internals/httpUtils.d.ts +1 -8
- package/dist/links/internals/httpUtils.d.ts.map +1 -1
- package/dist/links/internals/httpUtils.js +1 -30
- package/dist/links/internals/httpUtils.mjs +2 -30
- package/dist/links/internals/subscriptions.d.ts +20 -0
- package/dist/links/internals/subscriptions.d.ts.map +1 -0
- package/dist/links/internals/urlWithConnectionParams.d.ts +2 -1
- package/dist/links/internals/urlWithConnectionParams.d.ts.map +1 -1
- package/dist/links/internals/urlWithConnectionParams.js +3 -2
- package/dist/links/internals/urlWithConnectionParams.mjs +3 -2
- package/dist/links/loggerLink.d.ts +5 -5
- package/dist/links/loggerLink.d.ts.map +1 -1
- package/dist/links/loggerLink.js +25 -21
- package/dist/links/loggerLink.mjs +25 -21
- package/dist/links/retryLink.d.ts +29 -0
- package/dist/links/retryLink.d.ts.map +1 -0
- package/dist/links/retryLink.js +65 -0
- package/dist/links/retryLink.mjs +63 -0
- package/dist/links/types.d.ts +4 -23
- package/dist/links/types.d.ts.map +1 -1
- package/dist/links/wsLink.d.ts +54 -6
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/links/wsLink.js +244 -175
- package/dist/links/wsLink.mjs +245 -176
- 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 +14 -11
- package/src/TRPCClientError.ts +1 -1
- package/src/createTRPCClient.ts +28 -23
- package/src/internals/TRPCUntypedClient.ts +26 -15
- package/src/internals/inputWithTrackedEventId.ts +15 -0
- package/src/internals/signals.ts +54 -0
- package/src/internals/transformer.ts +2 -2
- package/src/internals/types.ts +1 -1
- package/src/links/HTTPBatchLinkOptions.ts +1 -1
- package/src/links/httpBatchLink.ts +3 -3
- package/src/links/httpBatchStreamLink.ts +7 -4
- package/src/links/httpLink.ts +2 -2
- package/src/links/httpSubscriptionLink.ts +172 -115
- package/src/links/internals/httpUtils.ts +1 -41
- package/src/links/internals/subscriptions.ts +26 -0
- package/src/links/internals/urlWithConnectionParams.ts +8 -2
- package/src/links/loggerLink.ts +21 -9
- package/src/links/retryLink.ts +101 -0
- package/src/links/types.ts +8 -46
- package/src/links/wsLink.ts +308 -181
- package/src/links.ts +1 -1
- package/src/unstable-internals.ts +1 -0
- package/dist/links/internals/retryLink.d.ts +0 -9
- package/dist/links/internals/retryLink.d.ts.map +0 -1
- package/dist/links/types.js +0 -7
- package/dist/links/types.mjs +0 -5
- package/src/links/internals/retryLink.ts +0 -53
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
import { observable } from '@trpc/server/observable';
|
|
1
|
+
import { behaviorSubject, observable } from '@trpc/server/observable';
|
|
2
|
+
import type {
|
|
3
|
+
TRPC_ERROR_CODE_NUMBER,
|
|
4
|
+
TRPCErrorShape,
|
|
5
|
+
TRPCResult,
|
|
6
|
+
} from '@trpc/server/rpc';
|
|
7
|
+
import { TRPC_ERROR_CODES_BY_KEY } from '@trpc/server/rpc';
|
|
2
8
|
import type {
|
|
3
9
|
AnyClientTypes,
|
|
10
|
+
EventSourceLike,
|
|
4
11
|
inferClientTypes,
|
|
5
12
|
InferrableClientTypes,
|
|
6
|
-
SSEMessage,
|
|
7
13
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
8
14
|
import {
|
|
9
15
|
run,
|
|
10
16
|
sseStreamConsumer,
|
|
11
17
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
18
|
+
import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
|
|
19
|
+
import { raceAbortSignals } from '../internals/signals';
|
|
12
20
|
import { TRPCClientError } from '../TRPCClientError';
|
|
21
|
+
import type { TRPCConnectionState } from '../unstable-internals';
|
|
13
22
|
import { getTransformer, type TransformerOptions } from '../unstable-internals';
|
|
14
23
|
import { getUrl } from './internals/httpUtils';
|
|
15
|
-
import type { CallbackOrValue } from './internals/urlWithConnectionParams';
|
|
16
24
|
import {
|
|
17
25
|
resultOf,
|
|
18
26
|
type UrlOptionsWithConnectionParams,
|
|
19
27
|
} from './internals/urlWithConnectionParams';
|
|
20
|
-
import type { TRPCLink } from './types';
|
|
28
|
+
import type { Operation, TRPCLink } from './types';
|
|
21
29
|
|
|
22
30
|
async function urlWithConnectionParams(
|
|
23
31
|
opts: UrlOptionsWithConnectionParams,
|
|
@@ -34,21 +42,49 @@ async function urlWithConnectionParams(
|
|
|
34
42
|
return url;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
type HTTPSubscriptionLinkOptions<
|
|
45
|
+
type HTTPSubscriptionLinkOptions<
|
|
46
|
+
TRoot extends AnyClientTypes,
|
|
47
|
+
TEventSource extends EventSourceLike.AnyConstructor = typeof EventSource,
|
|
48
|
+
> = {
|
|
49
|
+
/**
|
|
50
|
+
* EventSource ponyfill
|
|
51
|
+
*/
|
|
52
|
+
EventSource?: TEventSource;
|
|
38
53
|
/**
|
|
39
54
|
* EventSource options or a callback that returns them
|
|
40
55
|
*/
|
|
41
|
-
eventSourceOptions?:
|
|
56
|
+
eventSourceOptions?:
|
|
57
|
+
| EventSourceLike.InitDictOf<TEventSource>
|
|
58
|
+
| ((opts: {
|
|
59
|
+
op: Operation;
|
|
60
|
+
}) =>
|
|
61
|
+
| EventSourceLike.InitDictOf<TEventSource>
|
|
62
|
+
| Promise<EventSourceLike.InitDictOf<TEventSource>>);
|
|
42
63
|
} & TransformerOptions<TRoot> &
|
|
43
64
|
UrlOptionsWithConnectionParams;
|
|
44
65
|
|
|
66
|
+
/**
|
|
67
|
+
* tRPC error codes that are considered retryable
|
|
68
|
+
* With out of the box SSE, the client will reconnect when these errors are encountered
|
|
69
|
+
*/
|
|
70
|
+
const codes5xx: TRPC_ERROR_CODE_NUMBER[] = [
|
|
71
|
+
TRPC_ERROR_CODES_BY_KEY.BAD_GATEWAY,
|
|
72
|
+
TRPC_ERROR_CODES_BY_KEY.SERVICE_UNAVAILABLE,
|
|
73
|
+
TRPC_ERROR_CODES_BY_KEY.GATEWAY_TIMEOUT,
|
|
74
|
+
TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR,
|
|
75
|
+
];
|
|
76
|
+
|
|
45
77
|
/**
|
|
46
78
|
* @see https://trpc.io/docs/client/links/httpSubscriptionLink
|
|
47
79
|
*/
|
|
48
80
|
export function unstable_httpSubscriptionLink<
|
|
49
81
|
TInferrable extends InferrableClientTypes,
|
|
82
|
+
TEventSource extends EventSourceLike.AnyConstructor,
|
|
50
83
|
>(
|
|
51
|
-
opts: HTTPSubscriptionLinkOptions<
|
|
84
|
+
opts: HTTPSubscriptionLinkOptions<
|
|
85
|
+
inferClientTypes<TInferrable>,
|
|
86
|
+
TEventSource
|
|
87
|
+
>,
|
|
52
88
|
): TRPCLink<TInferrable> {
|
|
53
89
|
const transformer = getTransformer(opts.transformer);
|
|
54
90
|
|
|
@@ -56,97 +92,142 @@ export function unstable_httpSubscriptionLink<
|
|
|
56
92
|
return ({ op }) => {
|
|
57
93
|
return observable((observer) => {
|
|
58
94
|
const { type, path, input } = op;
|
|
95
|
+
|
|
59
96
|
/* istanbul ignore if -- @preserve */
|
|
60
97
|
if (type !== 'subscription') {
|
|
61
98
|
throw new Error('httpSubscriptionLink only supports subscriptions');
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const onStarted = () => {
|
|
93
|
-
observer.next({
|
|
94
|
-
result: {
|
|
95
|
-
type: 'started',
|
|
96
|
-
},
|
|
97
|
-
context: {
|
|
98
|
-
eventSource,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
+
let lastEventId: string | undefined = undefined;
|
|
102
|
+
const ac = new AbortController();
|
|
103
|
+
const signal = raceAbortSignals(op.signal, ac.signal);
|
|
104
|
+
const eventSourceStream = sseStreamConsumer<{
|
|
105
|
+
EventSource: TEventSource;
|
|
106
|
+
data: Partial<{
|
|
107
|
+
id?: string;
|
|
108
|
+
data: unknown;
|
|
109
|
+
}>;
|
|
110
|
+
error: TRPCErrorShape;
|
|
111
|
+
}>({
|
|
112
|
+
url: async () =>
|
|
113
|
+
getUrl({
|
|
114
|
+
transformer,
|
|
115
|
+
url: await urlWithConnectionParams(opts),
|
|
116
|
+
input: inputWithTrackedEventId(input, lastEventId),
|
|
117
|
+
path,
|
|
118
|
+
type,
|
|
119
|
+
signal: null,
|
|
120
|
+
}),
|
|
121
|
+
init: () => resultOf(opts.eventSourceOptions, { op }),
|
|
122
|
+
signal,
|
|
123
|
+
deserialize: transformer.output.deserialize,
|
|
124
|
+
EventSource:
|
|
125
|
+
opts.EventSource ??
|
|
126
|
+
(globalThis.EventSource as never as TEventSource),
|
|
127
|
+
});
|
|
101
128
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
const connectionState = behaviorSubject<
|
|
130
|
+
TRPCConnectionState<TRPCClientError<any>>
|
|
131
|
+
>({
|
|
132
|
+
type: 'state',
|
|
133
|
+
state: 'connecting',
|
|
134
|
+
error: null,
|
|
135
|
+
});
|
|
107
136
|
|
|
108
|
-
|
|
137
|
+
const connectionSub = connectionState.subscribe({
|
|
138
|
+
next(state) {
|
|
109
139
|
observer.next({
|
|
110
|
-
result:
|
|
111
|
-
type: 'state',
|
|
112
|
-
state: 'pending',
|
|
113
|
-
},
|
|
140
|
+
result: state,
|
|
114
141
|
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
run(async () => {
|
|
145
|
+
for await (const chunk of eventSourceStream) {
|
|
146
|
+
switch (chunk.type) {
|
|
147
|
+
case 'ping':
|
|
148
|
+
// do nothing
|
|
149
|
+
break;
|
|
150
|
+
case 'data':
|
|
151
|
+
const chunkData = chunk.data;
|
|
152
|
+
|
|
153
|
+
let result: TRPCResult<unknown>;
|
|
154
|
+
if (chunkData.id) {
|
|
155
|
+
// if the `tracked()`-helper is used, we always have an `id` field
|
|
156
|
+
lastEventId = chunkData.id;
|
|
157
|
+
result = {
|
|
158
|
+
id: chunkData.id,
|
|
159
|
+
data: chunkData,
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
result = {
|
|
163
|
+
data: chunkData.data,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
observer.next({
|
|
168
|
+
result,
|
|
169
|
+
context: {
|
|
170
|
+
eventSource: chunk.eventSource,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case 'connected': {
|
|
175
|
+
observer.next({
|
|
176
|
+
result: {
|
|
177
|
+
type: 'started',
|
|
178
|
+
},
|
|
179
|
+
context: {
|
|
180
|
+
eventSource: chunk.eventSource,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
connectionState.next({
|
|
184
|
+
type: 'state',
|
|
185
|
+
state: 'pending',
|
|
186
|
+
error: null,
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'serialized-error': {
|
|
191
|
+
const error = TRPCClientError.from({ error: chunk.error });
|
|
192
|
+
|
|
193
|
+
if (codes5xx.includes(chunk.error.code)) {
|
|
194
|
+
//
|
|
195
|
+
connectionState.next({
|
|
196
|
+
type: 'state',
|
|
197
|
+
state: 'connecting',
|
|
198
|
+
error,
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
//
|
|
203
|
+
// non-retryable error, cancel the subscription
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
case 'connecting': {
|
|
207
|
+
const lastState = connectionState.get();
|
|
208
|
+
|
|
209
|
+
const error = chunk.event && TRPCClientError.from(chunk.event);
|
|
210
|
+
if (!error && lastState.state === 'connecting') {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
connectionState.next({
|
|
215
|
+
type: 'state',
|
|
216
|
+
state: 'connecting',
|
|
217
|
+
error,
|
|
218
|
+
});
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 'timeout': {
|
|
222
|
+
connectionState.next({
|
|
223
|
+
type: 'state',
|
|
224
|
+
state: 'connecting',
|
|
225
|
+
error: new TRPCClientError(
|
|
226
|
+
`Timeout of ${chunk.ms}ms reached while waiting for a response`,
|
|
227
|
+
),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
121
230
|
}
|
|
122
|
-
|
|
123
|
-
const error =
|
|
124
|
-
globalThis.ErrorEvent && event instanceof ErrorEvent
|
|
125
|
-
? TRPCClientError.from(event.error)
|
|
126
|
-
: TRPCClientError.from(new Error(`Unknown EventSource error`));
|
|
127
|
-
|
|
128
|
-
observer.next({
|
|
129
|
-
result: {
|
|
130
|
-
type: 'state',
|
|
131
|
-
state: 'connecting',
|
|
132
|
-
data: error,
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const iterable = sseStreamConsumer<Partial<SSEMessage>>({
|
|
138
|
-
from: eventSource,
|
|
139
|
-
deserialize: transformer.output.deserialize,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
for await (const chunk of iterable) {
|
|
143
|
-
// if the `sse({})`-helper is used, we always have an `id` field
|
|
144
|
-
const data = 'id' in chunk ? chunk : chunk.data;
|
|
145
|
-
observer.next({
|
|
146
|
-
result: {
|
|
147
|
-
data,
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
231
|
}
|
|
151
232
|
|
|
152
233
|
observer.next({
|
|
@@ -155,38 +236,14 @@ export function unstable_httpSubscriptionLink<
|
|
|
155
236
|
},
|
|
156
237
|
});
|
|
157
238
|
observer.complete();
|
|
158
|
-
|
|
159
|
-
observer.next({
|
|
160
|
-
result: {
|
|
161
|
-
type: 'state',
|
|
162
|
-
state: 'idle',
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
239
|
}).catch((error) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
observer.next({
|
|
169
|
-
result: {
|
|
170
|
-
type: 'state',
|
|
171
|
-
state: 'error',
|
|
172
|
-
data: TRPCClientError.from(trpcError),
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
observer.error(trpcError);
|
|
240
|
+
observer.error(TRPCClientError.from(error));
|
|
176
241
|
});
|
|
177
242
|
|
|
178
243
|
return () => {
|
|
179
244
|
observer.complete();
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
result: {
|
|
183
|
-
type: 'state',
|
|
184
|
-
state: 'idle',
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
eventSource?.close();
|
|
189
|
-
unsubscribed = true;
|
|
245
|
+
ac.abort();
|
|
246
|
+
connectionSub.unsubscribe();
|
|
190
247
|
};
|
|
191
248
|
});
|
|
192
249
|
};
|
|
@@ -30,7 +30,7 @@ export type HTTPLinkBaseOptions<
|
|
|
30
30
|
/**
|
|
31
31
|
* Send all requests `as POST`s requests regardless of the procedure type
|
|
32
32
|
* The HTTP handler must separately allow overriding the method. See:
|
|
33
|
-
* @
|
|
33
|
+
* @see https://trpc.io/docs/rpc
|
|
34
34
|
*/
|
|
35
35
|
methodOverride?: 'POST';
|
|
36
36
|
} & TransformerOptions<TRoot>;
|
|
@@ -240,43 +240,3 @@ export async function httpRequest(
|
|
|
240
240
|
meta,
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Merges multiple abort signals into a single one
|
|
246
|
-
* - When all signals have been aborted, the merged signal will be aborted
|
|
247
|
-
*/
|
|
248
|
-
export function mergeAbortSignals(
|
|
249
|
-
opts: {
|
|
250
|
-
signal: Maybe<AbortSignal>;
|
|
251
|
-
}[],
|
|
252
|
-
): AbortController {
|
|
253
|
-
const ac = new AbortController();
|
|
254
|
-
|
|
255
|
-
if (opts.some((o) => !o.signal)) {
|
|
256
|
-
return ac;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const count = opts.length;
|
|
260
|
-
|
|
261
|
-
let abortedCount = 0;
|
|
262
|
-
|
|
263
|
-
const onAbort = () => {
|
|
264
|
-
if (++abortedCount === count) {
|
|
265
|
-
ac.abort();
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
for (const o of opts) {
|
|
270
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
271
|
-
const signal = o.signal!;
|
|
272
|
-
if (signal.aborted) {
|
|
273
|
-
onAbort();
|
|
274
|
-
} else {
|
|
275
|
-
signal.addEventListener('abort', onAbort, {
|
|
276
|
-
once: true,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return ac;
|
|
282
|
-
}
|
|
@@ -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;
|
|
@@ -2,9 +2,15 @@ import { type TRPCRequestInfo } from '@trpc/server/http';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get the result of a value or function that returns a value
|
|
5
|
+
* It also optionally accepts typesafe arguments for the function
|
|
5
6
|
*/
|
|
6
|
-
export const resultOf = <T
|
|
7
|
-
|
|
7
|
+
export const resultOf = <T, TArgs extends any[]>(
|
|
8
|
+
value: T | ((...args: TArgs) => T),
|
|
9
|
+
...args: TArgs
|
|
10
|
+
): T => {
|
|
11
|
+
return typeof value === 'function'
|
|
12
|
+
? (value as (...args: TArgs) => T)(...args)
|
|
13
|
+
: value;
|
|
8
14
|
};
|
|
9
15
|
|
|
10
16
|
/**
|
package/src/links/loggerLink.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
21
|
+
type EnableFnOptions<TRouter extends InferrableClientTypes> =
|
|
19
22
|
| {
|
|
20
23
|
direction: 'down';
|
|
21
|
-
result:
|
|
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:
|
|
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 ||
|
|
203
|
+
(props.result instanceof Error ||
|
|
204
|
+
('error' in props.result.result && props.result.result.error))
|
|
197
205
|
? 'error'
|
|
198
206
|
: 'log';
|
|
199
207
|
|
|
@@ -201,7 +209,7 @@ const defaultLogger =
|
|
|
201
209
|
};
|
|
202
210
|
|
|
203
211
|
/**
|
|
204
|
-
* @
|
|
212
|
+
* @see https://trpc.io/docs/v11/client/links/loggerLink
|
|
205
213
|
*/
|
|
206
214
|
export function loggerLink<TRouter extends AnyRouter = AnyRouter>(
|
|
207
215
|
opts: LoggerLinkOptions<TRouter> = {},
|
|
@@ -219,24 +227,28 @@ export function loggerLink<TRouter extends AnyRouter = AnyRouter>(
|
|
|
219
227
|
return ({ op, next }) => {
|
|
220
228
|
return observable((observer) => {
|
|
221
229
|
// ->
|
|
222
|
-
enabled({ ...op, direction: 'up' })
|
|
230
|
+
if (enabled({ ...op, direction: 'up' })) {
|
|
223
231
|
logger({
|
|
224
232
|
...op,
|
|
225
233
|
direction: 'up',
|
|
226
234
|
});
|
|
235
|
+
}
|
|
227
236
|
const requestStartTime = Date.now();
|
|
228
237
|
function logResult(
|
|
229
|
-
result:
|
|
238
|
+
result:
|
|
239
|
+
| OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
|
|
240
|
+
| TRPCClientError<TRouter>,
|
|
230
241
|
) {
|
|
231
242
|
const elapsedMs = Date.now() - requestStartTime;
|
|
232
243
|
|
|
233
|
-
enabled({ ...op, direction: 'down', result })
|
|
244
|
+
if (enabled({ ...op, direction: 'down', result })) {
|
|
234
245
|
logger({
|
|
235
246
|
...op,
|
|
236
247
|
direction: 'down',
|
|
237
248
|
elapsedMs,
|
|
238
249
|
result,
|
|
239
250
|
});
|
|
251
|
+
}
|
|
240
252
|
}
|
|
241
253
|
return next(op)
|
|
242
254
|
.pipe(
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* istanbul ignore file -- @preserve */
|
|
2
|
+
// We're not actually exporting this link
|
|
3
|
+
import type { Unsubscribable } from '@trpc/server/observable';
|
|
4
|
+
import { observable } from '@trpc/server/observable';
|
|
5
|
+
import type { InferrableClientTypes } from '@trpc/server/unstable-core-do-not-import';
|
|
6
|
+
import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
|
|
7
|
+
import type { TRPCClientError } from '../TRPCClientError';
|
|
8
|
+
import type { Operation, TRPCLink } from './types';
|
|
9
|
+
|
|
10
|
+
interface RetryLinkOptions<TInferrable extends InferrableClientTypes> {
|
|
11
|
+
/**
|
|
12
|
+
* The retry function
|
|
13
|
+
*/
|
|
14
|
+
retry: (opts: RetryFnOptions<TInferrable>) => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RetryFnOptions<TInferrable extends InferrableClientTypes> {
|
|
18
|
+
/**
|
|
19
|
+
* The operation that failed
|
|
20
|
+
*/
|
|
21
|
+
op: Operation;
|
|
22
|
+
/**
|
|
23
|
+
* The error that occurred
|
|
24
|
+
*/
|
|
25
|
+
error: TRPCClientError<TInferrable>;
|
|
26
|
+
/**
|
|
27
|
+
* The number of attempts that have been made (including the first call)
|
|
28
|
+
*/
|
|
29
|
+
attempts: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @see https://trpc.io/docs/v11/client/links/retryLink
|
|
34
|
+
*/
|
|
35
|
+
export function retryLink<TInferrable extends InferrableClientTypes>(
|
|
36
|
+
opts: RetryLinkOptions<TInferrable>,
|
|
37
|
+
): TRPCLink<TInferrable> {
|
|
38
|
+
// initialized config
|
|
39
|
+
return () => {
|
|
40
|
+
// initialized in app
|
|
41
|
+
return (callOpts) => {
|
|
42
|
+
// initialized for request
|
|
43
|
+
return observable((observer) => {
|
|
44
|
+
let next$: Unsubscribable;
|
|
45
|
+
|
|
46
|
+
let lastEventId: string | undefined = undefined;
|
|
47
|
+
|
|
48
|
+
attempt(1);
|
|
49
|
+
|
|
50
|
+
function opWithLastEventId() {
|
|
51
|
+
const op = callOpts.op;
|
|
52
|
+
if (!lastEventId) {
|
|
53
|
+
return op;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...op,
|
|
58
|
+
input: inputWithTrackedEventId(op.input, lastEventId),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function attempt(attempts: number) {
|
|
63
|
+
const op = opWithLastEventId();
|
|
64
|
+
|
|
65
|
+
next$ = callOpts.next(op).subscribe({
|
|
66
|
+
error(error) {
|
|
67
|
+
const shouldRetry = opts.retry({
|
|
68
|
+
op,
|
|
69
|
+
attempts,
|
|
70
|
+
error,
|
|
71
|
+
});
|
|
72
|
+
if (shouldRetry) {
|
|
73
|
+
attempt(attempts + 1);
|
|
74
|
+
} else {
|
|
75
|
+
observer.error(error);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
next(envelope) {
|
|
79
|
+
//
|
|
80
|
+
if (
|
|
81
|
+
(!envelope.result.type || envelope.result.type === 'data') &&
|
|
82
|
+
envelope.result.id
|
|
83
|
+
) {
|
|
84
|
+
//
|
|
85
|
+
lastEventId = envelope.result.id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
observer.next(envelope);
|
|
89
|
+
},
|
|
90
|
+
complete() {
|
|
91
|
+
observer.complete();
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return () => {
|
|
96
|
+
next$.unsubscribe();
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
}
|