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

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 +236 -155
  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} +5 -5
  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} +148 -111
  82. package/dist/unstable-core-do-not-import/stream/{stream.mjs → jsonl.mjs} +147 -110
  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} +163 -110
  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,17 +1,21 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import {
3
+ isObservable,
4
+ observableToAsyncIterable,
5
+ } from '../../observable/observable';
2
6
  import { getErrorShape } from '../error/getErrorShape';
3
7
  import { getTRPCErrorFromUnknown, TRPCError } from '../error/TRPCError';
4
8
  import type { ProcedureType } from '../procedure';
5
9
  import {
6
- callProcedure,
7
10
  type AnyRouter,
8
11
  type inferRouterContext,
9
12
  type inferRouterError,
10
13
  } from '../router';
11
14
  import type { TRPCResponse } from '../rpc';
12
- import { isPromise, jsonlStreamProducer } from '../stream/stream';
15
+ import { isPromise, jsonlStreamProducer } from '../stream/jsonl';
16
+ import { sseHeaders, sseStreamProducer } from '../stream/sse';
13
17
  import { transformTRPCResponse } from '../transformer';
14
- import { isObject } from '../utils';
18
+ import { isAsyncIterable, isObject } from '../utils';
15
19
  import { getRequestInfo } from './contentType';
16
20
  import { getHTTPStatusCode } from './getHTTPStatusCode';
