Skip to content

Commit 1622138

Browse files
feat: migrate Prisma from 6.14.0 to 7.7.0 with driver adapters
- Bump prisma, @prisma/client to 7.7.0, add @prisma/adapter-pg - Switch to engine-less client (engineType = 'client') with PrismaPg adapter - Remove binaryTargets and metrics preview feature from schema.prisma - Remove url/directUrl from datasource block (Prisma 7 requirement) - Create prisma.config.ts for CLI tools (migrations) - Rewrite db.server.ts to use PrismaPg adapter for writer + replica clients - Drop $metrics: remove from metrics.ts, delete configurePrismaMetrics from tracer.server.ts - Update PrismaClientKnownRequestError import path (runtime/library -> runtime/client) - Update all PrismaClient instantiation sites to use adapter pattern: testcontainers, tests/utils.ts, scripts, benchmark producer - Exclude prisma.config.ts from TypeScript build Co-Authored-By: Eric Allam <eallam@icloud.com>
1 parent 7c95207 commit 1622138

File tree

14 files changed

+561
-314
lines changed

14 files changed

+561
-314
lines changed

apps/webapp/app/db.server.ts

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type PrismaTransactionClient,
88
type PrismaTransactionOptions,
99
} from "@trigger.dev/database";
10+
import { PrismaPg } from "@prisma/adapter-pg";
1011
import invariant from "tiny-invariant";
1112
import { z } from "zod";
1213
import { env } from "./env.server";
@@ -109,21 +110,22 @@ function getClient() {
109110
const { DATABASE_URL } = process.env;
110111
invariant(typeof DATABASE_URL === "string", "DATABASE_URL env var not set");
111112

112-
const databaseUrl = extendQueryParams(DATABASE_URL, {
113-
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
114-
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
115-
connection_timeout: env.DATABASE_CONNECTION_TIMEOUT.toString(),
116-
application_name: env.SERVICE_NAME,
117-
});
113+
const databaseUrl = new URL(DATABASE_URL);
114+
115+
// Set application_name as a query param on the connection string (pg understands this)
116+
databaseUrl.searchParams.set("application_name", env.SERVICE_NAME);
118117

119118
console.log(`🔌 setting up prisma client to ${redactUrlSecrets(databaseUrl)}`);
120119

120+
const adapter = new PrismaPg({
121+
connectionString: databaseUrl.href,
122+
max: env.DATABASE_CONNECTION_LIMIT,
123+
idleTimeoutMillis: env.DATABASE_POOL_TIMEOUT * 1000,
124+
connectionTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
125+
});
126+
121127
const client = new PrismaClient({
122-
datasources: {
123-
db: {
124-
url: databaseUrl.href,
125-
},
126-
},
128+
adapter,
127129
log: [
128130
// events
129131
{
@@ -233,21 +235,20 @@ function getReplicaClient() {
233235
return;
234236
}
235237

236-
const replicaUrl = extendQueryParams(env.DATABASE_READ_REPLICA_URL, {
237-
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
238-
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
239-
connection_timeout: env.DATABASE_CONNECTION_TIMEOUT.toString(),
240-
application_name: env.SERVICE_NAME,
241-
});
238+
const replicaUrl = new URL(env.DATABASE_READ_REPLICA_URL);
239+
replicaUrl.searchParams.set("application_name", env.SERVICE_NAME);
242240

243241
console.log(`🔌 setting up read replica connection to ${redactUrlSecrets(replicaUrl)}`);
244242

243+
const adapter = new PrismaPg({
244+
connectionString: replicaUrl.href,
245+
max: env.DATABASE_CONNECTION_LIMIT,
246+
idleTimeoutMillis: env.DATABASE_POOL_TIMEOUT * 1000,
247+
connectionTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
248+
});
249+
245250
const replicaClient = new PrismaClient({
246-
datasources: {
247-
db: {
248-
url: replicaUrl.href,
249-
},
250-
},
251+
adapter,
251252
log: [
252253
// events
253254
{
@@ -350,19 +351,6 @@ function getReplicaClient() {
350351
return replicaClient;
351352
}
352353

353-
function extendQueryParams(hrefOrUrl: string | URL, queryParams: Record<string, string>) {
354-
const url = new URL(hrefOrUrl);
355-
const query = url.searchParams;
356-
357-
for (const [key, val] of Object.entries(queryParams)) {
358-
query.set(key, val);
359-
}
360-
361-
url.search = query.toString();
362-
363-
return url;
364-
}
365-
366354
function redactUrlSecrets(hrefOrUrl: string | URL) {
367355
const url = new URL(hrefOrUrl);
368356
url.password = "";

apps/webapp/app/routes/metrics.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
2-
import { prisma } from "~/db.server";
32
import { metricsRegister } from "~/metrics.server";
43

54
export async function loader({ request }: LoaderFunctionArgs) {
@@ -13,14 +12,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
1312
}
1413
}
1514

16-
// We need to remove empty lines from the prisma metrics, grafana doesn't like them
17-
const prismaMetrics = (await prisma.$metrics.prometheus()).replace(/^\s*[\r\n]/gm, "");
1815
const coreMetrics = await metricsRegister.metrics();
1916

20-
// Order matters, core metrics end with `# EOF`, prisma metrics don't
21-
const metrics = prismaMetrics + coreMetrics;
22-
23-
return new Response(metrics, {
17+
return new Response(coreMetrics, {
2418
headers: {
2519
"Content-Type": metricsRegister.contentType,
2620
},

apps/webapp/app/v3/tracer.server.ts

Lines changed: 0 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ import { LoggerSpanExporter } from "./telemetry/loggerExporter.server";
5454
import { CompactMetricExporter } from "./telemetry/compactMetricExporter.server";
5555
import { logger } from "~/services/logger.server";
5656
import { flattenAttributes } from "@trigger.dev/core/v3";
57-
import { prisma } from "~/db.server";
5857
import { metricsRegister } from "~/metrics.server";
59-
import type { Prisma } from "@trigger.dev/database";
6058
import { performance } from "node:perf_hooks";
6159

6260
export const SEMINTATTRS_FORCE_RECORDING = "forceRecording";
@@ -330,221 +328,12 @@ function setupMetrics() {
330328

331329
const meter = meterProvider.getMeter("trigger.dev", "3.3.12");
332330

333-
configurePrismaMetrics({ meter });
334331
configureNodejsMetrics({ meter });
335332
configureHostMetrics({ meterProvider });
336333

337334
return meter;
338335
}
339336

340-
function configurePrismaMetrics({ meter }: { meter: Meter }) {
341-
// Counters
342-
const queriesTotal = meter.createObservableCounter("db.client.queries.total", {
343-
description: "Total number of Prisma Client queries executed",
344-
unit: "queries",
345-
});
346-
const datasourceQueriesTotal = meter.createObservableCounter("db.datasource.queries.total", {
347-
description: "Total number of datasource queries executed",
348-
unit: "queries",
349-
});
350-
const connectionsOpenedTotal = meter.createObservableCounter("db.pool.connections.opened.total", {
351-
description: "Total number of pool connections opened",
352-
unit: "connections",
353-
});
354-
const connectionsClosedTotal = meter.createObservableCounter("db.pool.connections.closed.total", {
355-
description: "Total number of pool connections closed",
356-
unit: "connections",
357-
});
358-
359-
// Gauges
360-
const queriesActive = meter.createObservableGauge("db.client.queries.active", {
361-
description: "Number of currently active Prisma Client queries",
362-
unit: "queries",
363-
});
364-
const queriesWait = meter.createObservableGauge("db.client.queries.wait", {
365-
description: "Number of queries currently waiting for a connection",
366-
unit: "queries",
367-
});
368-
const totalGauge = meter.createObservableGauge("db.pool.connections.total", {
369-
description: "Open Prisma-pool connections",
370-
unit: "connections",
371-
});
372-
const busyGauge = meter.createObservableGauge("db.pool.connections.busy", {
373-
description: "Connections currently executing queries",
374-
unit: "connections",
375-
});
376-
const freeGauge = meter.createObservableGauge("db.pool.connections.free", {
377-
description: "Idle (free) connections in the pool",
378-
unit: "connections",
379-
});
380-
381-
// Histogram statistics as gauges
382-
const queriesWaitTimeCount = meter.createObservableGauge("db.client.queries.wait_time.count", {
383-
description: "Number of wait time observations",
384-
unit: "observations",
385-
});
386-
const queriesWaitTimeSum = meter.createObservableGauge("db.client.queries.wait_time.sum", {
387-
description: "Total wait time across all observations",
388-
unit: "ms",
389-
});
390-
const queriesWaitTimeMean = meter.createObservableGauge("db.client.queries.wait_time.mean", {
391-
description: "Average wait time for a connection",
392-
unit: "ms",
393-
});
394-
395-
const queriesDurationCount = meter.createObservableGauge("db.client.queries.duration.count", {
396-
description: "Number of query duration observations",
397-
unit: "observations",
398-
});
399-
const queriesDurationSum = meter.createObservableGauge("db.client.queries.duration.sum", {
400-
description: "Total query duration across all observations",
401-
unit: "ms",
402-
});
403-
const queriesDurationMean = meter.createObservableGauge("db.client.queries.duration.mean", {
404-
description: "Average duration of Prisma Client queries",
405-
unit: "ms",
406-
});
407-
408-
const datasourceQueriesDurationCount = meter.createObservableGauge(
409-
"db.datasource.queries.duration.count",
410-
{
411-
description: "Number of datasource query duration observations",
412-
unit: "observations",
413-
}
414-
);
415-
const datasourceQueriesDurationSum = meter.createObservableGauge(
416-
"db.datasource.queries.duration.sum",
417-
{
418-
description: "Total datasource query duration across all observations",
419-
unit: "ms",
420-
}
421-
);
422-
const datasourceQueriesDurationMean = meter.createObservableGauge(
423-
"db.datasource.queries.duration.mean",
424-
{
425-
description: "Average duration of datasource queries",
426-
unit: "ms",
427-
}
428-
);
429-
430-
// Single helper so we hit Prisma only once per scrape ---------------------
431-
async function readPrismaMetrics() {
432-
const metrics = await prisma.$metrics.json();
433-
434-
// Extract counter values
435-
const counters: Record<string, number> = {};
436-
for (const counter of metrics.counters) {
437-
counters[counter.key] = counter.value;
438-
}
439-
440-
// Extract gauge values
441-
const gauges: Record<string, number> = {};
442-
for (const gauge of metrics.gauges) {
443-
gauges[gauge.key] = gauge.value;
444-
}
445-
446-
// Extract histogram values
447-
const histograms: Record<string, Prisma.MetricHistogram> = {};
448-
for (const histogram of metrics.histograms) {
449-
histograms[histogram.key] = histogram.value;
450-
}
451-
452-
return {
453-
counters: {
454-
queriesTotal: counters["prisma_client_queries_total"] ?? 0,
455-
datasourceQueriesTotal: counters["prisma_datasource_queries_total"] ?? 0,
456-
connectionsOpenedTotal: counters["prisma_pool_connections_opened_total"] ?? 0,
457-
connectionsClosedTotal: counters["prisma_pool_connections_closed_total"] ?? 0,
458-
},
459-
gauges: {
460-
queriesActive: gauges["prisma_client_queries_active"] ?? 0,
461-
queriesWait: gauges["prisma_client_queries_wait"] ?? 0,
462-
connectionsOpen: gauges["prisma_pool_connections_open"] ?? 0,
463-
connectionsBusy: gauges["prisma_pool_connections_busy"] ?? 0,
464-
connectionsIdle: gauges["prisma_pool_connections_idle"] ?? 0,
465-
},
466-
histograms: {
467-
queriesWait: histograms["prisma_client_queries_wait_histogram_ms"],
468-
queriesDuration: histograms["prisma_client_queries_duration_histogram_ms"],
469-
datasourceQueriesDuration: histograms["prisma_datasource_queries_duration_histogram_ms"],
470-
},
471-
};
472-
}
473-
474-
meter.addBatchObservableCallback(
475-
async (res) => {
476-
const { counters, gauges, histograms } = await readPrismaMetrics();
477-
478-
// Observe counters
479-
res.observe(queriesTotal, counters.queriesTotal);
480-
res.observe(datasourceQueriesTotal, counters.datasourceQueriesTotal);
481-
res.observe(connectionsOpenedTotal, counters.connectionsOpenedTotal);
482-
res.observe(connectionsClosedTotal, counters.connectionsClosedTotal);
483-
484-
// Observe gauges
485-
res.observe(queriesActive, gauges.queriesActive);
486-
res.observe(queriesWait, gauges.queriesWait);
487-
res.observe(totalGauge, gauges.connectionsOpen);
488-
res.observe(busyGauge, gauges.connectionsBusy);
489-
res.observe(freeGauge, gauges.connectionsIdle);
490-
491-
// Observe histogram statistics as gauges
492-
if (histograms.queriesWait) {
493-
res.observe(queriesWaitTimeCount, histograms.queriesWait.count);
494-
res.observe(queriesWaitTimeSum, histograms.queriesWait.sum);
495-
res.observe(
496-
queriesWaitTimeMean,
497-
histograms.queriesWait.count > 0
498-
? histograms.queriesWait.sum / histograms.queriesWait.count
499-
: 0
500-
);
501-
}
502-
503-
if (histograms.queriesDuration) {
504-
res.observe(queriesDurationCount, histograms.queriesDuration.count);
505-
res.observe(queriesDurationSum, histograms.queriesDuration.sum);
506-
res.observe(
507-
queriesDurationMean,
508-
histograms.queriesDuration.count > 0
509-
? histograms.queriesDuration.sum / histograms.queriesDuration.count
510-
: 0
511-
);
512-
}
513-
514-
if (histograms.datasourceQueriesDuration) {
515-
res.observe(datasourceQueriesDurationCount, histograms.datasourceQueriesDuration.count);
516-
res.observe(datasourceQueriesDurationSum, histograms.datasourceQueriesDuration.sum);
517-
res.observe(
518-
datasourceQueriesDurationMean,
519-
histograms.datasourceQueriesDuration.count > 0
520-
? histograms.datasourceQueriesDuration.sum / histograms.datasourceQueriesDuration.count
521-
: 0
522-
);
523-
}
524-
},
525-
[
526-
queriesTotal,
527-
datasourceQueriesTotal,
528-
connectionsOpenedTotal,
529-
connectionsClosedTotal,
530-
queriesActive,
531-
queriesWait,
532-
totalGauge,
533-
busyGauge,
534-
freeGauge,
535-
queriesWaitTimeCount,
536-
queriesWaitTimeSum,
537-
queriesWaitTimeMean,
538-
queriesDurationCount,
539-
queriesDurationSum,
540-
queriesDurationMean,
541-
datasourceQueriesDurationCount,
542-
datasourceQueriesDurationSum,
543-
datasourceQueriesDurationMean,
544-
]
545-
);
546-
}
547-
548337
function configureNodejsMetrics({ meter }: { meter: Meter }) {
549338
if (!env.INTERNAL_OTEL_NODEJS_METRICS_ENABLED) {
550339
return;

apps/webapp/test/runsReplicationBenchmark.producer.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { PrismaClient } from "@trigger.dev/database";
8+
import { PrismaPg } from "@prisma/adapter-pg";
89
import { performance } from "node:perf_hooks";
910

1011
interface ProducerConfig {
@@ -91,13 +92,8 @@ function generateError() {
9192
}
9293

9394
async function runProducer(config: ProducerConfig) {
94-
const prisma = new PrismaClient({
95-
datasources: {
96-
db: {
97-
url: config.postgresUrl,
98-
},
99-
},
100-
});
95+
const adapter = new PrismaPg(config.postgresUrl);
96+
const prisma = new PrismaClient({ adapter });
10197

10298
try {
10399
console.log(

internal-packages/database/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",
77
"dependencies": {
8-
"@prisma/client": "6.14.0",
8+
"@prisma/adapter-pg": "7.7.0",
9+
"@prisma/client": "7.7.0",
910
"decimal.js": "^10.6.0"
1011
},
1112
"devDependencies": {
1213
"@types/decimal.js": "^7.4.3",
13-
"prisma": "6.14.0",
14+
"prisma": "7.7.0",
1415
"rimraf": "6.0.1"
1516
},
1617
"scripts": {
@@ -25,4 +26,4 @@
2526
"build": "pnpm run clean && tsc --noEmit false --outDir dist --declaration",
2627
"dev": "tsc --noEmit false --outDir dist --declaration --watch"
2728
}
28-
}
29+
}

0 commit comments

Comments
 (0)