create-better-t-stack 3.0.12 → 3.1.1

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.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-91MwhQvg.js";
2
+ import { createBtsCli } from "./src-D0KtbO1R.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-91MwhQvg.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-D0KtbO1R.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -347,17 +347,17 @@ function ensureSingleWebAndNative(frontends) {
347
347
  if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
348
348
  if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
349
349
  }
350
- const FULLSTACK_FRONTENDS$1 = ["next"];
350
+ const FULLSTACK_FRONTENDS$1 = ["next", "tanstack-start"];
351
351
  function validateSelfBackendCompatibility(providedFlags, options, config) {
352
352
  const backend = config.backend || options.backend;
353
353
  const frontends = config.frontend || options.frontend || [];
354
354
  if (backend === "self") {
355
355
  const { web, native } = splitFrontends(frontends);
356
- if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.");
356
+ if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) currently only supports Next.js and TanStack Start frontends. Please use --frontend next or --frontend tanstack-start. Support for Nuxt and SvelteKit will be added in a future update.");
357
357
  if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
358
358
  }
359
359
  const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
360
- if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next or choose a different backend. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.");
360
+ if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) currently only supports Next.js and TanStack Start frontends. Please use --frontend next or --frontend tanstack-start or choose a different backend. Support for Nuxt and SvelteKit will be added in a future update.");
361
361
  }
362
362
  function validateWorkersCompatibility(providedFlags, options, config) {
363
363
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
@@ -680,7 +680,7 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
680
680
 
681
681
  //#endregion
682
682
  //#region src/prompts/backend.ts
683
- const FULLSTACK_FRONTENDS = ["next"];
683
+ const FULLSTACK_FRONTENDS = ["next", "tanstack-start"];
684
684
  async function getBackendFrameworkChoice(backendFramework, frontends) {
685
685
  if (backendFramework !== void 0) return backendFramework;
686
686
  const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
@@ -2757,8 +2757,8 @@ async function setupFrontendTemplates(projectDir, context) {
2757
2757
  const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
2758
2758
  if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
2759
2759
  }
2760
- if (context.backend === "self" && reactFramework === "next" && context.api !== "none") {
2761
- const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/next`);
2760
+ if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start") && context.api !== "none") {
2761
+ const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/${reactFramework}`);
2762
2762
  if (await fs.pathExists(apiFullstackDir)) await processAndCopyFiles("**/*", apiFullstackDir, webAppDir, context);
2763
2763
  }
2764
2764
  }
@@ -2956,8 +2956,8 @@ async function setupAuthTemplate(projectDir, context) {
2956
2956
  if (reactFramework) {
2957
2957
  const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
2958
2958
  if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
2959
- if (context.backend === "self" && reactFramework === "next") {
2960
- const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/next`);
2959
+ if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start")) {
2960
+ const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/${reactFramework}`);
2961
2961
  if (await fs.pathExists(authFullstackSrc)) await processAndCopyFiles("**/*", authFullstackSrc, webAppDir, context);
2962
2962
  }
2963
2963
  }
@@ -3102,8 +3102,8 @@ async function setupExamplesTemplate(projectDir, context) {
3102
3102
  if (reactFramework) {
3103
3103
  const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
3104
3104
  if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
3105
- if (context.backend === "self" && reactFramework === "next") {
3106
- const exampleFullstackSrc = path.join(exampleBaseDir, "fullstack/next");
3105
+ if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start")) {
3106
+ const exampleFullstackSrc = path.join(exampleBaseDir, `fullstack/${reactFramework}`);
3107
3107
  if (await fs.pathExists(exampleFullstackSrc)) await processAndCopyFiles("**/*", exampleFullstackSrc, webAppDir, context, false);
3108
3108
  }
3109
3109
  }
@@ -4194,7 +4194,11 @@ function getApiDependencies(api, frontendType) {
4194
4194
  ] };
4195
4195
  else if (api === "trpc") deps.server = { dependencies: ["@trpc/server", "@trpc/client"] };
