|
import { reset, seed } from 'drizzle-seed'; |
|
import { sql } from 'drizzle-orm'; |
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres'; |
|
import { performance } from 'perf_hooks'; |
|
import * as schema from './schema'; // Replace with your schema file |
|
import { db } from './db'; // Replace with your DB connection |
|
|
|
// Define schema type |
|
type AppSchema = typeof schema; |
|
|
|
const typedDb = db as NodePgDatabase<AppSchema>; |
|
|
|
// Result type for seeding |
|
type SeedingResult = { |
|
success: boolean; |
|
skipped?: boolean; |
|
time?: number; |
|
error?: string; |
|
}; |
|
|
|
const MAX_RETRIES = 3; |
|
const RETRY_DELAY_MS = 1000; |
|
|
|
// Simple schema validation |
|
function validateSchema(schema: AppSchema): void { |
|
const requiredTables = ['users'] as const; // Adjust as needed |
|
const missingTables = requiredTables.filter((table) => !(table in schema)); |
|
if (missingTables.length > 0) { |
|
throw new Error(`Missing required tables: ${missingTables.join(', ')}`); |
|
} |
|
} |
|
|
|
/** |
|
* Seeds the database by first generating main tables with drizzle-seed, |
|
* then manually seeding junction tables to avoid duplicate key violations. |
|
*/ |
|
export async function seedDatabase(): Promise<SeedingResult> { |
|
const startTime = performance.now(); |
|
const log = (message: string) => console.log(message); |
|
|
|
try { |
|
log('๐ Validating schema...'); |
|
validateSchema(schema); |
|
log('โ
Schema validated'); |
|
|
|
log('๐ Testing DB connection...'); |
|
await typedDb.execute(sql`SELECT 1`); |
|
log('โ
DB connection OK'); |
|
|
|
log('๐ Checking for existing data...'); |
|
const hasData = await checkDatabaseHasData(typedDb); |
|
if (hasData) { |
|
log('โฉ Data exists - skipping seeding'); |
|
return { success: true, skipped: true, time: performance.now() - startTime }; |
|
} |
|
log('โน๏ธ No data found - proceeding with seeding'); |
|
|
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { |
|
try { |
|
log(`๐ Seeding database (attempt ${attempt + 1}/${MAX_RETRIES})...`); |
|
await typedDb.transaction(async (tx) => { |
|
// Step 1: Reset the database |
|
log('๐งน Resetting database...'); |
|
await reset(tx, schema); |
|
|
|
// Step 2: Seed main tables |
|
log('๐ฑ Seeding main tables...'); |
|
const mainTables = { |
|
users: schema.users, // Example main table |
|
categories: schema.categories, // Example main table |
|
// Add your main tables here |
|
}; |
|
await seed(tx, mainTables); |
|
|
|
// Step 3: Fetch generated IDs |
|
log('๐ Fetching data for junction tables...'); |
|
const users = await tx.select().from(schema.users).limit(3); |
|
const categories = await tx.select().from(schema.categories).limit(3); |
|
|
|
if (users.length === 0 || categories.length === 0) { |
|
throw new Error('Insufficient data for junction tables'); |
|
} |
|
|
|
// Step 4: Seed junction tables |
|
log('๐ฟ Seeding junction tables...'); |
|
|
|
// Example junction table: userCategories |
|
const userCategoriesData = [ |
|
{ userId: users[0].id, categoryId: categories[0].id }, |
|
{ userId: users[0].id, categoryId: categories[1].id }, |
|
{ userId: users[1].id, categoryId: categories[0].id }, |
|
]; |
|
await tx.insert(schema.userCategories).values(userCategoriesData).onConflictDoNothing(); |
|
log('โ
Seeded userCategories'); |
|
}); |
|
|
|
const totalTime = performance.now() - startTime; |
|
log(`๐ Seeding completed in ${Math.round(totalTime)}ms`); |
|
return { success: true, time: totalTime }; |
|
} catch (error) { |
|
const errorMessage = error instanceof Error ? error.message : String(error); |
|
log(`โ ๏ธ Failed on attempt ${attempt + 1}: ${errorMessage}`); |
|
if (attempt === MAX_RETRIES - 1) { |
|
log('โ All attempts failed'); |
|
return { success: false, error: errorMessage, time: performance.now() - startTime }; |
|
} |
|
log(`โณ Retrying in ${RETRY_DELAY_MS}ms...`); |
|
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); |
|
} |
|
} |
|
|
|
// Fallback (shouldnโt reach here) |
|
throw new Error('Unexpected exit from retry loop'); |
|
} catch (error) { |
|
const errorMessage = error instanceof Error ? error.message : String(error); |
|
log(`โ Seeding setup failed: ${errorMessage}`); |
|
return { success: false, error: errorMessage, time: performance.now() - startTime }; |
|
} |
|
} |
|
|
|
// Check if database already has data |
|
async function checkDatabaseHasData(db: NodePgDatabase<AppSchema>): Promise<boolean> { |
|
const tables = ['users'] as const; // Adjust as needed |
|
for (const table of tables) { |
|
try { |
|
const result = await db.execute<{ exists: boolean }>(sql` |
|
SELECT EXISTS(SELECT 1 FROM ${sql.identifier(table)}) as exists |
|
`); |
|
if (result.rows[0]?.exists) { |
|
console.log(`โน๏ธ Found data in '${table}'`); |
|
return true; |
|
} |
|
} catch (error) { |
|
console.warn(`โ ๏ธ Error checking '${table}': ${error instanceof Error ? error.message : String(error)}`); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
// Run the seeder |
|
seedDatabase().then((result) => console.log('Seeding Result:', result)); |