@trpc/server 11.0.0-rc.632 → 11.0.0-rc.637

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.
Files changed (36) hide show
  1. package/dist/adapters/node-http/writeResponse.d.ts.map +1 -1
  2. package/dist/adapters/node-http/writeResponse.js +0 -5
  3. package/dist/adapters/node-http/writeResponse.mjs +0 -5
  4. package/dist/adapters/ws.js +1 -1
  5. package/dist/adapters/ws.mjs +1 -1
  6. package/dist/bundle-analysis.json +84 -84
  7. package/dist/index.js +1 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/unstable-core-do-not-import/clientish/serialize.d.ts +1 -1
  10. package/dist/unstable-core-do-not-import/clientish/serialize.d.ts.map +1 -1
  11. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  12. package/dist/unstable-core-do-not-import/http/resolveResponse.js +3 -3
  13. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +3 -3
  14. package/dist/unstable-core-do-not-import/initTRPC.js +2 -2
  15. package/dist/unstable-core-do-not-import/initTRPC.mjs +2 -2
  16. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts +2 -2
  17. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts.map +1 -1
  18. package/dist/unstable-core-do-not-import/rootConfig.d.ts +14 -14
  19. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  20. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +7 -10
  21. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
  22. package/dist/unstable-core-do-not-import/stream/sse.d.ts +11 -1
  23. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
  24. package/dist/unstable-core-do-not-import/stream/sse.js +129 -65
  25. package/dist/unstable-core-do-not-import/stream/sse.mjs +129 -65
  26. package/dist/unstable-core-do-not-import/transformer.d.ts +1 -4
  27. package/dist/unstable-core-do-not-import/transformer.d.ts.map +1 -1
  28. package/package.json +2 -2
  29. package/src/adapters/node-http/writeResponse.ts +0 -5
  30. package/src/unstable-core-do-not-import/clientish/serialize.ts +1 -1
  31. package/src/unstable-core-do-not-import/http/resolveResponse.ts +3 -4
  32. package/src/unstable-core-do-not-import/initTRPC.ts +1 -1
  33. package/src/unstable-core-do-not-import/procedureBuilder.ts +10 -9
  34. package/src/unstable-core-do-not-import/rootConfig.ts +17 -17
  35. package/src/unstable-core-do-not-import/rpc/envelopes.ts +7 -12
  36. package/src/unstable-core-do-not-import/stream/sse.ts +155 -67
@@ -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({ comment: 'ping' });
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 stream = new ReadableStream<ConsumerStreamResult<TConfig>>({
238
- async start(controller) {
239
- const [url, init] = await Promise.all([opts.url(), opts.init()]);
240
- const eventSource = (_es = new opts.EventSource(
241
- url,
242
- init,
243
- ) as InstanceType<TConfig['EventSource']>);
244
-
245
- controller.enqueue({
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: 'opened',
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
- eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (_msg) => {
258
- const msg = _msg as EventSourceLike.MessageEvent;
300
+ eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (_msg) => {
301
+ const msg = _msg as EventSourceLike.MessageEvent;
259
302
 
260
- controller.enqueue({
261
- type: 'serialized-error',
262
- error: deserialize(JSON.parse(msg.data)),
263
- eventSource,
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: 'connecting',
311
+ type: 'ping',
272
312
  eventSource,
273
- event,
274
313
  });
275
- }
276
- });
277
- eventSource.addEventListener('message', (_msg) => {
278
- const msg = _msg as EventSourceLike.MessageEvent;
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
- const chunk = deserialize(JSON.parse(msg.data));
329
+ const chunk = deserialize(JSON.parse(msg.data));
281
330
 
282
- const def: SSEvent = {
283
- data: chunk,
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 (msg.lastEventId) {
286
- def.id = msg.lastEventId;
348
+ if (signal.aborted) {
349
+ onAbort();
350
+ } else {
351
+ signal.addEventListener('abort', onAbort);
287
352
  }
288
- controller.enqueue({
289
- type: 'data',
290
- data: def as inferTrackedOutput<TConfig['data']>,
291
- eventSource,
292
- });
293
- });
353
+ },
354
+ cancel() {
355
+ _es?.close();
356
+ },
357
+ });
294
358
 
295
- const onAbort = () => {
296
- controller.close();
297
- eventSource.close();
298
- };
299
- if (signal.aborted) {
300
- onAbort();
301
- } else {
302
- signal.addEventListener('abort', onAbort);
303
- }
304
- },
305
- cancel() {
306
- _es?.close();
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
- const reader = stream.getReader();
373
+ let stream = getNewStreamAndReader();
312
374
 
313
375
  const iterator: AsyncIterator<ConsumerStreamResult<TConfig>> = {
314
376
  async next() {
315
- const value = await reader.read();
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
- if (value.done) {
404
+ // console.debug('result', result, 'done', result.done);
405
+ if (result.done) {
318
406
  return {
319
- value: undefined,
407
+ value: result.value,
320
408
  done: true,
321
409
  };
322
410
  }
323
411
  return {
324
- value: value.value,
412
+ value: result.value,
325
413
  done: false,
326
414
  };
327
415
  },
328
416
  async return() {
329
- reader.releaseLock();
417
+ await stream.cancel();
330
418
  return {
331
419
  value: undefined,
332
420
  done: true,