From 2131802a4e1713cfd225a8d6945e4bfdaa037743 Mon Sep 17 00:00:00 2001 From: Aidan McAlister Date: Wed, 7 Jan 2026 10:45:50 -0500 Subject: [PATCH] fix: fixed failed push for v7 and improved push/pull speed --- .../lib/prismaSchemaEditor/schemaApi.ts | 19 +-- schema-api-routes/package.json | 2 +- schema-api-routes/src/index.ts | 10 +- schema-api-routes/src/routes/schema/pull.ts | 121 ++++++++------- .../src/routes/schema/push-force.ts | 110 +++++++------- schema-api-routes/src/routes/schema/push.ts | 138 +++++++++--------- 6 files changed, 204 insertions(+), 196 deletions(-) diff --git a/claim-db-worker/lib/prismaSchemaEditor/schemaApi.ts b/claim-db-worker/lib/prismaSchemaEditor/schemaApi.ts index e97f425..cd8bccc 100644 --- a/claim-db-worker/lib/prismaSchemaEditor/schemaApi.ts +++ b/claim-db-worker/lib/prismaSchemaEditor/schemaApi.ts @@ -1,7 +1,8 @@ +const API_URL = "https://create-db-schema-api-routes.vercel.app/api/schema"; +// const API_URL = "http://localhost:4141/api/schema"; + export const formatSchema = async (schema: string): Promise => { - const response = await fetch( - "https://create-db-schema-api-routes.vercel.app/api/schema/format", - { + const response = await fetch(`${API_URL}/format`, { method: "POST", headers: { "Content-Type": "application/json", @@ -31,9 +32,7 @@ export const pushSchema = async ( details?: string; requiresForceReset?: boolean; }> => { - const response = await fetch( - "https://create-db-schema-api-routes.vercel.app/api/schema/push", - { + const response = await fetch(`${API_URL}/push`, { method: "POST", headers: { "Content-Type": "application/json", @@ -65,9 +64,7 @@ export const pullSchema = async ( isEmpty?: boolean; message?: string; }> => { - const response = await fetch( - "https://create-db-schema-api-routes.vercel.app/api/schema/pull", - { + const response = await fetch(`${API_URL}/pull`, { method: "POST", headers: { "Content-Type": "application/json", @@ -95,9 +92,7 @@ export const forcePushSchema = async ( schema: string, connectionString: string ): Promise => { - const response = await fetch( - "https://create-db-schema-api-routes.vercel.app/api/schema/push-force", - { + const response = await fetch(`${API_URL}/push-force`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/schema-api-routes/package.json b/schema-api-routes/package.json index d98c200..4531ced 100644 --- a/schema-api-routes/package.json +++ b/schema-api-routes/package.json @@ -4,7 +4,7 @@ "dependencies": { "@hono/node-server": "^1.8.2", "hono": "^4.9.4", - "prisma": "^5.10.0", + "prisma": "^7.2.0", "vercel": "^46.0.2" }, "devDependencies": { diff --git a/schema-api-routes/src/index.ts b/schema-api-routes/src/index.ts index 3e68e38..c416253 100644 --- a/schema-api-routes/src/index.ts +++ b/schema-api-routes/src/index.ts @@ -1,4 +1,5 @@ import { Hono } from "hono"; +import { serve } from "@hono/node-server"; import { cors } from "hono/cors"; import formatRoute from "./routes/schema/format.js"; import pullRoute from "./routes/schema/pull.js"; @@ -41,7 +42,7 @@ app.use( ); app.get("/", (c) => { - return c.json({ message: "Hello World" }); + return c.json({ message: "This is the schema API routes server for the create-db web app" }); }); app.route("/api/schema/format", formatRoute); @@ -50,3 +51,10 @@ app.route("/api/schema/push", pushRoute); app.route("/api/schema/push-force", pushForceRoute); export default app; + +const port = 4141; +console.log(`Server is running on http://localhost:${port}`); +serve({ + fetch: app.fetch, + port, +}); diff --git a/schema-api-routes/src/routes/schema/pull.ts b/schema-api-routes/src/routes/schema/pull.ts index 14e111d..af1b9cd 100644 --- a/schema-api-routes/src/routes/schema/pull.ts +++ b/schema-api-routes/src/routes/schema/pull.ts @@ -1,12 +1,10 @@ import { Hono } from "hono"; -import { writeFile, readFile, unlink } from "fs/promises"; -import { spawn } from "child_process"; +import { writeFile, readFile, rm, mkdir } from "fs/promises"; import { execSync } from "child_process"; const app = new Hono(); -app.post("/", async (c) => { - const minimalSchema = `generator client { +const minimalSchema = `generator client { provider = "prisma-client" output = "../app/generated/client" } @@ -15,74 +13,69 @@ datasource db { provider = "postgresql" }`; - try { - const connectionString = c.req.header("X-Connection-String"); +async function createPrismaWorkspace( + connectionString: string, + schema: string +): Promise<{ workDir: string; schemaPath: string; cleanup: () => Promise }> { + const timestamp = Date.now(); + const workDir = `/tmp/prisma-${timestamp}`; + const schemaPath = `${workDir}/schema.prisma`; + const configPath = `${workDir}/prisma.config.ts`; - if (!connectionString) { - return c.json( - { error: "Connection string not provided in headers" }, - 400 - ); - } + await mkdir(workDir, { recursive: true }); + + await writeFile(schemaPath, schema); - const tempDir = "/tmp"; - const schemaPath = `${tempDir}/schema-${Date.now()}.prisma`; - const envPath = `${tempDir}/.env-${Date.now()}`; + const configContent = `import { defineConfig } from "prisma/config"; - try { - await writeFile(envPath, `DATABASE_URL="${connectionString}"`); - await writeFile(schemaPath, minimalSchema); +export default defineConfig({ + schema: "./schema.prisma", + datasource: { + url: "${connectionString}", + }, +}); +`; + await writeFile(configPath, configContent); + return { + workDir, + schemaPath, + cleanup: async () => { try { - const result = execSync(`npx prisma db pull --schema=${schemaPath}`, { - env: { - ...process.env, - DATABASE_URL: connectionString, - npm_config_cache: "/tmp/.npm", - npm_config_prefix: "/tmp/.npm", - }, - cwd: process.cwd(), - encoding: "utf8", - stdio: "pipe", - }); - - const stdout = result; - const stderr = ""; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - if (errorMessage.includes("The introspected database was empty")) { - return c.json({ - success: true, - schema: minimalSchema, - message: "Database is empty - showing minimal schema", - isEmpty: true, - }); - } - - throw error; + await rm(workDir, { recursive: true, force: true }); + } catch (e) { + console.error("Cleanup error:", e); } + }, + }; +} + +app.post("/", async (c) => { + const connectionString = c.req.header("X-Connection-String"); + if (!connectionString) { + return c.json({ error: "Connection string not provided in headers" }, 400); + } - const schemaContent = await readFile(schemaPath, "utf-8"); + const workspace = await createPrismaWorkspace(connectionString, minimalSchema); - return c.json({ - success: true, - schema: schemaContent, - message: "Schema pulled successfully", - }); - } finally { - try { - await unlink(schemaPath); - await unlink(envPath); - } catch (cleanupError) { - console.error("Cleanup error:", cleanupError); - } - } + try { + execSync("pnpx prisma db pull", { + cwd: workspace.workDir, + encoding: "utf8", + stdio: "pipe", + }); + + const schemaContent = await readFile(workspace.schemaPath, "utf-8"); + + return c.json({ + success: true, + schema: schemaContent, + message: "Schema pulled successfully", + }); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); + const message = error instanceof Error ? error.message : String(error); - if (errorMessage.includes("The introspected database was empty")) { + if (message.includes("The introspected database was empty")) { return c.json({ success: true, schema: minimalSchema, @@ -94,10 +87,12 @@ datasource db { return c.json( { error: "Failed to pull schema", - details: errorMessage, + details: message, }, 500 ); + } finally { + await workspace.cleanup(); } }); diff --git a/schema-api-routes/src/routes/schema/push-force.ts b/schema-api-routes/src/routes/schema/push-force.ts index 891443e..5ca1c5e 100644 --- a/schema-api-routes/src/routes/schema/push-force.ts +++ b/schema-api-routes/src/routes/schema/push-force.ts @@ -1,69 +1,71 @@ import { Hono } from "hono"; -import { writeFile, unlink } from "fs/promises"; -import { spawn } from "child_process"; +import { writeFile, rm, mkdir } from "fs/promises"; import { execSync } from "child_process"; const app = new Hono(); -app.post("/", async (c) => { - try { - const body = await c.req.json(); - const { schema } = body as { - schema: string; - }; +async function createPrismaWorkspace( + connectionString: string, + schema: string +): Promise<{ workDir: string; cleanup: () => Promise }> { + const timestamp = Date.now(); + const workDir = `/tmp/prisma-${timestamp}`; + const schemaPath = `${workDir}/schema.prisma`; + const configPath = `${workDir}/prisma.config.ts`; - if (!schema) { - return c.json({ error: "Schema is required" }, 400); - } + await mkdir(workDir, { recursive: true }); - const connectionString = c.req.header("X-Connection-String"); + await writeFile(schemaPath, schema); - if (!connectionString) { - return c.json( - { error: "Connection string not provided in headers" }, - 400 - ); - } + const configContent = `import { defineConfig } from "prisma/config"; - const tempDir = "/tmp"; - const schemaPath = `${tempDir}/schema-${Date.now()}.prisma`; - const envPath = `${tempDir}/.env-${Date.now()}`; - - try { - await writeFile(schemaPath, schema); - await writeFile(envPath, `DATABASE_URL="${connectionString}"`); +export default defineConfig({ + schema: "./schema.prisma", + datasource: { + url: "${connectionString}", + }, +}); +`; + await writeFile(configPath, configContent); + return { + workDir, + cleanup: async () => { try { - const result = execSync( - `npx prisma db push --schema=${schemaPath} --accept-data-loss --force-reset`, - { - env: { - ...process.env, - DATABASE_URL: connectionString, - npm_config_cache: "/tmp/.npm", - npm_config_prefix: "/tmp/.npm", - }, - cwd: process.cwd(), - encoding: "utf8", - stdio: "pipe", - } - ); - } catch (error) { - throw error; + await rm(workDir, { recursive: true, force: true }); + } catch (e) { + console.error("Cleanup error:", e); } + }, + }; +} - return c.json({ - success: true, - message: "Schema pushed successfully with force reset", - }); - } finally { - try { - await unlink(schemaPath); - await unlink(envPath); - } catch (cleanupError) { - console.error("Cleanup error:", cleanupError); - } - } +app.post("/", async (c) => { + const body = await c.req.json(); + const { schema } = body as { schema: string }; + + if (!schema) { + return c.json({ error: "Schema is required" }, 400); + } + + const connectionString = c.req.header("X-Connection-String"); + if (!connectionString) { + return c.json({ error: "Connection string not provided in headers" }, 400); + } + + const workspace = await createPrismaWorkspace(connectionString, schema); + + try { + execSync("pnpx prisma db push --accept-data-loss --force-reset", { + cwd: workspace.workDir, + encoding: "utf8", + stdio: "pipe", + }); + + return c.json({ + success: true, + message: "Schema pushed successfully with force reset", + }); } catch (error) { return c.json( { @@ -72,6 +74,8 @@ app.post("/", async (c) => { }, 500 ); + } finally { + await workspace.cleanup(); } }); diff --git a/schema-api-routes/src/routes/schema/push.ts b/schema-api-routes/src/routes/schema/push.ts index 2900b9e..21ce884 100644 --- a/schema-api-routes/src/routes/schema/push.ts +++ b/schema-api-routes/src/routes/schema/push.ts @@ -1,91 +1,97 @@ import { Hono } from "hono"; -import { writeFile, unlink } from "fs/promises"; -import { spawn } from "child_process"; +import { writeFile, rm, mkdir } from "fs/promises"; import { execSync } from "child_process"; const app = new Hono(); -app.post("/", async (c) => { - try { - const body = await c.req.json(); - const { schema } = body as { - schema: string; - }; - - if (!schema) { - return c.json({ error: "Schema is required" }, 400); - } +async function createPrismaWorkspace( + connectionString: string, + schema: string +): Promise<{ workDir: string; schemaPath: string; cleanup: () => Promise }> { + const timestamp = Date.now(); + const workDir = `/tmp/prisma-${timestamp}`; + const schemaPath = `${workDir}/schema.prisma`; + const configPath = `${workDir}/prisma.config.ts`; - const connectionString = c.req.header("X-Connection-String"); + await mkdir(workDir, { recursive: true }); - if (!connectionString) { - return c.json( - { error: "Connection string not provided in headers" }, - 400 - ); - } + await writeFile(schemaPath, schema); - const tempDir = "/tmp"; - const schemaPath = `${tempDir}/schema-${Date.now()}.prisma`; - const envPath = `${tempDir}/.env-${Date.now()}`; + const configContent = `import { defineConfig } from "prisma/config"; - try { - await writeFile(schemaPath, schema); - await writeFile(envPath, `DATABASE_URL="${connectionString}"`); +export default defineConfig({ + schema: "./schema.prisma", + datasource: { + url: "${connectionString}", + }, +}); +`; + await writeFile(configPath, configContent); + return { + workDir, + schemaPath, + cleanup: async () => { try { - const result = execSync( - `npx prisma db push --schema=${schemaPath} --accept-data-loss`, - { - env: { - ...process.env, - DATABASE_URL: connectionString, - npm_config_cache: "/tmp/.npm", - npm_config_prefix: "/tmp/.npm", - }, - cwd: process.cwd(), - encoding: "utf8", - stdio: "pipe", - } - ); - } catch (error) { - if ( - error instanceof Error && - (error.message.includes("data loss") || - error.message.includes("force-reset") || - error.message.includes("reset") || - error.message.includes("neither a built-in type")) - ) { - return c.json({ - requiresForceReset: true, - message: - "This operation will reset your database and lose all data. Please confirm to continue.", - }); - } else { - throw error; - } + await rm(workDir, { recursive: true, force: true }); + } catch (e) { + console.error("Cleanup error:", e); } + }, + }; +} +app.post("/", async (c) => { + const body = await c.req.json(); + const { schema } = body as { schema: string }; + + if (!schema) { + return c.json({ error: "Schema is required" }, 400); + } + + const connectionString = c.req.header("X-Connection-String"); + if (!connectionString) { + return c.json({ error: "Connection string not provided in headers" }, 400); + } + + const workspace = await createPrismaWorkspace(connectionString, schema); + + try { + execSync("pnpx prisma db push --accept-data-loss", { + cwd: workspace.workDir, + encoding: "utf8", + stdio: "pipe", + }); + + return c.json({ + success: true, + message: "Schema pushed successfully", + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + + if ( + message.includes("data loss") || + message.includes("force-reset") || + message.includes("reset") || + message.includes("neither a built-in type") + ) { return c.json({ - success: true, - message: "Schema pushed successfully", + requiresForceReset: true, + message: + "This operation will reset your database and lose all data. Please confirm to continue.", }); - } finally { - try { - await unlink(schemaPath); - await unlink(envPath); - } catch (cleanupError) { - console.error("Cleanup error:", cleanupError); - } } - } catch (error) { + return c.json( { error: "Failed to push schema", - details: error instanceof Error ? error.message : String(error), + details: message, }, 500 ); + } finally { + await workspace.cleanup(); } });