#!/usr/bin/env node import 'dotenv/config'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { performance } from 'node:perf_hooks'; import chalk from 'chalk'; import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; import { MssqlProvider } from '../src/providers/mssql/index.js'; import { MetricsCollector, instrumentStore } from './benchmark/instrument.js'; import { fillDatabase, benchmarkFilledDatabase, testLifecycleDeletions } from './benchmark/workload.js'; import { BenchmarkReporter } from './benchmark/reporter.js'; // CLI argument parsing function parseArgs() { const args = process.argv.slice(2); const options = { driver: 'both', scale: 'small', iterations: 5, reportFile: 'BENCHMARK_REPORT.md', keepDb: false, }; for (const arg of args) { if (arg.startsWith('--driver=')) { options.driver = arg.split('=')[1].toLowerCase(); } else if (arg.startsWith('--scale=')) { options.scale = arg.split('=')[1].toLowerCase(); } else if (arg.startsWith('--iterations=')) { options.iterations = parseInt(arg.split('=')[1], 10); } else if (arg.startsWith('--report-file=')) { options.reportFile = arg.split('=')[1]; } else if (arg === '--keep-db') { options.keepDb = true; } } return options; } async function runSqliteBenchmark(options) { console.log(chalk.blue(`\n▶ Starting SQLite benchmark (scale: ${options.scale}, iterations: ${options.iterations})...`)); const tempDir = mkdtempSync(join(tmpdir(), 'libdashstore-bench-sqlite-')); const dbFile = join(tempDir, 'benchmark.db'); const storageDir = join(tempDir, 'storage'); const provider = new BetterSqlite3Provider({ filename: dbFile, storageDir, }); await provider.initialize(); const rawStore = await LibDashStore.create(provider); const metrics = new MetricsCollector(); const { store } = instrumentStore(rawStore, metrics); const startTotal = performance.now(); try { console.log(chalk.dim(' 1. Filling SQLite database using domain APIs...')); const fillStart = performance.now(); const ctx = await fillDatabase(store, options); console.log(chalk.green(` ✓ Filled SQLite DB in ${((performance.now() - fillStart) / 1000).toFixed(2)}s`)); console.log(chalk.dim(' 2. Benchmarking queries and lookups on filled database...')); const benchStart = performance.now(); await benchmarkFilledDatabase(store, ctx, options); console.log(chalk.green(` ✓ Read queries completed in ${((performance.now() - benchStart) / 1000).toFixed(2)}s`)); console.log(chalk.dim(' 3. Testing lifecycle deletions...')); const delStart = performance.now(); await testLifecycleDeletions(store, ctx); console.log(chalk.green(` ✓ Deletions completed in ${((performance.now() - delStart) / 1000).toFixed(2)}s`)); const totalElapsedMs = performance.now() - startTotal; const stats = metrics.getAllStats(); return { stats, totalElapsedMs }; } finally { provider.close(); if (!options.keepDb) { try { rmSync(tempDir, { recursive: true, force: true }); } catch { // ignore } } else { console.log(chalk.dim(` [i] SQLite test database kept at: ${tempDir}`)); } } } async function runMssqlBenchmark(options) { console.log(chalk.blue(`\n▶ Starting MSSQL benchmark (scale: ${options.scale}, iterations: ${options.iterations})...`)); const config = { server: process.env.MSSQL_SERVER || '127.0.0.1', database: process.env.MSSQL_DATABASE || 'dashback', user: process.env.MSSQL_USER || 'dashback', password: process.env.MSSQL_PASSWORD || 'dashback', options: { encrypt: false, trustServerCertificate: true, }, }; const provider = new MssqlProvider(config); await provider.ensurePool(); console.log(chalk.dim(' Resetting MSSQL schema (purge & init)...')); await provider.purge(); await provider.initialize(); const rawStore = await LibDashStore.create(provider); const metrics = new MetricsCollector(); const { store } = instrumentStore(rawStore, metrics); const startTotal = performance.now(); try { console.log(chalk.dim(' 1. Filling MSSQL database using domain APIs...')); const fillStart = performance.now(); const ctx = await fillDatabase(store, options); console.log(chalk.green(` ✓ Filled MSSQL DB in ${((performance.now() - fillStart) / 1000).toFixed(2)}s`)); console.log(chalk.dim(' 2. Benchmarking queries and lookups on filled database...')); const benchStart = performance.now(); await benchmarkFilledDatabase(store, ctx, options); console.log(chalk.green(` ✓ Read queries completed in ${((performance.now() - benchStart) / 1000).toFixed(2)}s`)); console.log(chalk.dim(' 3. Testing lifecycle deletions...')); const delStart = performance.now(); await testLifecycleDeletions(store, ctx); console.log(chalk.green(` ✓ Deletions completed in ${((performance.now() - delStart) / 1000).toFixed(2)}s`)); const totalElapsedMs = performance.now() - startTotal; const stats = metrics.getAllStats(); return { stats, totalElapsedMs }; } finally { await provider.close(); } } async function main() { const options = parseArgs(); console.log(chalk.bold.yellow(`========================================================================`)); console.log(chalk.bold.yellow(` LIBDASHSTORE DATABASE DRIVERS TEST & BENCHMARK SYSTEM`)); console.log(chalk.bold.yellow(`========================================================================`)); console.log(`Driver mode : ${chalk.cyan(options.driver)}`); console.log(`Scale : ${chalk.cyan(options.scale)}`); console.log(`Iterations : ${chalk.cyan(options.iterations)}`); console.log(`Report File : ${chalk.cyan(options.reportFile)}`); let sqliteResult = null; let mssqlResult = null; if (options.driver === 'both' || options.driver === 'sqlite') { sqliteResult = await runSqliteBenchmark(options); BenchmarkReporter.printDriverReport('better-sqlite3', sqliteResult.stats, sqliteResult.totalElapsedMs); } if (options.driver === 'both' || options.driver === 'mssql') { mssqlResult = await runMssqlBenchmark(options); BenchmarkReporter.printDriverReport('mssql', mssqlResult.stats, mssqlResult.totalElapsedMs); } if (sqliteResult && mssqlResult) { BenchmarkReporter.printComparisonReport( mssqlResult.stats, sqliteResult.stats, mssqlResult.totalElapsedMs, sqliteResult.totalElapsedMs, ); } const markdown = BenchmarkReporter.generateMarkdownReport({ mssqlStats: mssqlResult?.stats, sqliteStats: sqliteResult?.stats, mssqlElapsedMs: mssqlResult?.totalElapsedMs, sqliteElapsedMs: sqliteResult?.totalElapsedMs, options, }); if (options.reportFile) { BenchmarkReporter.saveReportToFile(options.reportFile, markdown); } console.log(chalk.bold.green(`\n✓ All database driver tests & benchmarks finished successfully!`)); } main().catch((err) => { console.error(chalk.bold.red(`\nBenchmark failed with error:`), err); process.exit(1); });