@trpc/client 11.0.0-rc.560 → 11.0.0-rc.563
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/bundle-analysis.json +81 -65
- package/dist/internals/TRPCUntypedClient.js +1 -1
- package/dist/internals/TRPCUntypedClient.mjs +1 -1
- package/dist/internals/signals.d.ts +12 -0
- package/dist/internals/signals.d.ts.map +1 -0
- package/dist/internals/signals.js +44 -0
- package/dist/internals/signals.mjs +41 -0
- package/dist/links/httpBatchLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.js +3 -2
- package/dist/links/httpBatchLink.mjs +4 -3
- package/dist/links/httpBatchStreamLink.d.ts.map +1 -1
- package/dist/links/httpBatchStreamLink.js +5 -3
- package/dist/links/httpBatchStreamLink.mjs +6 -4
- package/dist/links/httpSubscriptionLink.d.ts +3 -14
- package/dist/links/httpSubscriptionLink.d.ts.map +1 -1
- package/dist/links/httpSubscriptionLink.js +45 -133
- package/dist/links/httpSubscriptionLink.mjs +46 -134
- package/dist/links/internals/httpUtils.d.ts +0 -7
- package/dist/links/internals/httpUtils.d.ts.map +1 -1
- package/dist/links/internals/httpUtils.js +0 -29
- package/dist/links/internals/httpUtils.mjs +1 -29
- package/package.json +4 -4
- package/src/internals/TRPCUntypedClient.ts +1 -1
- package/src/internals/signals.ts +51 -0
- package/src/links/httpBatchLink.ts +3 -3
- package/src/links/httpBatchStreamLink.ts +7 -4
- package/src/links/httpSubscriptionLink.ts +62 -200
- package/src/links/internals/httpUtils.ts +0 -40
|
@@ -5,6 +5,7 @@ import type { AnyRootTypes } from '@trpc/server/unstable-core-do-not-import';
|
|
|
5
5
|
import { jsonlStreamConsumer } from '@trpc/server/unstable-core-do-not-import';
|
|
6
6
|
import type { BatchLoader } from '../internals/dataLoader';
|
|
7
7
|
import { dataLoader } from '../internals/dataLoader';
|
|
8
|
+
import { allAbortSignals, raceAbortSignals } from '../internals/signals';
|
|
8
9
|
import type { NonEmptyArray } from '../internals/types';
|
|
9
10
|
import { TRPCClientError } from '../TRPCClientError';
|
|
10
11
|
import type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
fetchHTTPResponse,
|
|
14
15
|
getBody,
|
|
15
16
|
getUrl,
|
|
16
|
-
mergeAbortSignals,
|
|
17
17
|
resolveHTTPLinkOptions,
|
|
18
18
|
} from './internals/httpUtils';
|
|
19
19
|
import type { Operation, TRPCLink } from './types';
|
|
@@ -67,11 +67,14 @@ export function unstable_httpBatchStreamLink<TRouter extends AnyRouter>(
|
|
|
67
67
|
const path = batchOps.map((op) => op.path).join(',');
|
|
68
68
|
const inputs = batchOps.map((op) => op.input);
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const batchSignals = allAbortSignals(
|
|
71
|
+
...batchOps.map((op) => op.signal),
|
|
72
|
+
);
|
|
73
|
+
const abortController = new AbortController();
|
|
71
74
|
|
|
72
75
|
const responsePromise = fetchHTTPResponse({
|
|
73
76
|
...resolvedOpts,
|
|
74
|
-
signal:
|
|
77
|
+
signal: raceAbortSignals(batchSignals, abortController.signal),
|
|
75
78
|
type,
|
|
76
79
|
contentTypeHeader: 'application/json',
|
|
77
80
|
trpcAcceptHeader: 'application/jsonl',
|
|
@@ -106,7 +109,7 @@ export function unstable_httpBatchStreamLink<TRouter extends AnyRouter>(
|
|
|
106
109
|
error,
|
|
107
110
|
});
|
|
108
111
|
},
|
|
109
|
-
abortController
|
|
112
|
+
abortController,
|
|
110
113
|
});
|
|
111
114
|
const promises = Object.keys(batchOps).map(
|
|
112
115
|
async (key): Promise<HTTPResult> => {
|
|
@@ -3,11 +3,13 @@ import type {
|
|
|
3
3
|
AnyClientTypes,
|
|
4
4
|
inferClientTypes,
|
|
5
5
|
InferrableClientTypes,
|
|
6
|
+
SSEStreamConsumerOptions,
|
|
6
7
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
7
8
|
import {
|
|
8
9
|
run,
|
|
9
10
|
sseStreamConsumer,
|
|
10
11
|
} from '@trpc/server/unstable-core-do-not-import';
|
|
12
|
+
import { raceAbortSignals } from '../internals/signals';
|
|
11
13
|
import { TRPCClientError } from '../TRPCClientError';
|
|
12
14
|
import { getTransformer, type TransformerOptions } from '../unstable-internals';
|
|
13
15
|
import { getUrl } from './internals/httpUtils';
|
|
@@ -33,31 +35,15 @@ async function urlWithConnectionParams(
|
|
|
33
35
|
return url;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
type RecreateOnErrorOpt =
|
|
37
|
-
| {
|
|
38
|
-
type: 'raw';
|
|
39
|
-
event: Event;
|
|
40
|
-
}
|
|
41
|
-
| {
|
|
42
|
-
type: 'http-error';
|
|
43
|
-
status: number;
|
|
44
|
-
event: Event;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
38
|
type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes> = {
|
|
48
39
|
/**
|
|
49
40
|
* EventSource options or a callback that returns them
|
|
50
41
|
*/
|
|
51
42
|
eventSourceOptions?: CallbackOrValue<EventSourceInit>;
|
|
52
43
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* This is useful where a long running subscription might be interrupted by a recoverable network error,
|
|
56
|
-
* but the existing authorization in a header or URI has expired in the mean-time
|
|
44
|
+
* @see https://trpc.io/docs/client/links/httpSubscriptionLink#updatingConfig
|
|
57
45
|
*/
|
|
58
|
-
|
|
59
|
-
opt: RecreateOnErrorOpt,
|
|
60
|
-
) => boolean | Promise<boolean>;
|
|
46
|
+
experimental_shouldRecreateOnError?: SSEStreamConsumerOptions['shouldRecreateOnError'];
|
|
61
47
|
} & TransformerOptions<TRoot> &
|
|
62
48
|
UrlOptionsWithConnectionParams;
|
|
63
49
|
|
|
@@ -75,102 +61,73 @@ export function unstable_httpSubscriptionLink<
|
|
|
75
61
|
return ({ op }) => {
|
|
76
62
|
return observable((observer) => {
|
|
77
63
|
const { type, path, input } = op;
|
|
64
|
+
|
|
78
65
|
/* istanbul ignore if -- @preserve */
|
|
79
66
|
if (type !== 'subscription') {
|
|
80
67
|
throw new Error('httpSubscriptionLink only supports subscriptions');
|
|
81
68
|
}
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
const ac = new AbortController();
|
|
71
|
+
const signal = raceAbortSignals(op.signal, ac.signal);
|
|
72
|
+
const eventSourceStream = sseStreamConsumer<
|
|
73
|
+
Partial<{
|
|
74
|
+
id?: string;
|
|
75
|
+
data: unknown;
|
|
76
|
+
}>
|
|
77
|
+
>({
|
|
78
|
+
url: async () =>
|
|
79
|
+
getUrl({
|
|
80
|
+
transformer,
|
|
81
|
+
url: await urlWithConnectionParams(opts),
|
|
82
|
+
input,
|
|
83
|
+
path,
|
|
84
|
+
type,
|
|
85
|
+
signal: null,
|
|
86
|
+
}),
|
|
87
|
+
init: () => resultOf(opts.eventSourceOptions),
|
|
88
|
+
signal,
|
|
89
|
+
deserialize: transformer.output.deserialize,
|
|
90
|
+
shouldRecreateOnError: opts.experimental_shouldRecreateOnError,
|
|
91
|
+
});
|
|
85
92
|
|
|
86
93
|
run(async () => {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
eventSource.addEventListener('open', onStarted, { once: true });
|
|
117
|
-
|
|
118
|
-
const iterable = sseStreamConsumer<
|
|
119
|
-
Partial<{
|
|
120
|
-
id?: string;
|
|
121
|
-
data: unknown;
|
|
122
|
-
}>
|
|
123
|
-
>({
|
|
124
|
-
from: eventSource,
|
|
125
|
-
deserialize: transformer.output.deserialize,
|
|
126
|
-
tryHandleError: async (ev) => {
|
|
127
|
-
if (
|
|
128
|
-
typeof opts.shouldRecreateOnError !== 'function' ||
|
|
129
|
-
!eventSource
|
|
130
|
-
) {
|
|
131
|
-
return false;
|
|
94
|
+
for await (const chunk of eventSourceStream) {
|
|
95
|
+
switch (chunk.type) {
|
|
96
|
+
case 'data':
|
|
97
|
+
const chunkData = chunk.data;
|
|
98
|
+
|
|
99
|
+
// if the `tracked()`-helper is used, we always have an `id` field
|
|
100
|
+
const data = 'id' in chunkData ? chunkData : chunkData.data;
|
|
101
|
+
|
|
102
|
+
observer.next({
|
|
103
|
+
result: {
|
|
104
|
+
data,
|
|
105
|
+
},
|
|
106
|
+
context: {
|
|
107
|
+
eventSource: chunk.eventSource,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
case 'opened': {
|
|
112
|
+
observer.next({
|
|
113
|
+
result: {
|
|
114
|
+
type: 'started',
|
|
115
|
+
},
|
|
116
|
+
context: {
|
|
117
|
+
eventSource: chunk.eventSource,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
132
121
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!shouldRestart) {
|
|
141
|
-
return false;
|
|
122
|
+
case 'error': {
|
|
123
|
+
// TODO: handle in https://github.com/trpc/trpc/issues/5871
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'connecting': {
|
|
127
|
+
// TODO: handle in https://github.com/trpc/trpc/issues/5871
|
|
128
|
+
break;
|
|
142
129
|
}
|
|
143
|
-
|
|
144
|
-
eventSource.restart(
|
|
145
|
-
getUrl({
|
|
146
|
-
transformer,
|
|
147
|
-
url: await urlWithConnectionParams(opts),
|
|
148
|
-
input,
|
|
149
|
-
path,
|
|
150
|
-
type,
|
|
151
|
-
signal: null,
|
|
152
|
-
}),
|
|
153
|
-
await resultOf(opts.eventSourceOptions),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
return true;
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
for await (const chunk of iterable) {
|
|
161
|
-
if (!chunk.ok) {
|
|
162
|
-
// TODO: handle in https://github.com/trpc/trpc/issues/5871
|
|
163
|
-
continue;
|
|
164
130
|
}
|
|
165
|
-
const chunkData = chunk.data;
|
|
166
|
-
|
|
167
|
-
// if the `tracked()`-helper is used, we always have an `id` field
|
|
168
|
-
const data = 'id' in chunkData ? chunkData : chunkData.data;
|
|
169
|
-
observer.next({
|
|
170
|
-
result: {
|
|
171
|
-
data,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
131
|
}
|
|
175
132
|
|
|
176
133
|
observer.next({
|
|
@@ -185,104 +142,9 @@ export function unstable_httpSubscriptionLink<
|
|
|
185
142
|
|
|
186
143
|
return () => {
|
|
187
144
|
observer.complete();
|
|
188
|
-
|
|
189
|
-
unsubscribed = true;
|
|
145
|
+
ac.abort();
|
|
190
146
|
};
|
|
191
147
|
});
|
|
192
148
|
};
|
|
193
149
|
};
|
|
194
150
|
}
|
|
195
|
-
|
|
196
|
-
function createRecreateOnErrorOpts(ev: Event): RecreateOnErrorOpt {
|
|
197
|
-
if ('status' in ev && typeof ev.status === 'number') {
|
|
198
|
-
return {
|
|
199
|
-
type: 'http-error',
|
|
200
|
-
status: ev.status,
|
|
201
|
-
event: ev,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
type: 'raw',
|
|
207
|
-
event: ev,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* We wrap EventSource so that is can be reinitialized with new options
|
|
213
|
-
*/
|
|
214
|
-
class EventSourceWrapper implements Partial<EventSource> {
|
|
215
|
-
private es: EventSource;
|
|
216
|
-
|
|
217
|
-
private listeners: Partial<
|
|
218
|
-
Record<
|
|
219
|
-
keyof EventSourceEventMap,
|
|
220
|
-
Parameters<EventSource['addEventListener']>[]
|
|
221
|
-
>
|
|
222
|
-
> = {};
|
|
223
|
-
private *getAllEventListeners() {
|
|
224
|
-
for (const _type in this.listeners) {
|
|
225
|
-
const type = _type as keyof typeof this.listeners;
|
|
226
|
-
for (const listener of this.listeners[type] ?? []) {
|
|
227
|
-
yield listener;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
constructor(url: string, options: EventSourceInit | undefined) {
|
|
233
|
-
this.es = new EventSource(url, options);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
restart(url: string, options: EventSourceInit | undefined) {
|
|
237
|
-
for (const [type, callback, options] of this.getAllEventListeners()) {
|
|
238
|
-
this.es.removeEventListener(type, callback, options);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
this.es.close();
|
|
242
|
-
this.es = new EventSource(url, options);
|
|
243
|
-
|
|
244
|
-
for (const [type, callback, options] of this.getAllEventListeners()) {
|
|
245
|
-
this.es.addEventListener(type, callback, options);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
close() {
|
|
250
|
-
this.listeners = {};
|
|
251
|
-
this.es.close();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
getEventSource() {
|
|
255
|
-
return this.es;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
get readyState() {
|
|
259
|
-
return this.es.readyState;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
addEventListener<TEvent extends keyof EventSourceEventMap>(
|
|
263
|
-
type: TEvent,
|
|
264
|
-
listener: (this: EventSource, ev: EventSourceEventMap[TEvent]) => any,
|
|
265
|
-
options?: boolean | AddEventListenerOptions,
|
|
266
|
-
) {
|
|
267
|
-
this.listeners[type] ??= [];
|
|
268
|
-
this.listeners[type].push([type, listener as any, options]);
|
|
269
|
-
|
|
270
|
-
this.es.addEventListener(type, listener, options);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
removeEventListener<TEvent extends keyof EventSourceEventMap>(
|
|
274
|
-
type: TEvent,
|
|
275
|
-
listener: (this: EventSource, ev: EventSourceEventMap[TEvent]) => any,
|
|
276
|
-
options?: boolean | EventListenerOptions,
|
|
277
|
-
) {
|
|
278
|
-
this.listeners[type] ??= [];
|
|
279
|
-
|
|
280
|
-
const indexToRemove = this.listeners[type]?.findIndex(
|
|
281
|
-
([_type, thisListener]) => thisListener === listener,
|
|
282
|
-
);
|
|
283
|
-
if (typeof indexToRemove === 'number' && indexToRemove >= 0) {
|
|
284
|
-
this.listeners[type].splice(indexToRemove, 1);
|
|
285
|
-
}
|
|
286
|
-
this.es.removeEventListener(type, listener, options);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
@@ -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
|
-
}
|