@trpc/server 11.0.0-rc.373 → 11.0.0-rc.374

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 (31) hide show
  1. package/dist/bundle-analysis.json +116 -97
  2. package/dist/unstable-core-do-not-import/clientish/serialize.d.ts +1 -1
  3. package/dist/unstable-core-do-not-import/clientish/serialize.d.ts.map +1 -1
  4. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  5. package/dist/unstable-core-do-not-import/http/resolveResponse.js +79 -52
  6. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +78 -51
  7. package/dist/unstable-core-do-not-import/initTRPC.d.ts.map +1 -1
  8. package/dist/unstable-core-do-not-import/initTRPC.js +2 -1
  9. package/dist/unstable-core-do-not-import/initTRPC.mjs +2 -1
  10. package/dist/unstable-core-do-not-import/rootConfig.d.ts +6 -0
  11. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  12. package/dist/unstable-core-do-not-import/stream/stream.d.ts +94 -0
  13. package/dist/unstable-core-do-not-import/stream/stream.d.ts.map +1 -0
  14. package/dist/unstable-core-do-not-import/stream/stream.js +466 -0
  15. package/dist/unstable-core-do-not-import/stream/stream.mjs +462 -0
  16. package/dist/unstable-core-do-not-import/utils.d.ts +2 -1
  17. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  18. package/dist/unstable-core-do-not-import/utils.js +4 -0
  19. package/dist/unstable-core-do-not-import/utils.mjs +4 -1
  20. package/dist/unstable-core-do-not-import.d.ts +1 -0
  21. package/dist/unstable-core-do-not-import.d.ts.map +1 -1
  22. package/dist/unstable-core-do-not-import.js +5 -0
  23. package/dist/unstable-core-do-not-import.mjs +2 -1
  24. package/package.json +2 -2
  25. package/src/unstable-core-do-not-import/clientish/serialize.ts +1 -0
  26. package/src/unstable-core-do-not-import/http/resolveResponse.ts +83 -52
  27. package/src/unstable-core-do-not-import/initTRPC.ts +1 -0
  28. package/src/unstable-core-do-not-import/rootConfig.ts +7 -0
  29. package/src/unstable-core-do-not-import/stream/stream.ts +580 -0
  30. package/src/unstable-core-do-not-import/utils.ts +7 -1
  31. package/src/unstable-core-do-not-import.ts +1 -0
@@ -9,8 +9,9 @@ import {
9
9
  type inferRouterError,
10
10
  } from '../router';
11
11
  import type { TRPCResponse } from '../rpc';
12
+ import { isPromise, jsonlStreamProducer } from '../stream/stream';
12
13
  import { transformTRPCResponse } from '../transformer';
13
- import { getBatchStreamFormatter } from './batchStreamFormatter';
14
+ import { isObject } from '../utils';
14
15
  import { getRequestInfo } from './contentType';
15
16
  import { getHTTPStatusCode } from './getHTTPStatusCode';
