@trpc/next 11.0.0-next.91 → 11.0.0-rc.329
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.
- package/README.md +5 -5
- package/dist/app-dir/client.d.ts +3 -3
- package/dist/app-dir/client.d.ts.map +1 -1
- package/dist/app-dir/client.js +5 -105
- package/dist/app-dir/client.mjs +4 -102
- package/dist/app-dir/create-action-hook.d.ts +18 -6
- package/dist/app-dir/create-action-hook.d.ts.map +1 -1
- package/dist/app-dir/create-action-hook.js +108 -0
- package/dist/app-dir/create-action-hook.mjs +105 -0
- package/dist/app-dir/formDataToObject.js +34 -0
- package/dist/app-dir/formDataToObject.mjs +32 -0
- package/dist/app-dir/links/nextCache.d.ts +4 -3
- package/dist/app-dir/links/nextCache.d.ts.map +1 -1
- package/dist/app-dir/links/nextCache.js +9 -10
- package/dist/app-dir/links/nextCache.mjs +8 -7
- package/dist/app-dir/links/nextHttp.d.ts +11 -5
- package/dist/app-dir/links/nextHttp.d.ts.map +1 -1
- package/dist/app-dir/links/nextHttp.js +22 -23
- package/dist/app-dir/links/nextHttp.mjs +22 -21
- package/dist/app-dir/server.d.ts +19 -12
- package/dist/app-dir/server.d.ts.map +1 -1
- package/dist/app-dir/server.js +39 -55
- package/dist/app-dir/server.mjs +29 -43
- package/dist/app-dir/shared.d.ts +19 -13
- package/dist/app-dir/shared.d.ts.map +1 -1
- package/dist/{shared-e49b9cdc.js → app-dir/shared.js} +1 -1
- package/dist/{shared-f6996341.mjs → app-dir/shared.mjs} +2 -2
- package/dist/app-dir/types.d.ts +23 -11
- package/dist/app-dir/types.d.ts.map +1 -1
- package/dist/bundle-analysis.json +56 -44
- package/dist/createTRPCNext.d.ts +10 -8
- package/dist/createTRPCNext.d.ts.map +1 -1
- package/dist/createTRPCNext.js +38 -0
- package/dist/createTRPCNext.mjs +36 -0
- package/dist/index.js +4 -190
- package/dist/index.mjs +2 -185
- package/dist/ssrPrepass.d.ts +3 -0
- package/dist/ssrPrepass.d.ts.map +1 -0
- package/dist/ssrPrepass.js +139 -0
- package/dist/ssrPrepass.mjs +137 -0
- package/dist/withTRPC.d.ts +41 -13
- package/dist/withTRPC.d.ts.map +1 -1
- package/dist/withTRPC.js +86 -0
- package/dist/withTRPC.mjs +84 -0
- package/package.json +36 -25
- package/src/app-dir/client.ts +4 -4
- package/src/app-dir/create-action-hook.tsx +49 -19
- package/src/app-dir/links/nextCache.ts +20 -8
- package/src/app-dir/links/nextHttp.ts +50 -30
- package/src/app-dir/server.ts +86 -34
- package/src/app-dir/shared.ts +52 -25
- package/src/app-dir/types.ts +41 -29
- package/src/createTRPCNext.tsx +25 -16
- package/src/ssrPrepass.ts +185 -0
- package/src/withTRPC.tsx +102 -180
- package/ssrPrepass/index.d.ts +1 -0
- package/ssrPrepass/index.js +1 -0
- package/dist/shared-642894f4.js +0 -19
package/src/app-dir/server.ts
CHANGED
|
@@ -3,34 +3,37 @@ import {
|
|
|
3
3
|
clientCallTypeToProcedureType,
|
|
4
4
|
createTRPCUntypedClient,
|
|
5
5
|
} from '@trpc/client';
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
AnyProcedure,
|
|
8
|
-
|
|
8
|
+
AnyRootTypes,
|
|
9
9
|
AnyRouter,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
ErrorHandlerOptions,
|
|
11
|
+
inferClientTypes,
|
|
12
12
|
inferProcedureInput,
|
|
13
13
|
MaybePromise,
|
|
14
|
+
RootConfig,
|
|
14
15
|
Simplify,
|
|
15
|
-
|
|
16
|
-
} from '@trpc/server';
|
|
17
|
-
import { TRPCResponse } from '@trpc/server/rpc';
|
|
16
|
+
TRPCResponse,
|
|
17
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
18
18
|
import {
|
|
19
19
|
createRecursiveProxy,
|
|
20
20
|
getErrorShape,
|
|
21
|
+
getTRPCErrorFromUnknown,
|
|
21
22
|
transformTRPCResponse,
|
|
22
|
-
|
|
23
|
+
TRPCError,
|
|
24
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
23
25
|
import { revalidateTag } from 'next/cache';
|
|
26
|
+
import { isNotFoundError } from 'next/dist/client/components/not-found';
|
|
27
|
+
import { isRedirectError } from 'next/dist/client/components/redirect';
|
|
24
28
|
import { cache } from 'react';
|
|
25
29
|
import { formDataToObject } from './formDataToObject';
|
|
26
|
-
import {
|
|
30
|
+
import type {
|
|
27
31
|
ActionHandlerDef,
|
|
28
32
|
CreateTRPCNextAppRouterOptions,
|
|
29
|
-
generateCacheTag,
|
|
30
33
|
inferActionDef,
|
|
31
|
-
isFormData,
|
|
32
34
|
} from './shared';
|
|
33
|
-
import {
|
|
35
|
+
import { generateCacheTag, isFormData } from './shared';
|
|
36
|
+
import type { NextAppDirDecorateRouterRecord } from './types';
|
|
34
37
|
|
|
35
38
|
// ts-prune-ignore-next
|
|
36
39
|
export function experimental_createTRPCNextAppDirServer<
|
|
@@ -58,12 +61,21 @@ export function experimental_createTRPCNextAppDirServer<
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
return (client[procedureType] as any)(procedurePath, ...callOpts.args);
|
|
61
|
-
}) as
|
|
62
|
-
TRouter['_def']['_config'],
|
|
64
|
+
}) as NextAppDirDecorateRouterRecord<
|
|
65
|
+
TRouter['_def']['_config']['$types'],
|
|
63
66
|
TRouter['_def']['record']
|
|
64
67
|
>;
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Rethrow errors that should be handled by Next.js
|
|
72
|
+
*/
|
|
73
|
+
const throwNextErrors = (error: TRPCError) => {
|
|
74
|
+
const { cause } = error;
|
|
75
|
+
if (isRedirectError(cause) || isNotFoundError(cause)) {
|
|
76
|
+
throw error.cause;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
67
79
|
/**
|
|
68
80
|
* @internal
|
|
69
81
|
*/
|
|
@@ -73,36 +85,63 @@ export type TRPCActionHandler<TDef extends ActionHandlerDef> = (
|
|
|
73
85
|
|
|
74
86
|
export function experimental_createServerActionHandler<
|
|
75
87
|
TInstance extends {
|
|
76
|
-
_config:
|
|
88
|
+
_config: RootConfig<AnyRootTypes>;
|
|
77
89
|
},
|
|
78
90
|
>(
|
|
79
91
|
t: TInstance,
|
|
80
|
-
opts:
|
|
81
|
-
|
|
92
|
+
opts: (object extends TInstance['_config']['$types']['ctx']
|
|
93
|
+
? {
|
|
94
|
+
createContext?: () => MaybePromise<
|
|
95
|
+
TInstance['_config']['$types']['ctx']
|
|
96
|
+
>;
|
|
97
|
+
}
|
|
98
|
+
: {
|
|
99
|
+
createContext: () => MaybePromise<
|
|
100
|
+
TInstance['_config']['$types']['ctx']
|
|
101
|
+
>;
|
|
102
|
+
}) & {
|
|
82
103
|
/**
|
|
83
104
|
* Transform form data to a `Record` before passing it to the procedure
|
|
84
105
|
* @default true
|
|
85
106
|
*/
|
|
86
107
|
normalizeFormData?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Called when an error occurs in the handler
|
|
110
|
+
*/
|
|
111
|
+
onError?: (
|
|
112
|
+
opts: ErrorHandlerOptions<TInstance['_config']['$types']['ctx']>,
|
|
113
|
+
) => void;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Rethrow errors that should be handled by Next.js
|
|
117
|
+
* @default true
|
|
118
|
+
*/
|
|
119
|
+
rethrowNextErrors?: boolean;
|
|
87
120
|
},
|
|
88
121
|
) {
|
|
89
122
|
const config = t._config;
|
|
90
|
-
const {
|
|
123
|
+
const {
|
|
124
|
+
normalizeFormData = true,
|
|
125
|
+
createContext,
|
|
126
|
+
rethrowNextErrors = true,
|
|
127
|
+
} = opts;
|
|
91
128
|
|
|
92
|
-
const transformer = config.transformer
|
|
129
|
+
const transformer = config.transformer;
|
|
93
130
|
|
|
94
131
|
// TODO allow this to take a `TRouter` in addition to a `AnyProcedure`
|
|
95
132
|
return function createServerAction<TProc extends AnyProcedure>(
|
|
96
133
|
proc: TProc,
|
|
97
|
-
): TRPCActionHandler<
|
|
134
|
+
): TRPCActionHandler<
|
|
135
|
+
Simplify<inferActionDef<inferClientTypes<TInstance>, TProc>>
|
|
136
|
+
> {
|
|
98
137
|
return async function actionHandler(
|
|
99
138
|
rawInput: FormData | inferProcedureInput<TProc>,
|
|
100
139
|
) {
|
|
101
|
-
|
|
140
|
+
let ctx: TInstance['_config']['$types']['ctx'] | undefined = undefined;
|
|
102
141
|
try {
|
|
103
|
-
|
|
142
|
+
ctx = (await createContext?.()) ?? {};
|
|
104
143
|
if (normalizeFormData && isFormData(rawInput)) {
|
|
105
|
-
// Normalizes
|
|
144
|
+
// Normalizes FormData so we can use `z.object({})` etc on the server
|
|
106
145
|
try {
|
|
107
146
|
rawInput = formDataToObject(rawInput);
|
|
108
147
|
} catch {
|
|
@@ -115,13 +154,15 @@ export function experimental_createServerActionHandler<
|
|
|
115
154
|
rawInput = transformer.input.deserialize(rawInput);
|
|
116
155
|
}
|
|
117
156
|
|
|
118
|
-
const data =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
157
|
+
const data = proc._def.experimental_caller
|
|
158
|
+
? await proc(rawInput as any)
|
|
159
|
+
: await proc({
|
|
160
|
+
input: undefined,
|
|
161
|
+
ctx,
|
|
162
|
+
path: '',
|
|
163
|
+
getRawInput: async () => rawInput,
|
|
164
|
+
type: proc._def.type,
|
|
165
|
+
});
|
|
125
166
|
|
|
126
167
|
const transformedJSON = transformTRPCResponse(config, {
|
|
127
168
|
result: {
|
|
@@ -131,22 +172,33 @@ export function experimental_createServerActionHandler<
|
|
|
131
172
|
return transformedJSON;
|
|
132
173
|
} catch (cause) {
|
|
133
174
|
const error = getTRPCErrorFromUnknown(cause);
|
|
175
|
+
|
|
176
|
+
opts.onError?.({
|
|
177
|
+
ctx,
|
|
178
|
+
error,
|
|
179
|
+
input: rawInput,
|
|
180
|
+
path: '',
|
|
181
|
+
type: proc._def.type,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
rethrowNextErrors && throwNextErrors(error);
|
|
185
|
+
|
|
134
186
|
const shape = getErrorShape({
|
|
135
187
|
config,
|
|
136
188
|
ctx,
|
|
137
189
|
error,
|
|
138
190
|
input: rawInput,
|
|
139
|
-
path: '
|
|
191
|
+
path: '',
|
|
140
192
|
type: proc._def.type,
|
|
141
193
|
});
|
|
142
194
|
|
|
143
|
-
// TODO: send the right HTTP header?!
|
|
144
|
-
|
|
145
195
|
return transformTRPCResponse(t._config, {
|
|
146
196
|
error: shape,
|
|
147
197
|
});
|
|
148
198
|
}
|
|
149
|
-
} as TRPCActionHandler<
|
|
199
|
+
} as TRPCActionHandler<
|
|
200
|
+
inferActionDef<TInstance['_config']['$types'], TProc>
|
|
201
|
+
>;
|
|
150
202
|
};
|
|
151
203
|
}
|
|
152
204
|
|
package/src/app-dir/shared.ts
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
CreateTRPCClientOptions,
|
|
3
3
|
Resolver,
|
|
4
4
|
TRPCUntypedClient,
|
|
5
5
|
} from '@trpc/client';
|
|
6
|
-
import {
|
|
6
|
+
import type { inferProcedureOutput } from '@trpc/server';
|
|
7
|
+
import type {
|
|
8
|
+
AnyClientTypes,
|
|
7
9
|
AnyProcedure,
|
|
8
10
|
AnyQueryProcedure,
|
|
9
|
-
|
|
11
|
+
AnyRootTypes,
|
|
10
12
|
AnyRouter,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
inferProcedureInput,
|
|
14
|
+
inferTransformedProcedureOutput,
|
|
13
15
|
ProtectedIntersection,
|
|
14
|
-
|
|
15
|
-
} from '@trpc/server';
|
|
16
|
-
import { createRecursiveProxy } from '@trpc/server/
|
|
16
|
+
RouterRecord,
|
|
17
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
18
|
+
import { createRecursiveProxy } from '@trpc/server/unstable-core-do-not-import';
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* @internal
|
|
20
22
|
*/
|
|
21
|
-
export type UseProcedureRecord<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
?
|
|
27
|
-
|
|
23
|
+
export type UseProcedureRecord<
|
|
24
|
+
TRoot extends AnyRootTypes,
|
|
25
|
+
TRecord extends RouterRecord,
|
|
26
|
+
> = {
|
|
27
|
+
[TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
|
|
28
|
+
? $Value extends RouterRecord
|
|
29
|
+
? UseProcedureRecord<TRoot, $Value>
|
|
30
|
+
: $Value extends AnyQueryProcedure
|
|
31
|
+
? Resolver<{
|
|
32
|
+
input: inferProcedureInput<$Value>;
|
|
33
|
+
output: inferTransformedProcedureOutput<TRoot, $Value>;
|
|
34
|
+
errorShape: TRoot['errorShape'];
|
|
35
|
+
transformer: TRoot['transformer'];
|
|
36
|
+
}>
|
|
37
|
+
: never
|
|
38
|
+
: never;
|
|
28
39
|
};
|
|
29
40
|
|
|
30
41
|
export function createUseProxy<TRouter extends AnyRouter>(
|
|
@@ -34,18 +45,31 @@ export function createUseProxy<TRouter extends AnyRouter>(
|
|
|
34
45
|
const path = opts.path.join('.');
|
|
35
46
|
|
|
36
47
|
return client.query(path, ...opts.args);
|
|
37
|
-
}) as UseProcedureRecord<
|
|
48
|
+
}) as UseProcedureRecord<
|
|
49
|
+
TRouter['_def']['_config']['$types'],
|
|
50
|
+
TRouter['_def']['record']
|
|
51
|
+
>;
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
type NextAppRouterUse<TRouter extends AnyRouter> = {
|
|
41
55
|
<TData extends Promise<unknown>[]>(
|
|
42
|
-
cb: (
|
|
56
|
+
cb: (
|
|
57
|
+
t: UseProcedureRecord<
|
|
58
|
+
TRouter['_def']['_config']['$types'],
|
|
59
|
+
TRouter['_def']['record']
|
|
60
|
+
>,
|
|
61
|
+
) => [...TData],
|
|
43
62
|
): {
|
|
44
|
-
[TKey in keyof TData]:
|
|
63
|
+
[TKey in keyof TData]: Awaited<TData[TKey]>;
|
|
45
64
|
};
|
|
46
65
|
<TData extends Promise<unknown>>(
|
|
47
|
-
cb: (
|
|
48
|
-
|
|
66
|
+
cb: (
|
|
67
|
+
t: UseProcedureRecord<
|
|
68
|
+
TRouter['_def']['_config']['$types'],
|
|
69
|
+
TRouter['_def']['record']
|
|
70
|
+
>,
|
|
71
|
+
) => TData,
|
|
72
|
+
): Awaited<TData>;
|
|
49
73
|
};
|
|
50
74
|
type CreateTRPCNextAppRouterBase<TRouter extends AnyRouter> = {
|
|
51
75
|
use: NextAppRouterUse<TRouter>;
|
|
@@ -53,7 +77,10 @@ type CreateTRPCNextAppRouterBase<TRouter extends AnyRouter> = {
|
|
|
53
77
|
export type CreateTRPCNextAppRouter<TRouter extends AnyRouter> =
|
|
54
78
|
ProtectedIntersection<
|
|
55
79
|
CreateTRPCNextAppRouterBase<TRouter>,
|
|
56
|
-
UseProcedureRecord<
|
|
80
|
+
UseProcedureRecord<
|
|
81
|
+
TRouter['_def']['_config']['$types'],
|
|
82
|
+
TRouter['_def']['record']
|
|
83
|
+
>
|
|
57
84
|
>;
|
|
58
85
|
|
|
59
86
|
/**
|
|
@@ -94,10 +121,10 @@ export interface ActionHandlerDef {
|
|
|
94
121
|
* @internal
|
|
95
122
|
*/
|
|
96
123
|
export type inferActionDef<
|
|
97
|
-
|
|
124
|
+
TRoot extends AnyClientTypes,
|
|
98
125
|
TProc extends AnyProcedure,
|
|
99
126
|
> = {
|
|
100
|
-
input:
|
|
101
|
-
output: TProc
|
|
102
|
-
errorShape:
|
|
127
|
+
input: inferProcedureInput<TProc>;
|
|
128
|
+
output: inferProcedureOutput<TProc>;
|
|
129
|
+
errorShape: TRoot['errorShape'];
|
|
103
130
|
};
|
package/src/app-dir/types.ts
CHANGED
|
@@ -1,47 +1,59 @@
|
|
|
1
|
-
import { Resolver } from '@trpc/client';
|
|
2
|
-
import {
|
|
3
|
-
AnyMutationProcedure,
|
|
1
|
+
import type { Resolver } from '@trpc/client';
|
|
2
|
+
import type {
|
|
4
3
|
AnyProcedure,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
AnyRootTypes,
|
|
5
|
+
inferProcedureInput,
|
|
6
|
+
inferTransformedProcedureOutput,
|
|
7
|
+
ProcedureType,
|
|
8
|
+
RouterRecord,
|
|
9
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
10
|
+
|
|
11
|
+
type ResolverDef = {
|
|
12
|
+
input: any;
|
|
13
|
+
output: any;
|
|
14
|
+
transformer: boolean;
|
|
15
|
+
errorShape: any;
|
|
16
|
+
};
|
|
12
17
|
|
|
13
18
|
export type DecorateProcedureServer<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
> =
|
|
19
|
+
TType extends ProcedureType,
|
|
20
|
+
TDef extends ResolverDef,
|
|
21
|
+
> = TType extends 'query'
|
|
17
22
|
? {
|
|
18
|
-
query: Resolver<
|
|
23
|
+
query: Resolver<TDef>;
|
|
19
24
|
revalidate: (
|
|
20
|
-
input?:
|
|
25
|
+
input?: TDef['input'],
|
|
21
26
|
) => Promise<
|
|
22
27
|
{ revalidated: false; error: string } | { revalidated: true }
|
|
23
28
|
>;
|
|
24
29
|
}
|
|
25
|
-
:
|
|
30
|
+
: TType extends 'mutation'
|
|
26
31
|
? {
|
|
27
|
-
mutate: Resolver<
|
|
32
|
+
mutate: Resolver<TDef>;
|
|
28
33
|
}
|
|
29
|
-
:
|
|
34
|
+
: TType extends 'subscription'
|
|
30
35
|
? {
|
|
31
|
-
subscribe: Resolver<
|
|
36
|
+
subscribe: Resolver<TDef>;
|
|
32
37
|
}
|
|
33
38
|
: never;
|
|
34
39
|
|
|
35
|
-
export type
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
export type NextAppDirDecorateRouterRecord<
|
|
41
|
+
TRoot extends AnyRootTypes,
|
|
42
|
+
TRecord extends RouterRecord,
|
|
38
43
|
> = {
|
|
39
|
-
[TKey in keyof
|
|
40
|
-
?
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
[TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
|
|
45
|
+
? $Value extends RouterRecord
|
|
46
|
+
? NextAppDirDecorateRouterRecord<TRoot, $Value>
|
|
47
|
+
: $Value extends AnyProcedure
|
|
48
|
+
? DecorateProcedureServer<
|
|
49
|
+
$Value['_def']['type'],
|
|
50
|
+
{
|
|
51
|
+
input: inferProcedureInput<$Value>;
|
|
52
|
+
output: inferTransformedProcedureOutput<TRoot, $Value>;
|
|
53
|
+
errorShape: TRoot['errorShape'];
|
|
54
|
+
transformer: TRoot['transformer'];
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
: never
|
|
46
58
|
: never;
|
|
47
59
|
};
|
package/src/createTRPCNext.tsx
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/* istanbul ignore file -- @preserve */
|
|
2
2
|
// We're testing this through E2E-testing
|
|
3
|
+
import type {
|
|
4
|
+
CreateReactUtils,
|
|
5
|
+
DecorateRouterRecord,
|
|
6
|
+
TRPCUseQueries,
|
|
7
|
+
TRPCUseSuspenseQueries,
|
|
8
|
+
} from '@trpc/react-query/shared';
|
|
3
9
|
import {
|
|
4
10
|
createReactDecoration,
|
|
5
11
|
createReactQueryUtils,
|
|
6
|
-
CreateReactUtils,
|
|
7
12
|
createRootHooks,
|
|
8
|
-
DecoratedProcedureRecord,
|
|
9
|
-
TRPCUseQueries,
|
|
10
13
|
} from '@trpc/react-query/shared';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
import type {
|
|
15
|
+
AnyRouter,
|
|
16
|
+
ProtectedIntersection,
|
|
17
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
18
|
+
import { createFlatProxy } from '@trpc/server/unstable-core-do-not-import';
|
|
19
|
+
import type { NextPageContext } from 'next/types';
|
|
14
20
|
import { useMemo } from 'react';
|
|
15
|
-
import {
|
|
21
|
+
import type { WithTRPCNoSSROptions, WithTRPCSSROptions } from './withTRPC';
|
|
22
|
+
import { withTRPC } from './withTRPC';
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* @internal
|
|
@@ -24,15 +31,16 @@ export interface CreateTRPCNextBase<
|
|
|
24
31
|
/**
|
|
25
32
|
* @deprecated renamed to `useUtils` and will be removed in a future tRPC version
|
|
26
33
|
*
|
|
27
|
-
* @
|
|
34
|
+
* @link https://trpc.io/docs/v11/client/react/useUtils
|
|
28
35
|
*/
|
|
29
36
|
useContext(): CreateReactUtils<TRouter, TSSRContext>;
|
|
30
37
|
/**
|
|
31
|
-
* @
|
|
38
|
+
* @link https://trpc.io/docs/v11/client/react/useUtils
|
|
32
39
|
*/
|
|
33
40
|
useUtils(): CreateReactUtils<TRouter, TSSRContext>;
|
|
34
41
|
withTRPC: ReturnType<typeof withTRPC<TRouter, TSSRContext>>;
|
|
35
42
|
useQueries: TRPCUseQueries<TRouter>;
|
|
43
|
+
useSuspenseQueries: TRPCUseSuspenseQueries<TRouter>;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/**
|
|
@@ -41,23 +49,20 @@ export interface CreateTRPCNextBase<
|
|
|
41
49
|
export type CreateTRPCNext<
|
|
42
50
|
TRouter extends AnyRouter,
|
|
43
51
|
TSSRContext extends NextPageContext,
|
|
44
|
-
TFlags,
|
|
45
52
|
> = ProtectedIntersection<
|
|
46
53
|
CreateTRPCNextBase<TRouter, TSSRContext>,
|
|
47
|
-
|
|
48
|
-
TRouter['_def']['_config'],
|
|
49
|
-
TRouter['_def']['record']
|
|
50
|
-
TFlags
|
|
54
|
+
DecorateRouterRecord<
|
|
55
|
+
TRouter['_def']['_config']['$types'],
|
|
56
|
+
TRouter['_def']['record']
|
|
51
57
|
>
|
|
52
58
|
>;
|
|
53
59
|
|
|
54
60
|
export function createTRPCNext<
|
|
55
61
|
TRouter extends AnyRouter,
|
|
56
62
|
TSSRContext extends NextPageContext = NextPageContext,
|
|
57
|
-
TFlags = null,
|
|
58
63
|
>(
|
|
59
64
|
opts: WithTRPCNoSSROptions<TRouter> | WithTRPCSSROptions<TRouter>,
|
|
60
|
-
): CreateTRPCNext<TRouter, TSSRContext
|
|
65
|
+
): CreateTRPCNext<TRouter, TSSRContext> {
|
|
61
66
|
const hooks = createRootHooks<TRouter, TSSRContext>(opts);
|
|
62
67
|
|
|
63
68
|
// TODO: maybe set TSSRContext to `never` when using `WithTRPCNoSSROptions`
|
|
@@ -78,6 +83,10 @@ export function createTRPCNext<
|
|
|
78
83
|
return hooks.useQueries;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
if (key === 'useSuspenseQueries') {
|
|
87
|
+
return hooks.useSuspenseQueries;
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
if (key === 'withTRPC') {
|
|
82
91
|
return _withTRPC;
|
|
83
92
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heavily based on urql's ssr
|
|
3
|
+
* https://github.com/FormidableLabs/urql/blob/main/packages/next-urql/src/with-urql-client.ts
|
|
4
|
+
*/
|
|
5
|
+
import type { DehydratedState } from '@tanstack/react-query';
|
|
6
|
+
import { dehydrate } from '@tanstack/react-query';
|
|
7
|
+
import { createTRPCUntypedClient } from '@trpc/client';
|
|
8
|
+
import type { CoercedTransformerParameters } from '@trpc/client/unstable-internals';
|
|
9
|
+
import { getTransformer } from '@trpc/client/unstable-internals';
|
|
10
|
+
import type { TRPCClientError, TRPCClientErrorLike } from '@trpc/react-query';
|
|
11
|
+
import { getQueryClient } from '@trpc/react-query/shared';
|
|
12
|
+
import type {
|
|
13
|
+
AnyRouter,
|
|
14
|
+
Dict,
|
|
15
|
+
Maybe,
|
|
16
|
+
} from '@trpc/server/unstable-core-do-not-import';
|
|
17
|
+
import type {
|
|
18
|
+
AppContextType,
|
|
19
|
+
NextPageContext,
|
|
20
|
+
} from 'next/dist/shared/lib/utils';
|
|
21
|
+
import { createElement } from 'react';
|
|
22
|
+
import type { TRPCPrepassHelper, TRPCPrepassProps } from './withTRPC';
|
|
23
|
+
|
|
24
|
+
function transformQueryOrMutationCacheErrors<
|
|
25
|
+
TState extends
|
|
26
|
+
| DehydratedState['mutations'][0]
|
|
27
|
+
| DehydratedState['queries'][0],
|
|
28
|
+
>(result: TState): TState {
|
|
29
|
+
const error = result.state.error as Maybe<TRPCClientError<any>>;
|
|
30
|
+
if (error instanceof Error && error.name === 'TRPCClientError') {
|
|
31
|
+
const newError: TRPCClientErrorLike<any> = {
|
|
32
|
+
message: error.message,
|
|
33
|
+
data: error.data,
|
|
34
|
+
shape: error.shape,
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
...result,
|
|
38
|
+
state: {
|
|
39
|
+
...result.state,
|
|
40
|
+
error: newError,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const ssrPrepass: TRPCPrepassHelper = (opts) => {
|
|
48
|
+
const { parent, WithTRPC, AppOrPage } = opts;
|
|
49
|
+
type $PrepassProps = TRPCPrepassProps<AnyRouter, any>;
|
|
50
|
+
|
|
51
|
+
const transformer = getTransformer(
|
|
52
|
+
(parent as CoercedTransformerParameters).transformer,
|
|
53
|
+
);
|
|
54
|
+
WithTRPC.getInitialProps = async (appOrPageCtx: AppContextType) => {
|
|
55
|
+
const shouldSsr = async () => {
|
|
56
|
+
if (typeof window !== 'undefined') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (typeof parent.ssr === 'function') {
|
|
60
|
+
try {
|
|
61
|
+
return await parent.ssr({ ctx: appOrPageCtx.ctx });
|
|
62
|
+
} catch (e) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parent.ssr;
|
|
67
|
+
};
|
|
68
|
+
const ssrEnabled = await shouldSsr();
|
|
69
|
+
const AppTree = appOrPageCtx.AppTree;
|
|
70
|
+
|
|
71
|
+
// Determine if we are wrapping an App component or a Page component.
|
|
72
|
+
const isApp = !!appOrPageCtx.Component;
|
|
73
|
+
const ctx: NextPageContext = isApp
|
|
74
|
+
? appOrPageCtx.ctx
|
|
75
|
+
: (appOrPageCtx as any as NextPageContext);
|
|
76
|
+
|
|
77
|
+
// Run the wrapped component's getInitialProps function.
|
|
78
|
+
let pageProps: Dict<unknown> = {};
|
|
79
|
+
if (AppOrPage.getInitialProps) {
|
|
80
|
+
const originalProps = await AppOrPage.getInitialProps(
|
|
81
|
+
appOrPageCtx as any,
|
|
82
|
+
);
|
|
83
|
+
const originalPageProps = isApp
|
|
84
|
+
? originalProps.pageProps ?? {}
|
|
85
|
+
: originalProps;
|
|
86
|
+
|
|
87
|
+
pageProps = {
|
|
88
|
+
...originalPageProps,
|
|
89
|
+
...pageProps,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const getAppTreeProps = (props: Record<string, unknown>) =>
|
|
93
|
+
isApp ? { pageProps: props } : props;
|
|
94
|
+
|
|
95
|
+
if (typeof window !== 'undefined' || !ssrEnabled) {
|
|
96
|
+
return getAppTreeProps(pageProps);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const config = parent.config({ ctx });
|
|
100
|
+
const trpcClient = createTRPCUntypedClient(config);
|
|
101
|
+
const queryClient = getQueryClient(config);
|
|
102
|
+
|
|
103
|
+
const trpcProp: $PrepassProps = {
|
|
104
|
+
config,
|
|
105
|
+
trpcClient,
|
|
106
|
+
queryClient,
|
|
107
|
+
ssrState: 'prepass',
|
|
108
|
+
ssrContext: ctx,
|
|
109
|
+
};
|
|
110
|
+
const prepassProps = {
|
|
111
|
+
pageProps,
|
|
112
|
+
trpc: trpcProp,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const reactDomServer = await import('react-dom/server');
|
|
116
|
+
|
|
117
|
+
// Run the prepass step on AppTree. This will run all trpc queries on the server.
|
|
118
|
+
// multiple prepass ensures that we can do batching on the server
|
|
119
|
+
while (true) {
|
|
120
|
+
// render full tree
|
|
121
|
+
reactDomServer.renderToString(createElement(AppTree, prepassProps));
|
|
122
|
+
if (!queryClient.isFetching()) {
|
|
123
|
+
// the render didn't cause the queryClient to fetch anything
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// wait until the query cache has settled it's promises
|
|
128
|
+
await new Promise<void>((resolve) => {
|
|
129
|
+
const unsub = queryClient.getQueryCache().subscribe((event) => {
|
|
130
|
+
if (event?.query.getObserversCount() === 0) {
|
|
131
|
+
resolve();
|
|
132
|
+
unsub();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const dehydratedCache = dehydrate(queryClient, {
|
|
138
|
+
shouldDehydrateQuery(query) {
|
|
139
|
+
// filter out queries that are marked as trpc: { ssr: false } or are not enabled, but make sure errors are dehydrated
|
|
140
|
+
const isExcludedFromSSr =
|
|
141
|
+
query.state.fetchStatus === 'idle' &&
|
|
142
|
+
query.state.status === 'pending';
|
|
143
|
+
return !isExcludedFromSSr;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
// since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects
|
|
147
|
+
const dehydratedCacheWithErrors = {
|
|
148
|
+
...dehydratedCache,
|
|
149
|
+
queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors),
|
|
150
|
+
mutations: dehydratedCache.mutations.map(
|
|
151
|
+
transformQueryOrMutationCacheErrors,
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// dehydrate query client's state and add it to the props
|
|
156
|
+
pageProps['trpcState'] = transformer.input.serialize(
|
|
157
|
+
dehydratedCacheWithErrors,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const appTreeProps = getAppTreeProps(pageProps);
|
|
161
|
+
|
|
162
|
+
const meta =
|
|
163
|
+
parent.responseMeta?.({
|
|
164
|
+
ctx,
|
|
165
|
+
clientErrors: [...dehydratedCache.queries, ...dehydratedCache.mutations]
|
|
166
|
+
.map((v) => v.state.error)
|
|
167
|
+
.flatMap((err) =>
|
|
168
|
+
err instanceof Error && err.name === 'TRPCClientError'
|
|
169
|
+
? [err as TRPCClientError<AnyRouter>]
|
|
170
|
+
: [],
|
|
171
|
+
),
|
|
172
|
+
}) ?? {};
|
|
173
|
+
|
|
174
|
+
for (const [key, value] of Object.entries(meta.headers ?? {})) {
|
|
175
|
+
if (typeof value === 'string') {
|
|
176
|
+
ctx.res?.setHeader(key, value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (meta.status && ctx.res) {
|
|
180
|
+
ctx.res.statusCode = meta.status;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return appTreeProps;
|
|
184
|
+
};
|
|
185
|
+
};
|