@trpc/server 11.0.0-rc.632 → 11.0.0-rc.633
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/adapters/node-http/writeResponse.d.ts.map +1 -1
- package/dist/adapters/node-http/writeResponse.js +0 -5
- package/dist/adapters/node-http/writeResponse.mjs +0 -5
- package/dist/adapters/ws.js +1 -1
- package/dist/adapters/ws.mjs +1 -1
- package/dist/bundle-analysis.json +82 -82
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/http/resolveResponse.js +3 -3
- package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +3 -3
- package/dist/unstable-core-do-not-import/initTRPC.js +2 -2
- package/dist/unstable-core-do-not-import/initTRPC.mjs +2 -2
- package/dist/unstable-core-do-not-import/rootConfig.d.ts +14 -14
- package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +7 -10
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.d.ts +11 -1
- package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.js +129 -65
- package/dist/unstable-core-do-not-import/stream/sse.mjs +129 -65
- package/dist/unstable-core-do-not-import/transformer.d.ts +1 -4
- package/dist/unstable-core-do-not-import/transformer.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/adapters/node-http/writeResponse.ts +0 -5
- package/src/unstable-core-do-not-import/http/resolveResponse.ts +3 -4
- package/src/unstable-core-do-not-import/initTRPC.ts +1 -1
- package/src/unstable-core-do-not-import/rootConfig.ts +17 -17
- package/src/unstable-core-do-not-import/rpc/envelopes.ts +7 -12
- package/src/unstable-core-do-not-import/stream/sse.ts +155 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/server",
|
|
3
|
-
"version": "11.0.0-rc.
|
|
3
|
+
"version": "11.0.0-rc.633+55512768a",
|
|
4
4
|
"description": "The tRPC server library",
|
|
5
5
|
"author": "KATT",
|
|
6
6
|
"license": "MIT",
|
|
@@ -156,5 +156,5 @@
|
|
|
156
156
|
"next": "*",
|
|
157
157
|
"ws": "*"
|
|
158
158
|
},
|
|
159
|
-
"gitHead": "
|
|
159
|
+
"gitHead": "55512768ac9e57c472c9b03d8e39f606a8c1e1c6"
|
|
160
160
|
}
|
|
@@ -300,9 +300,8 @@ export async function resolveResponse<TRouter extends AnyRouter>(
|
|
|
300
300
|
const isStreamCall = req.headers.get('trpc-accept') === 'application/jsonl';
|
|
301
301
|
|
|
302
302
|
const experimentalIterablesAndDeferreds =
|
|
303
|
-
|
|
304
|
-
const experimentalSSE =
|
|
305
|
-
router._def._config.experimental?.sseSubscriptions?.enabled ?? true;
|
|
303
|
+
config.iterablesAndDeferreds ?? true;
|
|
304
|
+
const experimentalSSE = config.sse?.enabled ?? true;
|
|
306
305
|
try {
|
|
307
306
|
const [infoError, info] = infoTuple;
|
|
308
307
|
if (infoError) {
|
|
@@ -465,7 +464,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
|
|
|
465
464
|
});
|
|
466
465
|
|
|
467
466
|
const stream = sseStreamProducer({
|
|
468
|
-
...config.
|
|
467
|
+
...config.sse,
|
|
469
468
|
data: iterable,
|
|
470
469
|
abortCtrl: result?.abortCtrl ?? new AbortController(),
|
|
471
470
|
serialize: (v) => config.transformer.output.serialize(v),
|
|
@@ -76,6 +76,7 @@ class TRPCBuilder<TContext extends object, TMeta extends object> {
|
|
|
76
76
|
}>;
|
|
77
77
|
|
|
78
78
|
const config: RootConfig<$Root> = {
|
|
79
|
+
...opts,
|
|
79
80
|
transformer: getDataTransformer(opts?.transformer ?? defaultTransformer),
|
|
80
81
|
isDev:
|
|
81
82
|
opts?.isDev ??
|
|
@@ -89,7 +90,6 @@ class TRPCBuilder<TContext extends object, TMeta extends object> {
|
|
|
89
90
|
* @internal
|
|
90
91
|
*/
|
|
91
92
|
$types: null as any,
|
|
92
|
-
experimental: opts?.experimental ?? {},
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
{
|
|
@@ -65,26 +65,26 @@ export interface RootConfig<TTypes extends RootTypes> {
|
|
|
65
65
|
|
|
66
66
|
defaultMeta?: TTypes['meta'] extends object ? TTypes['meta'] : never;
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Enable support for returning async iterables and returning deferred promises when using `httpBatchStreamLink`
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
72
|
+
iterablesAndDeferreds?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Options for server-sent events (SSE) subscriptions
|
|
75
|
+
* @see https://trpc.io/docs/client/links/httpSubscriptionLink
|
|
76
|
+
*/
|
|
77
|
+
sse?: {
|
|
69
78
|
/**
|
|
70
|
-
* Enable
|
|
79
|
+
* Enable server-sent events (SSE) subscriptions
|
|
71
80
|
* @default true
|
|
72
81
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* Enable server-sent events (SSE) subscriptions
|
|
80
|
-
* @default true
|
|
81
|
-
*/
|
|
82
|
-
enabled?: boolean;
|
|
83
|
-
} & Pick<
|
|
84
|
-
SSEStreamProducerOptions,
|
|
85
|
-
'ping' | 'emitAndEndImmediately' | 'maxDurationMs'
|
|
86
|
-
>;
|
|
87
|
-
};
|
|
82
|
+
enabled?: boolean;
|
|
83
|
+
} & Pick<
|
|
84
|
+
SSEStreamProducerOptions,
|
|
85
|
+
'ping' | 'emitAndEndImmediately' | 'maxDurationMs'
|
|
86
|
+
>;
|
|
87
|
+
experimental?: {};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
@@ -63,14 +63,15 @@ export interface TRPCRequest
|
|
|
63
63
|
|
|
64
64
|
export interface TRPCResult<TData = unknown> {
|
|
65
65
|
data: TData;
|
|
66
|
+
type?: 'data';
|
|
67
|
+
/**
|
|
68
|
+
* The id of the message to keep track of in case of a reconnect
|
|
69
|
+
*/
|
|
70
|
+
id?: string;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
export interface TRPCSuccessResponse<TData>
|
|
69
|
-
extends JSONRPC2.ResultResponse<
|
|
70
|
-
TRPCResult<TData> & {
|
|
71
|
-
type?: 'data';
|
|
72
|
-
}
|
|
73
|
-
> {}
|
|
74
|
+
extends JSONRPC2.ResultResponse<TRPCResult<TData>> {}
|
|
74
75
|
|
|
75
76
|
export interface TRPCErrorResponse<
|
|
76
77
|
TError extends TRPCErrorShape = TRPCErrorShape,
|
|
@@ -111,13 +112,7 @@ export interface TRPCResultMessage<TData>
|
|
|
111
112
|
extends JSONRPC2.ResultResponse<
|
|
112
113
|
| { type: 'started'; data?: never }
|
|
113
114
|
| { type: 'stopped'; data?: never }
|
|
114
|
-
|
|
|
115
|
-
type: 'data';
|
|
116
|
-
/**
|
|
117
|
-
* The id of the message to keep track of in case of a reconnect
|
|
118
|
-
*/
|
|
119
|
-
id?: string;
|
|
120
|
-
})
|
|
115
|
+
| TRPCResult<TData>
|
|
121
116
|
> {}
|
|
122
117
|
|
|
123
118
|
export type TRPCResponseMessage<
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Unpromise } from '../../vendor/unpromise';
|
|
1
2
|
import { getTRPCErrorFromUnknown } from '../error/TRPCError';
|
|
2
3
|
import { isAbortError } from '../http/isAbortError';
|
|
3
4
|
import type { MaybePromise } from '../types';
|
|
@@ -49,6 +50,7 @@ export interface SSEStreamProducerOptions<TValue = unknown> {
|
|
|
49
50
|
formatError?: (opts: { error: unknown }) => unknown;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
const PING_EVENT = 'ping';
|
|
52
54
|
const SERIALIZED_ERROR_EVENT = 'serialized-error';
|
|
53
55
|
|
|
54
56
|
type SSEvent = Partial<{
|
|
@@ -110,7 +112,7 @@ export function sseStreamProducer<TValue = unknown>(
|
|
|
110
112
|
|
|
111
113
|
for await (value of iterable) {
|
|
112
114
|
if (value === PING_SYM) {
|
|
113
|
-
stream.controller.enqueue({
|
|
115
|
+
stream.controller.enqueue({ event: PING_EVENT, data: '' });
|
|
114
116
|
continue;
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -199,12 +201,23 @@ interface ConsumerStreamResultConnecting<TConfig extends ConsumerConfig>
|
|
|
199
201
|
type: 'connecting';
|
|
200
202
|
event: EventSourceLike.EventOf<TConfig['EventSource']> | null;
|
|
201
203
|
}
|
|
204
|
+
interface ConsumerStreamResultTimeout<TConfig extends ConsumerConfig>
|
|
205
|
+
extends ConsumerStreamResultBase<TConfig> {
|
|
206
|
+
type: 'timeout';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface ConsumerStreamResultPing<TConfig extends ConsumerConfig>
|
|
210
|
+
extends ConsumerStreamResultBase<TConfig> {
|
|
211
|
+
type: 'ping';
|
|
212
|
+
}
|
|
202
213
|
|
|
203
214
|
type ConsumerStreamResult<TConfig extends ConsumerConfig> =
|
|
204
215
|
| ConsumerStreamResultData<TConfig>
|
|
205
216
|
| ConsumerStreamResultError<TConfig>
|
|
206
217
|
| ConsumerStreamResultOpened<TConfig>
|
|
207
|
-
| ConsumerStreamResultConnecting<TConfig
|
|
218
|
+
| ConsumerStreamResultConnecting<TConfig>
|
|
219
|
+
| ConsumerStreamResultTimeout<TConfig>
|
|
220
|
+
| ConsumerStreamResultPing<TConfig>;
|
|
208
221
|
|
|
209
222
|
export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
|
|
210
223
|
url: () => MaybePromise<string>;
|
|
@@ -214,6 +227,10 @@ export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
|
|
|
214
227
|
signal: AbortSignal;
|
|
215
228
|
deserialize?: Deserialize;
|
|
216
229
|
EventSource: TConfig['EventSource'];
|
|
230
|
+
/**
|
|
231
|
+
* Reconnect after inactivity in milliseconds
|
|
232
|
+
*/
|
|
233
|
+
reconnectAfterInactivityMs?: number;
|
|
217
234
|
}
|
|
218
235
|
|
|
219
236
|
interface ConsumerConfig {
|
|
@@ -222,6 +239,31 @@ interface ConsumerConfig {
|
|
|
222
239
|
EventSource: EventSourceLike.AnyConstructor;
|
|
223
240
|
}
|
|
224
241
|
|
|
242
|
+
async function withTimeout<T>(opts: {
|
|
243
|
+
promise: Promise<T>;
|
|
244
|
+
timeoutMs: number;
|
|
245
|
+
onTimeout: () => Promise<NoInfer<T>>;
|
|
246
|
+
}): Promise<T> {
|
|
247
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
248
|
+
|
|
249
|
+
const timeoutPromise = new Promise<null>((resolve) => {
|
|
250
|
+
timeoutId = setTimeout(() => {
|
|
251
|
+
resolve(null);
|
|
252
|
+
}, opts.timeoutMs);
|
|
253
|
+
});
|
|
254
|
+
let res;
|
|
255
|
+
try {
|
|
256
|
+
res = await Unpromise.race([opts.promise, timeoutPromise]);
|
|
257
|
+
} finally {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
259
|
+
clearTimeout(timeoutId!);
|
|
260
|
+
}
|
|
261
|
+
if (res === null) {
|
|
262
|
+
return await opts.onTimeout();
|
|
263
|
+
}
|
|
264
|
+
return res;
|
|
265
|
+
}
|
|
266
|
+
|
|
225
267
|
/**
|
|
226
268
|
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
227
269
|
*/
|
|
@@ -234,99 +276,145 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
|
|
|
234
276
|
|
|
235
277
|
let _es: InstanceType<TConfig['EventSource']> | null = null;
|
|
236
278
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
type: 'connecting',
|
|
247
|
-
eventSource: _es,
|
|
248
|
-
event: null,
|
|
249
|
-
});
|
|
250
|
-
eventSource.addEventListener('open', () => {
|
|
279
|
+
const createStream = () =>
|
|
280
|
+
new ReadableStream<ConsumerStreamResult<TConfig>>({
|
|
281
|
+
async start(controller) {
|
|
282
|
+
const [url, init] = await Promise.all([opts.url(), opts.init()]);
|
|
283
|
+
const eventSource = (_es = new opts.EventSource(
|
|
284
|
+
url,
|
|
285
|
+
init,
|
|
286
|
+
) as InstanceType<TConfig['EventSource']>);
|
|
287
|
+
|
|
251
288
|
controller.enqueue({
|
|
252
|
-
type: '
|
|
253
|
-
eventSource,
|
|
289
|
+
type: 'connecting',
|
|
290
|
+
eventSource: _es,
|
|
291
|
+
event: null,
|
|
292
|
+
});
|
|
293
|
+
eventSource.addEventListener('open', () => {
|
|
294
|
+
controller.enqueue({
|
|
295
|
+
type: 'opened',
|
|
296
|
+
eventSource,
|
|
297
|
+
});
|
|
254
298
|
});
|
|
255
|
-
});
|
|
256
299
|
|
|
257
|
-
|
|
258
|
-
|
|
300
|
+
eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (_msg) => {
|
|
301
|
+
const msg = _msg as EventSourceLike.MessageEvent;
|
|
259
302
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
303
|
+
controller.enqueue({
|
|
304
|
+
type: 'serialized-error',
|
|
305
|
+
error: deserialize(JSON.parse(msg.data)),
|
|
306
|
+
eventSource,
|
|
307
|
+
});
|
|
264
308
|
});
|
|
265
|
-
|
|
266
|
-
eventSource.addEventListener('error', (event) => {
|
|
267
|
-
if (eventSource.readyState === EventSource.CLOSED) {
|
|
268
|
-
controller.error(event);
|
|
269
|
-
} else {
|
|
309
|
+
eventSource.addEventListener(PING_EVENT, () => {
|
|
270
310
|
controller.enqueue({
|
|
271
|
-
type: '
|
|
311
|
+
type: 'ping',
|
|
272
312
|
eventSource,
|
|
273
|
-
event,
|
|
274
313
|
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
314
|
+
});
|
|
315
|
+
eventSource.addEventListener('error', (event) => {
|
|
316
|
+
if (eventSource.readyState === EventSource.CLOSED) {
|
|
317
|
+
controller.error(event);
|
|
318
|
+
} else {
|
|
319
|
+
controller.enqueue({
|
|
320
|
+
type: 'connecting',
|
|
321
|
+
eventSource,
|
|
322
|
+
event,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
eventSource.addEventListener('message', (_msg) => {
|
|
327
|
+
const msg = _msg as EventSourceLike.MessageEvent;
|
|
279
328
|
|
|
280
|
-
|
|
329
|
+
const chunk = deserialize(JSON.parse(msg.data));
|
|
281
330
|
|
|
282
|
-
|
|
283
|
-
|
|
331
|
+
const def: SSEvent = {
|
|
332
|
+
data: chunk,
|
|
333
|
+
};
|
|
334
|
+
if (msg.lastEventId) {
|
|
335
|
+
def.id = msg.lastEventId;
|
|
336
|
+
}
|
|
337
|
+
controller.enqueue({
|
|
338
|
+
type: 'data',
|
|
339
|
+
data: def as inferTrackedOutput<TConfig['data']>,
|
|
340
|
+
eventSource,
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const onAbort = () => {
|
|
345
|
+
controller.close();
|
|
346
|
+
eventSource.close();
|
|
284
347
|
};
|
|
285
|
-
if (
|
|
286
|
-
|
|
348
|
+
if (signal.aborted) {
|
|
349
|
+
onAbort();
|
|
350
|
+
} else {
|
|
351
|
+
signal.addEventListener('abort', onAbort);
|
|
287
352
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
});
|
|
353
|
+
},
|
|
354
|
+
cancel() {
|
|
355
|
+
_es?.close();
|
|
356
|
+
},
|
|
357
|
+
});
|
|
294
358
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
},
|
|
308
|
-
});
|
|
359
|
+
const getNewStreamAndReader = () => {
|
|
360
|
+
const stream = createStream();
|
|
361
|
+
const reader = stream.getReader();
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
reader,
|
|
365
|
+
cancel: () => {
|
|
366
|
+
reader.releaseLock();
|
|
367
|
+
return stream.cancel();
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
};
|
|
309
371
|
return {
|
|
310
372
|
[Symbol.asyncIterator]() {
|
|
311
|
-
|
|
373
|
+
let stream = getNewStreamAndReader();
|
|
312
374
|
|
|
313
375
|
const iterator: AsyncIterator<ConsumerStreamResult<TConfig>> = {
|
|
314
376
|
async next() {
|
|
315
|
-
|
|
377
|
+
let promise = stream.reader.read();
|
|
378
|
+
|
|
379
|
+
if (opts.reconnectAfterInactivityMs) {
|
|
380
|
+
promise = withTimeout({
|
|
381
|
+
promise,
|
|
382
|
+
timeoutMs: opts.reconnectAfterInactivityMs,
|
|
383
|
+
onTimeout: async () => {
|
|
384
|
+
// Close and release old reader
|
|
385
|
+
await stream.cancel();
|
|
386
|
+
|
|
387
|
+
// Create new reader
|
|
388
|
+
stream = getNewStreamAndReader();
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
value: {
|
|
392
|
+
type: 'timeout',
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
394
|
+
eventSource: _es!,
|
|
395
|
+
},
|
|
396
|
+
done: false,
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const result = await promise;
|
|
316
403
|
|
|
317
|
-
|
|
404
|
+
// console.debug('result', result, 'done', result.done);
|
|
405
|
+
if (result.done) {
|
|
318
406
|
return {
|
|
319
|
-
value:
|
|
407
|
+
value: result.value,
|
|
320
408
|
done: true,
|
|
321
409
|
};
|
|
322
410
|
}
|
|
323
411
|
return {
|
|
324
|
-
value:
|
|
412
|
+
value: result.value,
|
|
325
413
|
done: false,
|
|
326
414
|
};
|
|
327
415
|
},
|
|
328
416
|
async return() {
|
|
329
|
-
|
|
417
|
+
await stream.cancel();
|
|
330
418
|
return {
|
|
331
419
|
value: undefined,
|
|
332
420
|
done: true,
|