17
21
  import type {
@@ -20,12 +24,28 @@ import type {
20
24
  TRPCRequestInfo,
21
25
  } from './types';
22
26
 
23
- const HTTP_METHOD_PROCEDURE_TYPE_MAP: Record<
24
- string,
25
- ProcedureType | undefined
27
+ type HTTPMethods =
28
+ | 'GET'
29
+ | 'POST'
30
+ | 'HEAD'
31
+ | 'OPTIONS'
32
+ | 'PUT'
33
+ | 'DELETE'
34
+ | 'PATCH';
35
+
36
+ const TYPE_ACCEPTED_METHOD_MAP: Record<ProcedureType, HTTPMethods[]> = {
37
+ mutation: ['POST'],
38
+ query: ['GET'],
39
+ subscription: ['GET'],
40
+ };
41
+ const TYPE_ACCEPTED_METHOD_MAP_WITH_METHOD_OVERRIDE: Record<
42
+ ProcedureType,
43
+ HTTPMethods[]
26
44
  > = {
27
- GET: 'query',
28
- POST: 'mutation',
45
+ // never allow GET to do a mutation
46
+ mutation: ['POST'],
47
+ query: ['GET', 'POST'],
48
+ subscription: ['GET', 'POST'],
29
49
  };
30
50
 
31
51
  interface ResolveHTTPRequestOptions<TRouter extends AnyRouter>
@@ -42,19 +62,17 @@ interface ResolveHTTPRequestOptions<TRouter extends AnyRouter>
42
62
  function initResponse<TRouter extends AnyRouter, TRequest>(initOpts: {
43
63
  ctx: inferRouterContext<TRouter> | undefined;
44
64
  info: TRPCRequestInfo | undefined;
45
- type: ProcedureType | 'unknown';
46
65
  responseMeta?: HTTPBaseHandlerOptions<TRouter, TRequest>['responseMeta'];
47
- untransformedJSON?:
66
+ untransformedJSON:
48
67
  | TRPCResponse<unknown, inferRouterError<TRouter>>
49
68
  | TRPCResponse<unknown, inferRouterError<TRouter>>[]
50
- | undefined;
69
+ | null;
51
70
  errors: TRPCError[];
52
71
  headers: Headers;
53
72
  }) {
54
73
  const {
55
74
  ctx,
56
75
  info,
57
- type,
58
76
  responseMeta,
59
77
  untransformedJSON,
60
78
  errors = [],
@@ -75,10 +93,12 @@ function initResponse<TRouter extends AnyRouter, TRequest>(initOpts: {
75
93
  ctx,
76
94
  info,
77
95
  paths: info?.calls.map((call) => call.path),
78
- type,
79
96
  data,
80
97
  errors,
81
98
  eagerGeneration,
99
+ type:
100
+ info?.calls.find((call) => call.procedure?._def.type)?.procedure?._def
101
+ .type ?? 'unknown',
82
102
  }) ?? {};
83
103
 
84
104
  if (meta.headers) {
@@ -155,6 +175,25 @@ function caughtErrorToData<TRouter extends AnyRouter>(
155
175
  };
156
176
  }
157
177
 
178
+ /**
179
+ * Check if a value is a stream-like object
180
+ * - if it's an async iterable
181
+ * - if it's an object with async iterables or promises
182
+ */
183
+ function isDataStream(v: unknown) {
184
+ if (!isObject(v)) {
185
+ return false;
186
+ }
187
+
188
+ if (isAsyncIterable(v)) {
189
+ return true;
190
+ }
191
+
192
+ return (
193
+ Object.values(v).some(isPromise) || Object.values(v).some(isAsyncIterable)
194
+ );
195
+ }
196
+
158
197
  export async function resolveResponse<TRouter extends AnyRouter>(
159
198
  opts: ResolveHTTPRequestOptions<TRouter>,
160
199
  ): Promise<Response> {
@@ -170,26 +209,33 @@ export async function resolveResponse<TRouter extends AnyRouter>(
170
209
  status: 204,
171
210
  });
172
211
  }
212
+
173
213
  const allowBatching = opts.allowBatching ?? opts.batching?.enabled ?? true;
174
214
  const allowMethodOverride =
175
215
  (opts.allowMethodOverride ?? false) && req.method === 'POST';
176
-
177
- const type =
178
- HTTP_METHOD_PROCEDURE_TYPE_MAP[req.method] ?? ('unknown' as const);
179
216
  let ctx: inferRouterContext<TRouter> | undefined = undefined;
180
217
  let info: TRPCRequestInfo | undefined = undefined;
181
218
 
219
+ const methodMapper = allowMethodOverride
220
+ ? TYPE_ACCEPTED_METHOD_MAP_WITH_METHOD_OVERRIDE
221
+ : TYPE_ACCEPTED_METHOD_MAP;
222
+
223
+ /**
224
+ * @deprecated
225
+ */
182
226
  const isStreamCall = req.headers.get('trpc-accept') === 'application/jsonl';
183
227
 
184
228
  const experimentalIterablesAndDeferreds =
185
- config.experimental?.iterablesAndDeferreds ?? false;
186
-
229
+ router._def._config.experimental?.iterablesAndDeferreds ?? true;
230
+ const experimentalSSE =
231
+ router._def._config.experimental?.sseSubscriptions?.enabled ?? true;
187
232
  try {
188
233
  info = getRequestInfo({
189
234
  req,
190
235
  path: decodeURIComponent(opts.path),
191
- config: config,
236
+ router,
192
237
  searchParams: url.searchParams,
238
+ headers: opts.req.headers,
193
239
  });
194
240
 
195
241
  // we create context early so that error handlers may access context information
@@ -206,56 +252,49 @@ export async function resolveResponse<TRouter extends AnyRouter>(
206
252
  message: `Batching is not enabled on the server`,
207
253
  });
208
254
  }
209
- if (type === 'unknown') {
255
+ /* istanbul ignore if -- @preserve */
256
+ if (isStreamCall && !info.isBatchCall) {
210
257
  throw new TRPCError({
211
- message: `Unexpected request method ${req.method}`,
212
- code: 'METHOD_NOT_SUPPORTED',
258
+ message: `Streaming requests must be batched (you can do a batch of 1)`,
259
+ code: 'BAD_REQUEST',
213
260
  });
214
261
  }
215
262
 
216
- const errors: TRPCError[] = [];
217
-
218
- const promises: Promise<
219
- TRPCResponse<unknown, inferRouterError<TRouter>>
220
- >[] = info.calls.map(async (call) => {
263
+ type RPCResult =
264
+ | [result: null, error: TRPCError]
265
+ | [result: unknown, error?: never];
266
+ const rpcCalls = info.calls.map(async (call): Promise<RPCResult> => {
267
+ const proc = call.procedure;
221
268
  try {
222
- const data = await callProcedure({
223
- procedures: opts.router._def.procedures,
269
+ if (!proc) {
270
+ throw new TRPCError({
271
+ code: 'NOT_FOUND',
272
+ message: `No procedure found on path "${call.path}"`,
273
+ });
274
+ }
275
+
276
+ if (!methodMapper[proc._def.type].includes(req.method as HTTPMethods)) {
277
+ throw new TRPCError({
278
+ code: 'METHOD_NOT_SUPPORTED',
279
+ message: `Unsupported ${req.method}-request to ${proc._def.type} procedure at path "${call.path}"`,
280
+ });
281
+ }
282
+ /* istanbul ignore if -- @preserve */
283
+ if (proc._def.type === 'subscription' && info!.isBatchCall) {
284
+ throw new TRPCError({
285
+ code: 'BAD_REQUEST',
286
+ message: `Cannot batch subscription calls`,
287
+ });
288
+ }
289
+ const data: unknown = await proc({
224
290
  path: call.path,
225
291
  getRawInput: call.getRawInput,
226
292
  ctx,
227
- type,
228
- allowMethodOverride,
293
+ type: proc._def.type,
229
294
  });
230
-
231
- if (
232
- (!isStreamCall || !experimentalIterablesAndDeferreds) &&
233
- isObject(data) &&
234
- (Symbol.asyncIterator in data || Object.values(data).some(isPromise))
235
- ) {
236
- if (!isStreamCall) {
237
- throw new TRPCError({
238
- code: 'UNSUPPORTED_MEDIA_TYPE',
239
- message:
240
- 'Cannot return async iterable or nested promises in non-streaming response',
241
- });
242
- }
243
- if (!experimentalIterablesAndDeferreds) {
244
- throw new TRPCError({
245
- code: 'INTERNAL_SERVER_ERROR',
246
- message: 'Missing experimental flag "iterablesAndDeferreds"',
247
- });
248
- }
249
- }
250
-
251
- return {
252
- result: {
253
- data,
254
- },
255
- };
295
+ return [data];
256
296
  } catch (cause) {
257
297
  const error = getTRPCErrorFromUnknown(cause);
258
- errors.push(error);
259
298
  const input = call.result();
260
299
 
261
300
  opts.onError?.({
@@ -263,141 +302,272 @@ export async function resolveResponse<TRouter extends AnyRouter>(
263
302
  path: call.path,
264
303
  input,
265
304
  ctx,
266
- type: type,
305
+ type: call.procedure?._def.type ?? 'unknown',
267
306
  req: opts.req,
268
307
  });
269
308
 
270
- return {
271
- error: getErrorShape({
272
- config,
273
- error,
274
- type,
275
- path: call.path,
276
- input,
277
- ctx,
278
- }),
279
- };
309
+ return [null, error];
280
310
  }
281
311
  });
282
- if (!isStreamCall) {
283
- headers.set('content-type', 'application/json');
284
- /**
285
- * Non-streaming response:
286
- * - await all responses in parallel, blocking on the slowest one
287
- * - create headers with known response body
288
- * - return a complete HTTPResponse
289
- */
290
312
 
291
- const untransformedJSON = await Promise.all(promises);
313
+ // ----------- response handlers -----------
314
+ if (!info.isBatchCall) {
315
+ const [call] = info.calls;
316
+ const [data, error] = await rpcCalls[0]!;
292
317
 
293
- const errors = untransformedJSON.flatMap((response) =>
294
- 'error' in response ? [response.error] : [],
295
- );
318
+ switch (info.type) {
319
+ case 'unknown':
320
+ case 'mutation':
321
+ case 'query': {
322
+ // httpLink
323
+ headers.set('content-type', 'application/json');
296
324
 
325
+ if (isDataStream(data)) {
326
+ throw new TRPCError({
327
+ code: 'UNSUPPORTED_MEDIA_TYPE',
328
+ message:
329
+ 'Cannot use stream-like response in non-streaming request - use httpBatchStreamLink',
330
+ });
331
+ }
332
+ const res: TRPCResponse<unknown, inferRouterError<TRouter>> = error
333
+ ? {
334
+ error: getErrorShape({
335
+ config,
336
+ ctx,
337
+ error,
338
+ input: call!.result(),
339
+ path: call!.path,
340
+ type: info.type,
341
+ }),
342
+ }
343
+ : { result: { data } };
344
+
345
+ const headResponse = initResponse({
346
+ ctx,
347
+ info,
348
+ responseMeta: opts.responseMeta,
349
+ errors: error ? [error] : [],
350
+ headers,
351
+ untransformedJSON: [res],
352
+ });
353
+ return new Response(
354
+ JSON.stringify(transformTRPCResponse(config, res)),
355
+ {
356
+ status: headResponse.status,
357
+ headers,
358
+ },
359
+ );
360
+ }
361
+ case 'subscription': {
362
+ // httpSubscriptionLink
363
+
364
+ if (!experimentalSSE) {
365
+ throw new TRPCError({
366
+ code: 'METHOD_NOT_SUPPORTED',
367
+ message: 'Missing experimental flag "sseSubscriptions"',
368
+ });
369
+ }
370
+
371
+ if (!isObservable(data) && !isAsyncIterable(data)) {
372
+ throw new TRPCError({
373
+ message: `Subscription ${
374
+ call!.path
375
+ } did not return an observable or a AsyncGenerator`,
376
+ code: 'INTERNAL_SERVER_ERROR',
377
+ });
378
+ }
379
+ const dataAsIterable = isObservable(data)
380
+ ? observableToAsyncIterable(data)
381
+ : data;
382
+ const stream = sseStreamProducer({
383
+ data: dataAsIterable,
384
+ serialize: (v) => config.transformer.output.serialize(v),
385
+ });
386
+ for (const [key, value] of Object.entries(sseHeaders)) {
387
+ headers.set(key, value);
388
+ }
389
+
390
+ const headResponse = initResponse({
391
+ ctx,
392
+ info,
393
+ responseMeta: opts.responseMeta,
394
+ errors: [],
395
+ headers,
396
+ untransformedJSON: null,
397
+ });
398
+
399
+ return new Response(stream, {
400
+ headers,
401
+ status: headResponse.status,
402
+ });
403
+ }
404
+ }
405
+ }
406
+
407
+ // batch response handlers
408
+ if (info.accept === 'application/jsonl') {
409
+ // httpBatchStreamLink
410
+ headers.set('content-type', 'application/json');
411
+ headers.set('transfer-encoding', 'chunked');
297
412
  const headResponse = initResponse({
298
413
  ctx,
299
414
  info,
300
- type,
301
415
  responseMeta: opts.responseMeta,
302
- untransformedJSON,
303
- errors,
416
+ errors: [],
304
417
  headers,
418
+ untransformedJSON: null,
305
419
  });
420
+ const stream = jsonlStreamProducer({
421
+ /**
422
+ * Example structure for `maxDepth: 4`:
423
+ * {
424
+ * // 1
425
+ * 0: {
426
+ * // 2
427
+ * result: {
428
+ * // 3
429
+ * data: // 4
430
+ * }
431
+ * }
432
+ * }
433
+ */
434
+ maxDepth: experimentalIterablesAndDeferreds ? 4 : 3,
435
+ data: rpcCalls.map(async (res) => {
436
+ const [result, error] = await res;
437
+
438
+ const call = info!.calls[0];
439
+ if (error) {
440
+ return {
441
+ error: getErrorShape({
442
+ config,
443
+ ctx,
444
+ error,
445
+ input: call!.result(),
446
+ path: call!.path,
447
+ type: call!.procedure?._def.type ?? 'unknown',
448
+ }),
449
+ };
450
+ }
306
451
 
307
- // return body stuff
308
- const result = info.isBatchCall
309
- ? untransformedJSON
310
- : untransformedJSON[0]!;
311
- const transformedJSON = transformTRPCResponse(
312
- router._def._config,
313
- result,
314
- );
315
- const body = JSON.stringify(transformedJSON);
316
-
317
- return new Response(body, {
318
- status: headResponse.status,
452
+ /**
453
+ * Not very pretty, but we need to wrap nested data in promises
454
+ * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
455
+ */
456
+ const data = isObservable(result)
457
+ ? observableToAsyncIterable(result)
458
+ : Promise.resolve(result);
459
+ return {
460
+ result: Promise.resolve({
461
+ data,
462
+ }),
463
+ };
464
+ }),
465
+ serialize: config.transformer.output.serialize,
466
+ onError: (cause) => {
467
+ opts.onError?.({
468
+ error: getTRPCErrorFromUnknown(cause),
469
+ path: undefined,
470
+ input: undefined,
471
+ ctx,
472
+ req: opts.req,
473
+ type: info?.type ?? 'unknown',
474
+ });
475
+ },
476
+
477
+ formatError(errorOpts) {
478
+ const call = info?.calls[errorOpts.path[0] as any];
479
+
480
+ const shape = getErrorShape({
481
+ config,
482
+ ctx,
483
+ error: getTRPCErrorFromUnknown(errorOpts.error),
484
+ input: call?.result(),
485
+ path: call?.path,
486
+ type: call?.procedure?._def.type ?? 'unknown',
487
+ });
488
+
489
+ return shape;
490
+ },
491
+ });
492
+
493
+ return new Response(stream, {
319
494
  headers,
495
+ status: headResponse.status,
320
496
  });
321
497
  }
322
498
 
323
- headers.set('content-type', 'application/json');
324
- headers.set('transfer-encoding', 'chunked');
499
+ // httpBatchLink
325
500
  /**
326
- * Streaming response:
327
- * - block on none, call `onChunk` as soon as each response is ready
328
- * - create headers with minimal data (cannot know the response body in advance)
329
- * - return void
501
+ * Non-streaming response:
502
+ * - await all responses in parallel, blocking on the slowest one
503
+ * - create headers with known response body
504
+ * - return a complete HTTPResponse
330
505
  */
331
- const headResponse = initResponse({
332
- ctx,
333
- info,
334
- type,
335
- responseMeta: opts.responseMeta,
336
- errors: [],
337
- headers,
338
- });
339
-
340
- const stream = jsonlStreamProducer({
341
- /**
342
- * Example structure for `maxDepth: 4`:
343
- * {
344
- * // 1
345
- * 0: {
346
- * // 2
347
- * result: {
348
- * // 3
349
- * data: // 4
350
- * }
351
- * }
352
- * }
353
- */
354
- maxDepth: experimentalIterablesAndDeferreds ? 4 : 3,
355
- formatError(errorOpts) {
356
- const call = info?.calls[errorOpts.path[0] as any];
506
+ headers.set('content-type', 'application/json');
507
+ const results: RPCResult[] = (await Promise.all(rpcCalls)).map(
508
+ (res): RPCResult => {
509
+ const [data, error] = res;
510
+ if (error) {
511
+ return res;
512
+ }
357
513
 
358
- return getErrorShape({
359
- config,
360
- ctx,
361
- error: getTRPCErrorFromUnknown(errorOpts.error),
362
- input: call?.result(),
363
- path: call?.path,
364
- type,
365
- });
514
+ if (isDataStream(data)) {
515
+ return [
516
+ null,
517
+ new TRPCError({
518
+ code: 'UNSUPPORTED_MEDIA_TYPE',
519
+ message:
520
+ 'Cannot use stream-like response in non-streaming request - use httpBatchStreamLink',
521
+ }),
522
+ ];
523
+ }
524
+ return res;
366
525
  },
367
- data: promises.map(async (it) => {
368
- const response = await it;
369
- if ('result' in response) {
370
- /**
371
- * Not very pretty, but we need to wrap nested data in promises
372
- * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
373
- */
526
+ );
527
+ const resultAsRPCResponse = results.map(
528
+ (
529
+ [data, error],
530
+ index,
531
+ ): TRPCResponse<unknown, inferRouterError<TRouter>> => {
532
+ const call = info!.calls[index]!;
533
+ if (error) {
374
534
  return {
375
- ...response,
376
- result: Promise.resolve({
377
- ...response.result,
378
- data: Promise.resolve(response.result.data),
535
+ error: getErrorShape({
536
+ config,
537
+ ctx,
538
+ error,
539
+ input: call.result(),
540
+ path: call.path,
541
+ type: call.procedure?._def.type ?? 'unknown',
379
542
  }),
380
543
  };
381
544
  }
382
- return response;
383
- }),
384
- serialize: config.transformer.output.serialize,
385
- onError: (cause) => {
386
- opts.onError?.({
387
- error: getTRPCErrorFromUnknown(cause),
388
- path: undefined,
389
- input: undefined,
390
- ctx,
391
- type,
392
- req: opts.req,
393
- });
545
+ return {
546
+ result: { data },
547
+ };
394
548
  },
395
- });
549
+ );
396
550
 
397
- return new Response(stream, {
551
+ const errors = results
552
+ .map(([_, error]) => error)
553
+ .filter(Boolean) as TRPCError[];
554
+
555
+ const headResponse = initResponse({
556
+ ctx,
557
+ info,
558
+ responseMeta: opts.responseMeta,
559
+ untransformedJSON: resultAsRPCResponse,
560
+ errors,
398
561
  headers,
399
- status: headResponse.status,
400
562
  });
563
+
564
+ return new Response(
565
+ JSON.stringify(transformTRPCResponse(config, resultAsRPCResponse)),
566
+ {
567
+ status: headResponse.status,
568
+ headers,
569
+ },
570
+ );
401
571
  } catch (cause) {
402
572
  // we get here if
403
573
  // - batching is called when it's not enabled
@@ -409,13 +579,12 @@ export async function resolveResponse<TRouter extends AnyRouter>(
409
579
  const { error, untransformedJSON, body } = caughtErrorToData(cause, {
410
580
  opts,
411
581
  ctx,
412
- type,
582
+ type: info?.type ?? 'unknown',
413
583
  });
414
584
 
415
585
  const headResponse = initResponse({
416
586
  ctx,
417
587
  info,
418
- type,
419
588
  responseMeta: opts.responseMeta,
420
589
  untransformedJSON,
421
590
  errors: [error],