@trpc/client 11.0.0-alpha-tmp-subscription-connection-state.489 → 11.0.0-alpha-tmp-12-06-react.667
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 +118 -92
- 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 -98
- package/dist/links/httpSubscriptionLink.mjs +132 -100
- 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 +49 -5
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/links/wsLink.js +210 -155
- package/dist/links/wsLink.mjs +211 -156
- 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 -123
- 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 +276 -173
- 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,6 +1,13 @@
|
|
|
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
13
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
@@ -8,15 +15,17 @@ import {
|
|
|
8
15
|
run,
|
|
9
16
|
sseStreamConsumer,
|
|
10
17
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
18
|
+
import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
|
|
19
|
+
import { raceAbortSignals } from '../internals/signals';
|
|
11
20
|
import { TRPCClientError } from '../TRPCClientError';
|
|
21
|
+
import type { TRPCConnectionState } from '../unstable-internals';
|
|
12
22
|
import { getTransformer, type TransformerOptions } from '../unstable-internals';
|
|
13
23
|
import { getUrl } from './internals/httpUtils';
|
|
14
|
-
import type { CallbackOrValue } from './internals/urlWithConnectionParams';
|
|
15
24
|
import {
|
|
16
25
|
resultOf,
|
|
17
26
|
type UrlOptionsWithConnectionParams,
|
|
18
27
|
} from './internals/urlWithConnectionParams';
|
|
19
|
-
import type { TRPCLink } from './types';
|
|
28
|
+
import type { Operation, TRPCLink } from './types';
|
|
20
29
|
|
|
21
30
|
async function urlWithConnectionParams(
|
|
22
31
|
opts: UrlOptionsWithConnectionParams,
|
|
@@ -33,21 +42,49 @@ async function urlWithConnectionParams(
|
|
|
33
42
|
return url;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
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;
|
|
37
53
|
/**
|
|
38
54
|
* EventSource options or a callback that returns them
|
|
39
55
|
*/
|
|
40
|
-
eventSourceOptions?:
|
|
56
|
+
eventSourceOptions?:
|
|
57
|
+
| EventSourceLike.InitDictOf<TEventSource>
|
|
58
|
+
| ((opts: {
|
|
59
|
+
op: Operation;
|
|
60
|
+
}) =>
|
|
61
|
+
| EventSourceLike.InitDictOf<TEventSource>
|
|
62
|
+
| Promise<EventSourceLike.InitDictOf<TEventSource>>);
|
|
41
63
|
} & TransformerOptions<TRoot> &
|
|
42
64
|
UrlOptionsWithConnectionParams;
|
|
43
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
|
+
|
|
44
77
|
/**
|
|
45
78
|
* @see https://trpc.io/docs/client/links/httpSubscriptionLink
|
|
46
79
|
*/
|
|
47
80
|
export function unstable_httpSubscriptionLink<
|
|
48
81
|
TInferrable extends InferrableClientTypes,
|
|
82
|
+
TEventSource extends EventSourceLike.AnyConstructor,
|
|
49
83
|
>(
|
|
50
|
-
opts: HTTPSubscriptionLinkOptions<
|
|
84
|
+
opts: HTTPSubscriptionLinkOptions<
|
|
85
|
+
inferClientTypes<TInferrable>,
|
|
86
|
+
TEventSource
|
|
87
|
+
>,
|
|
51
88
|
): TRPCLink<TInferrable> {
|
|
52
89
|
const transformer = getTransformer(opts.transformer);
|
|
53
90
|
|
|
@@ -55,106 +92,142 @@ export function unstable_httpSubscriptionLink<
|
|
|
55
92
|
return ({ op }) => {
|
|
56
93
|
return observable((observer) => {
|
|
57
94
|
const { type, path, input } = op;
|
|
95
|
+
|
|
58
96
|
/* istanbul ignore if -- @preserve */
|
|
59
97
|
if (type !== 'subscription') {
|
|
60
98
|
throw new Error('httpSubscriptionLink only supports subscriptions');
|
|
61
99
|
}
|
|
62
100
|
|
|
63
|
-
let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
url: await urlWithConnectionParams(opts),
|
|
70
|
-
input,
|
|
71
|
-
path,
|
|
72
|
-
type,
|
|
73
|
-
signal: null,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const eventSourceOptions = await resultOf(opts.eventSourceOptions);
|
|
77
|
-
/* istanbul ignore if -- @preserve */
|
|
78
|
-
if (unsubscribed) {
|
|
79
|
-
// already unsubscribed - rare race condition
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
eventSource = new EventSource(url, eventSourceOptions);
|
|
83
|
-
observer.next({
|
|
84
|
-
result: {
|
|
85
|
-
type: 'state',
|
|
86
|
-
state: 'connecting',
|
|
87
|
-
data: null,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const onStarted = () => {
|
|
92
|
-
observer.next({
|
|
93
|
-
result: {
|
|
94
|
-
type: 'started',
|
|
95
|
-
},
|
|
96
|
-
context: {
|
|
97
|
-
eventSource,
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
102
|
-
eventSource!.removeEventListener('open', onStarted);
|
|
103
|
-
};
|
|
104
|
-
// console.log('starting', new Date());
|
|
105
|
-
eventSource.addEventListener('open', onStarted);
|
|
106
|
-
|
|
107
|
-
eventSource.addEventListener('open', () => {
|
|
108
|
-
observer.next({
|
|
109
|
-
result: {
|
|
110
|
-
type: 'state',
|
|
111
|
-
state: 'pending',
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
eventSource.addEventListener('error', (event) => {
|
|
117
|
-
// sseStreamConsumer handles this already
|
|
118
|
-
if (eventSource?.readyState === EventSource.CLOSED) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const error =
|
|
123
|
-
globalThis.ErrorEvent && event instanceof ErrorEvent
|
|
124
|
-
? TRPCClientError.from(event.error)
|
|
125
|
-
: TRPCClientError.from(new Error(`Unknown EventSource error`));
|
|
126
|
-
|
|
127
|
-
observer.next({
|
|
128
|
-
result: {
|
|
129
|
-
type: 'state',
|
|
130
|
-
state: 'connecting',
|
|
131
|
-
data: error,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const iterable = sseStreamConsumer<{
|
|
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<{
|
|
137
107
|
id?: string;
|
|
138
|
-
data
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
});
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
const connectionState = behaviorSubject<
|
|
130
|
+
TRPCConnectionState<TRPCClientError<any>>
|
|
131
|
+
>({
|
|
132
|
+
type: 'state',
|
|
133
|
+
state: 'connecting',
|
|
134
|
+
error: null,
|
|
135
|
+
});
|
|
150
136
|
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
const connectionSub = connectionState.subscribe({
|
|
138
|
+
next(state) {
|
|
153
139
|
observer.next({
|
|
154
|
-
result:
|
|
155
|
-
data,
|
|
156
|
-
},
|
|
140
|
+
result: state,
|
|
157
141
|
});
|
|
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
|
+
}
|
|
230
|
+
}
|
|
158
231
|
}
|
|
159
232
|
|
|
160
233
|
observer.next({
|
|
@@ -163,38 +236,14 @@ export function unstable_httpSubscriptionLink<
|
|
|
163
236
|
},
|
|
164
237
|
});
|
|
165
238
|
observer.complete();
|
|
166
|
-
|
|
167
|
-
observer.next({
|
|
168
|
-
result: {
|
|
169
|
-
type: 'state',
|
|
170
|
-
state: 'idle',
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
239
|
}).catch((error) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
observer.next({
|
|
177
|
-
result: {
|
|
178
|
-
type: 'state',
|
|
179
|
-
state: 'error',
|
|
180
|
-
data: TRPCClientError.from(trpcError),
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
observer.error(trpcError);
|
|
240
|
+
observer.error(TRPCClientError.from(error));
|
|
184
241
|
});
|
|
185
242
|
|
|
186
243
|
return () => {
|
|
187
244
|
observer.complete();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
result: {
|
|
191
|
-
type: 'state',
|
|
192
|
-
state: 'idle',
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
eventSource?.close();
|
|
197
|
-
unsubscribed = true;
|
|
245
|
+
ac.abort();
|
|
246
|
+
connectionSub.unsubscribe();
|
|
198
247
|
};
|
|
199
248
|
});
|
|
200
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
|
+
}
|