Skip to content

Commit

Permalink
chore: reuse prisma workspace to speed up db commands (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanFlurry committed Mar 7, 2024
2 parents 61f9ede + 3955f35 commit b75acc7
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 108 deletions.
1 change: 0 additions & 1 deletion src/build/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ async function buildSteps(
project: Project,
opts: BuildOpts,
) {
// TODO: This does not reuse the Prisma dir or the database connection, so is extra slow on the first run. Figure out how to make this use one `migrateDev` command and pass in any modified modules For now, these need to be migrated individually because `prisma migrate dev` is an interactive command. Also, making a database change and waiting for all other databases to re-apply will take a long tim.e
for (const module of project.modules.values()) {
if (module.db) {
buildStep(buildState, {
Expand Down
21 changes: 8 additions & 13 deletions src/migrate/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,18 @@
// Wrapper around `prisma migrate deploy`

import { Module, Project } from "../project/mod.ts";
import { forEachPrismaSchema } from "./mod.ts";
import { forEachPrismaSchema, runPrismaCommand } from "./mod.ts";

export async function migrateDeploy(project: Project, modules: Module[]) {
await forEachPrismaSchema(project, modules, async ({ databaseUrl, tempDir }) => {
// Generate migrations & client
const status = await new Deno.Command("deno", {
args: ["run", "-A", "npm:prisma@5.9.1", "migrate", "deploy"],
cwd: tempDir,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
await forEachPrismaSchema(project, modules, async ({ databaseUrl }) => {
await runPrismaCommand(project, {
args: [
"migrate",
"deploy",
],
env: {
DATABASE_URL: databaseUrl,
},
}).output();
if (!status.success) {
throw new Error("Failed to generate migrations");
}
});
});
}
2 changes: 2 additions & 0 deletions src/migrate/deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * as esbuild from "npm:esbuild@^0.20.0";
export { polyfillNodeForDeno } from "npm:@rivet-gg/esbuild-plugin-polyfill-node@^0.4.0";
export { Client as PostgresClient } from "https://deno.land/x/postgres@v0.17.2/mod.ts";
import dedent from "npm:dedent@^1.5.1";
export { dedent };
17 changes: 4 additions & 13 deletions src/migrate/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { assert, copy, emptyDir, exists, resolve } from "../deps.ts";
import { Module, Project } from "../project/mod.ts";
import { runPrismaCommand } from "./mod.ts";
import { forEachPrismaSchema } from "./mod.ts";

export interface MigrateDevOpts {
Expand All @@ -24,28 +25,18 @@ export async function migrateDev(
project,
modules,
async ({ databaseUrl, module, tempDir }) => {
// Generate migrations & client
const status = await new Deno.Command("deno", {
// Generate migrations
await runPrismaCommand(project, {
args: [
"run",
"-A",
"npm:prisma@5.9.1",
"migrate",
"dev",
"--skip-generate",
...(opts.createOnly ? ["--create-only"] : []),
],
cwd: tempDir,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
env: {
DATABASE_URL: databaseUrl,
},
}).output();
if (!status.success) {
throw new Error("Failed to generate migrations");
}
});

// Copy back migrations dir
//
Expand Down
24 changes: 6 additions & 18 deletions src/migrate/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { resolve } from "../deps.ts";
import { buildPrismaPackage } from "./build_prisma_esm.ts";
import { Module, Project } from "../project/mod.ts";
import { forEachPrismaSchema } from "./mod.ts";
import { forEachPrismaSchema, runPrismaCommand } from "./mod.ts";

export async function generateClient(
project: Project,
Expand All @@ -14,27 +14,15 @@ export async function generateClient(
await forEachPrismaSchema(
project,
modules,
async ({ databaseUrl, tempDir, generatedClientDir }) => {
// Generate migrations & client
const status = await new Deno.Command("deno", {
args: [
"run",
"-A",
"npm:prisma@5.9.1",
"generate",
],
cwd: tempDir,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
async ({ databaseUrl, generatedClientDir }) => {
// Generate client
await runPrismaCommand(project, {
args: ["generate"],
env: {
DATABASE_URL: databaseUrl,
PRISMA_CLIENT_FORCE_WASM: "true",
},
}).output();
if (!status.success) {
throw new Error("Failed to generate migrations");
}
});

// Specify the path to the library & binary types
for (
Expand Down
125 changes: 95 additions & 30 deletions src/migrate/mod.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { copy, resolve } from "../deps.ts";
import { PostgresClient } from "./deps.ts";
import { dedent, PostgresClient } from "./deps.ts";
import { Module, ModuleDatabase, Project } from "../project/mod.ts";
import { assertValidString } from "./validate.ts";
import { emptyDir } from "../deps.ts";

const PRISMA_VERSION = "5.9.1";

export type ForEachDatabaseCallback = (
opts: { databaseUrl: string; module: Module; db: ModuleDatabase },
Expand Down Expand Up @@ -48,7 +51,7 @@ export async function forEachDatabase(
} catch (cause) {
throw new Error("Failed to iterate databases", { cause });
} finally {
// await defaultClient.end();
await defaultClient.end();
}
}

Expand All @@ -62,48 +65,34 @@ export async function forEachPrismaSchema(
project,
modules,
async ({ databaseUrl, module, db }) => {
const tempDir = await Deno.makeTempDir();
const dbDir = resolve(module.path, "db");
const prismaDir = await preparePrismaWorkspace(project);

const srcDbDir = resolve(module.path, "db");
const dstDbDir = resolve(prismaDir, "db");
const generatedClientDir = resolve(
module.path,
"_gen",
"prisma",
);

// Duplicate db directory
await copy(dbDir, tempDir, { overwrite: true });

// TODO: This causes a weird error
// // Write package.json
// const packageJsonPath = resolve(tempDir, "package.json");
// const packageJson = JSON.stringify({
// "devDependencies": {
// "prisma": "^5.9.1"
// },
// "dependencies": {
// "@prisma/client": "^5.9.1"
// },
// "node": {
// "target": "20.11.1"
// }
// });
// await Deno.writeTextFile(packageJsonPath, packageJson);
// Copy db directory
await emptyDir(dstDbDir);
await copy(srcDbDir, dstDbDir, { overwrite: true });

// Append generator config
const tempSchemaPath = resolve(tempDir, "schema.prisma");
let schema = await Deno.readTextFile(tempSchemaPath);
schema += `
const schemaPath = resolve(dstDbDir, "schema.prisma");
let schema = await Deno.readTextFile(schemaPath);
schema += dedent`
generator client {
provider = "prisma-client-js"
output = "${generatedClientDir}"
previewFeatures = ["driverAdapters"]
// binaryTargets = ["native", "darwin", "darwin-arm64"]
}`;
await Deno.writeTextFile(tempSchemaPath, schema);
}
`;
await Deno.writeTextFile(schemaPath, schema);

// Callback
await callback({ databaseUrl, module, db, tempDir, generatedClientDir });
await callback({ databaseUrl, module, db, tempDir: prismaDir, generatedClientDir });
},
);
}
Expand All @@ -120,3 +109,79 @@ async function createDatabases(client: PostgresClient, db: ModuleDatabase) {
await client.queryObject(`CREATE DATABASE ${assertValidString(db.name)}`);
}
}

/**
* Context about the Prisma workspace for the current command.
*/
const PRISMA_WORKSPACE_STATE = {
didRunInstall: false,
};

/**
* Installs a copy of Prisma in to a directory that can be reused for any
* Prisma-related commands.
*/
async function preparePrismaWorkspace(project: Project): Promise<string> {
const prismaDir = resolve(project.path, "_gen", "prisma_workspace");
await Deno.mkdir(prismaDir, { recursive: true });

if (!PRISMA_WORKSPACE_STATE.didRunInstall) {
// Write package.json
const packageJson = JSON.stringify({
"devDependencies": {
"prisma": `^${PRISMA_VERSION}`,
},
"dependencies": {
"@prisma/client": `^${PRISMA_VERSION}`,
},
"node": {
"target": "20.11.1",
},
});
await Deno.writeTextFile(resolve(prismaDir, "package.json"), packageJson);

// Install dependencies
const installOutput = await new Deno.Command("npm", {
args: ["install", "--prefer-offline", "--no-audit", "--no-fund", "--progress=false"],
cwd: prismaDir,
stdout: "inherit",
stderr: "inherit",
}).output();
if (!installOutput.success) throw new Error("Failed to install prisma dependencies");

PRISMA_WORKSPACE_STATE.didRunInstall = true;
}

return prismaDir;
}

export interface RunPrismaCommandOpts {
args: string[];
env: Record<string, string>;
}

/**
* Run a Prisma command in the Prisma workspace.
*
* We don't use `deno run npm:prisma` because:
*
* - We already have Prisma installed in the workspace
* - There are minor bugs with Deno's compatability with Prisma
*/
export async function runPrismaCommand(
project: Project,
opts: RunPrismaCommandOpts & Deno.CommandOptions,
) {
const prismaDir = resolve(project.path, "_gen", "prisma_workspace");
const status = await new Deno.Command("npx", {
args: ["prisma", ...opts.args],
cwd: resolve(prismaDir, "db"),
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
env: opts.env,
}).output();
if (!status.success) {
throw new Error(`Failed to run: prisma ${opts.args.join(" ")}`);
}
}
24 changes: 5 additions & 19 deletions src/migrate/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,16 @@
// Wrapper around `prisma migrate dev`

import { Project } from "../project/mod.ts";
import { forEachPrismaSchema } from "./mod.ts";
import { forEachPrismaSchema, runPrismaCommand } from "./mod.ts";

export async function migrateReset(project: Project) {
await forEachPrismaSchema(project, [...project.modules.values()], async ({ databaseUrl, tempDir }) => {
await forEachPrismaSchema(project, [...project.modules.values()], async ({ databaseUrl }) => {
// Generate migrations & client
const status = await new Deno.Command("deno", {
args: [
"run",
"-A",
"npm:prisma@5.9.1",
"migrate",
"reset",
"--skip-generate",
],
cwd: tempDir,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
await runPrismaCommand(project, {
args: ["migrate", "reset", "--skip-generate"],
env: {
DATABASE_URL: databaseUrl,
},
}).output();
if (!status.success) {
throw new Error("Failed to generate migrations");
}
});
});
}
20 changes: 6 additions & 14 deletions src/migrate/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,16 @@
// Wrapper around `prisma migrate dev`

import { Project } from "../project/mod.ts";
import { forEachPrismaSchema } from "./mod.ts";
import { forEachPrismaSchema, runPrismaCommand } from "./mod.ts";

export async function migrateStatus(project: Project) {
await forEachPrismaSchema(project, [...project.modules.values()], async ({ databaseUrl, tempDir }) => {
// Generate migrations & client
console.log("Generating migrations");
const status = await new Deno.Command("deno", {
args: ["run", "-A", "npm:prisma@5.9.1", "migrate", "status"],
cwd: tempDir,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
await forEachPrismaSchema(project, [...project.modules.values()], async ({ databaseUrl }) => {
// Get status
await runPrismaCommand(project, {
args: ["migrate", "status"],
env: {
DATABASE_URL: databaseUrl,
},
}).output();
if (!status.success) {
throw new Error("Failed to generate migrations");
}
});
});
}

0 comments on commit b75acc7

Please sign in to comment.