@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.
Files changed (58) hide show
  1. package/README.md +5 -5
  2. package/dist/app-dir/client.d.ts +3 -3
  3. package/dist/app-dir/client.d.ts.map +1 -1
  4. package/dist/app-dir/client.js +5 -105
  5. package/dist/app-dir/client.mjs +4 -102
  6. package/dist/app-dir/create-action-hook.d.ts +18 -6
  7. package/dist/app-dir/create-action-hook.d.ts.map +1 -1
  8. package/dist/app-dir/create-action-hook.js +108 -0
  9. package/dist/app-dir/create-action-hook.mjs +105 -0
  10. package/dist/app-dir/formDataToObject.js +34 -0
  11. package/dist/app-dir/formDataToObject.mjs +32 -0
  12. package/dist/app-dir/links/nextCache.d.ts +4 -3
  13. package/dist/app-dir/links/nextCache.d.ts.map +1 -1
  14. package/dist/app-dir/links/nextCache.js +9 -10
  15. package/dist/app-dir/links/nextCache.mjs +8 -7
  16. package/dist/app-dir/links/nextHttp.d.ts +11 -5
  17. package/dist/app-dir/links/nextHttp.d.ts.map +1 -1
  18. package/dist/app-dir/links/nextHttp.js +22 -23
  19. package/dist/app-dir/links/nextHttp.mjs +22 -21
  20. package/dist/app-dir/server.d.ts +19 -12
  21. package/dist/app-dir/server.d.ts.map +1 -1
  22. package/dist/app-dir/server.js +39 -55
  23. package/dist/app-dir/server.mjs +29 -43
  24. package/dist/app-dir/shared.d.ts +19 -13
  25. package/dist/app-dir/shared.d.ts.map +1 -1
  26. package/dist/{shared-e49b9cdc.js → app-dir/shared.js} +1 -1
  27. package/dist/{shared-f6996341.mjs → app-dir/shared.mjs} +2 -2
  28. package/dist/app-dir/types.d.ts +23 -11
  29. package/dist/app-dir/types.d.ts.map +1 -1
  30. package/dist/bundle-analysis.json +56 -44
  31. package/dist/createTRPCNext.d.ts +10 -8
  32. package/dist/createTRPCNext.d.ts.map +1 -1
  33. package/dist/createTRPCNext.js +38 -0
  34. package/dist/createTRPCNext.mjs +36 -0
  35. package/dist/index.js +4 -190
  36. package/dist/index.mjs +2 -185
  37. package/dist/ssrPrepass.d.ts +3 -0
  38. package/dist/ssrPrepass.d.ts.map +1 -0
  39. package/dist/ssrPrepass.js +139 -0
  40. package/dist/ssrPrepass.mjs +137 -0
  41. package/dist/withTRPC.d.ts +41 -13
  42. package/dist/withTRPC.d.ts.map +1 -1
  43. package/dist/withTRPC.js +86 -0
  44. package/dist/withTRPC.mjs +84 -0
  45. package/package.json +36 -25
  46. package/src/app-dir/client.ts +4 -4
  47. package/src/app-dir/create-action-hook.tsx +49 -19
  48. package/src/app-dir/links/nextCache.ts +20 -8
  49. package/src/app-dir/links/nextHttp.ts +50 -30
  50. package/src/app-dir/server.ts +86 -34
  51. package/src/app-dir/shared.ts +52 -25
  52. package/src/app-dir/types.ts +41 -29
  53. package/src/createTRPCNext.tsx +25 -16
  54. package/src/ssrPrepass.ts +185 -0
  55. package/src/withTRPC.tsx +102 -180
  56. package/ssrPrepass/index.d.ts +1 -0
  57. package/ssrPrepass/index.js +1 -0
  58. package/dist/shared-642894f4.js +0 -19
package/src/withTRPC.tsx CHANGED
@@ -2,102 +2,103 @@
2
2
  * Heavily based on urql's ssr
3
3
  * https://github.com/FormidableLabs/urql/blob/main/packages/next-urql/src/with-urql-client.ts
4
4
  */
5
+ import type { DehydratedState, QueryClient } from '@tanstack/react-query';
6
+ import { HydrationBoundary, QueryClientProvider } from '@tanstack/react-query';
7
+ import type { CreateTRPCClientOptions, TRPCUntypedClient } from '@trpc/client';
8
+ import type { CoercedTransformerParameters } from '@trpc/client/unstable-internals';
5
9
  import {
6
- dehydrate,
7
- DehydratedState,
8
- HydrationBoundary,
9
- QueryClient,
10
- QueryClientProvider,
11
- } from '@tanstack/react-query';
12
- import {
13
- CreateTRPCClientOptions,
14
- createTRPCUntypedClient,
15
- TRPCUntypedClient,
16
- } from '@trpc/client';
17
- import { TRPCClientError, TRPCClientErrorLike } from '@trpc/react-query';
18
- import {
19
- createRootHooks,
10
+ getTransformer,
11
+ type TransformerOptions,
12
+ } from '@trpc/client/unstable-internals';
13
+ import type { TRPCClientError } from '@trpc/react-query';
14
+ import type {
20
15
  CreateTRPCReactOptions,
21
16
  CreateTRPCReactQueryClientConfig,
22
- getQueryClient,
23
17
  } from '@trpc/react-query/shared';
