@trpc/server 11.0.0-alpha-tmp-app-router-example.388 → 11.0.0-alpha-tmp-issues-5851-take-two.451

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 (139) hide show
  1. package/dist/@trpc/server/http.d.ts +1 -2
  2. package/dist/@trpc/server/http.d.ts.map +1 -1
  3. package/dist/@trpc/server/index.d.ts +1 -1
  4. package/dist/@trpc/server/index.d.ts.map +1 -1
  5. package/dist/@trpc/server/rpc.d.ts +1 -1
  6. package/dist/@trpc/server/rpc.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda/getPlanner.d.ts.map +1 -1
  8. package/dist/adapters/aws-lambda/getPlanner.js +19 -2
  9. package/dist/adapters/aws-lambda/getPlanner.mjs +19 -2
  10. package/dist/adapters/next-app-dir/nextAppDirCaller.d.ts.map +1 -1
  11. package/dist/adapters/next-app-dir/nextAppDirCaller.js +1 -1
  12. package/dist/adapters/next-app-dir/nextAppDirCaller.mjs +1 -1
  13. package/dist/adapters/next-app-dir/redirect.d.ts.map +1 -1
  14. package/dist/adapters/next.js +1 -1
  15. package/dist/adapters/next.mjs +1 -1
  16. package/dist/adapters/node-http/incomingMessageToRequest.d.ts +0 -1
  17. package/dist/adapters/node-http/incomingMessageToRequest.d.ts.map +1 -1
  18. package/dist/adapters/node-http/incomingMessageToRequest.js +3 -1
  19. package/dist/adapters/node-http/incomingMessageToRequest.mjs +3 -1
  20. package/dist/adapters/node-http/nodeHTTPRequestHandler.d.ts.map +1 -1
  21. package/dist/adapters/node-http/nodeHTTPRequestHandler.js +30 -7
  22. package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +30 -7
  23. package/dist/adapters/node-http/types.d.ts +0 -1
  24. package/dist/adapters/node-http/types.d.ts.map +1 -1
  25. package/dist/adapters/standalone.d.ts +0 -1
  26. package/dist/adapters/standalone.d.ts.map +1 -1
  27. package/dist/adapters/ws.d.ts +3 -4
  28. package/dist/adapters/ws.d.ts.map +1 -1
  29. package/dist/adapters/ws.js +172 -114
  30. package/dist/adapters/ws.mjs +172 -114
  31. package/dist/bundle-analysis.json +235 -154
  32. package/dist/http.js +3 -0
  33. package/dist/http.mjs +1 -0
  34. package/dist/index.js +7 -5
  35. package/dist/index.mjs +3 -2
  36. package/dist/observable/observable.d.ts +1 -0
  37. package/dist/observable/observable.d.ts.map +1 -1
  38. package/dist/observable/observable.js +55 -0
  39. package/dist/observable/observable.mjs +55 -1
  40. package/dist/unstable-core-do-not-import/createProxy.d.ts +3 -3
  41. package/dist/unstable-core-do-not-import/createProxy.d.ts.map +1 -1
  42. package/dist/unstable-core-do-not-import/createProxy.js +15 -6
  43. package/dist/unstable-core-do-not-import/createProxy.mjs +15 -6
  44. package/dist/unstable-core-do-not-import/http/contentType.d.ts +7 -4
  45. package/dist/unstable-core-do-not-import/http/contentType.d.ts.map +1 -1
  46. package/dist/unstable-core-do-not-import/http/contentType.js +60 -17
  47. package/dist/unstable-core-do-not-import/http/contentType.mjs +61 -18
  48. package/dist/unstable-core-do-not-import/http/formDataToObject.d.ts.map +1 -0
  49. package/dist/unstable-core-do-not-import/http/formDataToObject.js +40 -0
  50. package/dist/unstable-core-do-not-import/http/formDataToObject.mjs +38 -0
  51. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.d.ts.map +1 -1
  52. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.js +4 -4
  53. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.mjs +4 -4
  54. package/dist/unstable-core-do-not-import/http/parseConnectionParams.d.ts +4 -0
  55. package/dist/unstable-core-do-not-import/http/parseConnectionParams.d.ts.map +1 -0
  56. package/dist/unstable-core-do-not-import/http/parseConnectionParams.js +42 -0
  57. package/dist/unstable-core-do-not-import/http/parseConnectionParams.mjs +39 -0
  58. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  59. package/dist/unstable-core-do-not-import/http/resolveResponse.js +302 -149
  60. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +301 -148
  61. package/dist/unstable-core-do-not-import/http/types.d.ts +26 -2
  62. package/dist/unstable-core-do-not-import/http/types.d.ts.map +1 -1
  63. package/dist/unstable-core-do-not-import/initTRPC.d.ts +12 -12
  64. package/dist/unstable-core-do-not-import/initTRPC.d.ts.map +1 -1
  65. package/dist/unstable-core-do-not-import/middleware.d.ts +3 -3
  66. package/dist/unstable-core-do-not-import/middleware.d.ts.map +1 -1
  67. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts +3 -1
  68. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts.map +1 -1
  69. package/dist/unstable-core-do-not-import/rootConfig.d.ts +12 -0
  70. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  71. package/dist/unstable-core-do-not-import/router.d.ts +2 -2
  72. package/dist/unstable-core-do-not-import/router.d.ts.map +1 -1
  73. package/dist/unstable-core-do-not-import/router.js +7 -2
  74. package/dist/unstable-core-do-not-import/router.mjs +7 -2
  75. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +7 -0
  76. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
  77. package/dist/unstable-core-do-not-import/rpc/index.d.ts +1 -1
  78. package/dist/unstable-core-do-not-import/rpc/index.d.ts.map +1 -1
  79. package/dist/unstable-core-do-not-import/stream/{stream.d.ts → jsonl.d.ts} +12 -8
  80. package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -0
  81. package/dist/unstable-core-do-not-import/stream/{stream.js → jsonl.js} +158 -119
  82. package/dist/unstable-core-do-not-import/stream/{stream.mjs → jsonl.mjs} +157 -118
  83. package/dist/unstable-core-do-not-import/stream/sse.d.ts +86 -0
  84. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -0
  85. package/dist/unstable-core-do-not-import/stream/sse.js +178 -0
  86. package/dist/unstable-core-do-not-import/stream/sse.mjs +172 -0
  87. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts +18 -0
  88. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts.map +1 -0
  89. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.js +46 -0
  90. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.mjs +43 -0
  91. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts +10 -0
  92. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts.map +1 -0
  93. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.js +31 -0
  94. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.mjs +29 -0
  95. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts +7 -0
  96. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts.map +1 -0
  97. package/dist/unstable-core-do-not-import/transformer.d.ts +5 -5
  98. package/dist/unstable-core-do-not-import/utils.d.ts +4 -0
  99. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  100. package/dist/unstable-core-do-not-import/utils.js +4 -0
  101. package/dist/unstable-core-do-not-import/utils.mjs +4 -1
  102. package/dist/unstable-core-do-not-import.d.ts +5 -2
  103. package/dist/unstable-core-do-not-import.d.ts.map +1 -1
  104. package/dist/unstable-core-do-not-import.js +19 -7
  105. package/dist/unstable-core-do-not-import.mjs +6 -3
  106. package/package.json +6 -6
  107. package/src/@trpc/server/http.ts +7 -2
  108. package/src/@trpc/server/index.ts +1 -0
  109. package/src/@trpc/server/rpc.ts +1 -0
  110. package/src/adapters/aws-lambda/getPlanner.ts +21 -2
  111. package/src/adapters/next-app-dir/nextAppDirCaller.ts +2 -1
  112. package/src/adapters/node-http/incomingMessageToRequest.ts +3 -2
  113. package/src/adapters/node-http/nodeHTTPRequestHandler.ts +32 -7
  114. package/src/adapters/ws.ts +193 -107
  115. package/src/observable/observable.ts +63 -0
  116. package/src/unstable-core-do-not-import/createProxy.ts +23 -8
  117. package/src/unstable-core-do-not-import/http/contentType.ts +83 -21
  118. package/src/{adapters/next-app-dir → unstable-core-do-not-import/http}/formDataToObject.ts +18 -10
  119. package/src/unstable-core-do-not-import/http/getHTTPStatusCode.ts +4 -7
  120. package/src/unstable-core-do-not-import/http/parseConnectionParams.ts +49 -0
  121. package/src/unstable-core-do-not-import/http/resolveResponse.ts +333 -164
  122. package/src/unstable-core-do-not-import/http/types.ts +31 -2
  123. package/src/unstable-core-do-not-import/procedureBuilder.ts +8 -1
  124. package/src/unstable-core-do-not-import/rootConfig.ts +12 -0
  125. package/src/unstable-core-do-not-import/router.ts +47 -35
  126. package/src/unstable-core-do-not-import/rpc/envelopes.ts +9 -0
  127. package/src/unstable-core-do-not-import/rpc/index.ts +1 -0
  128. package/src/unstable-core-do-not-import/stream/{stream.ts → jsonl.ts} +186 -127
  129. package/src/unstable-core-do-not-import/stream/sse.ts +288 -0
  130. package/src/unstable-core-do-not-import/stream/utils/createDeferred.ts +48 -0
  131. package/src/unstable-core-do-not-import/stream/utils/createReadableStream.ts +31 -0
  132. package/src/unstable-core-do-not-import/stream/utils/createServer.ts +44 -0
  133. package/src/unstable-core-do-not-import/utils.ts +5 -0
  134. package/src/unstable-core-do-not-import.ts +5 -2
  135. package/dist/adapters/next-app-dir/formDataToObject.d.ts.map +0 -1
  136. package/dist/adapters/next-app-dir/formDataToObject.js +0 -34
  137. package/dist/adapters/next-app-dir/formDataToObject.mjs +0 -32
  138. package/dist/unstable-core-do-not-import/stream/stream.d.ts.map +0 -1
  139. /package/dist/{adapters/next-app-dir → unstable-core-do-not-import/http}/formDataToObject.d.ts +0 -0
