mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-13 02:17:05 +00:00
166 lines
4.3 KiB
JavaScript
166 lines
4.3 KiB
JavaScript
/**
|
|
* Centralized Prisma Client Singleton
|
|
* Prevents multiple Prisma clients from creating connection leaks
|
|
*/
|
|
|
|
const { PrismaClient } = require("@prisma/client");
|
|
|
|
// Parse DATABASE_URL and add connection pooling parameters
|
|
function getOptimizedDatabaseUrl() {
|
|
const originalUrl = process.env.DATABASE_URL;
|
|
|
|
if (!originalUrl) {
|
|
throw new Error("DATABASE_URL environment variable is required");
|
|
}
|
|
|
|
// Parse the URL
|
|
const url = new URL(originalUrl);
|
|
|
|
// Add connection pooling parameters - configurable via environment variables
|
|
const connectionLimit = process.env.DB_CONNECTION_LIMIT || "30";
|
|
const poolTimeout = process.env.DB_POOL_TIMEOUT || "20";
|
|
const connectTimeout = process.env.DB_CONNECT_TIMEOUT || "10";
|
|
const idleTimeout = process.env.DB_IDLE_TIMEOUT || "300";
|
|
const maxLifetime = process.env.DB_MAX_LIFETIME || "1800";
|
|
|
|
url.searchParams.set("connection_limit", connectionLimit);
|
|
url.searchParams.set("pool_timeout", poolTimeout);
|
|
url.searchParams.set("connect_timeout", connectTimeout);
|
|
url.searchParams.set("idle_timeout", idleTimeout);
|
|
url.searchParams.set("max_lifetime", maxLifetime);
|
|
|
|
// Log connection pool settings in development/debug mode
|
|
if (
|
|
process.env.ENABLE_LOGGING === "true" ||
|
|
process.env.LOG_LEVEL === "debug"
|
|
) {
|
|
console.log(
|
|
`[Database Pool] connection_limit=${connectionLimit}, pool_timeout=${poolTimeout}s, connect_timeout=${connectTimeout}s`,
|
|
);
|
|
}
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
// Singleton Prisma client instance
|
|
let prismaInstance = null;
|
|
|
|
function getPrismaClient() {
|
|
if (!prismaInstance) {
|
|
const optimizedUrl = getOptimizedDatabaseUrl();
|
|
|
|
prismaInstance = new PrismaClient({
|
|
datasources: {
|
|
db: {
|
|
url: optimizedUrl,
|
|
},
|
|
},
|
|
log:
|
|
process.env.PRISMA_LOG_QUERIES === "true"
|
|
? ["query", "info", "warn", "error"]
|
|
: ["warn", "error"],
|
|
errorFormat: "pretty",
|
|
});
|
|
|
|
// Handle graceful shutdown
|
|
process.on("beforeExit", async () => {
|
|
await prismaInstance.$disconnect();
|
|
});
|
|
|
|
process.on("SIGINT", async () => {
|
|
await prismaInstance.$disconnect();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on("SIGTERM", async () => {
|
|
await prismaInstance.$disconnect();
|
|
process.exit(0);
|
|
});
|
|
}
|
|
|
|
return prismaInstance;
|
|
}
|
|
|
|
// Connection health check
|
|
async function checkDatabaseConnection(prisma) {
|
|
try {
|
|
await prisma.$queryRaw`SELECT 1`;
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Database connection check failed:", error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Wait for database to be available with retry logic
|
|
async function waitForDatabase(prisma, options = {}) {
|
|
const maxAttempts =
|
|
options.maxAttempts ||
|
|
parseInt(process.env.PM_DB_CONN_MAX_ATTEMPTS, 10) ||
|
|
30;
|
|
const waitInterval =
|
|
options.waitInterval ||
|
|
parseInt(process.env.PM_DB_CONN_WAIT_INTERVAL, 10) ||
|
|
2;
|
|
|
|
if (process.env.ENABLE_LOGGING === "true") {
|
|
console.log(
|
|
`Waiting for database connection (max ${maxAttempts} attempts, ${waitInterval}s interval)...`,
|
|
);
|
|
}
|
|
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
try {
|
|
const isConnected = await checkDatabaseConnection(prisma);
|
|
if (isConnected) {
|
|
if (process.env.ENABLE_LOGGING === "true") {
|
|
console.log(
|
|
`Database connected successfully after ${attempt} attempt(s)`,
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
} catch {
|
|
// checkDatabaseConnection already logs the error
|
|
}
|
|
|
|
if (attempt < maxAttempts) {
|
|
if (process.env.ENABLE_LOGGING === "true") {
|
|
console.log(
|
|
`⏳ Database not ready (attempt ${attempt}/${maxAttempts}), retrying in ${waitInterval}s...`,
|
|
);
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, waitInterval * 1000));
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
`❌ Database failed to become available after ${maxAttempts} attempts`,
|
|
);
|
|
}
|
|
|
|
// Graceful disconnect with retry
|
|
async function disconnectPrisma(prisma, maxRetries = 3) {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
await prisma.$disconnect();
|
|
console.log("Database disconnected successfully");
|
|
return;
|
|
} catch (error) {
|
|
console.error(`Disconnect attempt ${i + 1} failed:`, error.message);
|
|
if (i === maxRetries - 1) {
|
|
console.error("Failed to disconnect from database after all retries");
|
|
} else {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getPrismaClient,
|
|
checkDatabaseConnection,
|
|
waitForDatabase,
|
|
disconnectPrisma,
|
|
};
|