@trpc/server 11.0.0-rc.354 → 11.0.0-rc.361

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 (162) hide show
  1. package/dist/@trpc/server/http.d.ts +0 -1
  2. package/dist/@trpc/server/http.d.ts.map +1 -1
  3. package/dist/@trpc/server/index.d.ts +2 -1
  4. package/dist/@trpc/server/index.d.ts.map +1 -1
  5. package/dist/adapters/aws-lambda/content-type/json/index.d.ts +10 -0
  6. package/dist/adapters/aws-lambda/content-type/json/index.d.ts.map +1 -0
  7. package/dist/adapters/aws-lambda/content-type/json/index.js +62 -0
  8. package/dist/adapters/aws-lambda/content-type/json/index.mjs +60 -0
  9. package/dist/adapters/aws-lambda/index.d.ts.map +1 -1
  10. package/dist/adapters/aws-lambda/index.js +19 -9
  11. package/dist/adapters/aws-lambda/index.mjs +19 -9
  12. package/dist/adapters/aws-lambda/utils.d.ts +3 -12
  13. package/dist/adapters/aws-lambda/utils.d.ts.map +1 -1
  14. package/dist/adapters/aws-lambda/utils.js +12 -1
  15. package/dist/adapters/aws-lambda/utils.mjs +12 -2
  16. package/dist/adapters/content-handlers/concurrentCache.d.ts +7 -0
  17. package/dist/adapters/content-handlers/concurrentCache.d.ts.map +1 -0
  18. package/dist/adapters/content-handlers/concurrentCache.js +17 -0
  19. package/dist/adapters/content-handlers/concurrentCache.mjs +15 -0
  20. package/dist/adapters/content-handlers/selectContentHandlerOrUnsupportedMediaType.d.ts +10 -0
  21. package/dist/adapters/content-handlers/selectContentHandlerOrUnsupportedMediaType.d.ts.map +1 -0
  22. package/dist/adapters/content-handlers/selectContentHandlerOrUnsupportedMediaType.js +33 -0
  23. package/dist/adapters/content-handlers/selectContentHandlerOrUnsupportedMediaType.mjs +31 -0
  24. package/dist/adapters/express.d.ts.map +1 -1
  25. package/dist/adapters/express.js +0 -1
  26. package/dist/adapters/express.mjs +0 -1
  27. package/dist/adapters/fastify/content-type/json/index.d.ts +8 -0
  28. package/dist/adapters/fastify/content-type/json/index.d.ts.map +1 -0
  29. package/dist/adapters/fastify/content-type/json/index.js +62 -0
  30. package/dist/adapters/fastify/content-type/json/index.mjs +60 -0
  31. package/dist/adapters/fastify/fastifyRequestHandler.d.ts +1 -9
  32. package/dist/adapters/fastify/fastifyRequestHandler.d.ts.map +1 -1
  33. package/dist/adapters/fastify/fastifyRequestHandler.js +10 -2
  34. package/dist/adapters/fastify/fastifyRequestHandler.mjs +10 -2
  35. package/dist/adapters/fastify/fastifyTRPCPlugin.d.ts +1 -1
  36. package/dist/adapters/fastify/fastifyTRPCPlugin.d.ts.map +1 -1
  37. package/dist/adapters/fastify/types.d.ts +11 -0
  38. package/dist/adapters/fastify/types.d.ts.map +1 -0
  39. package/dist/adapters/fetch/content-type/json/index.d.ts +9 -0
  40. package/dist/adapters/fetch/content-type/json/index.d.ts.map +1 -0
  41. package/dist/adapters/fetch/content-type/json/index.js +55 -0
  42. package/dist/adapters/fetch/content-type/json/index.mjs +53 -0
  43. package/dist/adapters/fetch/fetchRequestHandler.d.ts +1 -5
  44. package/dist/adapters/fetch/fetchRequestHandler.d.ts.map +1 -1
  45. package/dist/adapters/fetch/fetchRequestHandler.js +16 -2
  46. package/dist/adapters/fetch/fetchRequestHandler.mjs +16 -2
  47. package/dist/adapters/fetch/types.d.ts +9 -12
  48. package/dist/adapters/fetch/types.d.ts.map +1 -1
  49. package/dist/adapters/next-app-dir/nextAppDirCaller.d.ts +2 -7
  50. package/dist/adapters/next-app-dir/nextAppDirCaller.d.ts.map +1 -1
  51. package/dist/adapters/next.d.ts.map +1 -1
  52. package/dist/adapters/next.js +0 -1
  53. package/dist/adapters/next.mjs +0 -1
  54. package/dist/adapters/node-http/content-type/form-data/index.d.ts +4 -25
  55. package/dist/adapters/node-http/content-type/form-data/index.d.ts.map +1 -1
  56. package/dist/adapters/node-http/content-type/form-data/index.js +25 -128
  57. package/dist/adapters/node-http/content-type/form-data/index.mjs +25 -103
  58. package/dist/adapters/node-http/content-type/json/getPostBody.d.ts.map +1 -1
  59. package/dist/adapters/node-http/content-type/json/getPostBody.js +4 -12
  60. package/dist/adapters/node-http/content-type/json/getPostBody.mjs +4 -12
  61. package/dist/adapters/node-http/content-type/json/index.d.ts +4 -1
  62. package/dist/adapters/node-http/content-type/json/index.d.ts.map +1 -1
  63. package/dist/adapters/node-http/content-type/json/index.js +62 -10
  64. package/dist/adapters/node-http/content-type/json/index.mjs +62 -10
  65. package/dist/adapters/node-http/content-type/octet/index.d.ts +5 -0
  66. package/dist/adapters/node-http/content-type/octet/index.d.ts.map +1 -0
  67. package/dist/adapters/node-http/content-type/octet/index.js +19 -0
  68. package/dist/adapters/node-http/content-type/octet/index.mjs +17 -0
  69. package/dist/adapters/node-http/content-type/types.d.ts +8 -0
  70. package/dist/adapters/node-http/content-type/types.d.ts.map +1 -0
  71. package/dist/adapters/node-http/nodeHTTPRequestHandler.d.ts.map +1 -1
  72. package/dist/adapters/node-http/nodeHTTPRequestHandler.js +19 -21
  73. package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +20 -22
  74. package/dist/adapters/node-http/types.d.ts +8 -19
  75. package/dist/adapters/node-http/types.d.ts.map +1 -1
  76. package/dist/adapters/standalone.d.ts.map +1 -1
  77. package/dist/adapters/standalone.js +0 -1
  78. package/dist/adapters/standalone.mjs +0 -1
  79. package/dist/adapters/ws.d.ts +2 -12
  80. package/dist/adapters/ws.d.ts.map +1 -1
  81. package/dist/bundle-analysis.json +241 -230
  82. package/dist/http.js +0 -2
  83. package/dist/http.mjs +0 -1
  84. package/dist/index.js +2 -0
  85. package/dist/index.mjs +1 -0
  86. package/dist/unstable-core-do-not-import/contentTypeParsers.d.ts +16 -0
  87. package/dist/unstable-core-do-not-import/contentTypeParsers.d.ts.map +1 -0
  88. package/dist/unstable-core-do-not-import/contentTypeParsers.js +23 -0
  89. package/dist/unstable-core-do-not-import/contentTypeParsers.mjs +21 -0
  90. package/dist/unstable-core-do-not-import/http/contentType.d.ts +7 -15
  91. package/dist/unstable-core-do-not-import/http/contentType.d.ts.map +1 -1
  92. package/dist/unstable-core-do-not-import/http/index.d.ts +0 -1
  93. package/dist/unstable-core-do-not-import/http/index.d.ts.map +1 -1
  94. package/dist/unstable-core-do-not-import/http/resolveHTTPResponse.d.ts +5 -5
  95. package/dist/unstable-core-do-not-import/http/resolveHTTPResponse.d.ts.map +1 -1
  96. package/dist/unstable-core-do-not-import/http/resolveHTTPResponse.js +26 -20
  97. package/dist/unstable-core-do-not-import/http/resolveHTTPResponse.mjs +26 -20
  98. package/dist/unstable-core-do-not-import/http/types.d.ts +0 -2
  99. package/dist/unstable-core-do-not-import/http/types.d.ts.map +1 -1
  100. package/dist/unstable-core-do-not-import/rootConfig.d.ts +21 -0
  101. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  102. package/dist/unstable-core-do-not-import.js +0 -2
  103. package/dist/unstable-core-do-not-import.mjs +0 -1
  104. package/package.json +6 -7
  105. package/src/@trpc/server/http.ts +0 -1
  106. package/src/@trpc/server/index.ts +4 -0
  107. package/src/adapters/aws-lambda/content-type/json/index.ts +108 -0
  108. package/src/adapters/aws-lambda/index.ts +24 -9
  109. package/src/adapters/aws-lambda/utils.ts +21 -15
  110. package/src/adapters/content-handlers/concurrentCache.ts +16 -0
  111. package/src/adapters/content-handlers/selectContentHandlerOrUnsupportedMediaType.ts +45 -0
  112. package/src/adapters/express.ts +1 -6
  113. package/src/adapters/fastify/content-type/json/index.ts +106 -0
  114. package/src/adapters/fastify/fastifyRequestHandler.ts +15 -21
  115. package/src/adapters/fastify/fastifyTRPCPlugin.ts +1 -1
  116. package/src/adapters/fastify/types.ts +22 -0
  117. package/src/adapters/fetch/content-type/json/index.ts +90 -0
  118. package/src/adapters/fetch/fetchRequestHandler.ts +22 -10
  119. package/src/adapters/fetch/types.ts +22 -15
  120. package/src/adapters/next-app-dir/nextAppDirCaller.ts +2 -9
  121. package/src/adapters/next.ts +1 -6
  122. package/src/adapters/node-http/content-type/form-data/index.ts +33 -159
  123. package/src/adapters/node-http/content-type/json/getPostBody.ts +9 -18
  124. package/src/adapters/node-http/content-type/json/index.ts +98 -9
  125. package/src/adapters/node-http/content-type/octet/index.ts +27 -0
  126. package/src/adapters/node-http/{internals/contentType.ts → content-type/types.ts} +2 -14
  127. package/src/adapters/node-http/nodeHTTPRequestHandler.ts +22 -35
  128. package/src/adapters/node-http/types.ts +46 -46
  129. package/src/adapters/standalone.ts +1 -2
  130. package/src/adapters/ws.ts +9 -14
  131. package/src/unstable-core-do-not-import/contentTypeParsers.ts +37 -0
  132. package/src/unstable-core-do-not-import/http/contentType.ts +10 -85
  133. package/src/unstable-core-do-not-import/http/index.ts +0 -1
  134. package/src/unstable-core-do-not-import/http/resolveHTTPResponse.ts +30 -27
  135. package/src/unstable-core-do-not-import/http/types.ts +0 -2
  136. package/src/unstable-core-do-not-import/rootConfig.ts +31 -0
  137. package/dist/adapters/node-http/content-type/form-data/fileUploadHandler.d.ts +0 -73
  138. package/dist/adapters/node-http/content-type/form-data/fileUploadHandler.d.ts.map +0 -1
  139. package/dist/adapters/node-http/content-type/form-data/fileUploadHandler.js +0 -161
  140. package/dist/adapters/node-http/content-type/form-data/fileUploadHandler.mjs +0 -157
  141. package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.d.ts +0 -31
  142. package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.d.ts.map +0 -1
  143. package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.js +0 -29
  144. package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.mjs +0 -27
  145. package/dist/adapters/node-http/content-type/form-data/streamSlice.d.ts +0 -16
  146. package/dist/adapters/node-http/content-type/form-data/streamSlice.d.ts.map +0 -1
  147. package/dist/adapters/node-http/content-type/form-data/streamSlice.js +0 -46
  148. package/dist/adapters/node-http/content-type/form-data/streamSlice.mjs +0 -44
  149. package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts +0 -45
  150. package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts.map +0 -1
  151. package/dist/adapters/node-http/content-type/form-data/uploadHandler.js +0 -30
  152. package/dist/adapters/node-http/content-type/form-data/uploadHandler.mjs +0 -26
  153. package/dist/adapters/node-http/internals/contentType.d.ts +0 -9
  154. package/dist/adapters/node-http/internals/contentType.d.ts.map +0 -1
  155. package/dist/adapters/node-http/internals/contentType.js +0 -8
  156. package/dist/adapters/node-http/internals/contentType.mjs +0 -6
  157. package/dist/unstable-core-do-not-import/http/contentType.js +0 -54
  158. package/dist/unstable-core-do-not-import/http/contentType.mjs +0 -52
  159. package/src/adapters/node-http/content-type/form-data/fileUploadHandler.ts +0 -277
  160. package/src/adapters/node-http/content-type/form-data/memoryUploadHandler.ts +0 -56
  161. package/src/adapters/node-http/content-type/form-data/streamSlice.ts +0 -56
  162. package/src/adapters/node-http/content-type/form-data/uploadHandler.ts +0 -89
