diff --git a/.vscode/settings.json b/.vscode/settings.json index c3d9d94..ac72b45 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "typescript.tsdk": "./node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + "prettier.requireConfig": false } \ No newline at end of file diff --git a/@trpc/next-layout/client.ts b/@trpc/next-layout/client.ts new file mode 100644 index 0000000..6f05662 --- /dev/null +++ b/@trpc/next-layout/client.ts @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import { AnyRouter, ProcedureType } from "@trpc/server"; +import { createRecursiveProxy } from "@trpc/server/shared"; +import { createContext } from "react"; +import { DecoratedProcedureRecord } from "./createTRPCNextLayout"; +import superjson from "superjson"; +export type TRPCSchemaContext = Record<string, ProcedureType>; +export const TRPCSchemaContext = createContext<TRPCSchemaContext>(null as any); +export const TRPCSchemaContextProvider = TRPCSchemaContext.Provider; + +export async function createTRPCNextClientLayout< + TRouter extends AnyRouter +>(): Promise<DecoratedProcedureRecord<TRouter["_def"]["record"]>> { + const client = createTRPCClient({ + links: [httpBatchLink({ url: "/api/trpc" })], + transformer: superjson, + }); + + return createRecursiveProxy((callOpts) => { + const path = [...callOpts.path]; + path.pop(); + const flatPath = path.join("."); + // const schema = useContext(TRPCSchemaContext); + // const procedureType = schema[flatPath]; + + const { data } = useQuery([...path, callOpts.args[0]], () => { + return client.query(flatPath, callOpts.args[0]); + }); + return data; + }) as DecoratedProcedureRecord<TRouter["_def"]["record"]>; +} diff --git a/@trpc/next-layout/createTRPCNextLayout.ts b/@trpc/next-layout/createTRPCNextLayout.ts index a71b8bd..fe4e264 100644 --- a/@trpc/next-layout/createTRPCNextLayout.ts +++ b/@trpc/next-layout/createTRPCNextLayout.ts @@ -11,7 +11,7 @@ import { import { createRecursiveProxy } from "@trpc/server/shared"; import { use } from "react"; -interface CreateTRPCNextLayoutOptions<TRouter extends AnyRouter> { +export interface CreateTRPCNextLayoutOptions<TRouter extends AnyRouter> { router: TRouter; createContext: () => MaybePromise<inferRouterContext<TRouter>>; } @@ -23,7 +23,7 @@ export type DecorateProcedure<TProcedure extends AnyProcedure> = TProcedure extends AnyQueryProcedure ? { use( - input: inferProcedureInput<TProcedure>, + input: inferProcedureInput<TProcedure> // FIXME: maybe this should be cache options? // opts?: ): inferProcedureOutput<TProcedure>; @@ -41,7 +41,7 @@ type OmitNever<TType> = Pick< */ export type DecoratedProcedureRecord< TProcedures extends ProcedureRouterRecord, - TPath extends string = "", + TPath extends string = "" > = OmitNever<{ [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter ? DecoratedProcedureRecord< @@ -53,9 +53,9 @@ export type DecoratedProcedureRecord< : never; }>; -export function createTRPCNextLayout<TRouter extends AnyRouter>( - opts: CreateTRPCNextLayoutOptions<TRouter>, -): DecoratedProcedureRecord<TRouter["_def"]["record"]> { +export async function createTRPCNextLayout<TRouter extends AnyRouter>( + opts: CreateTRPCNextLayoutOptions<TRouter> +): Promise<DecoratedProcedureRecord<TRouter["_def"]["record"]>> { return createRecursiveProxy((callOpts) => { const path = [...callOpts.path]; path.pop(); @@ -65,7 +65,7 @@ export function createTRPCNextLayout<TRouter extends AnyRouter>( const caller = opts.router.createCaller(ctx); return caller.query(path.join("."), callOpts.args[0]) as any; - })(), + })() ) as any; }) as DecoratedProcedureRecord<TRouter["_def"]["record"]>; } diff --git a/@trpc/next-layout/index.ts b/@trpc/next-layout/index.ts index 61c958c..95ab9e7 100644 --- a/@trpc/next-layout/index.ts +++ b/@trpc/next-layout/index.ts @@ -1 +1,2 @@ export { createTRPCNextLayout } from "./createTRPCNextLayout"; + \ No newline at end of file diff --git a/@trpc/next-layout/package.json b/@trpc/next-layout/package.json new file mode 100644 index 0000000..a71b706 --- /dev/null +++ b/@trpc/next-layout/package.json @@ -0,0 +1,7 @@ +{ + "name": "@trpc/next-layout", + "exports": { + "./client": "./client.ts", + "./server": "./server.ts" + } +} diff --git a/@trpc/next-layout/server.ts b/@trpc/next-layout/server.ts new file mode 100644 index 0000000..43417c0 --- /dev/null +++ b/@trpc/next-layout/server.ts @@ -0,0 +1 @@ +export { createTRPCNextLayout as createTRPCNextServerLayout } from "./createTRPCNextLayout"; diff --git a/app/layout.tsx b/app/layout.tsx index 5623893..50f9bd9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,24 +12,24 @@ export default function RootLayout(props: Props) { const user = rsc.whoami.use(); return ( <ClientProvider> - <html lang='en'> + <html lang="en"> <head> <title>Next.js hello</title> <meta - name='viewport' - content='width=device-width, initial-scale=1.0' + name="viewport" + content="width=device-width, initial-scale=1.0" /> </head> <body> - <nav className='p-4'> - <ul className='flex flex-col sm:flex-row space-y-2 sm:space-y-0 space-x-2 justify-center items-center'> + <nav className="p-4"> + <ul className="flex flex-col sm:flex-row space-y-2 sm:space-y-0 space-x-2 justify-center items-center"> <li> - <Link href='/' className='text-indigo-500 underline'> + <Link href="/" className="text-indigo-500 underline"> Home </Link> </li> <li> - <Link href='/secret' className='text-indigo-500 underline'> + <Link href="/secret" className="text-indigo-500 underline"> Secret page </Link> </li> @@ -37,13 +37,13 @@ export default function RootLayout(props: Props) { {user ? ( <> Hi <em>{user.name}</em>.{" "} - <Link href='/api/auth/signout' className='underline'> + <Link href="/api/auth/signout" className="underline"> Logout </Link> </> ) : ( <> - <Link href='/api/auth/signin' className='button'> + <Link href="/api/auth/signin" className="button"> Login </Link> </> diff --git a/client/CreatePostForm.tsx b/client/CreatePostForm.tsx index 4dfd051..b5e770f 100644 --- a/client/CreatePostForm.tsx +++ b/client/CreatePostForm.tsx @@ -2,10 +2,15 @@ import { useRouter } from "next/navigation"; import { trpc } from "~/client/trpcClient"; +import { rsc } from "~/server-rsc/trpc/client"; import { Inputs } from "~/shared/utils"; export function CreatePostForm() { const addPost = trpc.post.add.useMutation(); + // const a = rsc.health.healthz.use(); + + const data = rsc.health.healthz.use(); + console.log(data); const router = useRouter(); @@ -36,16 +41,16 @@ export function CreatePostForm() { console.error({ cause }, "Failed to add post"); } }} - className='space-y-2' + className="space-y-2" > <fieldset> - <label htmlFor='title' className='label'> + <label htmlFor="title" className="label"> Title </label> <input - id='title' - name='title' - type='text' + id="title" + name="title" + type="text" disabled={addPost.isLoading} className={ "input" + (addPost.error?.data?.zod?.title ? " input--error" : "") @@ -57,13 +62,13 @@ export function CreatePostForm() { </fieldset> <fieldset> - <label htmlFor='text' className='label'> + <label htmlFor="text" className="label"> Text </label> <textarea - id='text' - name='text' + id="text" + name="text" disabled={addPost.isLoading} className={ "input" + (addPost.error?.data?.zod?.text ? " input--error" : "") @@ -74,7 +79,7 @@ export function CreatePostForm() { )} </fieldset> <fieldset> - <input type='submit' disabled={addPost.isLoading} className='button' /> + <input type="submit" disabled={addPost.isLoading} className="button" /> {addPost.error && !addPost.error.data?.zod && ( <p style={{ color: "red" }}> {addPost.error.message} diff --git a/next.config.js b/next.config.js index 4487c5f..cef843b 100644 --- a/next.config.js +++ b/next.config.js @@ -1,13 +1,17 @@ // @ts-check /* eslint-disable @typescript-eslint/no-var-requires */ -const { env } = require('./server/env'); +const { env } = require("./server/env"); /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { appDir: true, }, + webpack: (config, options) => { + config.experiments = { ...config.experiments, ...{ topLevelAwait: true } }; + return config; + }, }; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package.json b/package.json index d1a4d1a..d12c7c2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@trpc/client": "^10.0.0-rc.1", "@trpc/react-query": "^10.0.0-rc.1", "@trpc/server": "^10.0.0-rc.1", + "@trpc/next-layout": "file:@trpc/next-layout", "next": "^13.0.0", "next-auth": "4.14.0", "npm-run-all": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 519c24e..cea3d8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ specifiers: '@tailwindcss/typography': ^0.5.7 '@tanstack/react-query': ^4.13.0 '@trpc/client': ^10.0.0-rc.1 + '@trpc/next-layout': file:@trpc/next-layout '@trpc/react-query': ^10.0.0-rc.1 '@trpc/server': ^10.0.0-rc.1 '@types/node': 18.8.5 @@ -34,6 +35,7 @@ dependencies: '@tailwindcss/typography': 0.5.7_tailwindcss@3.2.1 '@tanstack/react-query': 4.13.0_biqbaboplfbrettd7655fr4n2y '@trpc/client': 10.0.0-rc.1_@trpc+server@10.0.0-rc.1 + '@trpc/next-layout': file:@trpc/next-layout '@trpc/react-query': 10.0.0-rc.1_j3pstw3g7plkegerh6sxuce5zm '@trpc/server': 10.0.0-rc.1 next: 13.0.0_biqbaboplfbrettd7655fr4n2y @@ -2668,3 +2670,8 @@ packages: /zod/3.19.1: resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} dev: false + + file:@trpc/next-layout: + resolution: {directory: '@trpc/next-layout', type: directory} + name: '@trpc/next-layout' + dev: false diff --git a/server-rsc/getUser.tsx b/server-rsc/getUser.tsx index eb9c7a7..4956f1a 100644 --- a/server-rsc/getUser.tsx +++ b/server-rsc/getUser.tsx @@ -1,5 +1,4 @@ import { getToken } from "next-auth/jwt"; -import { cookies } from "next/headers"; export interface User { id: string; @@ -7,6 +6,7 @@ export interface User { name: string; } export async function getUser(): Promise<User | null> { + const cookies = (await import("next/headers")).cookies; const $cookies = cookies(); const newCookies = new Map<string, string>(); diff --git a/server-rsc/trpc/client.ts b/server-rsc/trpc/client.ts new file mode 100644 index 0000000..5b1db2e --- /dev/null +++ b/server-rsc/trpc/client.ts @@ -0,0 +1,4 @@ +import { createTRPCNextClientLayout } from "@trpc/next-layout/client"; +import { AppRouter } from "~/server/routers/_app"; + +export const rsc = await createTRPCNextClientLayout<AppRouter>(); diff --git a/server-rsc/trpc/index.ts b/server-rsc/trpc/index.ts new file mode 100644 index 0000000..364a925 --- /dev/null +++ b/server-rsc/trpc/index.ts @@ -0,0 +1 @@ +export * from "./server"; diff --git a/server-rsc/trpc/package.json b/server-rsc/trpc/package.json new file mode 100644 index 0000000..a2af2a7 --- /dev/null +++ b/server-rsc/trpc/package.json @@ -0,0 +1,8 @@ +{ + "exports": { + ".": { + "react-server": "./server.ts", + "default": "./client.ts" + } + } +} diff --git a/server-rsc/trpc.ts b/server-rsc/trpc/server.ts similarity index 51% rename from server-rsc/trpc.ts rename to server-rsc/trpc/server.ts index cf21ad4..e3be7d3 100644 --- a/server-rsc/trpc.ts +++ b/server-rsc/trpc/server.ts @@ -1,11 +1,11 @@ -import { createTRPCNextLayout } from "~/@trpc/next-layout"; -import { createContext } from "~/server/context"; import { appRouter } from "~/server/routers/_app"; -import { getUser } from "./getUser"; +import { createContext } from "~/server/context"; +import { getUser } from "../getUser"; +import { createTRPCNextServerLayout } from "@trpc/next-layout/server"; -export const rsc = createTRPCNextLayout({ +export const rsc = await createTRPCNextServerLayout({ router: appRouter, - createContext() { + async createContext() { return createContext({ type: "rsc", getUser,