UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

247 lines (246 loc) 7.49 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import Database from "better-sqlite3"; import { trace } from "./debug-trace.js"; import { logger } from "../monitoring/logger.js"; function createTracedDatabase(filename, options) { const db = new Database(filename, options); if (options?.traceEnabled !== false) { return wrapDatabase(db, options?.slowQueryThreshold); } return db; } function wrapDatabase(db, slowQueryThreshold = 100) { const originalPrepare = db.prepare.bind(db); db.prepare = function(source) { const statement = originalPrepare(source); return wrapStatement(statement, source, slowQueryThreshold); }; const originalExec = db.exec.bind(db); db.exec = function(source) { return trace.traceSync( "query", `EXEC: ${source.substring(0, 50)}...`, {}, () => { const startTime = performance.now(); const result = originalExec(source); const duration = performance.now() - startTime; if (duration > slowQueryThreshold) { logger.warn(`Slow query detected: ${duration.toFixed(0)}ms`, { query: source.substring(0, 200), duration }); } return result; } ); }; const originalTransaction = db.transaction.bind(db); db.transaction = function(fn) { return originalTransaction(function(...args) { return trace.traceSync( "query", "TRANSACTION", { args: args.length }, () => { return fn.apply(this, args); } ); }); }; db.__queryStats = { totalQueries: 0, slowQueries: 0, totalDuration: 0, queryTypes: {} }; return db; } function wrapStatement(statement, source, slowQueryThreshold) { const queryType = source.trim().split(/\s+/)[0].toUpperCase(); const shortQuery = source.substring(0, 100).replace(/\s+/g, " "); const originalRun = statement.run.bind(statement); statement.run = function(...params) { return trace.traceSync( "query", `${queryType}: ${shortQuery}`, params, () => { const startTime = performance.now(); const result = originalRun(...params); const duration = performance.now() - startTime; updateQueryStats(statement, queryType, duration, slowQueryThreshold); if (duration > slowQueryThreshold) { logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, { query: shortQuery, params, duration, changes: result.changes }); } return result; } ); }; const originalGet = statement.get.bind(statement); statement.get = function(...params) { return trace.traceSync( "query", `${queryType} (get): ${shortQuery}`, params, () => { const startTime = performance.now(); const result = originalGet(...params); const duration = performance.now() - startTime; updateQueryStats(statement, queryType, duration, slowQueryThreshold); if (duration > slowQueryThreshold) { logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, { query: shortQuery, params, duration, found: result != null }); } return result; } ); }; const originalAll = statement.all.bind(statement); statement.all = function(...params) { return trace.traceSync( "query", `${queryType} (all): ${shortQuery}`, params, () => { const startTime = performance.now(); const result = originalAll(...params); const duration = performance.now() - startTime; updateQueryStats(statement, queryType, duration, slowQueryThreshold); if (duration > slowQueryThreshold) { logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, { query: shortQuery, params, duration, rows: result.length }); } if (result.length > 100 && queryType === "SELECT") { logger.warn(`Large result set: ${result.length} rows`, { query: shortQuery, suggestion: "Consider pagination or more specific queries" }); } return result; } ); }; const originalIterate = statement.iterate.bind(statement); statement.iterate = function(...params) { const startTime = performance.now(); let rowCount = 0; const iterator = originalIterate(...params); const wrappedIterator = { [Symbol.iterator]() { return this; }, next() { const result = iterator.next(); if (!result.done) { rowCount++; } else { const duration = performance.now() - startTime; updateQueryStats(statement, queryType, duration, slowQueryThreshold); if (duration > slowQueryThreshold) { logger.warn( `Slow ${queryType} iteration: ${duration.toFixed(0)}ms`, { query: shortQuery, params, duration, rows: rowCount } ); } } return result; } }; return wrappedIterator; }; return statement; } function updateQueryStats(statement, queryType, duration, slowQueryThreshold) { const db = statement.database; if (db.__queryStats) { db.__queryStats.totalQueries++; db.__queryStats.totalDuration += duration; if (duration > slowQueryThreshold) { db.__queryStats.slowQueries++; } if (!db.__queryStats.queryTypes[queryType]) { db.__queryStats.queryTypes[queryType] = 0; } db.__queryStats.queryTypes[queryType]++; } } function getQueryStatistics(db) { const stats = db.__queryStats; if (!stats) return null; return { ...stats, averageDuration: stats.totalQueries > 0 ? stats.totalDuration / stats.totalQueries : 0 }; } async function traceQuery(db, queryName, query, params, fn) { return trace.traceAsync("query", queryName, { query, params }, async () => { try { const result = fn(); if (query.includes("JOIN") || query.includes("GROUP BY")) { logger.debug(`Complex query executed: ${queryName}`, { query: query.substring(0, 200), params }); } return result; } catch (error) { logger.error(`Database query failed: ${queryName}`, error, { query, params, errorCode: error.code }); throw error; } }); } function createTracedTransaction(db, name, fn) { return trace.traceSync("query", `TRANSACTION: ${name}`, {}, () => { const startTime = performance.now(); try { const tx = db.transaction(fn); const result = tx.deferred(); const duration = performance.now() - startTime; logger.info(`Transaction completed: ${name}`, { duration, success: true }); return result; } catch (error) { const duration = performance.now() - startTime; logger.error(`Transaction failed: ${name}`, error, { duration, success: false }); throw error; } }); } export { createTracedDatabase, createTracedTransaction, getQueryStatistics, traceQuery, wrapDatabase };