import { Injectable, OnModuleDestroy } from '@nestjs/common'; import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaClient } from '@prisma/client'; import { requestContext } from '../common/request-context'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleDestroy { constructor() { const workerRole = process.env.CMPP_PROCESS_ROLE === 'worker'; const databaseUrl = workerRole ? process.env.API_WORKER_DATABASE_URL || process.env.DATABASE_URL : process.env.DATABASE_URL; const configuredPoolMax = Number(workerRole ? process.env.API_WORKER_DB_POOL_MAX ?? 8 : process.env.API_DB_POOL_MAX ?? 32); const poolMax = Number.isInteger(configuredPoolMax) && configuredPoolMax > 0 ? configuredPoolMax : workerRole ? 8 : 32; super({ adapter: new PrismaPg({ connectionString: databaseUrl ?? 'postgresql://cmpp:cmpp_password@localhost:5432/cmpp_platform?schema=public', // API capacity must be reserved independently from the heavier Worker // transactions; explicit bounds also protect PostgreSQL max_connections. max: poolMax, }), }); const operationLog = this.operationLog; Object.defineProperty(this, 'operationLog', { value: new Proxy(operationLog, { get(target, property, receiver) { if (property === 'create') { return (args: { data: Record }) => { const ipAddress = requestContext.getStore()?.ipAddress; return (target.create as (input: unknown) => unknown)({ ...args, data: { ...args.data, ipAddress: typeof args.data.ipAddress === 'string' ? args.data.ipAddress : ipAddress }, }); }; } const value = Reflect.get(target, property, receiver); return typeof value === 'function' ? value.bind(target) : value; }, }), configurable: true, }); } async onModuleDestroy() { await this.$disconnect(); } }