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

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 (71) hide show
  1. package/dist/adapters/aws-lambda/index.js +1 -1
  2. package/dist/adapters/aws-lambda/index.mjs +1 -1
  3. package/dist/adapters/express.js +1 -1
  4. package/dist/adapters/express.mjs +1 -1
  5. package/dist/adapters/fastify/fastifyRequestHandler.js +1 -1
  6. package/dist/adapters/fastify/fastifyRequestHandler.mjs +1 -1
  7. package/dist/adapters/fetch/fetchRequestHandler.js +1 -1
  8. package/dist/adapters/fetch/fetchRequestHandler.mjs +1 -1
  9. package/dist/adapters/next-app-dir/nextAppDirCaller.js +1 -1
  10. package/dist/adapters/next-app-dir/nextAppDirCaller.mjs +1 -1
  11. package/dist/adapters/next-app-dir/notFound.js +1 -1
  12. package/dist/adapters/next-app-dir/notFound.mjs +1 -1
  13. package/dist/adapters/next-app-dir/redirect.js +1 -1
  14. package/dist/adapters/next-app-dir/redirect.mjs +1 -1
  15. package/dist/adapters/next.js +1 -1
  16. package/dist/adapters/next.mjs +1 -1
  17. package/dist/adapters/node-http/incomingMessageToRequest.js +1 -1
  18. package/dist/adapters/node-http/incomingMessageToRequest.mjs +1 -1
  19. package/dist/adapters/node-http/nodeHTTPRequestHandler.js +1 -1
  20. package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +1 -1
  21. package/dist/adapters/node-http/writeResponse.js +1 -1
  22. package/dist/adapters/node-http/writeResponse.mjs +1 -1
  23. package/dist/adapters/standalone.js +1 -1
  24. package/dist/adapters/standalone.mjs +1 -1
  25. package/dist/adapters/ws.js +1 -1
  26. package/dist/adapters/ws.mjs +1 -1
  27. package/dist/bundle-analysis.json +107 -107
  28. package/dist/http.js +1 -1
  29. package/dist/http.mjs +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/index.mjs +1 -1
  32. package/dist/rpc.js +1 -1
  33. package/dist/rpc.mjs +1 -1
  34. package/dist/shared.js +1 -1
  35. package/dist/shared.mjs +1 -1
  36. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  37. package/dist/unstable-core-do-not-import/http/resolveResponse.js +0 -1
  38. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +0 -1
  39. package/dist/unstable-core-do-not-import/rootConfig.d.ts +1 -1
  40. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  41. package/dist/unstable-core-do-not-import/stream/jsonl.d.ts +1 -1
  42. package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -1
  43. package/dist/unstable-core-do-not-import/stream/jsonl.js +73 -47
  44. package/dist/unstable-core-do-not-import/stream/jsonl.mjs +73 -47
  45. package/dist/unstable-core-do-not-import/stream/sse.d.ts +20 -12
  46. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
  47. package/dist/unstable-core-do-not-import/stream/sse.js +22 -11
  48. package/dist/unstable-core-do-not-import/stream/sse.mjs +22 -11
  49. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts +0 -2
  50. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.d.ts.map +1 -1
  51. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.js +5 -8
  52. package/dist/unstable-core-do-not-import/stream/utils/asyncIterable.mjs +3 -6
  53. package/dist/unstable-core-do-not-import/stream/utils/{disposablePromiseTimer.d.ts → timerResource.d.ts} +2 -2
  54. package/dist/unstable-core-do-not-import/stream/utils/timerResource.d.ts.map +1 -0
  55. package/dist/unstable-core-do-not-import/stream/utils/{disposablePromiseTimer.js → timerResource.js} +2 -2
  56. package/dist/unstable-core-do-not-import/stream/utils/{disposablePromiseTimer.mjs → timerResource.mjs} +2 -2
  57. package/dist/unstable-core-do-not-import/stream/utils/withPing.d.ts.map +1 -1
  58. package/dist/unstable-core-do-not-import/stream/utils/withPing.js +3 -3
  59. package/dist/unstable-core-do-not-import/stream/utils/withPing.mjs +2 -2
  60. package/package.json +7 -3
  61. package/src/unstable-core-do-not-import/http/resolveResponse.ts +0 -1
  62. package/src/unstable-core-do-not-import/rootConfig.ts +1 -1
  63. package/src/unstable-core-do-not-import/stream/jsonl.ts +81 -51
  64. package/src/unstable-core-do-not-import/stream/sse.ts +56 -24
  65. package/src/unstable-core-do-not-import/stream/utils/asyncIterable.ts +5 -11
  66. package/src/unstable-core-do-not-import/stream/utils/{disposablePromiseTimer.ts → timerResource.ts} +1 -1
  67. package/src/unstable-core-do-not-import/stream/utils/withPing.ts +2 -5
  68. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts +0 -7
  69. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts.map +0 -1
  70. package/dist/unstable-core-do-not-import/stream/utils/disposablePromiseTimer.d.ts.map +0 -1
  71. package/src/unstable-core-do-not-import/stream/utils/createServer.ts +0 -44
