Skip to content

Commit

Permalink
Merge pull request #5 from ixahmedxi/ahmed/trpc
Browse files Browse the repository at this point in the history
trpc
  • Loading branch information
ixahmedxi authored Feb 7, 2024
2 parents a19ab1f + 35ece14 commit b725da0
Show file tree
Hide file tree
Showing 20 changed files with 446 additions and 44 deletions.
2 changes: 1 addition & 1 deletion apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import './src/env.js';

/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@orbitkit/db', '@orbitkit/auth'],
transpilePackages: ['@orbitkit/db', '@orbitkit/auth', '@orbitkit/trpc'],
};

export default nextConfig;
7 changes: 7 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@
"dependencies": {
"@orbitkit/auth": "workspace:^",
"@orbitkit/db": "workspace:^",
"@orbitkit/trpc": "workspace:^",
"@orbitkit/ui": "workspace:^",
"@t3-oss/env-nextjs": "^0.8.0",
"@tanstack/react-query": "4.36.1",
"@total-typescript/ts-reset": "^0.5.1",
"@trpc/client": "^10.45.1",
"@trpc/react-query": "^10.45.1",
"@trpc/server": "^10.45.1",
"geist": "^1.2.2",
"next": "14.1.0",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { type NextRequest } from 'next/server';

import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

import { appRouter, createTRPCContext } from '@orbitkit/trpc';

import { env } from '@/env';

const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext(req),
onError: ({ path, error }) => {
env.NODE_ENV === 'development' &&
console.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`,
);
},
});

export { handler as GET, handler as POST };
7 changes: 7 additions & 0 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import { redirect } from 'next/navigation';
import { getSession } from '@orbitkit/auth';
import { Avatar, AvatarFallback, AvatarImage } from '@orbitkit/ui/avatar';

import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { trpc } from '@/lib/trpc/server';

export default async function Home() {
const { user } = await getSession();
if (!user) {
return redirect('/login');
}

const hello = await trpc.greeting.protectedHello.query();

return (
<main className="container mx-auto py-6 px-6">
<h1>Next.js app</h1>
<h1>{hello.greeting}</h1>
<ThemeSwitcher />
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import { type FC, type PropsWithChildren } from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';

import { TRPCReactProvider } from '@/lib/trpc/client';

export const Providers: FC<PropsWithChildren> = ({ children }) => {
return (
<NextThemesProvider attribute="class" enableSystem>
{children}
<TRPCReactProvider>{children}</TRPCReactProvider>
</NextThemesProvider>
);
};
11 changes: 9 additions & 2 deletions apps/web/src/env.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { createEnv } from '@t3-oss/env-nextjs';
import { vercel } from '@t3-oss/env-nextjs/presets';
import { z } from 'zod';

import { env as authEnv } from '@orbitkit/auth/env';
import { env as dbEnv } from '@orbitkit/db/env';

export const env = createEnv({
extends: [dbEnv, authEnv, vercel],
server: {},
server: {
NODE_ENV: z.enum(['development', 'test', 'production']).optional(),
PORT: z.coerce.number().default(3000),
},
client: {},
runtimeEnv: {},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
PORT: process.env['PORT'],
},
emptyStringAsUndefined: true,
});
44 changes: 44 additions & 0 deletions apps/web/src/lib/trpc/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import { useState } from 'react';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import superjson from 'superjson';

import { type AppRouter } from '@orbitkit/trpc';

import { env } from '@/env';

import { getUrl } from './shared';

export const trpc = createTRPCReact<AppRouter>();

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());

const [trpcClient] = useState(() =>
trpc.createClient({
transformer: superjson,
links: [
loggerLink({
enabled: (op) =>
env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
url: getUrl(),
}),
],
}),
);

return (
<QueryClientProvider client={queryClient}>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</trpc.Provider>
</QueryClientProvider>
);
}
59 changes: 59 additions & 0 deletions apps/web/src/lib/trpc/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'server-only';

import { cache } from 'react';
import { headers } from 'next/headers';

import type { AppRouter } from '@orbitkit/trpc';

import {
createTRPCProxyClient,
loggerLink,
TRPCClientError,
} from '@trpc/client';
import { callProcedure } from '@trpc/server';
import { observable } from '@trpc/server/observable';
import { type TRPCErrorResponse } from '@trpc/server/rpc';
import superjson from 'superjson';

import { appRouter, createTRPCContext } from '@orbitkit/trpc';

const createContext = cache(() => {
const heads = new Headers(headers());
heads.set('x-trpc-source', 'rsc');

return createTRPCContext({
headers: heads,
});
});

export const trpc = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
() =>
({ op }) =>
observable((observer) => {
createContext()
.then((ctx) => {
return callProcedure({
procedures: appRouter._def.procedures,
path: op.path,
rawInput: op.input,
ctx,
type: op.type,
});
})
.then((data) => {
observer.next({ result: { data } });
observer.complete();
})
.catch((cause: TRPCErrorResponse) => {
observer.error(TRPCClientError.from(cause));
});
}),
],
});
18 changes: 18 additions & 0 deletions apps/web/src/lib/trpc/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';

import { type AppRouter } from '@orbitkit/trpc';

import { env } from '@/env';

function getBaseUrl() {
if (typeof window !== 'undefined') return '';
if (env.VERCEL_URL) return `https://${env.VERCEL_URL}`;
return `http://localhost:${env.PORT}`;
}

export function getUrl() {
return getBaseUrl() + '/api/trpc';
}

export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
3 changes: 3 additions & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ words:
- packagejson
- shadcn
- stylesheet
- superjson
- tailwindcss
- tanstack
- trpc
- tsbuildinfo
- tsconfigs
- tsup
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"prettier-plugin-packagejson": "^2.4.10",
"prettier-plugin-tailwindcss": "^0.5.11",
"rimraf": "^5.0.5",
"turbo": "^1.12.2",
"turbo": "^1.12.3",
"typescript": "^5.3.3"
},
"packageManager": "[email protected]",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"arctic": "^1.1.4",
"lucia": "^3.0.1",
"next": "14.1.0",
"oslo": "^1.0.4",
"oslo": "^1.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.22.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"typecheck": "tsc --noEmit --tsBuildInfoFile .tsbuildinfo"
},
"dependencies": {
"@neondatabase/serverless": "^0.7.2",
"@neondatabase/serverless": "^0.8.1",
"@t3-oss/env-core": "^0.8.0",
"drizzle-orm": "^0.29.3",
"pg": "^8.11.3",
Expand Down
7 changes: 7 additions & 0 deletions packages/trpc/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('eslint').Linter.Config} */
const config = {
root: true,
extends: ['orbitkit/base'],
};