@@ -0,0 +1,108 @@
1
+ // @trpc/server
2
+ import { TRPCError } from '../../../../@trpc/server';
3
+ import type {
4
+ AnyRouter,
5
+ CombinedDataTransformer,
6
+ } from '../../../../@trpc/server';
7
+ import type {
8
+ BaseContentTypeHandler,
9
+ HTTPRequest,
10
+ } from '../../../../@trpc/server/http';
11
+ import { createConcurrentCache } from '../../../content-handlers/concurrentCache';
12
+ import {
13
+ lambdaEventToHTTPBody,
14
+ type APIGatewayEvent,
15
+ type AWSLambdaOptions,
16
+ } from '../../utils';
17
+
18
+ export interface LambdaHTTPContentTypeHandler<
19
+ TRouter extends AnyRouter,
20
+ TEvent extends APIGatewayEvent,
21
+ > extends BaseContentTypeHandler<
22
+ AWSLambdaOptions<TRouter, TEvent> & {
23
+ event: TEvent;
24
+ req: HTTPRequest;
25
+ }
26
+ > {}
27
+
28
+ export const getLambdaHTTPJSONContentTypeHandler: <
29
+ TRouter extends AnyRouter,
30
+ TEvent extends APIGatewayEvent,
31
+ >() => LambdaHTTPContentTypeHandler<TRouter, TEvent> = () => {
32
+ const cache = createConcurrentCache();
33
+
34
+ return {
35
+ name: 'lambda-json',
36
+ isMatch: (headers) => {
37
+ return !!headers.get('content-type')?.startsWith('application/json');
38
+ },
39
+ getInputs: async (opts, info) => {
40
+ function getRawProcedureInputOrThrow() {
41
+ const { event, req } = opts;
42
+
43
+ try {
44
+ if (req.query.has('input')) {
45
+ const input = req.query.get('input');
46
+ if (!input) {
47
+ return undefined;
48
+ }
49
+
50
+ return JSON.parse(input);
51
+ }
52
+
53
+ const body = lambdaEventToHTTPBody(opts.event);
54
+ if (typeof body === 'string') {
55
+ // A mutation with no inputs will have req.body === ''
56
+ return body.length === 0 ? undefined : JSON.parse(body);
57
+ }
58
+ return event.body;
59
+ } catch (cause) {
60
+ throw new TRPCError({
61
+ code: 'PARSE_ERROR',
62
+ cause,
63
+ });
64
+ }
65
+ }
66
+
67
+ const deserializeInputValue = (
68
+ rawValue: unknown,
69
+ transformer: CombinedDataTransformer,
70
+ ) => {
71
+ return typeof rawValue !== 'undefined'
72
+ ? transformer.input.deserialize(rawValue)
73
+ : rawValue;
74
+ };
75
+
76
+ const rawInput = await cache.concurrentSafeGet('rawInput', () =>
77
+ getRawProcedureInputOrThrow(),
78
+ );
79
+ if (rawInput === undefined) {
80
+ return undefined;
81
+ }
82
+
83
+ const transformer = opts.router._def._config.transformer;
84
+
85
+ if (!info.isBatchCall) {
86
+ return cache.concurrentSafeGet('input', () =>
87
+ deserializeInputValue(rawInput, transformer),
88
+ );
89
+ }
90
+
91
+ /* istanbul ignore if */
92
+ if (
93
+ rawInput == null ||
94
+ typeof rawInput !== 'object' ||
95
+ Array.isArray(rawInput)
96
+ ) {
97
+ throw new TRPCError({
98
+ code: 'BAD_REQUEST',
99
+ message: '"input" needs to be an object when doing a batch call',
100
+ });
101
+ }
102
+
103
+ return cache.concurrentSafeGet(String(info.batch), () =>
104
+ deserializeInputValue(rawInput[info.batch], transformer),
105
+ );
106
+ },
107
+ };
108
+ };
@@ -24,6 +24,8 @@ import type {
24
24
  ResolveHTTPRequestOptionsContextFn,
25
25
  } from '../../@trpc/server/http';