@@ -3,7 +3,6 @@
3
3
  */
4
4
  export declare function withMaxDuration<T>(iterable: AsyncIterable<T>, opts: {
5
5
  maxDurationMs: number;
6
- abortCtrl: AbortController;
7
6
  }): AsyncGenerator<T>;
8
7
  /**
9
8
  * Derives a new {@link AsyncGenerator} based of {@link iterable}, that yields its first
@@ -13,6 +12,5 @@ export declare function withMaxDuration<T>(iterable: AsyncIterable<T>, opts: {
13
12
  export declare function takeWithGrace<T>(iterable: AsyncIterable<T>, opts: {
14
13
  count: number;
15
14
  gracePeriodMs: number;
16
- abortCtrl: AbortController;
17
15
  }): AsyncGenerator<T>;
18
16
  //# sourceMappingURL=asyncIterable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncIterable.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/asyncIterable.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAuB,eAAe,CAAC,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,eAAe,CAAA;CAAE,GAC1D,cAAc,CAAC,CAAC,CAAC,CA8BnB;AAED;;;;GAIG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE;IACJ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,eAAe,CAAC;CAC5B,GACA,cAAc,CAAC,CAAC,CAAC,CAoCnB"}
1
+ {"version":3,"file":"asyncIterable.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/asyncIterable.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAuB,eAAe,CAAC,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GAC9B,cAAc,CAAC,CAAC,CAAC,CA8BnB;AAED;;;;GAIG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE;IACJ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB,GACA,cAAc,CAAC,CAAC,CAAC,CAkCnB"}
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var unpromise = require('../../../vendor/unpromise/unpromise.js');
4
- var disposablePromiseTimer = require('./disposablePromiseTimer.js');
4
+ var timerResource = require('./timerResource.js');
5
5
 
6
6
  /**
7
7
  * Derives a new {@link AsyncGenerator} based on {@link iterable}, that automatically stops after the specified duration.
8
8
  */ async function* withMaxDuration(iterable, opts) {
9
9
  const iterator = iterable[Symbol.asyncIterator]();
10
- const timer = disposablePromiseTimer.disposablePromiseTimer(opts.maxDurationMs);
10
+ const timer = timerResource.timerResource(opts.maxDurationMs);
11
11
  try {
12
12
  const timerPromise = timer.start();
13
13
  // declaration outside the loop for garbage collection reasons
@@ -17,9 +17,8 @@ var disposablePromiseTimer = require('./disposablePromiseTimer.js');
17
17
  iterator.next(),
18
18
  timerPromise
19
19
  ]);