4196
4196
  if (frontendType.hasReactWeb) {
4197
- if (api === "orpc") deps.web = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
4197
+ if (api === "orpc") deps.web = { dependencies: [
4198
+ "@orpc/tanstack-query",
4199
+ "@orpc/client",
4200
+ "@orpc/server"
4201
+ ] };
4198
4202
  else if (api === "trpc") deps.web = { dependencies: [
4199
4203
  "@trpc/tanstack-react-query",
4200
4204
  "@trpc/client",
@@ -4204,7 +4208,8 @@ function getApiDependencies(api, frontendType) {
4204
4208
  dependencies: [
4205
4209
  "@tanstack/vue-query",
4206
4210
  "@orpc/tanstack-query",
4207
- "@orpc/client"
4211
+ "@orpc/client",
4212
+ "@orpc/server"
4208
4213
  ],
4209
4214
  devDependencies: ["@tanstack/vue-query-devtools"]
4210
4215
  };
@@ -4212,6 +4217,7 @@ function getApiDependencies(api, frontendType) {
4212
4217
  dependencies: [
4213
4218
  "@orpc/tanstack-query",
4214
4219
  "@orpc/client",
4220
+ "@orpc/server",
4215
4221
  "@tanstack/svelte-query"
4216
4222
  ],
4217
4223
  devDependencies: ["@tanstack/svelte-query-devtools"]
@@ -4220,6 +4226,7 @@ function getApiDependencies(api, frontendType) {
4220
4226
  dependencies: [
4221
4227
  "@orpc/tanstack-query",
4222
4228
  "@orpc/client",
4229
+ "@orpc/server",
4223
4230
  "@tanstack/solid-query"
4224
4231
  ],
4225
4232
  devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
@@ -4372,6 +4379,43 @@ async function setupBackendDependencies(config) {
4372
4379
  });
4373
4380
  }
4374
4381
 
4382
+ //#endregion
4383
+ //#region src/utils/better-auth-plugin-setup.ts
4384
+ async function setupBetterAuthPlugins(projectDir, config) {
4385
+ const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
4386
+ const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
4387
+ if (!authIndexFile) return;
4388
+ const pluginsToAdd = [];
4389
+ const importsToAdd = [];
4390
+ if (config.backend === "self" && config.frontend?.includes("tanstack-start")) {
4391
+ pluginsToAdd.push("reactStartCookies()");
4392
+ importsToAdd.push("import { reactStartCookies } from \"better-auth/react-start\";");
4393
+ }
4394
+ if (config.frontend?.includes("native-nativewind") || config.frontend?.includes("native-unistyles")) {
4395
+ pluginsToAdd.push("expo()");
4396
+ importsToAdd.push("import { expo } from \"@better-auth/expo\";");
4397
+ }
4398
+ if (pluginsToAdd.length === 0) return;
4399
+ importsToAdd.forEach((importStatement) => {
4400
+ if (!authIndexFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue().includes(importStatement.split("\"")[1]))) authIndexFile.insertImportDeclaration(0, {
4401
+ moduleSpecifier: importStatement.split("\"")[1],
4402
+ namedImports: [importStatement.split("{")[1].split("}")[0].trim()]
4403
+ });
4404
+ });
4405
+ const betterAuthCall = authIndexFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "betterAuth");
4406
+ if (betterAuthCall) {
4407
+ const configObject = betterAuthCall.getArguments()[0];
4408
+ if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
4409
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
4410
+ const pluginsArray = ensureArrayProperty(objLiteral, "plugins");
4411
+ pluginsToAdd.forEach((plugin) => {
4412
+ pluginsArray.addElement(plugin);
4413
+ });
4414
+ }
4415
+ }
4416
+ authIndexFile.save();
4417
+ }
4418
+
4375
4419
  //#endregion
4376
4420
  //#region src/helpers/core/auth-setup.ts
4377
4421
  async function setupAuth(config) {
@@ -4470,6 +4514,7 @@ async function setupAuth(config) {
4470
4514
  });
4471
4515
  }
4472
4516
  }