16
17
  import type {
@@ -48,6 +49,7 @@ function initResponse<TRouter extends AnyRouter, TRequest>(initOpts: {
48
49
  | TRPCResponse<unknown, inferRouterError<TRouter>>[]
49
50
  | undefined;
50
51
  errors: TRPCError[];
52
+ headers: Headers;
51
53
  }) {
52
54
  const {
53
55
  ctx,
@@ -56,12 +58,11 @@ function initResponse<TRouter extends AnyRouter, TRequest>(initOpts: {
56
58
  responseMeta,
57
59
  untransformedJSON,
58
60
  errors = [],
61
+ headers,
59
62
  } = initOpts;
60
63
 
61
64
  let status = untransformedJSON ? getHTTPStatusCode(untransformedJSON) : 200;
62
65
 
63
- const headers = new Headers([['Content-Type', 'application/json']]);
64
-
65
66
  const eagerGeneration = !untransformedJSON;
66
67
  const data = eagerGeneration
67
68
  ? []
@@ -106,7 +107,6 @@ function initResponse<TRouter extends AnyRouter, TRequest>(initOpts: {
106
107
 
107
108
  return {
108
109
  status,
109
- headers,
110
110
  };
111
111
  }
112
112
 
@@ -159,6 +159,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
159
159
  opts: ResolveHTTPRequestOptions<TRouter>,
160
160
  ): Promise<Response> {
161
161
  const { router, req } = opts;
162
+ const headers = new Headers([['vary', 'trpc-accept']]);
162
163
 
163
164
  const url = new URL(req.url);
164
165
 
@@ -177,7 +178,10 @@ export async function resolveResponse<TRouter extends AnyRouter>(
177
178
  let ctx: inferRouterContext<TRouter> | undefined = undefined;
178
179
  let info: TRPCRequestInfo | undefined = undefined;
179
180
 
180
- const isStreamCall = req.headers.get('trpc-batch-mode') === 'stream';
181
+ const isStreamCall = req.headers.get('trpc-accept') === 'application/jsonl';
182
+
183
+ const experimentalIterablesAndDeferreds =
184
+ router._def._config.experimental?.iterablesAndDeferreds ?? false;
181
185
 
182
186
  try {
183
187
  info = getRequestInfo({
@@ -222,6 +226,27 @@ export async function resolveResponse<TRouter extends AnyRouter>(
222
226
  type,
223
227
  allowMethodOverride,
224
228
  });
229
+
230
+ if (
231
+ (!isStreamCall || !experimentalIterablesAndDeferreds) &&
232
+ isObject(data) &&
233
+ (Symbol.asyncIterator in data || Object.values(data).some(isPromise))
234
+ ) {
235
+ if (!isStreamCall) {
236
+ throw new TRPCError({
237
+ code: 'UNSUPPORTED_MEDIA_TYPE',
238
+ message:
239
+ 'Cannot return async iterable or nested promises in non-streaming response',
240
+ });
241
+ }
242
+ if (!experimentalIterablesAndDeferreds) {
243
+ throw new TRPCError({
244
+ code: 'INTERNAL_SERVER_ERROR',
245
+ message: 'Missing experimental flag "iterablesAndDeferreds"',
246
+ });
247
+ }
248
+ }
249
+
225
250
  return {
226
251
  result: {
227
252
  data,
@@ -254,6 +279,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
254
279
  }
255
280
  });
256
281
  if (!isStreamCall) {
282
+ headers.set('content-type', 'application/json');
257
283
  /**
258
284
  * Non-streaming response:
259
285
  * - await all responses in parallel, blocking on the slowest one
@@ -262,6 +288,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
262
288
  */
263
289
 
264
290
  const untransformedJSON = await Promise.all(promises);
291
+
265
292
  const errors = untransformedJSON.flatMap((response) =>
266
293
  'error' in response ? [response.error] : [],
267
294
  );
@@ -273,6 +300,7 @@ export async function resolveResponse<TRouter extends AnyRouter>(
273
300
  responseMeta: opts.responseMeta,
274
301
  untransformedJSON,
275
302
  errors,
303
+ headers,
276
304
  });
277
305
 
278
306
  // return body stuff
@@ -287,10 +315,12 @@ export async function resolveResponse<TRouter extends AnyRouter>(
287
315
 
288
316
  return new Response(body, {
289
317
  status: headResponse.status,
290
- headers: headResponse.headers,
318
+ headers,
291
319
  });
292
320
  }
293
321
 
322
+ headers.set('content-type', 'application/json');
323
+ headers.set('transfer-encoding', 'chunked');
294
324
  /**
295
325
  * Streaming response:
296
326
  * - block on none, call `onChunk` as soon as each response is ready
@@ -303,56 +333,56 @@ export async function resolveResponse<TRouter extends AnyRouter>(
303
333
  type,
304
334
  responseMeta: opts.responseMeta,
305
335
  errors: [],
336
+ headers,
306
337
  });
307
338
 
308
- const encoder = new TextEncoderStream();
309
- const stream = encoder.readable;
310
- const controller = encoder.writable.getWriter();
311
- async function exec() {
312
- const indexedPromises = new Map(
313
- promises.map((promise, index) => [
314
- index,
315
- promise.then((r) => [index, r] as const),
316
- ]),
317
- );
318
- const formatter = getBatchStreamFormatter();
319
-
320
- while (indexedPromises.size > 0) {
321
- const [index, untransformedJSON] = await Promise.race(
322
- indexedPromises.values(),
323
- );
324
- indexedPromises.delete(index);
325
-
326
- try {
327
- const transformedJSON = transformTRPCResponse(
328
- router._def._config,
329
- untransformedJSON,
330
- );
331
- const body = JSON.stringify(transformedJSON);
332
-
333
- await controller.write(formatter(index, body));
334
- } catch (cause) {
335
- const call = info!.calls[index]!;
336
- const input = call.result();
337
- const { body } = caughtErrorToData(cause, {
338
- opts,
339
- ctx,
340
- type,
341
- path: call.path,
342
- input,
343
- });
344
-
345
- await controller.write(formatter(index, body));
339
+ const stream = jsonlStreamProducer({
340
+ /**
341
+ * Example structure for `maxDepth: 4`:
342
+ * {
343
+ * // 1
344
+ * 0: {
345
+ * // 2
346
+ * result: {
347
+ * // 3
348
+ * data: // 4
349
+ * }
350
+ * }
351
+ * }
352
+ */
353
+ maxDepth: experimentalIterablesAndDeferreds ? 4 : 3,
354
+ data: promises.map(async (it) => {
355
+ const response = await it;
356
+ if ('result' in response) {
357
+ /**
358
+ * Not very pretty, but we need to wrap nested data in promises
359
+ * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
360
+ */
361
+ return {
362
+ ...response,
363
+ result: Promise.resolve({
364
+ ...response.result,
365
+ data: Promise.resolve(response.result.data),
366
+ }),
367
+ };
346
368
  }
347
- }
348
-
349
- await controller.write(formatter.end());
350
- await controller.close();
351
- }
352
- exec().catch((err) => controller.abort(err));
369
+ return response;
370
+ }),
371
+ serialize: opts.router._def._config.transformer.output.serialize,
372
+ onError: (cause) => {
373
+ opts.onError?.({
374
+ error: getTRPCErrorFromUnknown(cause),
375
+ path: undefined,
376
+ input: undefined,
377
+ ctx,
378
+ type,
379
+ req: opts.req,
380
+ });
381
+ },
382
+ });
353
383
 
354
384
  return new Response(stream, {
355
- headers: headResponse.headers,
385
+ headers,
356
386
  status: headResponse.status,
357
387
  });
358
388
  } catch (cause) {
@@ -376,11 +406,12 @@ export async function resolveResponse<TRouter extends AnyRouter>(
376
406
  responseMeta: opts.responseMeta,
377
407
  untransformedJSON,
378
408
  errors: [error],
409
+ headers,
379
410
  });
380
411
 
381
412
  return new Response(body, {
382
413
  status: headResponse.status,
383
- headers: headResponse.headers,
414
+ headers,
384
415
  });
385
416
  }
386
417
  }
@@ -95,6 +95,7 @@ class TRPCBuilder<TContext extends object, TMeta extends object> {
95
95
  * @internal
96
96
  */
97
97
  $types: null as any,
98
+ experimental: opts?.experimental ?? {},
98
99
  };
99
100
 
100
101
  {
@@ -63,6 +63,13 @@ export interface RootConfig<TTypes extends RootTypes> {
63
63
  isDev: boolean;
64
64
 
65
65
  defaultMeta?: TTypes['meta'] extends object ? TTypes['meta'] : never;
66
+
67
+ experimental?: {
68
+ /**
69
+ * Enable support for returning async iterables and returning deferred promises when using `httpBatchStreamLink`
70
+ */
71
+ iterablesAndDeferreds?: boolean;
72
+ };
66
73
  }
67
74
 
68
75
  /**