Skip to content
This repository was archived by the owner on May 19, 2023. It is now read-only.

conditional rsc or trpc client #32

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.requireConfig": false
}
32 changes: 32 additions & 0 deletions @trpc/next-layout/client.ts
Original file line number Diff line number Diff line change
@@ -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" })],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the server I think we'd need some sort of server-link?

Copy link
Member Author

@sachinraja sachinraja Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't run on the server. @trpc/next-layout/server.ts is the one that runs on server components. The server-rsc directory has a package.json with the react-server condition to switch between server and client automatically

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"]>;
}
14 changes: 7 additions & 7 deletions @trpc/next-layout/createTRPCNextLayout.ts
Original file line number Diff line number Diff line change
@@ -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"]>;
}
1 change: 1 addition & 0 deletions @trpc/next-layout/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { createTRPCNextLayout } from "./createTRPCNextLayout";

7 changes: 7 additions & 0 deletions @trpc/next-layout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@trpc/next-layout",
"exports": {
"./client": "./client.ts",
"./server": "./server.ts"
}
}
1 change: 1 addition & 0 deletions @trpc/next-layout/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createTRPCNextLayout as createTRPCNextServerLayout } from "./createTRPCNextLayout";
18 changes: 9 additions & 9 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -12,38 +12,38 @@ 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>
<li>
{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>
</>
23 changes: 14 additions & 9 deletions client/CreatePostForm.tsx
Original file line number Diff line number Diff line change
@@ -2,10 +2,15 @@

import { useRouter } from "next/navigation";
import { trpc } from "~/client/trpcClient";
import { rsc } from "~/server-rsc/trpc/client";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm

Suggested change
import { rsc } from "~/server-rsc/trpc/client";
import { rsc } from "~/server-rsc/trpc";

does this still work?

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wat, this works?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see my other comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhhh, I missed that this was imported from the /client....

I think this isn't great as you'll have to keep track of which one you're using 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a mistake (auto import). server-rsc/trpc should work too.

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}
8 changes: 6 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server-rsc/getUser.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getToken } from "next-auth/jwt";
import { cookies } from "next/headers";

export interface User {
id: string;
email: string;
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>();
4 changes: 4 additions & 0 deletions server-rsc/trpc/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createTRPCNextClientLayout } from "@trpc/next-layout/client";
import { AppRouter } from "~/server/routers/_app";

export const rsc = await createTRPCNextClientLayout<AppRouter>();
1 change: 1 addition & 0 deletions server-rsc/trpc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./server";
8 changes: 8 additions & 0 deletions server-rsc/trpc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"exports": {
".": {
"react-server": "./server.ts",
"default": "./client.ts"
}
}
}
10 changes: 5 additions & 5 deletions server-rsc/trpc.ts → server-rsc/trpc/server.ts
Original file line number Diff line number Diff line change
@@ -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,