4517
+ if (authPackageDirExists && auth === "better-auth") await setupBetterAuthPlugins(projectDir, config);
4473
4518
  } catch (error) {
4474
4519
  consola.error(pc.red("Failed to configure authentication dependencies"));
4475
4520
  if (error instanceof Error) consola.error(pc.red(error.message));
@@ -4489,6 +4534,7 @@ function getClientServerVar(frontend, backend) {
4489
4534
  const hasNextJs = frontend.includes("next");
4490
4535
  const hasNuxt = frontend.includes("nuxt");
4491
4536
  const hasSvelte = frontend.includes("svelte");
4537
+ const hasTanstackStart = frontend.includes("tanstack-start");
4492
4538
  if (backend === "self") return {
4493
4539
  key: "",
4494
4540
  value: "",
@@ -4498,6 +4544,7 @@ function getClientServerVar(frontend, backend) {
4498
4544
  if (hasNextJs) key = "NEXT_PUBLIC_SERVER_URL";
4499
4545
  else if (hasNuxt) key = "NUXT_PUBLIC_SERVER_URL";
4500
4546
  else if (hasSvelte) key = "PUBLIC_SERVER_URL";
4547
+ else if (hasTanstackStart) key = "VITE_SERVER_URL";
4501
4548
  return {
4502
4549
  key,
4503
4550
  value: "http://localhost:3000",
@@ -4508,9 +4555,11 @@ function getConvexVar(frontend) {
4508
4555
  const hasNextJs = frontend.includes("next");
4509
4556
  const hasNuxt = frontend.includes("nuxt");
4510
4557
  const hasSvelte = frontend.includes("svelte");
4558
+ const hasTanstackStart = frontend.includes("tanstack-start");
4511
4559
  if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
4512
4560
  if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
4513
4561
  if (hasSvelte) return "PUBLIC_CONVEX_URL";
4562
+ if (hasTanstackStart) return "VITE_CONVEX_URL";
4514
4563
  return "VITE_CONVEX_URL";
4515
4564
  }
4516
4565
  async function addEnvVariablesToFile(filePath, variables) {
@@ -4625,7 +4674,8 @@ async function setupEnvironmentVariables(config) {
4625
4674
  const nativeDir = path.join(projectDir, "apps/native");
4626
4675
  if (await fs.pathExists(nativeDir)) {
4627
4676
  let envVarName = "EXPO_PUBLIC_SERVER_URL";
4628
- let serverUrl = backend === "self" ? "http://localhost:3001" : "http://localhost:3000";
4677
+ let serverUrl = "http://localhost:3000";
4678
+ if (backend === "self") serverUrl = "http://localhost:3001";
4629
4679
  if (backend === "convex") {
4630
4680
  envVarName = "EXPO_PUBLIC_CONVEX_URL";
4631
4681
  serverUrl = "https://<YOUR_CONVEX_URL>";
@@ -6397,7 +6447,7 @@ async function displayPostInstallInstructions(config) {
6397
6447
  const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
6398
6448
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
6399
6449
  const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
6400
- const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex, isBackendSelf) : "";
6450
+ const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex, isBackendSelf, frontend || []) : "";
6401
6451
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
6402
6452
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
6403
6453
  const clerkInstructions = isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
@@ -6441,9 +6491,9 @@ async function displayPostInstallInstructions(config) {
6441
6491
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
6442
6492
  if (!isConvex && !isBackendSelf) {
6443
6493
  output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
6444
- if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
6494
+ if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
6445
6495
  }
6446
- if (isBackendSelf && api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/rpc/api\n`;
6496
+ if (isBackendSelf && api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/api/rpc/api-reference\n`;
6447
6497
  if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
6448
6498
  if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
6449
6499
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
@@ -6463,7 +6513,7 @@ async function displayPostInstallInstructions(config) {
6463
6513
  output += pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack");
6464
6514
  consola$1.box(output);
6465
6515
  }
6466
- function getNativeInstructions(isConvex, isBackendSelf) {
6516
+ function getNativeInstructions(isConvex, isBackendSelf, _frontend) {
6467
6517
  const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
6468
6518
  const exampleUrl = isConvex ? "https://<YOUR_CONVEX_URL>" : isBackendSelf ? "http://<YOUR_LOCAL_IP>:3001" : "http://<YOUR_LOCAL_IP>:3000";
6469
6519
  const envFileName = ".env";
@@ -6578,28 +6628,34 @@ async function setupWorkspaceDependencies(projectDir, options) {
6578
6628
  projectDir: dbPackageDir
6579
6629
  });
6580
6630
  const authPackageDir = path.join(projectDir, "packages/auth");
6581
- if (await fs.pathExists(authPackageDir)) await addPackageDependency({
6582
- dependencies: commonDeps,
6583
- devDependencies: commonDevDeps,
6584
- customDependencies: { [`@${projectName}/db`]: workspaceVersion },
6585
- projectDir: authPackageDir
6586
- });
6631
+ if (await fs.pathExists(authPackageDir)) {
6632
+ const authDeps = {};
6633
+ if (options.database !== "none" && await fs.pathExists(dbPackageDir)) authDeps[`@${projectName}/db`] = workspaceVersion;
6634
+ await addPackageDependency({
6635
+ dependencies: commonDeps,
6636
+ devDependencies: commonDevDeps,
6637
+ customDependencies: authDeps,
6638
+ projectDir: authPackageDir
6639
+ });
6640
+ }
6587
6641
  const apiPackageDir = path.join(projectDir, "packages/api");
6588
- if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
6589
- dependencies: commonDeps,
6590
- devDependencies: commonDevDeps,
6591
- customDependencies: {
6592
- [`@${projectName}/auth`]: workspaceVersion,
6593
- [`@${projectName}/db`]: workspaceVersion
6594
- },
6595
- projectDir: apiPackageDir
6596
- });
6642
+ if (await fs.pathExists(apiPackageDir)) {
6643
+ const apiDeps = {};
6644
+ if (options.auth !== "none" && await fs.pathExists(authPackageDir)) apiDeps[`@${projectName}/auth`] = workspaceVersion;
6645
+ if (options.database !== "none" && await fs.pathExists(dbPackageDir)) apiDeps[`@${projectName}/db`] = workspaceVersion;
6646
+ await addPackageDependency({
6647
+ dependencies: commonDeps,
6648
+ devDependencies: commonDevDeps,
6649
+ customDependencies: apiDeps,
6650
+ projectDir: apiPackageDir
6651
+ });
6652
+ }
6597
6653
  const serverPackageDir = path.join(projectDir, "apps/server");
6598
6654
  if (await fs.pathExists(serverPackageDir)) {
6599
6655
  const serverDeps = {};
6600
- if (await fs.pathExists(apiPackageDir)) serverDeps[`@${projectName}/api`] = workspaceVersion;
6601
- if (await fs.pathExists(authPackageDir)) serverDeps[`@${projectName}/auth`] = workspaceVersion;
6602
- if (await fs.pathExists(dbPackageDir)) serverDeps[`@${projectName}/db`] = workspaceVersion;
6656
+ if (options.api !== "none" && await fs.pathExists(apiPackageDir)) serverDeps[`@${projectName}/api`] = workspaceVersion;
6657
+ if (options.auth !== "none" && await fs.pathExists(authPackageDir)) serverDeps[`@${projectName}/auth`] = workspaceVersion;
6658
+ if (options.database !== "none" && await fs.pathExists(dbPackageDir)) serverDeps[`@${projectName}/db`] = workspaceVersion;
6603
6659
  await addPackageDependency({
6604
6660
  dependencies: commonDeps,
6605
6661
  devDependencies: commonDevDeps,
@@ -6610,8 +6666,8 @@ async function setupWorkspaceDependencies(projectDir, options) {
6610
6666
  const webPackageDir = path.join(projectDir, "apps/web");
6611
6667
  if (await fs.pathExists(webPackageDir)) {
6612
6668
  const webDeps = {};
6613
- if (await fs.pathExists(apiPackageDir)) webDeps[`@${projectName}/api`] = workspaceVersion;
6614
- if (await fs.pathExists(authPackageDir)) webDeps[`@${projectName}/auth`] = workspaceVersion;
6669
+ if (options.api !== "none" && await fs.pathExists(apiPackageDir)) webDeps[`@${projectName}/api`] = workspaceVersion;
6670
+ if (options.auth !== "none" && await fs.pathExists(authPackageDir)) webDeps[`@${projectName}/auth`] = workspaceVersion;
6615
6671
  if (Object.keys(webDeps).length > 0) await addPackageDependency({
6616
6672
  customDependencies: webDeps,
6617
6673
  projectDir: webPackageDir
@@ -6620,7 +6676,7 @@ async function setupWorkspaceDependencies(projectDir, options) {
6620
6676
  const nativePackageDir = path.join(projectDir, "apps/native");
6621
6677
  if (await fs.pathExists(nativePackageDir)) {
6622
6678
  const nativeDeps = {};
6623
- if (await fs.pathExists(apiPackageDir)) nativeDeps[`@${projectName}/api`] = workspaceVersion;
6679
+ if (options.api !== "none" && await fs.pathExists(apiPackageDir)) nativeDeps[`@${projectName}/api`] = workspaceVersion;
6624
6680
  if (Object.keys(nativeDeps).length > 0) await addPackageDependency({
6625
6681
  customDependencies: nativeDeps,
6626
6682
  projectDir: nativePackageDir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.0.12",
3
+ "version": "3.1.1",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@ async function handleRequest(req: NextRequest) {
35
35
  if (rpcResult.response) return rpcResult.response;
36
36
 
37
37
  const apiResult = await apiHandler.handle(req, {
38
- prefix: "/api/rpc/api",
38
+ prefix: "/api/rpc/api-reference",
39
39
  context: await createContext(req),
40
40
  });
41
41
  if (apiResult.response) return apiResult.response;
@@ -0,0 +1,58 @@
1
+ import { createContext } from "@{{projectName}}/api/context";
2
+ import { appRouter } from "@{{projectName}}/api/routers/index";
3
+ import { OpenAPIHandler } from "@orpc/openapi/fetch";
4
+ import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
5
+ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
6
+ import { RPCHandler } from "@orpc/server/fetch";
7
+ import { onError } from "@orpc/server";
8
+ import { createFileRoute } from "@tanstack/react-router";
9
+
10
+ const rpcHandler = new RPCHandler(appRouter, {
11
+ interceptors: [
12
+ onError((error) => {
13
+ console.error(error);
14
+ }),
15
+ ],
16
+ });
17
+
18
+ const apiHandler = new OpenAPIHandler(appRouter, {
19
+ plugins: [
20
+ new OpenAPIReferencePlugin({
21
+ schemaConverters: [new ZodToJsonSchemaConverter()],
22
+ }),
23
+ ],
24
+ interceptors: [
25
+ onError((error) => {
26
+ console.error(error);
27
+ }),
28
+ ],
29
+ });
30
+
31
+ async function handle({ request }: { request: Request }) {
32
+ const rpcResult = await rpcHandler.handle(request, {
33
+ prefix: "/api/rpc",
34
+ context: await createContext({ req: request }),
35
+ });
36
+ if (rpcResult.response) return rpcResult.response;
37
+
38
+ const apiResult = await apiHandler.handle(request, {
39
+ prefix: "/api/rpc/api-reference",
40
+ context: await createContext({ req: request }),
41
+ });
42
+ if (apiResult.response) return apiResult.response;
43
+
44
+ return new Response("Not found", { status: 404 });
45
+ }
46
+
47
+ export const Route = createFileRoute('/api/rpc/$')({
48
+ server: {
49
+ handlers: {
50
+ HEAD: handle,
51
+ GET: handle,
52
+ POST: handle,
53
+ PUT: handle,
54
+ PATCH: handle,
55
+ DELETE: handle,
56
+ },
57
+ },
58
+ })
@@ -17,6 +17,24 @@ export async function createContext(req: NextRequest) {
17
17
  {{/if}}
18
18
  }
19
19
 
20
+ {{else if (and (eq backend 'self') (includes frontend "tanstack-start"))}}
21
+ {{#if (eq auth "better-auth")}}
22
+ import { auth } from "@{{projectName}}/auth";
23
+ {{/if}}
24
+
25
+ export async function createContext({ req }: { req: Request }) {
26
+ {{#if (eq auth "better-auth")}}
27
+ const session = await auth.api.getSession({
28
+ headers: req.headers,
29
+ });
30
+ return {
31
+ session,
32
+ };
33
+ {{else}}
34
+ return {};
35
+ {{/if}}
36
+ }
37
+
20
38
  {{else if (eq backend 'hono')}}
21
39
  import type { Context as HonoContext } from "hono";
22
40
  {{#if (eq auth "better-auth")}}
@@ -3,7 +3,15 @@ import { RPCLink } from "@orpc/client/fetch";
3
3
  import { createTanstackQueryUtils } from "@orpc/tanstack-query";
4
4
  import { QueryCache, QueryClient } from "@tanstack/react-query";
5
5
  import { toast } from "sonner";
6
+ {{#if (includes frontend "tanstack-start")}}
7
+ import { createRouterClient } from "@orpc/server";
8
+ import type { RouterClient } from "@orpc/server";
9
+ import { createIsomorphicFn } from "@tanstack/react-start";
10
+ import { appRouter } from "@{{projectName}}/api/routers/index";
11
+ import { createContext } from "@{{projectName}}/api/context";
12
+ {{else}}
6
13
  import type { AppRouterClient } from "@{{projectName}}/api/routers/index";
14
+ {{/if}}
7
15
 
8
16
  export const queryClient = new QueryClient({
9
17
  queryCache: new QueryCache({
@@ -20,6 +28,33 @@ export const queryClient = new QueryClient({
20
28
  }),
21
29
  });
22
30
 
31
+ {{#if (includes frontend "tanstack-start")}}
32
+ const getORPCClient = createIsomorphicFn()
33
+ .server(() =>
34
+ createRouterClient(appRouter, {
35
+ context: async ({ req }) => {
36
+ return createContext({ req });
37
+ },
38
+ }),
39
+ )
40
+ .client((): RouterClient<typeof appRouter> => {
41
+ const link = new RPCLink({
42
+ url: {{#if (eq backend "self")}}`${window.location.origin}/api/rpc`{{else}}`${import.meta.env.VITE_SERVER_URL}/rpc`{{/if}},
43
+ {{#if (eq auth "better-auth")}}
44
+ fetch(url, options) {
45
+ return fetch(url, {
46
+ ...options,
47
+ credentials: "include",
48
+ });
49
+ },
50
+ {{/if}}
51
+ });
52
+
53
+ return createORPCClient(link);
54
+ });
55
+
56
+ export const client: RouterClient<typeof appRouter> = getORPCClient();
57
+ {{else}}
23
58
  export const link = new RPCLink({
24
59
  {{#if (and (eq backend "self") (includes frontend "next"))}}
25
60
  url: `${typeof window !== "undefined" ? window.location.origin : "http://localhost:3001"}/api/rpc`,
@@ -49,5 +84,6 @@ export const link = new RPCLink({
49
84
  });
50
85
 
51
86
  export const client: AppRouterClient = createORPCClient(link)
87
+ {{/if}}
52
88
 
53
89
  export const orpc = createTanstackQueryUtils(client)
@@ -0,0 +1,22 @@
1
+ import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
2
+ import { appRouter } from '@{{projectName}}/api/routers/index'
3
+ import { createContext } from '@{{projectName}}/api/context'
4
+ import { createFileRoute } from '@tanstack/react-router'
5
+
6
+ function handler({ request }: { request: Request }) {
7
+ return fetchRequestHandler({
8
+ req: request,
9
+ router: appRouter,
10
+ createContext,
11
+ endpoint: '/api/trpc',
12
+ })
13
+ }
14
+
15
+ export const Route = createFileRoute('/api/trpc/$')({
16
+ server: {
17
+ handlers: {
18
+ GET: handler,
19
+ POST: handler,
20
+ },
21
+ },
22
+ })
@@ -20,6 +20,27 @@ export async function createContext(req: NextRequest) {
20
20
  {{/if}}
21
21
  }
22
22
 
23
+ {{else if (and (eq backend 'self') (includes frontend "tanstack-start"))}}
24
+ {{#if (eq auth "better-auth")}}
25
+ import { auth } from "@{{projectName}}/auth";
26
+ {{/if}}
27
+
28
+ export async function createContext({ req }: { req: Request }) {
29
+ {{#if (eq auth "better-auth")}}
30
+ const session = await auth.api.getSession({
31
+ headers: req.headers,
32
+ });
33
+ return {
34
+ session,
35
+ };
36
+ {{else}}
37
+ // No auth configured
38
+ return {
39
+ session: null,
40
+ };
41
+ {{/if}}
42
+ }
43
+
23
44
  {{else if (eq backend 'hono')}}
24
45
  import type { Context as HonoContext } from "hono";
25
46
  {{#if (eq auth "better-auth")}}
@@ -0,0 +1,15 @@
1
+ import { auth } from '@{{projectName}}/auth'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+
4
+ export const Route = createFileRoute('/api/auth/$')({
5
+ server: {
6
+ handlers: {
7
+ GET: ({ request }) => {
8
+ return auth.handler(request)
9
+ },
10
+ POST: ({ request }) => {
11
+ return auth.handler(request)
12
+ },
13
+ },
14
+ },
15
+ })
@@ -1,9 +1,6 @@
1
1
  {{#if (eq orm "prisma")}}
2
2
  import { betterAuth } from "better-auth";
3
3
  import { prismaAdapter } from "better-auth/adapters/prisma";
4
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
5
- import { expo } from "@better-auth/expo";
6
- {{/if}}
7
4
  {{#if (eq payments "polar")}}
8
5
  import { polar, checkout, portal } from "@polar-sh/better-auth";
9
6
  import { polarClient } from "./lib/payments";
@@ -26,6 +23,7 @@ export const auth = betterAuth({
26
23
  emailAndPassword: {
27
24
  enabled: true,
28
25
  },
26
+ {{#if (ne backend "self")}}
29
27
  advanced: {
30
28
  defaultCookieAttributes: {
31
29
  sameSite: "none",
@@ -33,6 +31,7 @@ export const auth = betterAuth({
33
31
  httpOnly: true,
34
32
  },
35
33
  },
34
+ {{/if}}
36
35
  {{#if (eq payments "polar")}}
37
36
  plugins: [
38
37
  polar({
@@ -53,14 +52,7 @@ export const auth = betterAuth({
53
52
  portal(),
54
53
  ],
55
54
  }),
56
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
57
- expo(),
58
- {{/if}}
59
55
  ],
60
- {{else}}
61
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
62
- plugins: [expo()],
63
- {{/if}}
64
56
  {{/if}}
65
57
  });
66
58
  {{/if}}
@@ -69,9 +61,6 @@ export const auth = betterAuth({
69
61
  {{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "none"))}}
70
62
  import { betterAuth } from "better-auth";
71
63
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
72
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
73
- import { expo } from "@better-auth/expo";
74
- {{/if}}
75
64
  {{#if (eq payments "polar")}}
76
65
  import { polar, checkout, portal } from "@polar-sh/better-auth";
77
66
  import { polarClient } from "./lib/payments";
@@ -95,6 +84,7 @@ export const auth = betterAuth({
95
84
  emailAndPassword: {
96
85
  enabled: true,
97
86
  },
87
+ {{#if (ne backend "self")}}
98
88
  advanced: {
99
89
  defaultCookieAttributes: {
100
90
  sameSite: "none",
@@ -102,6 +92,7 @@ export const auth = betterAuth({
102
92
  httpOnly: true,
103
93
  },
104
94
  },
95
+ {{/if}}
105
96
  {{#if (eq payments "polar")}}
106
97
  plugins: [
107
98
  polar({
@@ -122,14 +113,7 @@ export const auth = betterAuth({
122
113
  portal(),
123
114
  ],
124
115
  }),
125
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
126
- expo(),
127
- {{/if}}
128
116
  ],
129
- {{else}}
130
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
131
- plugins: [expo()],
132
- {{/if}}
133
117
  {{/if}}
134
118
  });
135
119
  {{/if}}
@@ -137,9 +121,6 @@ export const auth = betterAuth({
137
121
  {{#if (eq runtime "workers")}}
138
122
  import { betterAuth } from "better-auth";
139
123
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
140
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
141
- import { expo } from "@better-auth/expo";
142
- {{/if}}
143
124
  {{#if (eq payments "polar")}}
144
125
  import { polar, checkout, portal } from "@polar-sh/better-auth";
145
126
  import { polarClient } from "./lib/payments";
@@ -207,10 +188,6 @@ export const auth = betterAuth({
207
188
  ],
208
189
  }),
209
190
  ],
210
- {{else}}
211
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
212
- plugins: [expo()],
213
- {{/if}}
214
191
  {{/if}}
215
192
  });
216
193
  {{/if}}
@@ -219,9 +196,6 @@ export const auth = betterAuth({
219
196
  {{#if (eq orm "mongoose")}}
220
197
  import { betterAuth } from "better-auth";
221
198
  import { mongodbAdapter } from "better-auth/adapters/mongodb";
222
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
223
- import { expo } from "@better-auth/expo";
224
- {{/if}}
225
199
  {{#if (eq payments "polar")}}
226
200
  import { polar, checkout, portal } from "@polar-sh/better-auth";
227
201
  import { polarClient } from "./lib/payments";
@@ -239,6 +213,7 @@ export const auth = betterAuth({
239
213
  emailAndPassword: {
240
214
  enabled: true,
241
215
  },
216
+ {{#if (ne backend "self")}}
242
217
  advanced: {
243
218
  defaultCookieAttributes: {
244
219
  sameSite: "none",
@@ -246,6 +221,7 @@ export const auth = betterAuth({
246
221
  httpOnly: true,
247
222
  },
248
223
  },
224
+ {{/if}}
249
225
  {{#if (eq payments "polar")}}
250
226
  plugins: [
251
227
  polar({
@@ -266,23 +242,13 @@ export const auth = betterAuth({
266
242
  portal(),
267
243
  ],
268
244
  }),
269
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
270
- expo(),
271
- {{/if}}
272
245
  ],
273
- {{else}}
274
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
275
- plugins: [expo()],
276
- {{/if}}
277
246
  {{/if}}
278
247
  });
279
248
  {{/if}}
280
249
 
281
250
  {{#if (eq orm "none")}}
282
251
  import { betterAuth } from "better-auth";
283
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
284
- import { expo } from "@better-auth/expo";
285
- {{/if}}
286
252
  {{#if (eq payments "polar")}}
287
253
  import { polar, checkout, portal } from "@polar-sh/better-auth";
288
254
  import { polarClient } from "./lib/payments";
@@ -299,6 +265,7 @@ export const auth = betterAuth({
299
265
  emailAndPassword: {
300
266
  enabled: true,
301
267
  },
268
+ {{#if (ne backend "self")}}
302
269
  advanced: {
303
270
  defaultCookieAttributes: {
304
271
  sameSite: "none",
@@ -306,6 +273,7 @@ export const auth = betterAuth({
306
273
  httpOnly: true,
307
274
  },
308
275
  },
276
+ {{/if}}
309
277
  {{#if (eq payments "polar")}}
310
278
  plugins: [
311
279
  polar({
@@ -326,14 +294,7 @@ export const auth = betterAuth({
326
294
  portal(),
327
295
  ],
328
296
  }),
329
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
330
- expo(),
331
- {{/if}}
332
297
  ],
333
- {{else}}
334
- {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
335
- plugins: [expo()],
336
- {{/if}}
337
298
  {{/if}}
338
299
  });
339
- {{/if}}
300
+ {{/if}}
@@ -82,7 +82,7 @@ const app = new Elysia()
82
82
  })
83
83
  .all('/api*', async (context) => {
84
84
  const { response } = await apiHandler.handle(context.request, {
85
- prefix: '/api',
85
+ prefix: '/api-reference',
86
86
  context: await createContext({ context })
87
87
  })
88
88
  return response ?? new Response('Not Found', { status: 404 })
@@ -86,7 +86,7 @@ app.use(async (req, res, next) => {
86
86
  if (rpcResult.matched) return;
87
87
 
88
88
  const apiResult = await apiHandler.handle(req, res, {
89
- prefix: "/api",
89
+ prefix: "/api-reference",
90
90
  {{#if (eq auth "better-auth")}}
91
91
  context: await createContext({ req }),
92
92
  {{else}}
@@ -87,7 +87,7 @@ const fastify = Fastify({
87
87
 
88
88
  const apiResult = await apiHandler.handle(req, res, {
89
89
  context: await createContext(req.headers),
90
- prefix: "/api",
90
+ prefix: "/api-reference",
91
91
  });
92
92
 
93
93
  if (apiResult.matched) {
@@ -92,7 +92,7 @@ app.use("/*", async (c, next) => {
92
92
  }
93
93
 
94
94
  const apiResult = await apiHandler.handle(c.req.raw, {
95
- prefix: "/api",
95
+ prefix: "/api-reference",
96
96
  context: context,
97
97
  });
98
98
 
@@ -0,0 +1,31 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { google } from "@ai-sdk/google";
3
+ import { streamText, type UIMessage, convertToModelMessages } from "ai";
4
+
5
+ export const Route = createFileRoute("/api/ai/$")({
6
+ server: {
7
+ handlers: {
8
+ POST: async ({ request }) => {
9
+ try {
10
+ const { messages }: { messages: UIMessage[] } = await request.json();
11
+
12
+ const result = streamText({
13
+ model: google("gemini-2.5-flash"),
14
+ messages: convertToModelMessages(messages),
15
+ });
16
+
17
+ return result.toUIMessageStreamResponse();
18
+ } catch (error) {
19
+ console.error("AI API error:", error);
20
+ return new Response(
21
+ JSON.stringify({ error: "Failed to process AI request" }),
22
+ {
23
+ status: 500,
24
+ headers: { "Content-Type": "application/json" },
25
+ },
26
+ );
27
+ }
28
+ },
29
+ },
30
+ },
31
+ });
@@ -15,7 +15,7 @@ function RouteComponent() {
15
15
  const [input, setInput] = useState("");
16
16
  const { messages, sendMessage } = useChat({
17
17
  transport: new DefaultChatTransport({
18
- api: `${import.meta.env.VITE_SERVER_URL}/ai`,
18
+ api: {{#if (eq backend "self")}}"/api/ai"{{else}}`${import.meta.env.VITE_SERVER_URL}/ai`{{/if}},
19
19
  }),
20
20
  });
21
21
 
@@ -85,7 +85,7 @@ export const queryClient = new QueryClient({
85
85
  const trpcClient = createTRPCClient<AppRouter>({
86
86
  links: [
87
87
  httpBatchLink({
88
- url: `${import.meta.env.VITE_SERVER_URL}/trpc`,
88
+ url: {{#if (eq backend "self")}}"/api/trpc"{{else}}`${import.meta.env.VITE_SERVER_URL}/trpc`{{/if}},
89
89
  {{#if (eq auth "better-auth")}}
90
90
  fetch(url, options) {
91
91
  return fetch(url, {