module.exports = config;
31 changes: 31 additions & 0 deletions packages/trpc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@orbitkit/trpc",
"version": "0.1.0",
"private": true,
"description": "tRPC api for Orbitkit",
"license": "MIT",
"author": "Orbitkit",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint . --cache --max-warnings 0",
"typecheck": "tsc --noEmit --tsBuildInfoFile .tsbuildinfo"
},
"dependencies": {
"@orbitkit/auth": "workspace:^",
"@orbitkit/db": "workspace:^",
"@trpc/server": "^10.45.1",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@orbitkit/tsconfig": "workspace:^",
"@types/node": "^20.11.16",
"eslint-config-orbitkit": "workspace:^"
},
"volta": {
"extends": "../../package.json"
}
}
10 changes: 10 additions & 0 deletions packages/trpc/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { greetingRouter } from './routers/greeting';
import { router } from './trpc';

export const appRouter = router({
greeting: greetingRouter,
});

export type AppRouter = typeof appRouter;

export { createTRPCContext } from './trpc';
21 changes: 21 additions & 0 deletions packages/trpc/src/routers/greeting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from 'zod';

import { protectedProcedure, publicProcedure, router } from '../trpc';

export const greetingRouter = router({
hello: publicProcedure
.input(z.object({ name: z.string().optional() }).optional())
.query(({ input }) => {
return {
greeting: `Hello, ${input?.name ?? 'World'}!`,
};
}),

protectedHello: protectedProcedure
.input(z.object({ name: z.string().optional() }).optional())
.query(({ input, ctx }) => {
return {
greeting: `Hello, ${input?.name ?? 'World'}! Your user ID is ${ctx.session.user.id}`,
};
}),
});
Loading

0 comments on commit b725da0

Please sign in to comment.