diff --git a/README.md b/README.md index 707645a..c5015ac 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,17 @@ The Prisma schema is located under `apps/hotel-management-service-server/prisma/ ``` * Seed data lives in `apps/hotel-management-service-server/scripts/seed.ts`. +* **Seeding the database** + + Run the standalone seed script whenever you need fresh demo data: + + ```bash + cd apps/hotel-management-service-server + npm run seed # executes ts-node scripts/seed.ts + ``` + + The script now performs all operations inside a `try … finally` block and **awaits `client.$disconnect()`**, ensuring the PostgreSQL connection is cleanly closed even if an error occurs. + If you prefer a single command, `npm run db:init` already applies migrations **and** invokes the seed script in the correct order. --- diff --git a/apps/hotel-management-service-server/package.json b/apps/hotel-management-service-server/package.json index 06b5482..9f5209a 100644 --- a/apps/hotel-management-service-server/package.json +++ b/apps/hotel-management-service-server/package.json @@ -68,6 +68,7 @@ "jest": { "preset": "ts-jest", "testEnvironment": "node", + "detectOpenHandles": true, "moduleNameMapper": { "@app/custom-validators": "/src/validators" }, diff --git a/apps/hotel-management-service-server/scripts/customSeed.ts b/apps/hotel-management-service-server/scripts/customSeed.ts index 26ccaf4..676e1bf 100644 --- a/apps/hotel-management-service-server/scripts/customSeed.ts +++ b/apps/hotel-management-service-server/scripts/customSeed.ts @@ -1,7 +1,5 @@ import { PrismaClient } from "@prisma/client"; -export async function customSeed() { - const client = new PrismaClient(); - - client.$disconnect(); +export async function customSeed(client: PrismaClient) { + // TODO: add seeding logic here using the provided client instance } diff --git a/apps/hotel-management-service-server/scripts/seed.ts b/apps/hotel-management-service-server/scripts/seed.ts index 04cee65..574bdf9 100644 --- a/apps/hotel-management-service-server/scripts/seed.ts +++ b/apps/hotel-management-service-server/scripts/seed.ts @@ -2,6 +2,14 @@ import * as dotenv from "dotenv"; import { PrismaClient } from "@prisma/client"; import { customSeed } from "./customSeed"; +// Single PrismaClient instance for the whole script +const client = new PrismaClient(); + +// Extra safety: ensure disconnect on process exit +process.on("beforeExit", async () => { + await client.$disconnect(); +}); + if (require.main === module) { dotenv.config(); @@ -10,16 +18,24 @@ if (require.main === module) { if (!BCRYPT_SALT) { throw new Error("BCRYPT_SALT environment variable must be defined"); } + + // Execute the seed when run directly + seed().catch((err) => { + console.error("Failed to seed database", err); + process.exit(1); + }); } async function seed() { console.info("Seeding database..."); - const client = new PrismaClient(); - void client.$disconnect(); + try { + console.info("Seeding database with custom seed..."); + await customSeed(client); - console.info("Seeding database with custom seed..."); - customSeed(); - - console.info("Seeded database successfully"); + console.info("Seeded database successfully"); + } finally { + // Always disconnect, even if seeding throws + await client.$disconnect(); + } } diff --git a/apps/hotel-management-service-server/test/seed.spec.ts b/apps/hotel-management-service-server/test/seed.spec.ts new file mode 100644 index 0000000..e30a10b --- /dev/null +++ b/apps/hotel-management-service-server/test/seed.spec.ts @@ -0,0 +1,34 @@ +import { resolve } from 'path'; +import { spawn } from 'child_process'; + +/** + * Integration test to ensure the seed script exits cleanly + * without leaving open handles (detectOpenHandles enabled in Jest config). + */ + +describe('Database seed script', () => { + it('should run to completion with exit code 0', async () => { + // Path to the seed script (TypeScript). + const scriptPath = resolve(__dirname, '../scripts/seed.ts'); + + // Spawn the seed script via ts-node. + // Using stdio: 'inherit' helps observe any console output during CI failures. + const child = spawn( + process.execPath, // Node.js binary executing ts-node register and script + [ + // Register ts-node transpiler + '-r', + 'ts-node/register', + scriptPath, + ], + { stdio: 'inherit' }, + ); + + const exitCode: number = await new Promise((resolvePromise, rejectPromise) => { + child.on('error', rejectPromise); + child.on('exit', resolvePromise); + }); + + expect(exitCode).toBe(0); + }, 60_000); // generous timeout for DB operations +}); diff --git a/docs/seed-scripts-prisma-audit.md b/docs/seed-scripts-prisma-audit.md new file mode 100644 index 0000000..e5534df --- /dev/null +++ b/docs/seed-scripts-prisma-audit.md @@ -0,0 +1,40 @@ +# Prisma Seed Scripts Audit – Database Connection Handling + +Date: 2025-10-31 + +This audit searches the repository for potential mis-management of `PrismaClient` connections in seed / script files. We looked specifically for: + +* `new PrismaClient(` – new client instantiation +* `$disconnect()` – connection teardown + +--- + +## Findings + +| # | File Path | Line(s) | Code Snippet | Requires Change? | Recommended Action | +|---|-----------|---------|--------------|------------------|--------------------| +| 1 | `apps/hotel-management-service-server/scripts/seed.ts` | 18-19 | ```ts +const client = new PrismaClient(); +void client.$disconnect(); +``` | **Yes** | • Move `$disconnect()` into a `finally` block and `await` it.
• Prefer passing a shared `PrismaClient` instance to `customSeed()` instead of creating separate instances.
• Await `customSeed()` if it becomes async. | +| 2 | `apps/hotel-management-service-server/scripts/customSeed.ts` | 4-6 | ```ts +const client = new PrismaClient(); + +client.$disconnect(); +``` | **Yes** | • Accept a `PrismaClient` parameter rather than creating a new one.
• Ensure the caller handles cleanup in `finally` with `await client.$disconnect()`. | + +No other occurrences of `PrismaClient` instantiation or `$disconnect()` were found in the repository at the time of this audit. + +--- + +## Summary +Both seed scripts create their own `PrismaClient` instance and call `$disconnect()` without awaiting the returned promise. This risks the Node.js process exiting before the connection is cleanly closed, possibly causing connection leaks. + +**Refactor Plan (to be implemented in subsequent phases):** + +1. Instantiate a single `PrismaClient` in `seed.ts`. +2. Wrap all seeding logic in `try { … } finally { await client.$disconnect(); }`. +3. Pass the shared client into `customSeed()` (update its signature accordingly). +4. Use `process.on('beforeExit')` as a secondary safeguard if desired. + +This document will serve as reference for Phase 2 refactoring work.