20
- if (result === disposablePromiseTimer.disposablePromiseTimerResult) {
20
+ if (result === timerResource.disposablePromiseTimerResult) {
21
21
  // cancelled due to timeout
22
- opts.abortCtrl.abort();
23
22
  const res = await iterator.return?.();
24
23
  return res?.value;
25
24
  }
@@ -44,7 +43,7 @@ var disposablePromiseTimer = require('./disposablePromiseTimer.js');
44
43
  const iterator = iterable[Symbol.asyncIterator]();
45
44
  // declaration outside the loop for garbage collection reasons
46
45
  let result;
47
- const timer = disposablePromiseTimer.disposablePromiseTimer(opts.gracePeriodMs);
46
+ const timer = timerResource.timerResource(opts.gracePeriodMs);
48
47
  try {
49
48
  let count = opts.count;
50
49
  let timerPromise = new Promise(()=>{
@@ -55,7 +54,7 @@ var disposablePromiseTimer = require('./disposablePromiseTimer.js');
55
54
  iterator.next(),
56
55
  timerPromise
57
56
  ]);
58
- if (result === disposablePromiseTimer.disposablePromiseTimerResult) {
57
+ if (result === timerResource.disposablePromiseTimerResult) {
59
58
  // cancelled
60
59
  const res = await iterator.return?.();
61
60
  return res?.value;
@@ -66,8 +65,6 @@ var disposablePromiseTimer = require('./disposablePromiseTimer.js');
66
65
  yield result.value;
67
66
  if (--count === 0) {
68
67
  timerPromise = timer.start();
69
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
70
- timerPromise.then(()=>opts.abortCtrl.abort());
71
68
  }
72
69
  // free up reference for garbage collection
73
70
  result = null;
@@ -1,11 +1,11 @@
1
1
  import { Unpromise } from '../../../vendor/unpromise/unpromise.mjs';
2
- import { disposablePromiseTimerResult, disposablePromiseTimer } from './disposablePromiseTimer.mjs';
2
+ import { disposablePromiseTimerResult, timerResource } from './timerResource.mjs';
3
3
 
4
4
  /**
5
5
  * Derives a new {@link AsyncGenerator} based on {@link iterable}, that automatically stops after the specified duration.
6
6
  */ async function* withMaxDuration(iterable, opts) {
7
7
  const iterator = iterable[Symbol.asyncIterator]();
8
- const timer = disposablePromiseTimer(opts.maxDurationMs);
8
+ const timer = timerResource(opts.maxDurationMs);
9
9
  try {
10
10
  const timerPromise = timer.start();
11
11
  // declaration outside the loop for garbage collection reasons
@@ -17,7 +17,6 @@ import { disposablePromiseTimerResult, disposablePromiseTimer } from './disposab
17
17
  ]);
18
18
  if (result === disposablePromiseTimerResult) {
19
19
  // cancelled due to timeout
20
- opts.abortCtrl.abort();
21
20
  const res = await iterator.return?.();
22
21
  return res?.value;
23
22
  }
@@ -42,7 +41,7 @@ import { disposablePromiseTimerResult, disposablePromiseTimer } from './disposab
42
41
  const iterator = iterable[Symbol.asyncIterator]();
43
42
  // declaration outside the loop for garbage collection reasons
44
43
  let result;
45
- const timer = disposablePromiseTimer(opts.gracePeriodMs);
44
+ const timer = timerResource(opts.gracePeriodMs);
46
45
  try {
47
46
  let count = opts.count;
48
47
  let timerPromise = new Promise(()=>{
@@ -64,8 +63,6 @@ import { disposablePromiseTimerResult, disposablePromiseTimer } from './disposab
64
63
  yield result.value;
65
64
  if (--count === 0) {
66
65
  timerPromise = timer.start();
67
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
68
- timerPromise.then(()=>opts.abortCtrl.abort());
69
66
  }
70
67
  // free up reference for garbage collection
71
68
  result = null;
@@ -1,6 +1,6 @@
1
1
  export declare const disposablePromiseTimerResult: unique symbol;
2
- export declare function disposablePromiseTimer(ms: number): {
2
+ export declare function timerResource(ms: number): {
3
3
  start(): Promise<typeof disposablePromiseTimerResult>;
4
4
  [Symbol.dispose]: () => void;
5
5
  };
6
- //# sourceMappingURL=disposablePromiseTimer.d.ts.map
6
+ //# sourceMappingURL=timerResource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timerResource.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/timerResource.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,4BAA4B,eAAW,CAAC;AACrD,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM;;;EAsBvC"}
@@ -4,7 +4,7 @@
4
4
  var _Symbol;
5
5
  (_Symbol = Symbol).dispose ?? (_Symbol.dispose = Symbol());
6
6
  const disposablePromiseTimerResult = Symbol();
7
- function disposablePromiseTimer(ms) {
7
+ function timerResource(ms) {
8
8
  let timer = null;
9
9
  return {
10
10
  start () {
@@ -24,5 +24,5 @@ function disposablePromiseTimer(ms) {
24
24
  };
25
25
  }
26
26
 
27
- exports.disposablePromiseTimer = disposablePromiseTimer;
28
27
  exports.disposablePromiseTimerResult = disposablePromiseTimerResult;
28
+ exports.timerResource = timerResource;
@@ -2,7 +2,7 @@
2
2
  var _Symbol;
3
3
  (_Symbol = Symbol).dispose ?? (_Symbol.dispose = Symbol());
4
4
  const disposablePromiseTimerResult = Symbol();
5
- function disposablePromiseTimer(ms) {
5
+ function timerResource(ms) {
6
6
  let timer = null;
7
7
  return {
8
8
  start () {
@@ -22,4 +22,4 @@ function disposablePromiseTimer(ms) {
22
22
  };
23
23
  }
24
24
 
25
- export { disposablePromiseTimer, disposablePromiseTimerResult };
25
+ export { disposablePromiseTimerResult, timerResource };
@@ -1 +1 @@
1
- {"version":3,"file":"withPing.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/withPing.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,QAAQ,eAAiB,CAAC;AAEvC;;;GAGG;AACH,wBAAuB,QAAQ,CAAC,MAAM,EACpC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,cAAc,EAAE,MAAM,GACrB,cAAc,CAAC,MAAM,GAAG,OAAO,QAAQ,CAAC,CAmC1C"}
1
+ {"version":3,"file":"withPing.d.ts","sourceRoot":"","sources":["../../../../src/unstable-core-do-not-import/stream/utils/withPing.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,eAAiB,CAAC;AAEvC;;;GAGG;AACH,wBAAuB,QAAQ,CAAC,MAAM,EACpC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,cAAc,EAAE,MAAM,GACrB,cAAc,CAAC,MAAM,GAAG,OAAO,QAAQ,CAAC,CAmC1C"}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var unpromise = require('../../../vendor/unpromise/unpromise.js');
4
- var disposablePromiseTimer = require('./disposablePromiseTimer.js');
4
+ var timerResource = require('./timerResource.js');
5
5
 
6
6
  const PING_SYM = Symbol('ping');
7
7
  /**
@@ -13,13 +13,13 @@ const PING_SYM = Symbol('ping');
13
13
  let result;
14
14
  let nextPromise = iterator.next();
15
15
  while(true){
16
- const pingPromise = disposablePromiseTimer.disposablePromiseTimer(pingIntervalMs);
16
+ const pingPromise = timerResource.timerResource(pingIntervalMs);
17
17
  try {
18
18
  result = await unpromise.Unpromise.race([
19
19
  nextPromise,
20
20
  pingPromise.start()
21
21
  ]);
22
- if (result === disposablePromiseTimer.disposablePromiseTimerResult) {
22
+ if (result === timerResource.disposablePromiseTimerResult) {
23
23
  // cancelled
24
24
  yield PING_SYM;
25
25
  continue;
@@ -1,5 +1,5 @@
1
1
  import { Unpromise } from '../../../vendor/unpromise/unpromise.mjs';
2
- import { disposablePromiseTimerResult, disposablePromiseTimer } from './disposablePromiseTimer.mjs';
2
+ import { disposablePromiseTimerResult, timerResource } from './timerResource.mjs';
3
3
 
4
4
  const PING_SYM = Symbol('ping');
5
5
  /**
@@ -11,7 +11,7 @@ const PING_SYM = Symbol('ping');
11
11
  let result;
12
12
  let nextPromise = iterator.next();
13
13
  while(true){
14
- const pingPromise = disposablePromiseTimer(pingIntervalMs);
14
+ const pingPromise = timerResource(pingIntervalMs);
15
15
  try {
16
16
  result = await Unpromise.race([
17
17
  nextPromise,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-rc.637+19da060cb",
3
+ "version": "11.0.0-rc.642+5c904587e",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -110,7 +110,8 @@
110
110
  "rpc",
111
111
  "shared",
112
112
  "unstable-core-do-not-import",
113
- "!**/*.test.*"
113
+ "!**/*.test.*",
114
+ "!**/__tests__"
114
115
  ],
115
116
  "publishConfig": {
116
117
  "access": "public"
@@ -156,5 +157,8 @@
156
157
  "next": "*",
157
158
  "ws": "*"
158
159
  },
159
- "gitHead": "19da060cb0e05c75682ec813c606b51c6c52ee19"
160
+ "peerDependencies": {
161
+ "typescript": ">=5.6.2"
162
+ },
163
+ "gitHead": "5c904587e6f3e3eaadb1bbd80d6aa38d20c00800"
160
164
  }
@@ -466,7 +466,6 @@ export async function resolveResponse<TRouter extends AnyRouter>(
466
466
  const stream = sseStreamProducer({
467
467
  ...config.sse,
468
468
  data: iterable,
469
- abortCtrl: result?.abortCtrl ?? new AbortController(),
470
469
  serialize: (v) => config.transformer.output.serialize(v),
471
470
  formatError(errorOpts) {
472
471
  const error = getTRPCErrorFromUnknown(errorOpts.error);
@@ -82,7 +82,7 @@ export interface RootConfig<TTypes extends RootTypes> {
82
82
  enabled?: boolean;
83
83
  } & Pick<
84
84
  SSEStreamProducerOptions,
85
- 'ping' | 'emitAndEndImmediately' | 'maxDurationMs'
85
+ 'ping' | 'emitAndEndImmediately' | 'maxDurationMs' | 'client'
86
86
  >;
87
87
  experimental?: {};
88
88
  }
@@ -425,13 +425,20 @@ export async function jsonlStreamConsumer<THead>(opts: {
425
425
 
426
426
  type ControllerChunk = ChunkData | StreamInterruptedError;
427
427
  type ChunkController = ReadableStreamDefaultController<ControllerChunk>;
428
+ /**
429
+ * This is needed as new values can come in before the controller has read the chunk
430
+ * Not pretty, could likely be refactored and omitted somehow
431
+ */
432
+ const chunkDeferred = new Map<ChunkIndex, Deferred<ChunkController>>();
428
433
 
429
- const controllers = withRefCount(
430
- new Map<ChunkIndex, ChunkController>(),
431
- () => {
434
+ const controllers = new Map<ChunkIndex, ChunkController>();
435
+
436
+ const maybeAbort = () => {
437
+ if (chunkDeferred.size === 0 && controllers.size === 0) {
438
+ // nothing is listening to the stream anymore
432
439
  opts.abortController?.abort();
433
- },
434
- );
440
+ }
441
+ };
435
442
 
436
443
  function decodeChunkDefinition(value: ChunkDefinition) {
437
444
  const [_path, type, chunkId] = value;
@@ -440,6 +447,13 @@ export async function jsonlStreamConsumer<THead>(opts: {
440
447
 
441
448
  controllers.set(chunkId, stream.controller);
442
449
 
450
+ // resolve chunk deferred if it exists
451
+ const deferred = chunkDeferred.get(chunkId);
452
+ if (deferred) {
453
+ deferred.resolve(stream.controller);
454
+ chunkDeferred.delete(chunkId);
455
+ }
456
+
443
457
  switch (type) {
444
458
  case CHUNK_VALUE_TYPE_PROMISE: {
445
459
  return new Promise((resolve, reject) => {
@@ -473,60 +487,66 @@ export async function jsonlStreamConsumer<THead>(opts: {
473
487
  .catch(reject)
474
488
  .finally(() => {
475
489
  controllers.delete(chunkId);
490
+ maybeAbort();
476
491
  });
477
492
  });
478
493
  }
479
494
  case CHUNK_VALUE_TYPE_ASYNC_ITERABLE: {
480
- return {
481
- [Symbol.asyncIterator]: () => {
482
- const reader = stream.readable.getReader();
483
- const iterator: AsyncIterator<unknown> = {
484
- next: async () => {
485
- const { done, value } = await reader.read();
486
- if (value instanceof StreamInterruptedError) {
487
- throw value;
488
- }
489
- if (done) {
490
- controllers.delete(chunkId);
491
- return {
492
- done: true,
493
- value: undefined,
494
- };
495
- }
496
-
497
- const [_chunkId, status, data] = value as IterableChunk;
498
-
499
- switch (status) {
500
- case ASYNC_ITERABLE_STATUS_VALUE:
501
- return {
502
- done: false,
503
- value: decode(data),
504
- };
505
- case ASYNC_ITERABLE_STATUS_RETURN:
506
- controllers.delete(chunkId);
507
- return {
508
- done: true,
509
- value: decode(data),
510
- };
511
- case ASYNC_ITERABLE_STATUS_ERROR:
512
- controllers.delete(chunkId);
513
- throw (
514
- opts.formatError?.({ error: data }) ??
515
- new AsyncError(data)
516
- );
517
- }
518
- },
519
- return: async () => {
495
+ const reader = stream.readable.getReader();
496
+ const iterator: AsyncIterator<unknown> = {
497
+ next: async () => {
498
+ const { done, value } = await reader.read();
499
+ if (value instanceof StreamInterruptedError) {
500
+ throw value;
501
+ }
502
+ if (done) {
503
+ controllers.delete(chunkId);
504
+ maybeAbort();
505
+
506
+ return {
507
+ done: true,
508
+ value: undefined,
509
+ };
510
+ }
511
+
512
+ const [_chunkId, status, data] = value as IterableChunk;
513
+
514
+ switch (status) {
515
+ case ASYNC_ITERABLE_STATUS_VALUE:
516
+ return {
517
+ done: false,
518
+ value: decode(data),
519
+ };
520
+ case ASYNC_ITERABLE_STATUS_RETURN:
520
521
  controllers.delete(chunkId);
522
+ maybeAbort();
523
+
521
524
  return {
522
525
  done: true,
523
- value: undefined,
526
+ value: decode(data),
524
527
  };
525
- },
528
+ case ASYNC_ITERABLE_STATUS_ERROR:
529
+ controllers.delete(chunkId);
530
+ maybeAbort();
531
+
532
+ throw (
533
+ opts.formatError?.({ error: data }) ?? new AsyncError(data)
534
+ );
535
+ }
536
+ },
537
+ return: async () => {
538
+ controllers.delete(chunkId);
539
+ maybeAbort();
540
+
541
+ return {
542
+ done: true,
543
+ value: undefined,
526
544
  };
527
- return iterator;
528
545
  },
529
546
  };
547
+ return {
548
+ [Symbol.asyncIterator]: () => iterator,
549
+ };
530
550
  }
531
551
  }
532
552
  }
@@ -571,19 +591,29 @@ export async function jsonlStreamConsumer<THead>(opts: {
571
591
  headDeferred.resolve(head as THead);
572
592
  headDeferred = null;
573
593
 
574
- controllers.activate();
575
594
  return;
576
595
  }
577
596
  const chunk = chunkOrHead as ChunkData;
578
597
  const [idx] = chunk;
579
598
 
580
599
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
581
- const controller = controllers.get(idx)!;
582
- controller.enqueue(chunk);
600
+ let readController = controllers.get(idx)!;
601
+ if (!readController) {
602
+ let deferred = chunkDeferred.get(idx);
603
+ if (!deferred) {
604
+ deferred = createDeferred();
605
+ chunkDeferred.set(idx, deferred);
606
+ }
607
+ readController = await deferred.promise;
608
+ }
609
+ readController.enqueue(chunk);
583
610
  },
584
611
  close: closeOrAbort,
585
612
  abort: closeOrAbort,
586
613
  }),
614
+ {
615
+ signal: opts.abortController.signal,
616
+ },
587
617
  )
588
618
  .catch((error) => {
589
619
  opts.onError?.({ error });
@@ -16,7 +16,7 @@ type Deserialize = (value: any) => any;
16
16
  /**
17
17
  * @internal
18
18
  */
19
- export interface PingOptions {
19
+ export interface SSEPingOptions {
20
20
  /**
21
21
  * Enable ping comments sent from the server
22
22
  * @default false
@@ -29,15 +29,22 @@ export interface PingOptions {
29
29
  intervalMs?: number;
30
30
  }
31
31
 
32
+ export interface SSEClientOptions {
33
+ /**
34
+ * Timeout and reconnect after inactivity in milliseconds
35
+ * @default undefined
36
+ */
37
+ reconnectAfterInactivityMs?: number;
38
+ }
39
+
32
40
  export interface SSEStreamProducerOptions<TValue = unknown> {
33
41
  serialize?: Serialize;
34
42
  data: AsyncIterable<TValue>;
35
- abortCtrl: AbortController;
43
+
36
44
  maxDepth?: number;
37
- ping?: PingOptions;
45
+ ping?: SSEPingOptions;
38
46
  /**
39
47
  * Maximum duration in milliseconds for the request before ending the stream
40
- * Only useful for serverless runtimes
41
48
  * @default undefined
42
49
  */
43
50
  maxDurationMs?: number;
@@ -48,10 +55,16 @@ export interface SSEStreamProducerOptions<TValue = unknown> {
48
55
  */
49
56
  emitAndEndImmediately?: boolean;
50
57
  formatError?: (opts: { error: unknown }) => unknown;
58
+ /**
59
+ * Client-specific options - these will be sent to the client as part of the first message
60
+ * @default {}
61
+ */
62
+ client?: SSEClientOptions;
51
63
  }
52
64
 
53
65
  const PING_EVENT = 'ping';
54
66
  const SERIALIZED_ERROR_EVENT = 'serialized-error';
67
+ const CONNECTED_EVENT = 'connected';
55
68
 
56
69
  type SSEvent = Partial<{
57
70
  id: string;
@@ -67,14 +80,28 @@ export function sseStreamProducer<TValue = unknown>(
67
80
  opts: SSEStreamProducerOptions<TValue>,
68
81
  ) {
69
82
  const stream = createReadableStream<SSEvent>();
70
- stream.controller.enqueue({ comment: 'connected' });
71
83
 
72
84
  const { serialize = identity } = opts;
73
85
 
74
- const ping: Required<PingOptions> = {
86
+ const ping: Required<SSEPingOptions> = {
75
87
  enabled: opts.ping?.enabled ?? false,
76
88
  intervalMs: opts.ping?.intervalMs ?? 1000,
77
89
  };
90
+ const client: SSEClientOptions = opts.client ?? {};
91
+
92
+ stream.controller.enqueue({
93
+ event: CONNECTED_EVENT,
94
+ data: JSON.stringify(client),
95
+ });
96
+ if (
97
+ ping.enabled &&
98
+ client.reconnectAfterInactivityMs &&
99
+ ping.intervalMs > client.reconnectAfterInactivityMs
100
+ ) {
101
+ throw new Error(
102
+ `Ping interval must be less than client reconnect interval to prevent unnecessary reconnection - ping.intervalMs: ${ping.intervalMs} client.reconnectAfterInactivityMs: ${client.reconnectAfterInactivityMs}`,
103
+ );
104
+ }
78
105
 
79
106
  run(async () => {
80
107
  type TIteratorValue = Awaited<TValue> | typeof PING_SYM;
@@ -85,7 +112,6 @@ export function sseStreamProducer<TValue = unknown>(
85
112
  iterable = takeWithGrace(iterable, {
86
113
  count: 1,
87
114
  gracePeriodMs: 1,
88
- abortCtrl: opts.abortCtrl,
89
115
  });
90
116
  }
91
117
 
@@ -96,7 +122,6 @@ export function sseStreamProducer<TValue = unknown>(
96
122
  ) {
97
123
  iterable = withMaxDuration(iterable, {
98
124
  maxDurationMs: opts.maxDurationMs,
99
- abortCtrl: opts.abortCtrl,
100
125
  });
101
126
  }
102
127
 
@@ -191,11 +216,6 @@ interface ConsumerStreamResultError<TConfig extends ConsumerConfig>
191
216
  error: TConfig['error'];
192
217
  }
193
218
 
194
- interface ConsumerStreamResultOpened<TConfig extends ConsumerConfig>
195
- extends ConsumerStreamResultBase<TConfig> {
196
- type: 'opened';
197
- }
198
-
199
219
  interface ConsumerStreamResultConnecting<TConfig extends ConsumerConfig>
200
220
  extends ConsumerStreamResultBase<TConfig> {
201
221
  type: 'connecting';
@@ -204,20 +224,26 @@ interface ConsumerStreamResultConnecting<TConfig extends ConsumerConfig>
204
224
  interface ConsumerStreamResultTimeout<TConfig extends ConsumerConfig>
205
225
  extends ConsumerStreamResultBase<TConfig> {
206
226
  type: 'timeout';
227
+ ms: number;
207
228
  }
208
-
209
229
  interface ConsumerStreamResultPing<TConfig extends ConsumerConfig>
210
230
  extends ConsumerStreamResultBase<TConfig> {
211
231
  type: 'ping';
212
232
  }
213
233
 
234
+ interface ConsumerStreamResultConnected<TConfig extends ConsumerConfig>
235
+ extends ConsumerStreamResultBase<TConfig> {
236
+ type: 'connected';
237
+ options: SSEClientOptions;
238
+ }
239
+
214
240
  type ConsumerStreamResult<TConfig extends ConsumerConfig> =
215
241
  | ConsumerStreamResultData<TConfig>
216
242
  | ConsumerStreamResultError<TConfig>
217
- | ConsumerStreamResultOpened<TConfig>
218
243
  | ConsumerStreamResultConnecting<TConfig>
219
244
  | ConsumerStreamResultTimeout<TConfig>
220
- | ConsumerStreamResultPing<TConfig>;
245
+ | ConsumerStreamResultPing<TConfig>
246
+ | ConsumerStreamResultConnected<TConfig>;
221
247
 
222
248
  export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
223
249
  url: () => MaybePromise<string>;
@@ -227,10 +253,6 @@ export interface SSEStreamConsumerOptions<TConfig extends ConsumerConfig> {
227
253
  signal: AbortSignal;
228
254
  deserialize?: Deserialize;
229
255
  EventSource: TConfig['EventSource'];
230
- /**
231
- * Reconnect after inactivity in milliseconds
232
- */
233
- reconnectAfterInactivityMs?: number;
234
256
  }
235
257
 
236
258
  interface ConsumerConfig {
@@ -272,6 +294,8 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
272
294
  ): AsyncIterable<ConsumerStreamResult<TConfig>> {
273
295
  const { deserialize = (v) => v } = opts;
274
296
 
297
+ let clientOptions: SSEClientOptions = {};
298
+
275
299
  const signal = opts.signal;
276
300
 
277
301
  let _es: InstanceType<TConfig['EventSource']> | null = null;
@@ -290,9 +314,15 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
290
314
  eventSource: _es,
291
315
  event: null,
292
316
  });
293
- eventSource.addEventListener('open', () => {
317
+
318
+ eventSource.addEventListener(CONNECTED_EVENT, (_msg) => {
319
+ const msg = _msg as EventSourceLike.MessageEvent;
320
+ const options: SSEClientOptions = JSON.parse(msg.data);
321
+
322
+ clientOptions = options;
294
323
  controller.enqueue({
295
- type: 'opened',
324
+ type: 'connected',
325
+ options,
296
326
  eventSource,
297
327
  });
298
328
  });
@@ -376,10 +406,11 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
376
406
  async next() {
377
407
  let promise = stream.reader.read();
378
408
 
379
- if (opts.reconnectAfterInactivityMs) {
409
+ const timeoutMs = clientOptions.reconnectAfterInactivityMs;
410
+ if (timeoutMs) {
380
411
  promise = withTimeout({
381
412
  promise,
382
- timeoutMs: opts.reconnectAfterInactivityMs,
413
+ timeoutMs,
383
414
  onTimeout: async () => {
384
415
  // Close and release old reader
385
416
  await stream.cancel();
@@ -390,6 +421,7 @@ export function sseStreamConsumer<TConfig extends ConsumerConfig>(
390
421
  return {
391
422
  value: {
392
423
  type: 'timeout',
424
+ ms: timeoutMs,
393
425
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
394
426
  eventSource: _es!,
395
427
  },