Skip to content

Commit

Permalink
Feat: tRPC installer
Browse files Browse the repository at this point in the history
  • Loading branch information
OrJDev committed Mar 2, 2023
1 parent 9b7db58 commit 83d5d20
Show file tree
Hide file tree
Showing 21 changed files with 631 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-emus-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-jd-app": patch
---

Feat: tRPC installer
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ All addons are optional, you may select some, you may select all and you may sel

- [Prisma](https://github.com/prisma/prisma)
- [pRPC](https://github.com/orjdev/prpc)
- [tRPC](https://github.com/trpc/trpc)
- [TailwindCSS](https://github.com/tailwindlabs/tailwindcss)
- [UnoCSS](https://github.com/unocss/unocss)
- [AuthJS](https://github.com/nextauthjs/next-auth)
Expand Down
23 changes: 19 additions & 4 deletions src/helpers/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,17 @@ export async function getCtxWithInstallers(
let optInstallers = installers.filter(
(pkg) => !validInstallers.includes(pkg)
);
const opts = [["TailwindCSS", "UnoCSS"]];
const opts: Array<Array<TInstallers>> = [
["TailwindCSS", "UnoCSS"],
["tRPC", "pRPC"],
];
for (const opt of opts) {
for (const op of opt) {
if (validInstallers.includes(op)) {
optInstallers = optInstallers.filter((pkg) => !opt.includes(pkg));
optInstallers = optInstallers.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(pkg) => !opt.includes(pkg as any)
);
}
}
}
Expand All @@ -147,8 +153,17 @@ export async function getCtxWithInstallers(
pkgs = validInstallers as TInstallers[];
}
}
const ssr = true;

let ssr = true;
if (pkgs.includes("tRPC")) {
ssr = (
await inquirer.prompt<{ ssr: boolean }>({
name: "ssr",
type: "confirm",
message: "Do you want to use SSR with tRPC?",
default: true,
})
).ssr;
}
return {
...ctx,
installers: pkgs,
Expand Down
8 changes: 7 additions & 1 deletion src/helpers/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ const packages = {
// prpc
"@prpc/solid": "^0.2.0",
"@adeora/solid-query": "^0.20.0",
"@tanstack/solid-query": "^5.0.0-alpha.0", // unused, will be used soon
// trpc
"@tanstack/solid-query": "^5.0.0-alpha.0",
"@trpc/client": "^10.12.0",
"@trpc/server": "^10.12.0",
"solid-start-trpc": "^0.0.16",
"solid-trpc": "^0.0.11-rc.3",
"solid-trpc->ssr": "0.1.0-sssr.7",
// next auth
"@auth/solid-start": "^0.1.0",
"@auth/core": "^0.4.0",
Expand Down
47 changes: 32 additions & 15 deletions src/helpers/utils/getIndexLocation.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { type IUtil } from "~types";
import type { ICtx, IUtil } from "~types";

const getIndexLocation: IUtil = (ctx) => {
const usingPRPC = ctx.installers.includes("pRPC");
const usingTRPC = ctx.installers.includes("tRPC");
const usingTw =
ctx.installers.includes("TailwindCSS") || ctx.installers.includes("UnoCSS");
const usingAuth = ctx.installers.includes("AuthJS");
return createFileHelper(
usingTRPC || usingPRPC,
usingTw,
usingAuth,
usingTRPC ? "tRPC" : "pRPC",
ctx
);
};

export default getIndexLocation;

function createFileHelper(
usingRPC: boolean,
usingTw: boolean,
usingAuth: boolean,
rpc: "pRPC" | "tRPC",
ctx: ICtx
) {
const fileName = `${rpc.toLowerCase()}`;
let indexFile = "";
if (usingPRPC && usingTw && usingAuth) {
indexFile = "with-auth-prpc-tw.tsx";
} else if (usingPRPC && !usingTw && usingAuth) {
indexFile = "with-auth-prpc.tsx";
} else if (usingPRPC && usingTw) {
indexFile = "with-prpc-tw.tsx";
} else if (usingPRPC && !usingTw) {
indexFile = "with-prpc.tsx";
if (usingRPC && usingTw && usingAuth) {
indexFile = `with-auth-${fileName}-tw.tsx`;
} else if (usingRPC && !usingTw && usingAuth) {
indexFile = `with-auth-${fileName}.tsx`;
} else if (usingRPC && usingTw) {
indexFile = `with-${fileName}-tw.tsx`;
} else if (usingRPC && !usingTw) {
indexFile = `with-${fileName}.tsx`;
} else if (usingAuth && usingTw) {
indexFile = "with-auth-tw.tsx";
} else if (!usingPRPC && usingTw) {
} else if (!usingRPC && usingTw) {
indexFile = "with-tw.tsx";
} else if (usingAuth) {
indexFile = "with-auth.tsx";
}
indexFile = indexFile ? `${ctx.templateDir}/index/${indexFile}` : ``;
return indexFile;
};

export default getIndexLocation;
return indexFile ? `${ctx.templateDir}/index/${indexFile}` : ``;
}
26 changes: 26 additions & 0 deletions src/installers/tRPC/files/ssr-utils.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { QueryClient } from "@tanstack/solid-query";
import type { IAppRouter } from "~/server/trpc/router/_app";
import { createTRPCSolidStart } from "solid-trpc";
import { httpBatchLink } from "@trpc/client";

const getBaseUrl = () => {
if (typeof window !== "undefined") return "";
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
// replace example.com with your actual production url
if (process.env.NODE_ENV === "production") return "https://example.com";
return `http://localhost:${process.env.PORT ?? 3000}`;
};

export const trpc = createTRPCSolidStart<IAppRouter>({
config() {
return {
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
});

export const queryClient = new QueryClient();
21 changes: 21 additions & 0 deletions src/installers/tRPC/files/utils.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { QueryClient } from "@tanstack/solid-query";
import type { IAppRouter } from "~/server/trpc/router/_app";
import { createTRPCSolid } from "solid-trpc";
import { httpBatchLink } from "@trpc/client";

const getBaseUrl = () => {
if (typeof window !== "undefined") return "";
return `http://localhost:${process.env.PORT ?? 3000}`;
};

export const trpc = createTRPCSolid<IAppRouter>();

export const client = trpc.createClient({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
});

export const queryClient = new QueryClient();
51 changes: 51 additions & 0 deletions src/installers/tRPC/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { withPackages } from "~helpers/packages";
import type { IInstaller } from "~types";

const config: IInstaller = (ctx) => ({
files: [
{
path: `${ctx.templateDir}/trpc/server`,
to: `${ctx.userDir}/src/server/trpc`,
sep: true,
},
{
path: `${__dirname}/files/${ctx.ssr ? "ssr-utils" : "utils"}.txt`,
to: `${ctx.userDir}/src/utils/trpc.ts`,
},
{
path: `${__dirname}/utils/getRoot`,
type: "exec",
to: `${ctx.userDir}/src/root.tsx`,
},
{
path: `${ctx.templateDir}/trpc/api`,
to: `${ctx.userDir}/src/routes/api`,
},
{
path: `${__dirname}/utils/getTrpcUtils`,
to: `${ctx.userDir}/src/server/trpc/utils.ts`,
type: "exec",
},
{
path: `${__dirname}/utils/getTrpcContext`,
to: `${ctx.userDir}/src/server/trpc/context.ts`,
type: "exec",
},
{
path: `${__dirname}/utils/getMainRouter`,
to: `${ctx.userDir}/src/server/trpc/router/example.ts`,
type: "exec",
},
],
pkgs: withPackages({
normal: [
ctx.ssr ? "solid-trpc->ssr" : "solid-trpc",
"@tanstack/solid-query",
"solid-start-trpc",
"@trpc/client",
"@trpc/server",
],
}),
});

export default config;
28 changes: 28 additions & 0 deletions src/installers/tRPC/utils/getMainRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IUtil } from "~types";

const getMainRouter: IUtil = (ctx) => {
const useNextAuth = ctx.installers.includes("AuthJS");
return `import { z } from "zod";
import { procedure, router${
useNextAuth ? ", protectedProcedure" : ""
} } from "../utils";
export default router({
hello: procedure.input(z.object({ name: z.string() })).query(({ input }) => {
return \`Hello \${input.name}\`;
}),
random: procedure
.input(z.object({ num: z.number() }))
.mutation(({ input }) => {
return Math.floor(Math.random() * 100) / input.num;
}),${
useNextAuth
? `\n secret: protectedProcedure.query(({ ctx }) => {
return \`This is top secret - \${ctx.session.user.name}\`;
}),`
: ""
}
});
`;
};

export default getMainRouter;
52 changes: 52 additions & 0 deletions src/installers/tRPC/utils/getRoot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type IUtil } from "~types";

const getRoot: IUtil = (ctx) => {
return `// @refresh reload
import "./root.css";
import { Suspense } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
Title,
Link,
} from "solid-start";
import { trpc, queryClient${ctx.ssr ? "" : ", client"} } from "~/utils/trpc";
export default function Root() {
return (
<Html lang="en">
<Head>
<Title>Create JD App</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta name="theme-color" content="#026d56" />
<Meta name="description" content="Generated by create-jd-app" />
<Link rel="icon" href="/favicon.ico" />
</Head>
<Body>
<trpc.Provider${
ctx.ssr ? "" : " client={client}"
} queryClient={queryClient}>
<Suspense>
<ErrorBoundary>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
</trpc.Provider>
<Scripts />
</Body>
</Html>
);
}
`;
};

export default getRoot;
34 changes: 34 additions & 0 deletions src/installers/tRPC/utils/getTrpcContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { IUtil } from "~types";

const getTrpcContext: IUtil = (ctx) => {
const usePrisma = ctx.installers.includes("Prisma");
const useNextAuth = ctx.installers.includes("AuthJS");
return `import type { inferAsyncReturnType } from "@trpc/server";
import type { createSolidAPIHandlerContext } from "solid-start-trpc";${
usePrisma ? `\nimport { prisma } from "~/server/db/client";` : ""
}${
useNextAuth
? `\nimport { getSession } from "@auth/solid-start";\nimport { authOpts } from "~/routes/api/auth/[...solidauth]";`
: ""
}
export const createContextInner = async (
opts: createSolidAPIHandlerContext
) => {${
useNextAuth
? `\n const session = await getSession(opts.req, authOpts);`
: ""
}
return {
...opts,${usePrisma ? `\n prisma,` : ""}${
useNextAuth ? `\n session,` : ""
}
};
};
export const createContext = async (opts: createSolidAPIHandlerContext) => {
return await createContextInner(opts);
};
export type IContext = inferAsyncReturnType<typeof createContext>;
`;
};

export default getTrpcContext;
Loading

0 comments on commit 83d5d20

Please sign in to comment.