create-better-t-stack 2.40.3 → 2.40.4-canary.49f23d48

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.
Files changed (27) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +1 -1
  4. package/dist/{src-BOnnM-3r.js → src-D32Mc1TH.js} +200 -60
  5. package/package.json +1 -1
  6. package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +32 -3
  7. package/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs +1 -1
  8. package/templates/backend/server/elysia/src/index.ts.hbs +31 -2
  9. package/templates/backend/server/express/src/index.ts.hbs +38 -4
  10. package/templates/backend/server/fastify/src/index.ts.hbs +33 -2
  11. package/templates/backend/server/hono/src/index.ts.hbs +40 -5
  12. package/templates/db/drizzle/mysql/src/db/index.ts.hbs +25 -0
  13. package/templates/db/prisma/mongodb/src/db/index.ts.hbs +5 -0
  14. package/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs +6 -0
  15. package/templates/db/prisma/mysql/src/db/index.ts.hbs +12 -0
  16. package/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs +3 -0
  17. package/templates/db/prisma/postgres/src/db/index.ts.hbs +5 -0
  18. package/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs +3 -0
  19. package/templates/db/prisma/sqlite/prisma.config.ts.hbs +18 -1
  20. package/templates/db/prisma/sqlite/src/db/index.ts.hbs +16 -0
  21. package/templates/deploy/alchemy/alchemy.run.ts.hbs +4 -0
  22. package/templates/deploy/wrangler/server/wrangler.jsonc.hbs +5 -0
  23. package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +2 -2
  24. package/templates/db/prisma/mongodb/prisma/index.ts.hbs +0 -5
  25. package/templates/db/prisma/mysql/prisma/index.ts +0 -5
  26. package/templates/db/prisma/postgres/prisma/index.ts +0 -5
  27. package/templates/db/prisma/sqlite/prisma/index.ts +0 -5
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-BOnnM-3r.js";
2
+ import { createBtsCli } from "./src-D32Mc1TH.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.d.ts CHANGED
@@ -80,6 +80,7 @@ declare const DatabaseSetupSchema: z.ZodEnum<{
80
80
  turso: "turso";
81
81
  neon: "neon";
82
82
  "prisma-postgres": "prisma-postgres";
83
+ planetscale: "planetscale";
83
84
  "mongodb-atlas": "mongodb-atlas";
84
85
  supabase: "supabase";
85
86
  d1: "d1";
@@ -218,7 +219,7 @@ declare const router: trpcServer.TRPCBuiltRouter<{
218
219
  git?: boolean | undefined;
219
220
  packageManager?: "npm" | "pnpm" | "bun" | undefined;
220
221
  install?: boolean | undefined;
221
- dbSetup?: "none" | "turso" | "neon" | "prisma-postgres" | "mongodb-atlas" | "supabase" | "d1" | "docker" | undefined;
222
+ dbSetup?: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "d1" | "docker" | undefined;
222
223
  backend?: "none" | "next" | "hono" | "express" | "fastify" | "elysia" | "convex" | undefined;
223
224
  runtime?: "none" | "bun" | "node" | "workers" | undefined;
224
225
  api?: "none" | "trpc" | "orpc" | undefined;
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-BOnnM-3r.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-D32Mc1TH.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -67,6 +67,7 @@ const dependencyVersionMap = {
67
67
  "@clerk/clerk-expo": "^2.14.25",
68
68
  "drizzle-orm": "^0.44.2",
69
69
  "drizzle-kit": "^0.31.2",
70
+ "@planetscale/database": "^1.19.0",
70
71
  "@libsql/client": "^0.15.9",
71
72
  "@neondatabase/serverless": "^1.0.1",
72
73
  pg: "^8.14.1",
@@ -76,7 +77,10 @@ const dependencyVersionMap = {
76
77
  mysql2: "^3.14.0",
77
78
  "@prisma/client": "^6.15.0",
78
79
  prisma: "^6.15.0",
80
+ "@prisma/adapter-d1": "^6.15.0",
79
81
  "@prisma/extension-accelerate": "^2.0.2",
82
+ "@prisma/adapter-planetscale": "^6.15.0",
83
+ undici: "^7.15.0",
80
84
  mongoose: "^8.14.0",
81
85
  "vite-plugin-pwa": "^1.0.1",
82
86
  "@vite-pwa/assets-generator": "^1.0.0",
@@ -110,6 +114,8 @@ const dependencyVersionMap = {
110
114
  streamdown: "^1.1.6",
111
115
  "@orpc/server": "^1.8.6",
112
116
  "@orpc/client": "^1.8.6",
117
+ "@orpc/openapi": "^1.8.6",
118
+ "@orpc/zod": "^1.8.6",
113
119
  "@orpc/tanstack-query": "^1.8.6",
114
120
  "@trpc/tanstack-react-query": "^11.5.0",
115
121
  "@trpc/server": "^11.5.0",
@@ -134,7 +140,7 @@ const dependencyVersionMap = {
134
140
  "nitro-cloudflare-dev": "^0.2.2",
135
141
  "@sveltejs/adapter-cloudflare": "^7.2.1",
136
142
  "@cloudflare/workers-types": "^4.20250822.0",
137
- alchemy: "^0.63.0",
143
+ alchemy: "^0.65.0",
138
144
  nitropack: "^2.12.4",
139
145
  dotenv: "^17.2.1"
140
146
  };
@@ -233,6 +239,7 @@ const DatabaseSetupSchema = z.enum([
233
239
  "turso",
234
240
  "neon",
235
241
  "prisma-postgres",
242
+ "planetscale",
236
243
  "mongodb-atlas",
237
244
  "supabase",
238
245
  "d1",
@@ -496,8 +503,6 @@ function ensureSingleWebAndNative(frontends) {
496
503
  function validateWorkersCompatibility(providedFlags, options, config) {
497
504
  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.`);
498
505
  if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
499
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.orm && config.orm !== "drizzle" && config.orm !== "none") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
500
- if (providedFlags.has("orm") && config.orm && config.orm !== "drizzle" && config.orm !== "none" && config.runtime === "workers") exitWithError(`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`);
501
506
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
502
507
  if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
503
508
  if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.");
@@ -739,11 +744,10 @@ async function getDatabaseChoice(database, backend, runtime) {
739
744
 
740
745
  //#endregion
741
746
  //#region src/prompts/database-setup.ts
742
- async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
747
+ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
743
748
  if (backend === "convex") return "none";
744
749
  if (dbSetup !== void 0) return dbSetup;
745
750
  if (databaseType === "none") return "none";
746
- if (databaseType === "sqlite" && orm === "prisma") return "none";
747
751
  let options = [];
748
752
  if (databaseType === "sqlite") options = [
749
753
  {
@@ -768,6 +772,11 @@ async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
768
772
  label: "Neon Postgres",
769
773
  hint: "Serverless Postgres with branching capability"
770
774
  },
775
+ {
776
+ value: "planetscale",
777
+ label: "PlanetScale",
778
+ hint: "Serverless MySQL platform with branching (Postgres compatible)"
779
+ },
771
780
  {
772
781
  value: "supabase",
773
782
  label: "Supabase",
@@ -789,15 +798,23 @@ async function getDBSetupChoice(databaseType, dbSetup, orm, backend, runtime) {
789
798
  hint: "Manual setup"
790
799
  }
791
800
  ];
792
- else if (databaseType === "mysql") options = [{
793
- value: "docker",
794
- label: "Docker",
795
- hint: "Run locally with docker compose"
796
- }, {
797
- value: "none",
798
- label: "None",
799
- hint: "Manual setup"
800
- }];
801
+ else if (databaseType === "mysql") options = [
802
+ {
803
+ value: "planetscale",
804
+ label: "PlanetScale",
805
+ hint: "Serverless MySQL platform with branching"
806
+ },
807
+ {
808
+ value: "docker",
809
+ label: "Docker",
810
+ hint: "Run locally with docker compose"
811
+ },
812
+ {
813
+ value: "none",
814
+ label: "None",
815
+ hint: "Manual setup"
816
+ }
817
+ ];
801
818
  else if (databaseType === "mongodb") options = [
802
819
  {
803
820
  value: "mongodb-atlas",
@@ -995,12 +1012,11 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
995
1012
  if (backend === "convex") return "none";
996
1013
  if (!hasDatabase) return "none";
997
1014
  if (orm !== void 0) return orm;
998
- if (runtime === "workers") return "drizzle";
999
1015
  const options = [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]];
1000
1016
  const response = await select({
1001
1017
  message: "Select ORM",
1002
1018
  options,
1003
- initialValue: database === "mongodb" ? "prisma" : DEFAULT_CONFIG.orm
1019
+ initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
1004
1020
  });
1005
1021
  if (isCancel(response)) return exitCancelled("Operation cancelled");
1006
1022
  return response;
@@ -1333,7 +1349,7 @@ const getLatestCLIVersion = () => {
1333
1349
  */
1334
1350
  function isTelemetryEnabled() {
1335
1351
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1336
- const BTS_TELEMETRY = "1";
1352
+ const BTS_TELEMETRY = "0";
1337
1353
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1338
1354
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1339
1355
  return true;
@@ -1341,8 +1357,8 @@ function isTelemetryEnabled() {
1341
1357
 
1342
1358
  //#endregion
1343
1359
  //#region src/utils/analytics.ts
1344
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1345
- const POSTHOG_HOST = "https://us.i.posthog.com";
1360
+ const POSTHOG_API_KEY = "random";
1361
+ const POSTHOG_HOST = "random";
1346
1362
  function generateSessionId() {
1347
1363
  const rand = Math.random().toString(36).slice(2);
1348
1364
  const now = Date.now().toString(36);
@@ -1654,6 +1670,7 @@ function validateDatabaseSetup(config, providedFlags) {
1654
1670
  database: "postgres",
1655
1671
  errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
1656
1672
  },
1673
+ planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
1657
1674
  "mongodb-atlas": {
1658
1675
  database: "mongodb",
1659
1676
  errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
@@ -1672,7 +1689,9 @@ function validateDatabaseSetup(config, providedFlags) {
1672
1689
  };
1673
1690
  if (dbSetup && dbSetup !== "none") {
1674
1691
  const validation = setupValidations[dbSetup];
1675
- if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
1692
+ if (dbSetup === "planetscale") {
1693
+ if (database !== "postgres" && database !== "mysql") exitWithError(validation.errorMessage);
1694
+ } else if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
1676
1695
  if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
1677
1696
  if (dbSetup === "docker") {
1678
1697
  if (database === "sqlite") exitWithError("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
@@ -3047,8 +3066,11 @@ async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
3047
3066
  const s = spinner();
3048
3067
  try {
3049
3068
  s.start("Generating Cloudflare Workers types...");
3050
- const runCmd = packageManager === "npm" ? "npm" : packageManager;
3051
- await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
3069
+ const runCmd = getPackageExecutionCommand(packageManager, "wrangler types --env-interface CloudflareBindings");
3070
+ await execa(runCmd, {
3071
+ cwd: serverDir,
3072
+ shell: true
3073
+ });
3052
3074
  s.stop("Cloudflare Workers types generated successfully!");
3053
3075
  } catch {
3054
3076
  s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
@@ -3096,8 +3118,7 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
3096
3118
  if (!options?.skipAppScripts) pkg.scripts = {
3097
3119
  ...pkg.scripts,
3098
3120
  deploy: "alchemy deploy",
3099
- destroy: "alchemy destroy",
3100
- dev: "alchemy dev"
3121
+ destroy: "alchemy destroy"
3101
3122
  };
3102
3123
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3103
3124
  }
@@ -3122,8 +3143,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
3122
3143
  if (!options?.skipAppScripts) pkg.scripts = {
3123
3144
  ...pkg.scripts,
3124
3145
  deploy: "alchemy deploy",
3125
- destroy: "alchemy destroy",
3126
- dev: "alchemy dev"
3146
+ destroy: "alchemy destroy"
3127
3147
  };
3128
3148
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3129
3149
  }
@@ -3186,8 +3206,7 @@ async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, option
3186
3206
  if (!options?.skipAppScripts) pkg.scripts = {
3187
3207
  ...pkg.scripts,
3188
3208
  deploy: "alchemy deploy",
3189
- destroy: "alchemy destroy",
3190
- dev: "alchemy dev"
3209
+ destroy: "alchemy destroy"
3191
3210
  };
3192
3211
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3193
3212
  }
@@ -3208,8 +3227,7 @@ async function setupSolidAlchemyDeploy(projectDir, _packageManager, options) {
3208
3227
  if (!options?.skipAppScripts) pkg.scripts = {
3209
3228
  ...pkg.scripts,
3210
3229
  deploy: "alchemy deploy",
3211
- destroy: "alchemy destroy",
3212
- dev: "alchemy dev"
3230
+ destroy: "alchemy destroy"
3213
3231
  };
3214
3232
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3215
3233
  }
@@ -3234,8 +3252,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
3234
3252
  if (!options?.skipAppScripts) pkg.scripts = {
3235
3253
  ...pkg.scripts,
3236
3254
  deploy: "alchemy deploy",
3237
- destroy: "alchemy destroy",
3238
- dev: "alchemy dev"
3255
+ destroy: "alchemy destroy"
3239
3256
  };
3240
3257
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3241
3258
  }
@@ -3301,8 +3318,7 @@ async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, opt
3301
3318
  if (!options?.skipAppScripts) pkg.scripts = {
3302
3319
  ...pkg.scripts,
3303
3320
  deploy: "alchemy deploy",
3304
- destroy: "alchemy destroy",
3305
- dev: "alchemy dev"
3321
+ destroy: "alchemy destroy"
3306
3322
  };
3307
3323
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3308
3324
  }
@@ -3327,8 +3343,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
3327
3343
  if (!options?.skipAppScripts) pkg.scripts = {
3328
3344
  ...pkg.scripts,
3329
3345
  deploy: "alchemy deploy",
3330
- destroy: "alchemy destroy",
3331
- dev: "alchemy dev"
3346
+ destroy: "alchemy destroy"
3332
3347
  };
3333
3348
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
3334
3349
  }
@@ -3892,7 +3907,12 @@ function getFrontendType(frontend) {
3892
3907
  }
3893
3908
  function getApiDependencies(api, frontendType) {
3894
3909
  const deps = {};
3895
- if (api === "orpc") deps.server = { dependencies: ["@orpc/server", "@orpc/client"] };
3910
+ if (api === "orpc") deps.server = { dependencies: [
3911
+ "@orpc/server",
3912
+ "@orpc/client",
3913
+ "@orpc/openapi",
3914
+ "@orpc/zod"
3915
+ ] };
3896
3916
  else if (api === "trpc") deps.server = { dependencies: ["@trpc/server", "@trpc/client"] };
3897
3917
  if (frontendType.hasReactWeb) {
3898
3918
  if (api === "orpc") deps.web = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
@@ -4377,7 +4397,7 @@ async function setupEnvironmentVariables(config) {
4377
4397
  //#endregion
4378
4398
  //#region src/helpers/database-providers/d1-setup.ts
4379
4399
  async function setupCloudflareD1(config) {
4380
- const { projectDir, serverDeploy } = config;
4400
+ const { projectDir, serverDeploy, orm } = config;
4381
4401
  if (serverDeploy === "wrangler") {
4382
4402
  const envPath = path.join(projectDir, "apps/server", ".env");
4383
4403
  const variables = [
@@ -4401,6 +4421,22 @@ async function setupCloudflareD1(config) {
4401
4421
  await addEnvVariablesToFile(envPath, variables);
4402
4422
  } catch (_err) {}
4403
4423
  }
4424
+ if ((serverDeploy === "wrangler" || serverDeploy === "alchemy") && orm === "prisma") {
4425
+ const envPath = path.join(projectDir, "apps/server", ".env");
4426
+ const variables = [{
4427
+ key: "DATABASE_URL",
4428
+ value: "file:./local.db",
4429
+ condition: true
4430
+ }];
4431
+ try {
4432
+ await addEnvVariablesToFile(envPath, variables);
4433
+ } catch (_err) {}
4434
+ const serverDir = path.join(projectDir, "apps/server");
4435
+ await addPackageDependency({
4436
+ dependencies: ["@prisma/adapter-d1"],
4437
+ projectDir: serverDir
4438
+ });
4439
+ }
4404
4440
  }
4405
4441
 
4406
4442
  //#endregion
@@ -4706,6 +4742,71 @@ async function setupNeonPostgres(config) {
4706
4742
  }
4707
4743
  }
4708
4744
 
4745
+ //#endregion
4746
+ //#region src/helpers/database-providers/planetscale-setup.ts
4747
+ async function setupPlanetScale(config) {
4748
+ const { projectDir, database, orm } = config;
4749
+ const envPath = path.join(projectDir, "apps/server", ".env");
4750
+ if (database === "mysql" && orm === "drizzle") {
4751
+ const variables = [
4752
+ {
4753
+ key: "# enable foreign key constraints in database settings",
4754
+ value: "",
4755
+ condition: true
4756
+ },
4757
+ {
4758
+ key: "DATABASE_URL",
4759
+ value: "mysql://username:password@host/database?ssl={\"rejectUnauthorized\":true}",
4760
+ condition: true
4761
+ },
4762
+ {
4763
+ key: "DATABASE_HOST",
4764
+ value: "",
4765
+ condition: true
4766
+ },
4767
+ {
4768
+ key: "DATABASE_USERNAME",
4769
+ value: "",
4770
+ condition: true
4771
+ },
4772
+ {
4773
+ key: "DATABASE_PASSWORD",
4774
+ value: "",
4775
+ condition: true
4776
+ }
4777
+ ];
4778
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4779
+ await addEnvVariablesToFile(envPath, variables);
4780
+ }
4781
+ if (database === "postgres" && orm === "prisma") {
4782
+ const variables = [{
4783
+ key: "DATABASE_URL",
4784
+ value: "postgresql://username:password@host/database?sslaccept=strict",
4785
+ condition: true
4786
+ }];
4787
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4788
+ await addEnvVariablesToFile(envPath, variables);
4789
+ }
4790
+ if (database === "postgres" && orm === "drizzle") {
4791
+ const variables = [{
4792
+ key: "DATABASE_URL",
4793
+ value: "postgresql://username:password@host/database?sslmode=verify-full",
4794
+ condition: true
4795
+ }];
4796
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4797
+ await addEnvVariablesToFile(envPath, variables);
4798
+ }
4799
+ if (database === "mysql" && orm === "prisma") {
4800
+ const variables = [{
4801
+ key: "DATABASE_URL",
4802
+ value: "mysql://username:password@host/database?sslaccept=strict",
4803
+ condition: true
4804
+ }];
4805
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
4806
+ await addEnvVariablesToFile(envPath, variables);
4807
+ }
4808
+ }
4809
+
4709
4810
  //#endregion
4710
4811
  //#region src/helpers/database-providers/prisma-postgres-setup.ts
4711
4812
  const AVAILABLE_REGIONS = [
@@ -5254,7 +5355,16 @@ async function setupDatabase(config) {
5254
5355
  const serverDir = path.join(projectDir, "apps/server");
5255
5356
  if (!await fs.pathExists(serverDir)) return;
5256
5357
  try {
5257
- if (orm === "prisma") await addPackageDependency({
5358
+ if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
5359
+ dependencies: [
5360
+ "@prisma/client",
5361
+ "@prisma/adapter-planetscale",
5362
+ "undici"
5363
+ ],
5364
+ devDependencies: ["prisma"],
5365
+ projectDir: serverDir
5366
+ });
5367
+ else await addPackageDependency({
5258
5368
  dependencies: ["@prisma/client"],
5259
5369
  devDependencies: ["prisma"],
5260
5370
  projectDir: serverDir
@@ -5274,12 +5384,22 @@ async function setupDatabase(config) {
5274
5384
  devDependencies: ["drizzle-kit", "@types/ws"],
5275
5385
  projectDir: serverDir
5276
5386
  });
5387
+ else if (dbSetup === "planetscale") await addPackageDependency({
5388
+ dependencies: ["drizzle-orm", "pg"],
5389
+ devDependencies: ["drizzle-kit", "@types/pg"],
5390
+ projectDir: serverDir
5391
+ });
5277
5392
  else await addPackageDependency({
5278
5393
  dependencies: ["drizzle-orm", "pg"],
5279
5394
  devDependencies: ["drizzle-kit", "@types/pg"],
5280
5395
  projectDir: serverDir
5281
5396
  });
5282
- else if (database === "mysql") await addPackageDependency({
5397
+ else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
5398
+ dependencies: ["drizzle-orm", "@planetscale/database"],
5399
+ devDependencies: ["drizzle-kit"],
5400
+ projectDir: serverDir
5401
+ });
5402
+ else await addPackageDependency({
5283
5403
  dependencies: ["drizzle-orm", "mysql2"],
5284
5404
  devDependencies: ["drizzle-kit"],
5285
5405
  projectDir: serverDir
@@ -5295,7 +5415,10 @@ async function setupDatabase(config) {
5295
5415
  else if (database === "postgres") {
5296
5416
  if (dbSetup === "prisma-postgres") await setupPrismaPostgres(config);
5297
5417
  else if (dbSetup === "neon") await setupNeonPostgres(config);
5418
+ else if (dbSetup === "planetscale") await setupPlanetScale(config);
5298
5419
  else if (dbSetup === "supabase") await setupSupabase(config);
5420
+ } else if (database === "mysql") {
5421
+ if (dbSetup === "planetscale") await setupPlanetScale(config);
5299
5422
  } else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config);
5300
5423
  } catch (error) {
5301
5424
  s.stop(pc.red("Failed to set up database"));
@@ -5740,7 +5863,7 @@ async function getDockerStatus(database) {
5740
5863
  //#endregion
5741
5864
  //#region src/helpers/core/post-installation.ts
5742
5865
  async function displayPostInstallInstructions(config) {
5743
- const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy } = config;
5866
+ const { api, database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy } = config;
5744
5867
  const isConvex = backend === "convex";
5745
5868
  const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
5746
5869
  const cdCmd = `cd ${relativePath}`;
@@ -5788,7 +5911,11 @@ async function displayPostInstallInstructions(config) {
5788
5911
  output += `${pc.bold("Your project will be available at:")}\n`;
5789
5912
  if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
5790
5913
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
5791
- if (!isConvex) output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
5914
+ if (!isConvex) {
5915
+ output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
5916
+ if (api === "orpc") if (backend === "next") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
5917
+ else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
5918
+ }
5792
5919
  if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
5793
5920
  if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
5794
5921
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
@@ -5837,7 +5964,13 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
5837
5964
  instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
5838
5965
  instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
5839
5966
  }
5840
- if (dbSetup === "d1" && serverDeploy === "alchemy") instructions.push(`${pc.yellow("NOTE:")} D1 migrations are automatically handled by Alchemy`);
5967
+ if (dbSetup === "d1" && serverDeploy === "alchemy") {
5968
+ if (orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} D1 migrations are automatically handled by Alchemy`);
5969
+ else if (orm === "prisma") {
5970
+ instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
5971
+ instructions.push(`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
5972
+ }
5973
+ }
5841
5974
  if (orm === "prisma") {
5842
5975
  if (dbSetup === "turso") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires\n additional setup. Learn more at:\n https://www.prisma.io/docs/orm/overview/databases/turso`);
5843
5976
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
@@ -5924,7 +6057,7 @@ async function updateRootPackageJson(projectDir, options) {
5924
6057
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
5925
6058
  if (options.orm === "prisma") {
5926
6059
  scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
5927
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
6060
+ scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
5928
6061
  } else if (options.orm === "drizzle") {
5929
6062
  scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
5930
6063
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
@@ -5949,7 +6082,7 @@ async function updateRootPackageJson(projectDir, options) {
5949
6082
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
5950
6083
  if (options.orm === "prisma") {
5951
6084
  scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
5952
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
6085
+ scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
5953
6086
  } else if (options.orm === "drizzle") {
5954
6087
  scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
5955
6088
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
@@ -5974,7 +6107,7 @@ async function updateRootPackageJson(projectDir, options) {
5974
6107
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${backendPackageName}`;
5975
6108
  if (options.orm === "prisma") {
5976
6109
  scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
5977
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
6110
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
5978
6111
  } else if (options.orm === "drizzle") {
5979
6112
  scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
5980
6113
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
@@ -5999,7 +6132,7 @@ async function updateRootPackageJson(projectDir, options) {
5999
6132
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
6000
6133
  if (options.orm === "prisma") {
6001
6134
  scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6002
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6135
+ scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
6003
6136
  } else if (options.orm === "drizzle") {
6004
6137
  scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
6005
6138
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
@@ -6042,7 +6175,7 @@ async function updateServerPackageJson(projectDir, options) {
6042
6175
  scripts["db:push"] = "prisma db push";
6043
6176
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = "prisma studio";
6044
6177
  scripts["db:generate"] = "prisma generate";
6045
- if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "prisma migrate dev";
6178
+ scripts["db:migrate"] = "prisma migrate dev";
6046
6179
  } else if (options.orm === "drizzle") {
6047
6180
  scripts["db:push"] = "drizzle-kit push";
6048
6181
  if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = "drizzle-kit studio";
@@ -6365,7 +6498,7 @@ async function openUrl(url) {
6365
6498
 
6366
6499
  //#endregion
6367
6500
  //#region src/utils/sponsors.ts
6368
- const SPONSORS_JSON_URL = "https://sponsors.amanv.dev/sponsors.json";
6501
+ const SPONSORS_JSON_URL = "https://sponsors.better-t-stack.dev/sponsors.json";
6369
6502
  async function fetchSponsors(url = SPONSORS_JSON_URL) {
6370
6503
  const s = spinner();
6371
6504
  s.start("Fetching sponsors…");
@@ -6379,23 +6512,30 @@ async function fetchSponsors(url = SPONSORS_JSON_URL) {
6379
6512
  return sponsors$1;
6380
6513
  }
6381
6514
  function displaySponsors(sponsors$1) {
6382
- if (sponsors$1.length === 0) {
6515
+ const { total_sponsors } = sponsors$1.summary;
6516
+ if (total_sponsors === 0) {
6383
6517
  log.info("No sponsors found. You can be the first one! ✨");
6384
6518
  outro(pc.cyan("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
6385
6519
  return;
6386
6520
  }
6387
- sponsors$1.forEach((entry, idx) => {
6388
- const sponsor = entry.sponsor;
6389
- const displayName = sponsor.name ?? sponsor.login;
6390
- const tier = entry.tierName ? ` (${entry.tierName})` : "";
6391
- log.step(`${idx + 1}. ${pc.green(displayName)}${pc.yellow(tier)}`);
6392
- log.message(` ${pc.dim("GitHub:")} https://github.com/${sponsor.login}`);
6393
- const website = sponsor.websiteUrl ?? sponsor.linkUrl;
6394
- if (website) log.message(` ${pc.dim("Website:")} ${website}`);
6395
- });
6396
- log.message("");
6521
+ displaySponsorsBox(sponsors$1);
6522
+ if (total_sponsors - sponsors$1.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors$1.specialSponsors.length} more amazing sponsors.\n`));
6397
6523
  outro(pc.magenta("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
6398
6524
  }
6525
+ function displaySponsorsBox(sponsors$1) {
6526
+ if (sponsors$1.specialSponsors.length === 0) return;
6527
+ let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
6528
+ sponsors$1.specialSponsors.forEach((sponsor, idx) => {
6529
+ const displayName = sponsor.name ?? sponsor.githubId;
6530
+ const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
6531
+ output += `${pc.green(`• ${displayName}`)}${tier}\n`;
6532
+ output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
6533
+ const website = sponsor.websiteUrl ?? sponsor.githubUrl;
6534
+ if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
6535
+ if (idx < sponsors$1.specialSponsors.length - 1) output += "\n";
6536
+ });
6537
+ consola$1.box(output);
6538
+ }
6399
6539
 
6400
6540
  //#endregion
6401
6541
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.40.3",
3
+ "version": "2.40.4-canary.49f23d48",
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",
@@ -2,18 +2,47 @@
2
2
  import { createContext } from '@/lib/context'
3
3
  {{/if}}
4
4
  import { appRouter } from '@/routers'
5
+ import { OpenAPIHandler } from '@orpc/openapi/fetch'
6
+ import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins'
7
+ import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
5
8
  import { RPCHandler } from '@orpc/server/fetch'
9
+ import { onError } from '@orpc/server'
6
10
  import { NextRequest } from 'next/server'
7
11
 
8
- const handler = new RPCHandler(appRouter)
12
+ const rpcHandler = new RPCHandler(appRouter, {
13
+ interceptors: [
14
+ onError((error) => {
15
+ console.error(error)
16
+ }),
17
+ ],
18
+ })
19
+ const apiHandler = new OpenAPIHandler(appRouter, {
20
+ plugins: [
21
+ new OpenAPIReferencePlugin({
22
+ schemaConverters: [new ZodToJsonSchemaConverter()],
23
+ }),
24
+ ],
25
+ interceptors: [
26
+ onError((error) => {
27
+ console.error(error)
28
+ }),
29
+ ],
30
+ })
9
31
 
10
32
  async function handleRequest(req: NextRequest) {
11
- const { response } = await handler.handle(req, {
33
+ const rpcResult = await rpcHandler.handle(req, {
12
34
  prefix: '/rpc',
13
35
  context: {{#if (eq auth "better-auth")}}await createContext(req){{else}}{}{{/if}},
14
36
  })
37
+ if (rpcResult.response) return rpcResult.response
15
38
 
16
- return response ?? new Response('Not found', { status: 404 })
39
+ const apiResult = await apiHandler.handle(req, {
40
+ prefix: '/rpc/api',
41
+ context: {{#if (eq auth "better-auth")}}await createContext(req){{else}}{}{{/if}},
42
+ })
43
+ if (apiResult.response) return apiResult.response
44
+
45
+ return new Response('Not found', { status: 404 })
17
46
  }
18
47
 
19
48
  export const GET = handleRequest
@@ -4,7 +4,7 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
4
4
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
5
5
  import { expo } from "@better-auth/expo";
6
6
  {{/if}}
7
- import prisma from "../../prisma";
7
+ import prisma from "@/db";
8
8
 
9
9
  export const auth = betterAuth({
10
10
  database: prismaAdapter(prisma, {
@@ -10,7 +10,11 @@ import { appRouter } from "./routers/index";
10
10
  import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
11
11
  {{/if}}
12
12
  {{#if (eq api "orpc")}}
13
+ import { OpenAPIHandler } from "@orpc/openapi/fetch";
14
+ import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
15
+ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
13
16
  import { RPCHandler } from "@orpc/server/fetch";
17
+ import { onError } from "@orpc/server";
14
18
  import { appRouter } from "./routers";
15
19
  import { createContext } from "./lib/context";
16
20
  {{/if}}
@@ -19,7 +23,25 @@ import { auth } from "./lib/auth";
19
23
  {{/if}}
20
24
 
21
25
  {{#if (eq api "orpc")}}
22
- const handler = new RPCHandler(appRouter);
26
+ const rpcHandler = new RPCHandler(appRouter, {
27
+ interceptors: [
28
+ onError((error) => {
29
+ console.error(error);
30
+ }),
31
+ ],
32
+ });
33
+ const apiHandler = new OpenAPIHandler(appRouter, {
34
+ plugins: [
35
+ new OpenAPIReferencePlugin({
36
+ schemaConverters: [new ZodToJsonSchemaConverter()],
37
+ }),
38
+ ],
39
+ interceptors: [
40
+ onError((error) => {
41
+ console.error(error);
42
+ }),
43
+ ],
44
+ });
23
45
  {{/if}}
24
46
 
25
47
  {{#if (eq runtime "node")}}
@@ -48,12 +70,19 @@ const app = new Elysia()
48
70
  {{/if}}
49
71
  {{#if (eq api "orpc")}}
50
72
  .all('/rpc*', async (context) => {
51
- const { response } = await handler.handle(context.request, {
73
+ const { response } = await rpcHandler.handle(context.request, {
52
74
  prefix: '/rpc',
53
75
  context: await createContext({ context })
54
76
  })
55
77
  return response ?? new Response('Not Found', { status: 404 })
56
78
  })
79
+ .all('/api*', async (context) => {
80
+ const { response } = await apiHandler.handle(context.request, {
81
+ prefix: '/api',
82
+ context: await createContext({ context })
83
+ })
84
+ return response ?? new Response('Not Found', { status: 404 })
85
+ })
57
86
  {{/if}}
58
87
  {{#if (eq api "trpc")}}
59
88
  .all("/trpc/*", async (context) => {
@@ -5,7 +5,11 @@ import { createContext } from "./lib/context";
5
5
  import { appRouter } from "./routers/index";
6
6
  {{/if}}
7
7
  {{#if (eq api "orpc")}}
8
+ import { OpenAPIHandler } from "@orpc/openapi/node";
9
+ import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
10
+ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
8
11
  import { RPCHandler } from "@orpc/server/node";
12
+ import { onError } from "@orpc/server";
9
13
  import { appRouter } from "./routers";
10
14
  {{#if (eq auth "better-auth")}}
11
15
  import { createContext } from "./lib/context";
@@ -50,9 +54,28 @@ app.use(
50
54
  {{/if}}
51
55
 
52
56
  {{#if (eq api "orpc")}}
53
- const handler = new RPCHandler(appRouter);
54
- app.use("/rpc{*path}", async (req, res, next) => {
55
- const { matched } = await handler.handle(req, res, {
57
+ const rpcHandler = new RPCHandler(appRouter, {
58
+ interceptors: [
59
+ onError((error) => {
60
+ console.error(error);
61
+ }),
62
+ ],
63
+ });
64
+ const apiHandler = new OpenAPIHandler(appRouter, {
65
+ plugins: [
66
+ new OpenAPIReferencePlugin({
67
+ schemaConverters: [new ZodToJsonSchemaConverter()],
68
+ }),
69
+ ],
70
+ interceptors: [
71
+ onError((error) => {
72
+ console.error(error);
73
+ }),
74
+ ],
75
+ });
76
+
77
+ app.use(async (req, res, next) => {
78
+ const rpcResult = await rpcHandler.handle(req, res, {
56
79
  prefix: "/rpc",
57
80
  {{#if (eq auth "better-auth")}}
58
81
  context: await createContext({ req }),
@@ -60,7 +83,18 @@ app.use("/rpc{*path}", async (req, res, next) => {
60
83
  context: {},
61
84
  {{/if}}
62
85
  });
63
- if (matched) return;
86
+ if (rpcResult.matched) return;
87
+
88
+ const apiResult = await apiHandler.handle(req, res, {
89
+ prefix: "/api",
90
+ {{#if (eq auth "better-auth")}}
91
+ context: await createContext({ req }),
92
+ {{else}}
93
+ context: {},
94
+ {{/if}}
95
+ });
96
+ if (apiResult.matched) return;
97
+
64
98
  next();
65
99
  });
66
100
  {{/if}}
@@ -9,8 +9,12 @@ import { appRouter, type AppRouter } from "./routers/index";
9
9
  {{/if}}
10
10
 
11
11
  {{#if (eq api "orpc")}}
12
+ import { OpenAPIHandler } from "@orpc/openapi/node";
13
+ import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
14
+ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
12
15
  import { RPCHandler } from "@orpc/server/node";
13
16
  import { CORSPlugin } from "@orpc/server/plugins";
17
+ import { onError } from "@orpc/server";
14
18
  import { appRouter } from "./routers/index";
15
19
  import { createServer } from "node:http";
16
20
  {{#if (eq auth "better-auth")}}
@@ -40,7 +44,7 @@ const baseCorsConfig = {
40
44
  };
41
45
 
42
46
  {{#if (eq api "orpc")}}
43
- const handler = new RPCHandler(appRouter, {
47
+ const rpcHandler = new RPCHandler(appRouter, {
44
48
  plugins: [
45
49
  new CORSPlugin({
46
50
  origin: process.env.CORS_ORIGIN,
@@ -48,13 +52,31 @@ const handler = new RPCHandler(appRouter, {
48
52
  allowHeaders: ["Content-Type", "Authorization"],
49
53
  }),
50
54
  ],
55
+ interceptors: [
56
+ onError((error) => {
57
+ console.error(error);
58
+ }),
59
+ ],
60
+ });
61
+
62
+ const apiHandler = new OpenAPIHandler(appRouter, {
63
+ plugins: [
64
+ new OpenAPIReferencePlugin({
65
+ schemaConverters: [new ZodToJsonSchemaConverter()],
66
+ }),
67
+ ],
68
+ interceptors: [
69
+ onError((error) => {
70
+ console.error(error);
71
+ }),
72
+ ],
51
73
  });
52
74
 
53
75
  const fastify = Fastify({
54
76
  logger: true,
55
77
  serverFactory: (fastifyHandler) => {
56
78
  const server = createServer(async (req, res) => {
57
- const { matched } = await handler.handle(req, res, {
79
+ const { matched } = await rpcHandler.handle(req, res, {
58
80
  context: await createContext(req.headers),
59
81
  prefix: "/rpc",
60
82
  });
@@ -63,6 +85,15 @@ const fastify = Fastify({
63
85
  return;
64
86
  }
65
87
 
88
+ const apiResult = await apiHandler.handle(req, res, {
89
+ context: await createContext(req.headers),
90
+ prefix: "/api",
91
+ });
92
+
93
+ if (apiResult.matched) {
94
+ return;
95
+ }
96
+
66
97
  fastifyHandler(req, res);
67
98
  });
68
99
 
@@ -5,7 +5,11 @@ import "dotenv/config";
5
5
  import { env } from "cloudflare:workers";
6
6
  {{/if}}
7
7
  {{#if (eq api "orpc")}}
8
+ import { OpenAPIHandler } from "@orpc/openapi/fetch";
9
+ import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
10
+ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
8
11
  import { RPCHandler } from "@orpc/server/fetch";
12
+ import { onError } from "@orpc/server";
9
13
  import { createContext } from "./lib/context";
10
14
  import { appRouter } from "./routers/index";
11
15
  {{/if}}
@@ -54,17 +58,48 @@ app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
54
58
  {{/if}}
55
59
 
56
60
  {{#if (eq api "orpc")}}
57
- const handler = new RPCHandler(appRouter);
58
- app.use("/rpc/*", async (c, next) => {
61
+ export const apiHandler = new OpenAPIHandler(appRouter, {
62
+ plugins: [
63
+ new OpenAPIReferencePlugin({
64
+ schemaConverters: [new ZodToJsonSchemaConverter()],
65
+ }),
66
+ ],
67
+ interceptors: [
68
+ onError((error) => {
69
+ console.error(error);
70
+ }),
71
+ ],
72
+ });
73
+
74
+ export const rpcHandler = new RPCHandler(appRouter, {
75
+ interceptors: [
76
+ onError((error) => {
77
+ console.error(error);
78
+ }),
79
+ ],
80
+ });
81
+
82
+ app.use("/*", async (c, next) => {
59
83
  const context = await createContext({ context: c });
60
- const { matched, response } = await handler.handle(c.req.raw, {
84
+
85
+ const rpcResult = await rpcHandler.handle(c.req.raw, {
61
86
  prefix: "/rpc",
62
87
  context: context,
63
88
  });
64
89
 
65
- if (matched) {
66
- return c.newResponse(response.body, response);
90
+ if (rpcResult.matched) {
91
+ return c.newResponse(rpcResult.response.body, rpcResult.response);
67
92
  }
93
+
94
+ const apiResult = await apiHandler.handle(c.req.raw, {
95
+ prefix: "/api",
96
+ context: context,
97
+ });
98
+
99
+ if (apiResult.matched) {
100
+ return c.newResponse(apiResult.response.body, apiResult.response);
101
+ }
102
+
68
103
  await next();
69
104
  });
70
105
  {{/if}}
@@ -1,4 +1,15 @@
1
1
  {{#if (or (eq runtime "bun") (eq runtime "node"))}}
2
+ {{#if (eq dbSetup "planetscale")}}
3
+ import { drizzle } from "drizzle-orm/planetscale-serverless";
4
+
5
+ export const db = drizzle({
6
+ connection: {
7
+ host: process.env.DATABASE_HOST,
8
+ username: process.env.DATABASE_USERNAME,
9
+ password: process.env.DATABASE_PASSWORD,
10
+ },
11
+ });
12
+ {{else}}
2
13
  import { drizzle } from "drizzle-orm/mysql2";
3
14
 
4
15
  export const db = drizzle({
@@ -7,8 +18,21 @@ export const db = drizzle({
7
18
  },
8
19
  });
9
20
  {{/if}}
21
+ {{/if}}
10
22
 
11
23
  {{#if (eq runtime "workers")}}
24
+ {{#if (eq dbSetup "planetscale")}}
25
+ import { drizzle } from "drizzle-orm/planetscale-serverless";
26
+ import { env } from "cloudflare:workers";
27
+
28
+ export const db = drizzle({
29
+ connection: {
30
+ host: env.DATABASE_HOST,
31
+ username: env.DATABASE_USERNAME,
32
+ password: env.DATABASE_PASSWORD,
33
+ },
34
+ });
35
+ {{else}}
12
36
  import { drizzle } from "drizzle-orm/mysql2";
13
37
  import { env } from "cloudflare:workers";
14
38
 
@@ -18,3 +42,4 @@ export const db = drizzle({
18
42
  },
19
43
  });
20
44
  {{/if}}
45
+ {{/if}}
@@ -0,0 +1,5 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ export default prisma;
@@ -11,9 +11,15 @@ generator client {
11
11
  {{#if (eq runtime "workers")}}
12
12
  runtime = "workerd"
13
13
  {{/if}}
14
+ {{#if (eq dbSetup "planetscale")}}
15
+ previewFeatures = ["driverAdapters"]
16
+ {{/if}}
14
17
  }
15
18
 
16
19
  datasource db {
17
20
  provider = "mysql"
18
21
  url = env("DATABASE_URL")
22
+ {{#if (eq dbSetup "planetscale")}}
23
+ relationMode = "prisma"
24
+ {{/if}}
19
25
  }
@@ -0,0 +1,12 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+ {{#if (eq dbSetup "planetscale")}}
3
+ import { PrismaPlanetScale } from '@prisma/adapter-planetscale'
4
+ import { fetch as undiciFetch } from 'undici'
5
+
6
+ const adapter = new PrismaPlanetScale({ url: process.env.DATABASE_URL, fetch: undiciFetch })
7
+ const prisma = new PrismaClient({adapter});
8
+ {{else}}
9
+ const prisma = new PrismaClient();
10
+ {{/if}}
11
+
12
+ export default prisma;
@@ -19,4 +19,7 @@ datasource db {
19
19
  {{#if (eq dbSetup "supabase")}}
20
20
  directUrl = env("DIRECT_URL")
21
21
  {{/if}}
22
+ {{#if (eq dbSetup "planetscale")}}
23
+ relationMode = "prisma"
24
+ {{/if}}
22
25
  }
@@ -0,0 +1,5 @@
1
+ import { PrismaClient } from "../../prisma/generated/client";
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ export default prisma;
@@ -10,6 +10,9 @@ generator client {
10
10
  {{/if}}
11
11
  {{#if (eq runtime "workers")}}
12
12
  runtime = "workerd"
13
+ {{#if (eq dbSetup "d1")}}
14
+ previewFeatures = ["driverAdapters"]
15
+ {{/if}}
13
16
  {{/if}}
14
17
  }
15
18
 
@@ -1,10 +1,27 @@
1
1
  import "dotenv/config";
2
2
  import path from "node:path";
3
3
  import type { PrismaConfig } from "prisma";
4
+ {{#if (eq serverDeploy "wrangler")}}
5
+ import { PrismaD1 } from "@prisma/adapter-d1";
6
+ {{/if}}
4
7
 
5
8
  export default {
9
+ {{#if (eq serverDeploy "wrangler")}}
10
+ experimental: {
11
+ adapter: true
12
+ },
13
+ {{/if}}
6
14
  schema: path.join("prisma", "schema"),
7
15
  migrations: {
8
16
  path: path.join("prisma", "migrations"),
9
- }
17
+ },
18
+ {{#if (eq serverDeploy "wrangler")}}
19
+ async adapter() {
20
+ return new PrismaD1({
21
+ CLOUDFLARE_D1_TOKEN: process.env.CLOUDFLARE_D1_TOKEN!,
22
+ CLOUDFLARE_ACCOUNT_ID: process.env.CLOUDFLARE_ACCOUNT_ID!,
23
+ CLOUDFLARE_DATABASE_ID: process.env.CLOUDFLARE_DATABASE_ID!,
24
+ });
25
+ },
26
+ {{/if}}
10
27
  } satisfies PrismaConfig;
@@ -0,0 +1,16 @@
1
+ {{#if (and (eq dbSetup "d1") (eq runtime "workers"))}}
2
+ import { env } from "cloudflare:workers";
3
+ import { PrismaD1 } from "@prisma/adapter-d1";
4
+ import { PrismaClient } from "../../prisma/generated/client";
5
+
6
+ const adapter = new PrismaD1(env.DB);
7
+ const prisma = new PrismaClient({ adapter });
8
+
9
+ export default prisma;
10
+ {{else}}
11
+ import { PrismaClient } from "../../prisma/generated/client";
12
+
13
+ const prisma = new PrismaClient();
14
+
15
+ export default prisma;
16
+ {{/if}}
@@ -44,7 +44,11 @@ await Exec("db-generate", {
44
44
  });
45
45
 
46
46
  const db = await D1Database("database", {
47
+ {{#if (eq orm "prisma")}}
48
+ migrationsDir: "apps/server/prisma/migrations",
49
+ {{else if (eq orm "drizzle")}}
47
50
  migrationsDir: "apps/server/src/db/migrations",
51
+ {{/if}}
48
52
  });
49
53
  {{/if}}
50
54
 
@@ -27,7 +27,12 @@
27
27
  "database_name": "YOUR_DB_NAME",
28
28
  "database_id": "YOUR_DB_ID",
29
29
  "preview_database_id": "local-test-db",
30
+ {{#if (eq orm "drizzle")}}
30
31
  "migrations_dir": "./src/db/migrations"
32
+ {{/if}}
33
+ {{#if (eq orm "prisma")}}
34
+ "migrations_dir": "./prisma/migrations"
35
+ {{/if}}
31
36
  }
32
37
  ]
33
38
  {{/if}}
@@ -1,6 +1,6 @@
1
1
  {{#if (eq api "orpc")}}
2
2
  import z from "zod";
3
- import prisma from "../../prisma";
3
+ import prisma from "@/db";
4
4
  import { publicProcedure } from "../lib/orpc";
5
5
 
6
6
  export const todoRouter = {
@@ -52,7 +52,7 @@ export const todoRouter = {
52
52
  {{#if (eq api "trpc")}}
53
53
  import { TRPCError } from "@trpc/server";
54
54
  import z from "zod";
55
- import prisma from "../../prisma";
55
+ import prisma from "@/db";
56
56
  import { publicProcedure, router } from "../lib/trpc";
57
57
 
58
58
  export const todoRouter = router({
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;
@@ -1,5 +0,0 @@
1
- import { PrismaClient } from "./generated/client";
2
-
3
- const prisma = new PrismaClient();
4
-
5
- export default prisma;