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.
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/{src-BOnnM-3r.js → src-D32Mc1TH.js} +200 -60
- package/package.json +1 -1
- package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +32 -3
- package/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs +1 -1
- package/templates/backend/server/elysia/src/index.ts.hbs +31 -2
- package/templates/backend/server/express/src/index.ts.hbs +38 -4
- package/templates/backend/server/fastify/src/index.ts.hbs +33 -2
- package/templates/backend/server/hono/src/index.ts.hbs +40 -5
- package/templates/db/drizzle/mysql/src/db/index.ts.hbs +25 -0
- package/templates/db/prisma/mongodb/src/db/index.ts.hbs +5 -0
- package/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs +6 -0
- package/templates/db/prisma/mysql/src/db/index.ts.hbs +12 -0
- package/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs +3 -0
- package/templates/db/prisma/postgres/src/db/index.ts.hbs +5 -0
- package/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs +3 -0
- package/templates/db/prisma/sqlite/prisma.config.ts.hbs +18 -1
- package/templates/db/prisma/sqlite/src/db/index.ts.hbs +16 -0
- package/templates/deploy/alchemy/alchemy.run.ts.hbs +4 -0
- package/templates/deploy/wrangler/server/wrangler.jsonc.hbs +5 -0
- package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +2 -2
- package/templates/db/prisma/mongodb/prisma/index.ts.hbs +0 -5
- package/templates/db/prisma/mysql/prisma/index.ts +0 -5
- package/templates/db/prisma/postgres/prisma/index.ts +0 -5
- package/templates/db/prisma/sqlite/prisma/index.ts +0 -5
package/dist/cli.js
CHANGED
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
|
@@ -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.
|
|
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,
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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 = "
|
|
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 = "
|
|
1345
|
-
const POSTHOG_HOST = "
|
|
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 (
|
|
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
|
|
3051
|
-
await execa(runCmd,
|
|
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: [
|
|
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)
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
6388
|
-
|
|
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
|
+
"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
|
|
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
|
|
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
|
-
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
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
|
|
58
|
-
|
|
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
|
-
|
|
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}}
|
|
@@ -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;
|
|
@@ -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 "
|
|
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 "
|
|
55
|
+
import prisma from "@/db";
|
|
56
56
|
import { publicProcedure, router } from "../lib/trpc";
|
|
57
57
|
|
|
58
58
|
export const todoRouter = router({
|