26
26
  import { resolveHTTPResponse } from '../../@trpc/server/http';
27
+ import { selectContentHandlerOrUnsupportedMediaType } from '../content-handlers/selectContentHandlerOrUnsupportedMediaType';
28
+ import { getLambdaHTTPJSONContentTypeHandler } from './content-type/json';
27
29
  import type {
28
30
  APIGatewayEvent,
29
31
  APIGatewayResult,
@@ -50,18 +52,10 @@ function lambdaEventToHTTPRequest(event: APIGatewayEvent): HTTPRequest {
50
52
  }
51
53
  }
52
54
 
53
- let body: string | null | undefined;
54
- if (event.body && event.isBase64Encoded) {
55
- body = Buffer.from(event.body, 'base64').toString('utf8');
56
- } else {
57
- body = event.body;
58
- }
59
-
60
55
  return {
61
56
  method: getHTTPMethod(event),
62
57
  query: query,
63
58
  headers: event.headers,
64
- body: body,
65
59
  };
66
60
  }
67
61
 
@@ -109,18 +103,39 @@ export function awsLambdaRequestHandler<
109
103
  return async (event, context) => {
110
104
  const req = lambdaEventToHTTPRequest(event);
111
105
  const path = getPath(event);
106
+
112
107
  const createContext: ResolveHTTPRequestOptionsContextFn<TRouter> = async (
113
108
  innerOpts,
114
109
  ) => {
115
110
  return await opts.createContext?.({ event, context, ...innerOpts });
116
111
  };
117
112
 
113
+ const [contentTypeHandler, unsupportedMediaTypeError] =
114
+ selectContentHandlerOrUnsupportedMediaType(
115
+ [getLambdaHTTPJSONContentTypeHandler<TRouter, TEvent>()],
116
+ {
117
+ ...opts,
118
+ event,
119
+ req,
120
+ },
121
+ );
122
+
118
123
  const response = await resolveHTTPResponse({
119
124
  ...opts,
120
125
  createContext,
121
126
  req,
122
127
  path,
123
- error: null,
128
+ error: unsupportedMediaTypeError,
129
+ async getInput(info) {
130
+ return await contentTypeHandler?.getInputs(
131
+ {
132
+ ...opts,
133
+ event,
134
+ req,
135
+ },
136
+ info,
137
+ );
138
+ },
124
139
  onError(o) {
125
140
  opts?.onError?.({
126
141
  ...o,
@@ -14,7 +14,12 @@ import type {
14
14
  APIGatewayProxyStructuredResultV2,
15
15
  Context as APIGWContext,
16
16
  } from 'aws-lambda';
17
- import type { AnyRouter, inferRouterContext } from '../../@trpc/server'; // import @trpc/server
17
+ import type {
18
+ AnyRouter,
19
+ CreateContextCallback,
20
+ inferRouterContext,
21
+ } from '../../@trpc/server';
22
+ // import @trpc/server
18
23
 
19
24
  // @trpc/server
20
25
  import { TRPCError } from '../../@trpc/server';
@@ -50,20 +55,10 @@ export type AWSLambdaOptions<
50
55
  TEvent extends APIGatewayEvent,
51
56
  > =
52
57
  | HTTPBaseHandlerOptions<TRouter, TEvent> &
53
- (
54
- | {
55
- /**
56
- * @link https://trpc.io/docs/v11/context
57
- **/
58
- createContext: AWSLambdaCreateContextFn<TRouter, TEvent>;
59
- }
60
- | {
61
- /**
62
- * @link https://trpc.io/docs/v11/context
63
- **/
64
- createContext?: AWSLambdaCreateContextFn<TRouter, TEvent>;
65
- }
66
- );
58
+ CreateContextCallback<
59
+ inferRouterContext<AnyRouter>,
60
+ AWSLambdaCreateContextFn<TRouter, TEvent>
61
+ >;
67
62
 
68
63
  export function isPayloadV1(
69
64
  event: APIGatewayEvent,
@@ -162,3 +157,14 @@ export type APIGatewayPayloadFormatVersion =
162
157
  export const UNKNOWN_PAYLOAD_FORMAT_VERSION_ERROR_MESSAGE =
163
158
  'Custom payload format version not handled by this adapter. Please use either 1.0 or 2.0. More information here' +
164
159
  'https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html';
160
+
161
+ export function lambdaEventToHTTPBody(event: APIGatewayEvent) {
162
+ let body: string | null | undefined;
163
+ if (event.body && event.isBase64Encoded) {
164
+ body = Buffer.from(event.body, 'base64').toString('utf8');
165
+ } else {
166
+ body = event.body;
167
+ }
168
+
169
+ return body;
170
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A simple concurrent cache that ensures that only one promise is created for a given key.
3
+ */
4
+ export function createConcurrentCache() {
5
+ const cache = new Map<string, any>();
6
+
7
+ return {
8
+ concurrentSafeGet<TValue>(key: string, setter: () => TValue): TValue {
9
+ if (!cache.has(key)) {
10
+ cache.set(key, setter());
11
+ }
12
+
13
+ return cache.get(key) as TValue;
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,45 @@
1
+ import { TRPCError } from '../../@trpc/server';
2
+ import type {
3
+ BaseContentTypeHandler,
4
+ HTTPHeaders,
5
+ } from '../../@trpc/server/http';
6
+
7
+ interface MinimalHandlerOpts {
8
+ req: {
9
+ // TODO: This could probably only take Headers and do the conversion higher up,
10
+ // but that's a bigger refactor for another day
11
+ headers: HTTPHeaders | Headers;
12
+ };
13
+ }
14
+
15
+ export function selectContentHandlerOrUnsupportedMediaType<
16
+ THandlerOpts extends MinimalHandlerOpts,
17
+ THandler extends BaseContentTypeHandler<THandlerOpts>,
18
+ >(handlers: THandler[], opts: THandlerOpts) {
19
+ const headers = new Headers(opts.req.headers as HeadersInit);
20
+ const contentType = headers.get('content-type');
21
+
22
+ if (contentType === null) {
23
+ return [
24
+ undefined,
25
+ new TRPCError({
26
+ code: 'UNSUPPORTED_MEDIA_TYPE',
27
+ message:
28
+ 'No Content-Type header detected on the incoming request. This request may not be supported by your tRPC Adapter, or possibly by tRPC at all',
29
+ }),
30
+ ] as const;
31
+ }
32
+
33
+ const handler = handlers.find((handler) => handler.isMatch(headers));
34
+ if (!handler) {
35
+ return [
36
+ undefined,
37
+ new TRPCError({
38
+ code: 'UNSUPPORTED_MEDIA_TYPE',
39
+ message: `Invalid Content-Type header '${contentType}'. This request may not be supported by your tRPC Adapter, or possibly by tRPC at all`,
40
+ }),
41
+ ] as const;
42
+ }
43
+
44
+ return [handler] as const;
45
+ }
@@ -27,12 +27,7 @@ export function createExpressMiddleware<TRouter extends AnyRouter>(
27
27
  const endpoint = req.path.slice(1);
28
28
 
29
29
  await nodeHTTPRequestHandler({
30
- // FIXME: no typecasting should be needed here
31
- ...(opts as NodeHTTPHandlerOptions<
32
- AnyRouter,
33
- express.Request,
34
- express.Response
35
- >),
30
+ ...opts,
36
31
  req,
37
32
  res,
38
33
  path: endpoint,
@@ -0,0 +1,106 @@
1
+ // @trpc/server
2
+ import type { FastifyReply, FastifyRequest } from 'fastify';
3
+ import { TRPCError } from '../../../../@trpc/server';
4
+ import type {
5
+ AnyRouter,
6
+ CombinedDataTransformer,
7
+ } from '../../../../@trpc/server';
8
+ import type { BaseContentTypeHandler } from '../../../../@trpc/server/http';
9
+ import { createConcurrentCache } from '../../../content-handlers/concurrentCache';
10
+ import type { FastifyRequestHandlerOptions } from '../../types';
11
+
12
+ export interface FastifyHTTPContentTypeHandler<
13
+ TRouter extends AnyRouter,
14
+ TRequest extends FastifyRequest,
15
+ TResponse extends FastifyReply,
16
+ > extends BaseContentTypeHandler<
17
+ FastifyRequestHandlerOptions<TRouter, TRequest, TResponse>
18
+ > {}
19
+
20
+ export const getFastifyHTTPJSONContentTypeHandler: <
21
+ TRouter extends AnyRouter,
22
+ TRequest extends FastifyRequest,
23
+ TResponse extends FastifyReply,
24
+ >() => FastifyHTTPContentTypeHandler<TRouter, TRequest, TResponse> = () => {
25
+ const cache = createConcurrentCache();
26
+
27
+ return {
28
+ name: 'fastify-json',
29
+ isMatch: (headers) => {
30
+ return !!headers.get('content-type')?.startsWith('application/json');
31
+ },
32
+ getInputs: async (opts, info) => {
33
+ async function getRawProcedureInputOrThrow() {
34
+ const { req } = opts;
35
+
36
+ try {
37
+ if (req.method === 'GET') {
38
+ const query = opts.req.query
39
+ ? new URLSearchParams(opts.req.query as any)
40
+ : new URLSearchParams(opts.req.url.split('?')[1]);
41
+
42
+ const input = query.get('input');
43
+ if (!input) {
44
+ return undefined;
45
+ }
46
+
47
+ return JSON.parse(input);
48
+ }
49
+
50
+ const body = opts.req.body ?? 'null';
51
+ if (typeof body === 'string') {
52
+ // A mutation with no inputs will have req.body === ''
53
+ return body.length === 0 ? undefined : JSON.parse(body);
54
+ }
55
+
56
+ return body;
57
+ } catch (cause) {
58
+ throw new TRPCError({
59
+ code: 'PARSE_ERROR',
60
+ cause,
61
+ });
62
+ }
63
+ }
64
+
65
+ const deserializeInputValue = (
66
+ rawValue: unknown,
67
+ transformer: CombinedDataTransformer,
68
+ ) => {
69
+ return typeof rawValue !== 'undefined'
70
+ ? transformer.input.deserialize(rawValue)
71
+ : rawValue;
72
+ };
73
+
74
+ const rawInput = await cache.concurrentSafeGet('rawInput', () =>
75
+ getRawProcedureInputOrThrow(),
76
+ );
77
+ if (rawInput === undefined) {
78
+ return undefined;
79
+ }
80
+
81
+ const transformer = opts.router._def._config.transformer;
82
+
83
+ if (!info.isBatchCall) {
84
+ return cache.concurrentSafeGet('input', () =>
85
+ deserializeInputValue(rawInput, transformer),
86
+ );
87
+ }
88
+
89
+ /* istanbul ignore if */
90
+ if (
91
+ rawInput == null ||
92
+ typeof rawInput !== 'object' ||
93
+ Array.isArray(rawInput)
94
+ ) {
95
+ throw new TRPCError({
96
+ code: 'BAD_REQUEST',
97
+ message: '"input" needs to be an object when doing a batch call',
98
+ });
99
+ }
100
+
101
+ return cache.concurrentSafeGet(String(info.batch), () =>
102
+ deserializeInputValue(rawInput[info.batch], transformer),
103
+ );
104
+ },
105
+ };
106
+ };
@@ -7,12 +7,11 @@
7
7
  * import type { HTTPBaseHandlerOptions } from '@trpc/server/http'
8
8
  * ```
9
9
  */
10
- import { Readable } from 'node:stream';
10
+ import { Readable } from 'stream';
11
11
  import type { FastifyReply, FastifyRequest } from 'fastify';
12
12
  // @trpc/server
13
13
  import type { AnyRouter } from '../../@trpc/server';
14
14
  import type {
15
- HTTPBaseHandlerOptions,
16
15
  HTTPRequest,
17
16
  HTTPResponse,
18
17
  ResolveHTTPRequestOptionsContextFn,
@@ -22,24 +21,9 @@ import {
22
21
  getBatchStreamFormatter,
23
22
  resolveHTTPResponse,
24
23
  } from '../../@trpc/server/http';
25
- import type { NodeHTTPCreateContextOption } from '../node-http';
26
-
27
- export type FastifyHandlerOptions<
28
- TRouter extends AnyRouter,
29
- TRequest extends FastifyRequest,
30
- TResponse extends FastifyReply,
31
- > = HTTPBaseHandlerOptions<TRouter, TRequest> &
32
- NodeHTTPCreateContextOption<TRouter, TRequest, TResponse>;
33
-
34
- type FastifyRequestHandlerOptions<
35
- TRouter extends AnyRouter,
36
- TRequest extends FastifyRequest,
37
- TResponse extends FastifyReply,
38
- > = FastifyHandlerOptions<TRouter, TRequest, TResponse> & {
39
- req: TRequest;
40
- res: TResponse;
41
- path: string;
42
- };
24
+ import { selectContentHandlerOrUnsupportedMediaType } from '../content-handlers/selectContentHandlerOrUnsupportedMediaType';
25
+ import { getFastifyHTTPJSONContentTypeHandler } from './content-type/json';
26
+ import type { FastifyRequestHandlerOptions } from './types';
43
27
 
44
28
  export async function fastifyRequestHandler<
45
29
  TRouter extends AnyRouter,
@@ -63,7 +47,6 @@ export async function fastifyRequestHandler<
63
47
  query,
64
48
  method: opts.req.method,
65
49
  headers: opts.req.headers,
66
- body: opts.req.body ?? 'null',
67
50
  };
68
51
 
69
52
  let resolve: (value: FastifyReply) => void;
@@ -108,10 +91,21 @@ export async function fastifyRequestHandler<
108
91
  }
109
92
  };
110
93
 
94
+ const [contentTypeHandler, unsupportedMediaTypeError] =
95
+ selectContentHandlerOrUnsupportedMediaType(
96
+ [getFastifyHTTPJSONContentTypeHandler<TRouter, TRequest, TResponse>()],
97
+ opts,
98
+ );
99
+
111
100
  resolveHTTPResponse({
112
101
  ...opts,
113
102
  req,
103
+ error: unsupportedMediaTypeError,
104
+ async getInput(info) {
105
+ return await contentTypeHandler?.getInputs(opts, info);
106
+ },
114
107
  createContext,
108
+
115
109
  onError(o) {
116
110
  opts?.onError?.({ ...o, req: opts.req });
117
111
  },
@@ -9,13 +9,13 @@
9
9
  */
10
10
  /// <reference types="@fastify/websocket" />
11
11
  import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
12
- import type { FastifyHandlerOptions } from '.';
13
12
  // @trpc/server
14
13
  import type { AnyRouter } from '../../@trpc/server';
15
14
  import type { NodeHTTPCreateContextFnOptions } from '../node-http';
16
15
  import type { WSSHandlerOptions } from '../ws';
17
16
  import { getWSConnectionHandler } from '../ws';
18
17
  import { fastifyRequestHandler } from './fastifyRequestHandler';
18
+ import type { FastifyHandlerOptions } from './types';
19
19
 
20
20
  export interface FastifyTRPCPluginOptions<TRouter extends AnyRouter> {
21
21
  prefix?: string;
@@ -0,0 +1,22 @@
1
+ import type { FastifyReply, FastifyRequest } from 'fastify';
2
+ // @trpc/server
3
+ import type { AnyRouter } from '../../@trpc/server';
4
+ import type { HTTPBaseHandlerOptions } from '../../@trpc/server/http';
5
+ import type { NodeHTTPConditionCreateContextOption } from '../node-http';
6
+
7
+ export type FastifyHandlerOptions<
8
+ TRouter extends AnyRouter,
9
+ TRequest extends FastifyRequest,
10
+ TResponse extends FastifyReply,
11
+ > = HTTPBaseHandlerOptions<TRouter, TRequest> &
12
+ NodeHTTPConditionCreateContextOption<TRouter, TRequest, TResponse>;
13
+
14
+ export type FastifyRequestHandlerOptions<
15
+ TRouter extends AnyRouter,
16
+ TRequest extends FastifyRequest,
17
+ TResponse extends FastifyReply,
18
+ > = FastifyHandlerOptions<TRouter, TRequest, TResponse> & {
19
+ req: TRequest;
20
+ res: TResponse;
21
+ path: string;
22
+ };
@@ -0,0 +1,90 @@
1
+ // @trpc/server
2
+ import { TRPCError } from '../../../../@trpc/server';
3
+ import type {
4
+ AnyRouter,
5
+ CombinedDataTransformer,
6
+ } from '../../../../@trpc/server';
7
+ import type { BaseContentTypeHandler } from '../../../../@trpc/server/http';
8
+ import { createConcurrentCache } from '../../../content-handlers/concurrentCache';
9
+ import type { FetchHandlerRequestOptions } from '../../types';
10
+
11
+ export interface FetchHTTPContentTypeHandler<TRouter extends AnyRouter>
12
+ extends BaseContentTypeHandler<
13
+ FetchHandlerRequestOptions<TRouter> & {
14
+ url: URL;
15
+ }
16
+ > {}
17
+
18
+ export const getFetchHTTPJSONContentTypeHandler: <
19
+ TRouter extends AnyRouter,
20
+ >() => FetchHTTPContentTypeHandler<TRouter> = () => {
21
+ const cache = createConcurrentCache();
22
+
23
+ return {
24
+ name: 'fetch-json',
25
+ isMatch: (headers) => {
26
+ return !!headers.get('content-type')?.startsWith('application/json');
27
+ },
28
+ getInputs: async (opts, info) => {
29
+ async function getRawProcedureInputOrThrow() {
30
+ try {
31
+ if (opts.url.searchParams.has('input')) {
32
+ const input = opts.url.searchParams.get('input');
33
+ if (!input) {
34
+ return undefined;
35
+ }
36
+
37
+ return JSON.parse(input);
38
+ }
39
+
40
+ return await opts.req.json();
41
+ } catch (cause) {
42
+ throw new TRPCError({
43
+ code: 'PARSE_ERROR',
44
+ cause,
45
+ });
46
+ }
47
+ }
48
+
49
+ const deserializeInputValue = (
50
+ rawValue: unknown,
51
+ transformer: CombinedDataTransformer,
52
+ ) => {
53
+ return typeof rawValue !== 'undefined'
54
+ ? transformer.input.deserialize(rawValue)
55
+ : rawValue;
56
+ };
57
+
58
+ const rawInput = await cache.concurrentSafeGet('rawInput', () =>
59
+ getRawProcedureInputOrThrow(),
60
+ );
61
+ if (rawInput === undefined) {
62
+ return undefined;
63
+ }
64
+
65
+ const transformer = opts.router._def._config.transformer;
66
+
67
+ if (!info.isBatchCall) {
68
+ return cache.concurrentSafeGet('input', () =>
69
+ deserializeInputValue(rawInput, transformer),
70
+ );
71
+ }
72
+
73
+ /* istanbul ignore if */
74
+ if (
75
+ rawInput == null ||
76
+ typeof rawInput !== 'object' ||
77
+ Array.isArray(rawInput)
78
+ ) {
79
+ throw new TRPCError({
80
+ code: 'BAD_REQUEST',
81
+ message: '"input" needs to be an object when doing a batch call',
82
+ });
83
+ }
84
+
85
+ return cache.concurrentSafeGet(String(info.batch), () =>
86
+ deserializeInputValue(rawInput[info.batch], transformer),
87
+ );
88
+ },
89
+ };
90
+ };
@@ -21,14 +21,10 @@ import {
21
21
  resolveHTTPResponse,
22
22
  toURL,
23
23
  } from '../../@trpc/server/http';
24
+ import { selectContentHandlerOrUnsupportedMediaType } from '../content-handlers/selectContentHandlerOrUnsupportedMediaType';
25
+ import { getFetchHTTPJSONContentTypeHandler } from './content-type/json';
24
26
  import type { FetchHandlerOptions } from './types';
25
27
 
26
- export type FetchHandlerRequestOptions<TRouter extends AnyRouter> =
27
- FetchHandlerOptions<TRouter> & {
28
- req: Request;
29
- endpoint: string;
30
- };
31
-
32
28
  const trimSlashes = (path: string): string => {
33
29
  path = path.startsWith('/') ? path.slice(1) : path;
34
30
  path = path.endsWith('/') ? path.slice(0, -1) : path;
@@ -37,7 +33,7 @@ const trimSlashes = (path: string): string => {
37
33
  };
38
34
 
39
35
  export async function fetchRequestHandler<TRouter extends AnyRouter>(
40
- opts: FetchHandlerRequestOptions<TRouter>,
36
+ opts: FetchHandlerOptions<TRouter>,
41
37
  ): Promise<Response> {
42
38
  const resHeaders = new Headers();
43
39
 
@@ -57,9 +53,6 @@ export async function fetchRequestHandler<TRouter extends AnyRouter>(
57
53
  query: url.searchParams,
58
54
  method: opts.req.method,
59
55
  headers: Object.fromEntries(opts.req.headers),
60
- body: opts.req.headers.get('content-type')?.startsWith('application/json')
61
- ? await opts.req.text()
62
- : '',
63
56
  };
64
57
 
65
58
  let resolve: (value: Response) => void;
@@ -117,11 +110,30 @@ export async function fetchRequestHandler<TRouter extends AnyRouter>(
117
110
  }
118
111
  };
119
112
 
113
+ const [contentTypeHandler, unsupportedMediaTypeError] =
114
+ selectContentHandlerOrUnsupportedMediaType(
115
+ [getFetchHTTPJSONContentTypeHandler<TRouter>()],
116
+ {
117
+ ...opts,
118
+ url,
119
+ },
120
+ );
121
+
120
122
  resolveHTTPResponse({
121
123
  ...opts,
122
124
  req,
123
125
  createContext,
124
126
  path,
127
+ error: unsupportedMediaTypeError,
128
+ async getInput(info) {
129
+ return await contentTypeHandler?.getInputs(
130
+ {
131
+ ...opts,
132
+ url,
133
+ },
134
+ info,
135
+ );
136
+ },
125
137
  onError(o) {
126
138
  opts?.onError?.({ ...o, req: opts.req });
127
139
  },