@@ -1,19 +1,8 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- import { isAsyncIterable, isFunction, isObject } from '../utils';
3
-
4
- // ---------- utils
5
-
6
- function createReadableStream<TValue = unknown>() {
7
- let controller: ReadableStreamDefaultController<TValue> =
8
- null as unknown as ReadableStreamDefaultController<TValue>;
9
- const stream = new ReadableStream<TValue>({
10
- start(c) {
11
- controller = c;
12
- },
13
- });
14
-
15
- return [stream, controller] as const;
16
- }
1
+ import { getTRPCErrorFromUnknown } from '../error/TRPCError';
2
+ import { isAsyncIterable, isFunction, isObject, run } from '../utils';
3
+ import type { Deferred } from './utils/createDeferred';
4
+ import { createDeferred } from './utils/createDeferred';
5
+ import { createReadableStream } from './utils/createReadableStream';
17
6
 
18
7
  /**
19
8
  * A subset of the standard ReadableStream properties needed by tRPC internally.
@@ -65,19 +54,19 @@ type ChunkDefinition = [
65
54
  type: ChunkValueType,
66
55
  chunkId: ChunkIndex,
67
56
  ];
68
- type HydratedValue = [
57
+ type DehydratedValue = [
69
58
  // data
70
59
  [unknown],
71
60
  // chunk descriptions
72
61
  ...ChunkDefinition[],
73
62
  ];
74
63
 
75
- type Head = Record<string, HydratedValue>;
64
+ type Head = Record<string, DehydratedValue>;
76
65
  type PromiseChunk =
77
66
  | [
78
67
  chunkIndex: ChunkIndex,
79
68
  status: PROMISE_STATUS_FULFILLED,
80
- value: HydratedValue,
69
+ value: DehydratedValue,
81
70
  ]
82
71
  | [chunkIndex: ChunkIndex, status: PROMISE_STATUS_REJECTED, error: unknown];
83
72
  type IterableChunk =
@@ -85,7 +74,7 @@ type IterableChunk =
85
74
  | [
86
75
  chunkIndex: ChunkIndex,
87
76
  status: ASYNC_ITERABLE_STATUS_VALUE,
88
- value: HydratedValue,
77
+ value: DehydratedValue,
89
78
  ]
90
79
  | [
91
80
  chunkIndex: ChunkIndex,
@@ -105,18 +94,16 @@ export function isPromise(value: unknown): value is Promise<unknown> {
105
94
  type Serialize = (value: any) => any;
106
95
  type Deserialize = (value: any) => any;
107
96
 
97
+ type PathArray = readonly (string | number)[];
108
98
  export type ProducerOnError = (opts: {
109
99
  error: unknown;
110
- path: (string | number)[];
100
+ path: PathArray;
111
101
  }) => void;
112
102
  export interface ProducerOptions {
113
103
  serialize?: Serialize;
114
- data: Record<number, unknown>;
104
+ data: Record<string, unknown> | unknown[];
115
105
  onError?: ProducerOnError;
116
- formatError?: (opts: {
117
- error: unknown;
118
- path: (string | number)[];
119
- }) => unknown;
106
+ formatError?: (opts: { error: unknown; path: PathArray }) => unknown;
120
107
  maxDepth?: number;
121
108
  }
122
109
 
@@ -131,14 +118,15 @@ function createBatchStreamProducer(opts: ProducerOptions) {
131
118
  let counter = 0 as ChunkIndex;
132
119
  const placeholder = 0 as PlaceholderValue;
133
120
 
134
- const [stream, controller] = createReadableStream<ChunkData>();
121
+ const stream = createReadableStream<ChunkData>();
135
122
  const pending = new Set<ChunkIndex>();
123
+
136
124
  function maybeClose() {
137
- if (pending.size === 0) {
138
- controller.close();
125
+ if (pending.size === 0 && !stream.cancelled()) {
126
+ stream.controller.close();
139
127
  }
140
128
  }
141
- function hydratePromise(
129
+ function dehydratePromise(
142
130
  promise: Promise<unknown>,
143
131
  path: (string | number)[],
144
132
  ) {
@@ -152,19 +140,24 @@ function createBatchStreamProducer(opts: ProducerOptions) {
152
140
  }
153
141
  const idx = counter++ as ChunkIndex;
154
142
  pending.add(idx);
155
- const enqueue = (value: PromiseChunk) => {
156
- controller.enqueue(value);
157
- };
158
- promise
143
+
144
+ Promise.race([promise, stream.cancelledPromise])
159
145
  .then((it) => {
160
- enqueue([idx, PROMISE_STATUS_FULFILLED, hydrate(it, path)]);
146
+ if (it === null) {
147
+ return;
148
+ }
149
+ stream.controller.enqueue([
150
+ idx,
151
+ PROMISE_STATUS_FULFILLED,
152
+ dehydrate(it, path),
153
+ ]);
161
154
  })
162
- .catch((error) => {
163
- opts.onError?.({ error, path });
164
- enqueue([
155
+ .catch((cause) => {
156
+ opts.onError?.({ error: cause, path });
157
+ stream.controller.enqueue([
165
158
  idx,
166
159
  PROMISE_STATUS_REJECTED,
167
- opts.formatError?.({ error, path }),
160
+ opts.formatError?.({ error: cause, path }),
168
161
  ]);
169
162
  })
170
163
  .finally(() => {
@@ -173,7 +166,7 @@ function createBatchStreamProducer(opts: ProducerOptions) {
173
166
  });
174
167
  return idx;
175
168
  }
176
- function hydrateAsyncIterable(
169
+ function dehydrateAsyncIterable(
177
170
  iterable: AsyncIterable<unknown>,
178
171
  path: (string | number)[],
179
172
  ) {
@@ -187,28 +180,56 @@ function createBatchStreamProducer(opts: ProducerOptions) {
187
180
  }
188
181
  const idx = counter++ as ChunkIndex;
189
182
  pending.add(idx);
190
- void (async () => {
191
- try {
192
- for await (const item of iterable) {
193
- controller.enqueue([
183
+ run(async () => {
184
+ const iterator = iterable[Symbol.asyncIterator]();
185
+
186
+ while (true) {
187
+ const next = await Promise.race([
188
+ iterator.next().catch(getTRPCErrorFromUnknown),
189
+ stream.cancelledPromise,
190
+ ]);
191
+
192
+ if (next instanceof Error) {
193
+ opts.onError?.({ error: next, path });
194
+
195
+ stream.controller.enqueue([
194
196
  idx,
195
- ASYNC_ITERABLE_STATUS_VALUE,
196
- hydrate(item, path),
197
+ ASYNC_ITERABLE_STATUS_ERROR,
198
+ opts.formatError?.({ error: next, path }),
197
199
  ]);
200
+ return;
201
+ }
202
+ if (next === 'cancelled') {
203
+ await iterator.return?.();
204
+ break;
205
+ }
206
+ if (next.done) {
207
+ stream.controller.enqueue([idx, ASYNC_ITERABLE_STATUS_DONE]);
208
+ break;
198
209
  }
199
- controller.enqueue([idx, ASYNC_ITERABLE_STATUS_DONE]);
200
- } catch (error) {
201
- opts.onError?.({ error, path });
202
- controller.enqueue([
210
+ stream.controller.enqueue([
203
211
  idx,
204
- ASYNC_ITERABLE_STATUS_ERROR,
205
- opts.formatError?.({ error, path }),
212
+ ASYNC_ITERABLE_STATUS_VALUE,
213
+ dehydrate(next.value, path),
206
214
  ]);
207
- } finally {
208
- pending.delete(idx);
209
- maybeClose();
210
215
  }
211
- })();
216
+
217
+ pending.delete(idx);
218
+ maybeClose();
219
+ }).catch((cause) => {
220
+ // this shouldn't happen, but node crashes if we don't catch it
221
+ opts.onError?.({
222
+ error: new Error(
223
+ 'You found a bug - please report it on https://github.com/trpc/trpc',
224
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
225
+ // @ts-ignore https://github.com/tc39/proposal-error-cause
226
+ {
227
+ cause,
228
+ },
229
+ ),
230
+ path,
231
+ });
232
+ });
212
233
  return idx;
213
234
  }
214
235
  function checkMaxDepth(path: (string | number)[]) {
@@ -217,12 +238,12 @@ function createBatchStreamProducer(opts: ProducerOptions) {
217
238
  }
218
239
  return null;
219
240
  }
220
- function hydrateChunk(
241
+ function dehydrateChunk(
221
242
  value: unknown,
222
243
  path: (string | number)[],
223
244
  ): null | [type: ChunkValueType, chunkId: ChunkIndex] {
224
245
  if (isPromise(value)) {
225
- return [CHUNK_VALUE_TYPE_PROMISE, hydratePromise(value, path)];
246
+ return [CHUNK_VALUE_TYPE_PROMISE, dehydratePromise(value, path)];
226
247
  }
227
248
  if (isAsyncIterable(value)) {
228
249
  if (opts.maxDepth && path.length >= opts.maxDepth) {
@@ -230,13 +251,16 @@ function createBatchStreamProducer(opts: ProducerOptions) {
230
251
  }
231
252
  return [
232
253
  CHUNK_VALUE_TYPE_ASYNC_ITERABLE,
233
- hydrateAsyncIterable(value, path),
254
+ dehydrateAsyncIterable(value, path),
234
255
  ];
235
256
  }
236
257
  return null;
237
258
  }
238
- function hydrate(value: unknown, path: (string | number)[]): HydratedValue {
239
- const reg = hydrateChunk(value, path);
259
+ function dehydrate(
260
+ value: unknown,
261
+ path: (string | number)[],
262
+ ): DehydratedValue {
263
+ const reg = dehydrateChunk(value, path);
240
264
  if (reg) {
241
265
  return [[placeholder], [null, ...reg]];
242
266
  }
@@ -246,7 +270,7 @@ function createBatchStreamProducer(opts: ProducerOptions) {
246
270
  const newObj = {} as Record<string, unknown>;
247
271
  const asyncValues: ChunkDefinition[] = [];
248
272
  for (const [key, item] of Object.entries(value)) {
249
- const transformed = hydrateChunk(item, [...path, key]);
273
+ const transformed = dehydrateChunk(item, [...path, key]);
250
274
  if (!transformed) {
251
275
  newObj[key] = item;
252
276
  continue;
@@ -259,10 +283,10 @@ function createBatchStreamProducer(opts: ProducerOptions) {
259
283
 
260
284
  const newHead: Head = {};
261
285
  for (const [key, item] of Object.entries(data)) {
262
- newHead[key] = hydrate(item, [key]);
286
+ newHead[key] = dehydrate(item, [key]);
263
287
  }
264
288
 
265
- return [newHead, stream] as const;
289
+ return [newHead, stream.readable] as const;
266
290
  }
267
291
  /**
268
292
  * JSON Lines stream producer
@@ -296,6 +320,7 @@ export function jsonlStreamProducer(opts: ProducerOptions) {
296
320
  )
297
321
  .pipeThrough(new TextEncoderStream());
298
322
  }
323
+
299
324
  class StreamInterruptedError extends Error {
300
325
  constructor(cause?: unknown) {
301
326
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -313,7 +338,7 @@ export type ConsumerOnError = (opts: { error: unknown }) => void;
313
338
  const nodeJsStreamToReaderEsque = (source: NodeJSReadableStreamEsque) => {
314
339
  return {
315
340
  getReader() {
316
- const [stream, controller] = createReadableStream<Uint8Array>();
341
+ const { readable, controller } = createReadableStream<Uint8Array>();
317
342
  source.on('data', (chunk) => {
318
343
  controller.enqueue(chunk);
319
344
  });
@@ -323,7 +348,7 @@ const nodeJsStreamToReaderEsque = (source: NodeJSReadableStreamEsque) => {
323
348
  source.on('error', (error) => {
324
349
  controller.error(error);
325
350
  });
326
- return stream.getReader();
351
+ return readable.getReader();
327
352
  },
328
353
  };
329
354
  };
@@ -388,19 +413,6 @@ function createConsumerStream<THead>(
388
413
  );
389
414
  }
390
415
 
391
- function createDeferred<TValue>() {
392
- let resolve: (value: TValue) => void;
393
- let reject: (error: unknown) => void;
394
- const promise = new Promise<TValue>((res, rej) => {
395
- resolve = res;
396
- reject = rej;
397
- });
398
-
399
- return { promise, resolve: resolve!, reject: reject! };
400
- }
401
-
402
- type Deferred<TValue> = ReturnType<typeof createDeferred<TValue>>;
403
-
404
416
  /**
405
417
  * JSON Lines stream consumer
406
418
  * @see https://jsonlines.org/
@@ -412,6 +424,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
412
424
  formatError?: (opts: { error: unknown }) => Error;
413
425
  }) {
414
426
  const { deserialize = (v) => v } = opts;
427
+ const streamAbortController = new AbortController();
415
428
 
416
429
  let source = createConsumerStream<Head>(opts.from);
417
430
  if (deserialize) {
@@ -427,19 +440,28 @@ export async function jsonlStreamConsumer<THead>(opts: {
427
440
 
428
441
  type ControllerChunk = ChunkData | StreamInterruptedError;
429
442
  type ChunkController = ReadableStreamDefaultController<ControllerChunk>;
430
- const chunkDeferred = new Map<ChunkIndex, Deferred<ChunkController>>();
431
- const controllers = new Map<ChunkIndex, ChunkController>();
443
+ type ControllerWrapper = {
444
+ controller: ChunkController;
445
+ abortController: AbortController;
446
+ };
447
+ const chunkDeferred = new Map<ChunkIndex, Deferred<ControllerWrapper>>();
448
+ const controllers = new Map<ChunkIndex, ControllerWrapper>();
432
449
 
433
- function dehydrateChunkDefinition(value: ChunkDefinition) {
450
+ function hydrateChunkDefinition(value: ChunkDefinition, path: PathArray) {
434
451
  const [_path, type, chunkId] = value;
435
452
 
436
- const [stream, controller] = createReadableStream<ChunkData>();
437
- controllers.set(chunkId, controller);
453
+ const { readable, controller } = createReadableStream<ChunkData>();
454
+
455
+ const wrapper: ControllerWrapper = {
456
+ controller,
457
+ abortController: new AbortController(),
458
+ };
459
+ controllers.set(chunkId, wrapper);
438
460
 
439
461
  // resolve chunk deferred if it exists
440
462
  const deferred = chunkDeferred.get(chunkId);
441
463
  if (deferred) {
442
- deferred.resolve(controller);
464
+ deferred.resolve(wrapper);
443
465
  chunkDeferred.delete(chunkId);
444
466
  }
445
467
 
@@ -447,7 +469,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
447
469
  case CHUNK_VALUE_TYPE_PROMISE: {
448
470
  return new Promise((resolve, reject) => {
449
471
  // listen for next value in the stream
450
- const reader = stream.getReader();
472
+ const reader = readable.getReader();
451
473
  reader
452
474
  .read()
453
475
  .then((it) => {
@@ -463,7 +485,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
463
485
  const [_chunkId, status, data] = value as PromiseChunk;
464
486
  switch (status) {
465
487
  case PROMISE_STATUS_FULFILLED:
466
- resolve(dehydrate(data));
488
+ resolve(hydrate(data, path));
467
489
  break;
468
490
  case PROMISE_STATUS_REJECTED:
469
491
  reject(
@@ -481,51 +503,83 @@ export async function jsonlStreamConsumer<THead>(opts: {
481
503
  }
482
504
  case CHUNK_VALUE_TYPE_ASYNC_ITERABLE: {
483
505
  return {
484
- [Symbol.asyncIterator]: async function* () {
485
- const reader = stream.getReader();
486
- while (true) {
487
- const { done, value } = await reader.read();
488
- if (done) {
489
- break;
490
- }
491
- if (value instanceof StreamInterruptedError) {
492
- throw value;
493
- }
494
-
495
- const [_chunkId, status, data] = value as IterableChunk;
496
-
497
- switch (status) {
498
- case ASYNC_ITERABLE_STATUS_VALUE:
499
- yield dehydrate(data);
500
- break;
501
- case ASYNC_ITERABLE_STATUS_DONE:
502
- controllers.delete(chunkId);
503
- return;
504
- case ASYNC_ITERABLE_STATUS_ERROR:
506
+ [Symbol.asyncIterator]: () => {
507
+ const reader = readable.getReader();
508
+ const iterator: AsyncIterator<unknown> = {
509
+ next: async () => {
510
+ const { done, value } = await reader.read();
511
+ if (value instanceof StreamInterruptedError) {
512
+ throw value;
513
+ }
514
+ if (done) {
505
515
  controllers.delete(chunkId);
506
- throw (
507
- opts.formatError?.({ error: data }) ?? new AsyncError(data)
508
- );
509
- }
510
- }
516
+ return {
517
+ done: true,
518
+ value: undefined,
519
+ };
520
+ }
521
+
522
+ const [_chunkId, status, data] = value as IterableChunk;
523
+
524
+ switch (status) {
525
+ case ASYNC_ITERABLE_STATUS_VALUE:
526
+ return {
527
+ done: false,
528
+ value: hydrate(data, path),
529
+ };
530
+ case ASYNC_ITERABLE_STATUS_DONE:
531
+ controllers.delete(chunkId);
532
+ return {
533
+ done: true,
534
+ value: undefined,
535
+ };
536
+ case ASYNC_ITERABLE_STATUS_ERROR:
537
+ controllers.delete(chunkId);
538
+ throw (
539
+ opts.formatError?.({ error: data }) ??
540
+ new AsyncError(data)
541
+ );
542
+ }
543
+ },
544
+ return: async () => {
545
+ wrapper.abortController.abort();
546
+ if (
547
+ chunkDeferred.size === 0 &&
548
+ Array.from(controllers.values()).every(
549
+ (it) => it.abortController.signal.aborted,
550
+ )
551
+ ) {
552
+ // nothing is listening to the stream anymore
553
+ streamAbortController.abort();
554
+ }
555
+ return {
556
+ done: true,
557
+ value: undefined,
558
+ };
559
+ },
560
+ };
561
+ return iterator;
511
562
  },
512
563
  };
513
564
  }
514
565
  }
515
566
  }
516
567
 
517
- function dehydrate(value: HydratedValue): unknown {
568
+ function hydrate(value: DehydratedValue, path: PathArray): unknown {
518
569
  const [[data], ...asyncProps] = value;
519
570
 
520
571
  for (const value of asyncProps) {
521
- const dehydrated = dehydrateChunkDefinition(value);
522
-
523
- const [path] = value;
524
- if (path === null) {
525
- return dehydrated;
572
+ const [key] = value;
573
+ const hydrated = hydrateChunkDefinition(
574
+ value,
575
+ key === null ? path : [...path, key],
576
+ );
577
+
578
+ if (key === null) {
579
+ return hydrated;
526
580
  }
527
581
 
528
- (data as any)[path] = dehydrated;
582
+ (data as any)[key] = hydrated;
529
583
  }
530
584
  return data;
531
585
  }
@@ -538,7 +592,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
538
592
  deferred.reject(error);
539
593
  }
540
594
  chunkDeferred.clear();
541
- for (const controller of controllers.values()) {
595
+ for (const { controller } of controllers.values()) {
542
596
  controller.enqueue(error);
543
597
  controller.close();
544
598
  }
@@ -547,12 +601,17 @@ export async function jsonlStreamConsumer<THead>(opts: {
547
601
  source
548
602
  .pipeTo(
549
603
  new WritableStream({
604
+ start(controller) {
605
+ streamAbortController.signal.addEventListener('abort', () => {
606
+ controller.error(streamAbortController.signal.reason);
607
+ });
608
+ },
550
609
  async write(chunkOrHead) {
551
610
  if (headDeferred) {
552
611
  const head = chunkOrHead as Record<number | string, unknown>;
553
612
 
554
613
  for (const [key, value] of Object.entries(chunkOrHead)) {
555
- const parsed = dehydrate(value as any);
614
+ const parsed = hydrate(value as any, [key]);
556
615
  head[key] = parsed;
557
616
  }
558
617
  headDeferred.resolve(head as THead);
@@ -561,17 +620,17 @@ export async function jsonlStreamConsumer<THead>(opts: {
561
620
  }
562
621
  const chunk = chunkOrHead as ChunkData;
563
622
  const [idx] = chunk;
564
- let controller = controllers.get(idx);
565
- if (!controller) {
623
+ let wrapper = controllers.get(idx);
624
+ if (!wrapper) {
566
625
  let deferred = chunkDeferred.get(idx);
567
626
  if (!deferred) {
568
- deferred = createDeferred<ChunkController>();
627
+ deferred = createDeferred();
569
628
  chunkDeferred.set(idx, deferred);
570
629
  }
571
630
 
572
- controller = await deferred.promise;
631
+ wrapper = await deferred.promise;
573
632
  }
574
- controller.enqueue(chunk);
633
+ wrapper.controller.enqueue(chunk);
575
634
  },
576
635
  close: closeOrAbort,
577
636
  abort: closeOrAbort,