24
- import type { AnyRouter, Dict, Maybe } from '@trpc/server';
25
- import type { ResponseMeta } from '@trpc/server/http';
26
- import {
18
+ import { createRootHooks, getQueryClient } from '@trpc/react-query/shared';
19
+ import type {
20
+ AnyRouter,
21
+ Dict,
22
+ inferClientTypes,
23
+ ResponseMeta,
24
+ } from '@trpc/server/unstable-core-do-not-import';
25
+ import type {
27
26
  AppContextType,
28
27
  AppPropsType,
29
28
  NextComponentType,
30
29
  NextPageContext,
31
30
  } from 'next/dist/shared/lib/utils';
32
- import { NextRouter } from 'next/router';
33
- import React, { createElement, useState } from 'react';
34
- import ssrPrepass from 'react-ssr-prepass';
31
+ import type { NextRouter } from 'next/router';
32
+ import React, { useState } from 'react';
35
33
 
36
- function transformQueryOrMutationCacheErrors<
37
- TState extends
38
- | DehydratedState['mutations'][0]
39
- | DehydratedState['queries'][0],
40
- >(result: TState): TState {
41
- const error = result.state.error as Maybe<TRPCClientError<any>>;
42
- if (error instanceof Error && error.name === 'TRPCClientError') {
43
- const newError: TRPCClientErrorLike<any> = {
44
- message: error.message,
45
- data: error.data,
46
- shape: error.shape,
47
- };
48
- return {
49
- ...result,
50
- state: {
51
- ...result.state,
52
- error: newError,
53
- },
54
- };
55
- }
56
- return result;
57
- }
58
34
  export type WithTRPCConfig<TRouter extends AnyRouter> =
59
35
  CreateTRPCClientOptions<TRouter> &
60
36
  CreateTRPCReactQueryClientConfig & {
61
37
  abortOnUnmount?: boolean;
62
38
  };
63
39
 
64
- interface WithTRPCOptions<TRouter extends AnyRouter>
65
- extends CreateTRPCReactOptions<TRouter> {
66
- config: (info: { ctx?: NextPageContext }) => WithTRPCConfig<TRouter>;
67
- }
40
+ type WithTRPCOptions<TRouter extends AnyRouter> =
41
+ CreateTRPCReactOptions<TRouter> & {
42
+ config: (info: { ctx?: NextPageContext }) => WithTRPCConfig<TRouter>;
43
+ } & TransformerOptions<inferClientTypes<TRouter>>;
44
+
45
+ export type TRPCPrepassHelper = (opts: {
46
+ parent: WithTRPCSSROptions<AnyRouter>;
47
+ WithTRPC: NextComponentType<any, any, any>;
48
+ AppOrPage: NextComponentType<any, any, any>;
49
+ }) => void;
50
+ export type WithTRPCSSROptions<TRouter extends AnyRouter> =
51
+ WithTRPCOptions<TRouter> & {
52
+ /**
53
+ * If you enable this, you also need to add a `ssrPrepass`-prop
54
+ * @link https://trpc.io/docs/client/nextjs/ssr
55
+ */
56
+ ssr:
57
+ | true
58
+ | ((opts: { ctx: NextPageContext }) => boolean | Promise<boolean>);
59
+ responseMeta?: (opts: {
60
+ ctx: NextPageContext;
61
+ clientErrors: TRPCClientError<TRouter>[];
62
+ }) => ResponseMeta;
63
+ /**
64
+ * use `import { ssrPrepass } from '@trpc/next/ssrPrepass'`
65
+ * @link https://trpc.io/docs/client/nextjs/ssr
66
+ */
67
+ ssrPrepass: TRPCPrepassHelper;
68
+ };
68
69
 
69
- export interface WithTRPCSSROptions<TRouter extends AnyRouter>
70
- extends WithTRPCOptions<TRouter> {
71
- ssr: true;
72
- responseMeta?: (opts: {
73
- ctx: NextPageContext;
74
- clientErrors: TRPCClientError<TRouter>[];
75
- }) => ResponseMeta;
76
- }
77
- export interface WithTRPCNoSSROptions<TRouter extends AnyRouter>
78
- extends WithTRPCOptions<TRouter> {
79
- ssr?: false;
80
- }
70
+ export type WithTRPCNoSSROptions<TRouter extends AnyRouter> =
71
+ WithTRPCOptions<TRouter> & {
72
+ ssr?: false;
73
+ };
74
+
75
+ export type TRPCPrepassProps<
76
+ TRouter extends AnyRouter,
77
+ TSSRContext extends NextPageContext = NextPageContext,
78
+ > = {
79
+ config: WithTRPCConfig<TRouter>;
80
+ queryClient: QueryClient;
81
+ trpcClient: TRPCUntypedClient<TRouter>;
82
+ ssrState: 'prepass';
83
+ ssrContext: TSSRContext;
84
+ };
81
85
 
82
86
  export function withTRPC<
83
87
  TRouter extends AnyRouter,
84
88
  TSSRContext extends NextPageContext = NextPageContext,
85
89
  >(opts: WithTRPCNoSSROptions<TRouter> | WithTRPCSSROptions<TRouter>) {
86
90
  const { config: getClientConfig } = opts;
91
+ const transformer = getTransformer(
92
+ (opts as CoercedTransformerParameters).transformer,
93
+ );
87
94
 
88
- type TRPCPrepassProps = {
89
- config: WithTRPCConfig<TRouter>;
90
- queryClient: QueryClient;
91
- trpcClient: TRPCUntypedClient<TRouter>;
92
- ssrState: 'prepass';
93
- ssrContext: TSSRContext;
94
- };
95
+ type $PrepassProps = TRPCPrepassProps<TRouter, TSSRContext>;
95
96
  return (AppOrPage: NextComponentType<any, any, any>): NextComponentType => {
96
97
  const trpc = createRootHooks<TRouter, TSSRContext>(opts);
97
98
 
98
99
  const WithTRPC = (
99
100
  props: AppPropsType<NextRouter, any> & {
100
- trpc?: TRPCPrepassProps;
101
+ trpc?: $PrepassProps;
101
102
  },
102
103
  ) => {
103
104
  const [prepassProps] = useState(() => {
@@ -108,6 +109,7 @@ export function withTRPC<
108
109
  const config = getClientConfig({});
109
110
  const queryClient = getQueryClient(config);
110
111
  const trpcClient = trpc.createClient(config);
112
+
111
113
  return {
112
114
  abortOnUnmount: config.abortOnUnmount,
113
115
  queryClient,
@@ -120,10 +122,16 @@ export function withTRPC<
120
122
  const { queryClient, trpcClient, ssrState, ssrContext } = prepassProps;
121
123
 
122
124
  // allow normal components to be wrapped, not just app/pages
123
- const hydratedState = trpc.useDehydratedState(
124
- trpcClient,
125
- props.pageProps?.trpcState,
126
- );
125
+ const trpcState = props.pageProps?.trpcState;
126
+
127
+ const hydratedState: DehydratedState | undefined = React.useMemo(() => {
128
+ if (!trpcState) {
129
+ return trpcState;
130
+ }
131
+
132
+ return transformer.input.deserialize(trpcState);
133
+ // eslint-disable-next-line react-hooks/exhaustive-deps
134
+ }, [trpcState, trpcClient]);
127
135
 
128
136
  return (
129
137
  <trpc.Provider
@@ -142,123 +150,37 @@ export function withTRPC<
142
150
  );
143
151
  };
144
152
 
145
- if (AppOrPage.getInitialProps ?? opts.ssr) {
146
- WithTRPC.getInitialProps = async (appOrPageCtx: AppContextType) => {
147
- const AppTree = appOrPageCtx.AppTree;
153
+ if (opts.ssr) {
154
+ opts.ssrPrepass({
155
+ parent: opts,
156
+ AppOrPage,
157
+ WithTRPC,
158
+ });
159
+ } else if (AppOrPage.getInitialProps) {
160
+ // Allow combining `getServerSideProps` and `getInitialProps`
148
161
 
162
+ WithTRPC.getInitialProps = async (appOrPageCtx: AppContextType) => {
149
163
  // Determine if we are wrapping an App component or a Page component.
150
164
  const isApp = !!appOrPageCtx.Component;
151
- const ctx: NextPageContext = isApp
152
- ? appOrPageCtx.ctx
153
- : (appOrPageCtx as any as NextPageContext);
154
165
 
155
166
  // Run the wrapped component's getInitialProps function.
156
167
  let pageProps: Dict<unknown> = {};
157
- if (AppOrPage.getInitialProps) {
158
- const originalProps = await AppOrPage.getInitialProps(
159
- appOrPageCtx as any,
160
- );
161
- const originalPageProps = isApp
162
- ? originalProps.pageProps ?? {}
163
- : originalProps;
164
-
165
- pageProps = {
166
- ...originalPageProps,
167
- ...pageProps,
168
- };
169
- }
170
- const getAppTreeProps = (props: Record<string, unknown>) =>
171
- isApp ? { pageProps: props } : props;
172
-
173
- if (typeof window !== 'undefined' || !opts.ssr) {
174
- return getAppTreeProps(pageProps);
175
- }
176
-
177
- const config = getClientConfig({ ctx });
178
- const trpcClient = createTRPCUntypedClient(config);
179
- const queryClient = getQueryClient(config);
180
-
181
- const trpcProp: TRPCPrepassProps = {
182
- config,
183
- trpcClient,
184
- queryClient,
185
- ssrState: 'prepass',
186
- ssrContext: ctx as TSSRContext,
168
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169
+ const originalProps = await AppOrPage.getInitialProps!(
170
+ appOrPageCtx as any,
171
+ );
172
+ const originalPageProps = isApp
173
+ ? originalProps.pageProps ?? {}
174
+ : originalProps;
175
+
176
+ pageProps = {
177
+ ...originalPageProps,
178
+ ...pageProps,
187
179
  };
188
- const prepassProps = {
189
- pageProps,
190
- trpc: trpcProp,
191
- };
192
-
193
- // Run the prepass step on AppTree. This will run all trpc queries on the server.
194
- // multiple prepass ensures that we can do batching on the server
195
- while (true) {
196
- // render full tree
197
- await ssrPrepass(createElement(AppTree, prepassProps as any));
198
- if (!queryClient.isFetching()) {
199
- // the render didn't cause the queryClient to fetch anything
200
- break;
201
- }
202
-
203
- // wait until the query cache has settled it's promises
204
- await new Promise<void>((resolve) => {
205
- const unsub = queryClient.getQueryCache().subscribe((event) => {
206
- if (event?.query.getObserversCount() === 0) {
207
- resolve();
208
- unsub();
209
- }
210
- });
211
- });
212
- }
213
- const dehydratedCache = dehydrate(queryClient, {
214
- shouldDehydrateQuery() {
215
- // makes sure errors are also dehydrated
216
- return true;
217
- },
218
- });
219
- // since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects
220
- const dehydratedCacheWithErrors = {
221
- ...dehydratedCache,
222
- queries: dehydratedCache.queries.map(
223
- transformQueryOrMutationCacheErrors,
224
- ),
225
- mutations: dehydratedCache.mutations.map(
226
- transformQueryOrMutationCacheErrors,
227
- ),
228
- };
229
-
230
- // dehydrate query client's state and add it to the props
231
- pageProps.trpcState =
232
- trpcClient.runtime.combinedTransformer.output.serialize(
233
- dehydratedCacheWithErrors,
234
- );
235
-
236
- const appTreeProps = getAppTreeProps(pageProps);
237
-
238
- const meta =
239
- opts.responseMeta?.({
240
- ctx,
241
- clientErrors: [
242
- ...dehydratedCache.queries,
243
- ...dehydratedCache.mutations,
244
- ]
245
- .map((v) => v.state.error)
246
- .flatMap((err) =>
247
- err instanceof Error && err.name === 'TRPCClientError'
248
- ? [err as TRPCClientError<TRouter>]
249
- : [],
250
- ),
251
- }) ?? {};
180
+ const getAppTreeProps = (props: Dict<unknown>) =>
181
+ isApp ? { pageProps: props } : props;
252
182
 
253
- for (const [key, value] of Object.entries(meta.headers ?? {})) {
254
- if (typeof value === 'string') {
255
- ctx.res?.setHeader(key, value);
256
- }
257
- }
258
- if (meta.status && ctx.res) {
259
- ctx.res.statusCode = meta.status;
260
- }
261
- return appTreeProps;
183
+ return getAppTreeProps(pageProps);
262
184
  };
263
185
  }
264
186
 
@@ -0,0 +1 @@
1
+ export * from '../dist/ssrPrepass';
@@ -0,0 +1 @@
1
+ module.exports = require('../dist/ssrPrepass');
@@ -1,19 +0,0 @@
1
- import '@trpc/server/shared';
2
-
3
- /**
4
- * @internal
5
- */
6
- function generateCacheTag(procedurePath, input) {
7
- return input
8
- ? `${procedurePath}?input=${JSON.stringify(input)}`
9
- : procedurePath;
10
- }
11
- function isFormData(value) {
12
- if (typeof FormData === 'undefined') {
13
- // FormData is not supported
14
- return false;
15
- }
16
- return value instanceof FormData;
17
- }
18
-
19
- export { generateCacheTag as g, isFormData as i };