fix: add database URL parsing to PrismaService (#5656)

* fix: add database URL parsing to PrismaService

* fix: feedback

* chore: add pool connectivity check to PrismaService
This commit is contained in:
Mir Arif Hasan 2025-12-04 11:55:22 +06:00 committed by GitHub
parent 77d0956180
commit 52735a166d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 4 deletions

View file

@ -2,19 +2,35 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient, Prisma } from 'src/generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import pg from 'pg';
import { parseIntSafe } from 'src/utils';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
private pool: pg.Pool;
private readonly pool: pg.Pool;
constructor() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL environment variable is not set');
}
const { connectionString, schema, connectionLimit, connectTimeout } =
PrismaService.parseDatabaseUrl(databaseUrl);
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
connectionString,
max: connectionLimit ?? 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: connectTimeout ?? 5000,
});
const adapter = new PrismaPg(pool, {
schema,
});
const adapter = new PrismaPg(pool);
super({
adapter,
@ -26,8 +42,46 @@ export class PrismaService
this.pool = pool;
}
private static parseDatabaseUrl(databaseUrl: string): {
connectionString: string;
schema: string;
connectionLimit?: number;
connectTimeout?: number;
} {
try {
const url = new URL(databaseUrl);
const schema = url.searchParams.get('schema') || 'public';
const connectionLimit = url.searchParams.get('connection_limit');
const connectTimeout = url.searchParams.get('connect_timeout');
url.searchParams.delete('schema');
url.searchParams.delete('connection_limit');
url.searchParams.delete('connect_timeout');
return {
connectionString: url.toString(),
schema,
connectionLimit: parseIntSafe(connectionLimit),
connectTimeout: parseIntSafe(connectTimeout),
};
} catch (error) {
throw new Error(
`Invalid DATABASE_URL format: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
}
}
async onModuleInit() {
await this.$connect();
try {
// Verify pool connectivity
const client = await this.pool.connect();
client.release();
await this.$connect();
} catch (error) {
throw new Error(`Database connection failed: ${error.message}`);
}
}
async onModuleDestroy() {

View file

@ -22,6 +22,19 @@ export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Safely parses a string to an integer, returning undefined if the value is null, undefined, or results in NaN.
* @param value The string value to parse
* @returns The parsed integer or undefined
*/
export function parseIntSafe(
value: string | null | undefined,
): number | undefined {
if (!value) return undefined;
const parsed = parseInt(value, 10);
return !isNaN(parsed) ? parsed : undefined;
}
/**
* A workaround to throw an exception in an expression.
* JS throw keyword creates a statement not an expression.