agentdb
Version:
AgentDB - Frontier Memory Features with MCP Integration and Direct Vector Search: Causal reasoning, reflexion memory, skill library, automated learning, and raw vector similarity queries. 150x faster vector search. Full Claude Desktop support via Model Co
229 lines (194 loc) • 6.06 kB
text/typescript
/**
* Database System using sql.js (WASM SQLite)
* Pure JavaScript implementation with NO build dependencies
*
* SECURITY: Fixed SQL injection vulnerabilities:
* - PRAGMA commands validated against whitelist
* - Removed eval() usage (replaced with async import)
*/
import { validatePragmaCommand, ValidationError } from './security/input-validation.js';
import * as fs from 'fs';
import * as path from 'path';
// Type-only for compatibility
type Database = any;
let sqlJsWrapper: any = null;
/**
* Get sql.js database implementation (ONLY sql.js, no better-sqlite3)
*/
export async function getDatabaseImplementation(): Promise<any> {
// Return cached wrapper
if (sqlJsWrapper) {
return sqlJsWrapper;
}
try {
console.log('✅ Using sql.js (WASM SQLite, no build tools required)');
// sql.js requires async initialization
const mod = await import('sql.js');
const SQL = await mod.default();
// Create database wrapper
sqlJsWrapper = createSqlJsWrapper(SQL);
return sqlJsWrapper;
} catch (error) {
console.error('❌ Failed to initialize sql.js:', (error as Error).message);
throw new Error(
'Failed to initialize SQLite. Please ensure sql.js is installed:\n' +
'npm install sql.js'
);
}
}
/**
* Create a better-sqlite3 compatible wrapper around sql.js
* This allows AgentDB to work (with reduced performance) without native compilation
*/
function createSqlJsWrapper(SQL: any) {
return class SqlJsDatabase {
private db: any;
private filename: string;
constructor(filename: string, options?: any) {
this.filename = filename;
// In-memory database
if (filename === ':memory:') {
this.db = new SQL.Database();
} else {
// File-based database - use safe fs module (no eval)
try {
if (fs.existsSync(filename)) {
const buffer = fs.readFileSync(filename);
this.db = new SQL.Database(buffer);
} else {
this.db = new SQL.Database();
}
} catch (error) {
console.warn('⚠️ Could not read database file:', (error as Error).message);
this.db = new SQL.Database();
}
}
}
prepare(sql: string) {
const stmt = this.db.prepare(sql);
return {
run: (...params: any[]) => {
stmt.bind(params);
stmt.step();
stmt.reset();
return {
changes: this.db.getRowsModified(),
lastInsertRowid: this.db.exec('SELECT last_insert_rowid()')[0]?.values[0]?.[0] || 0
};
},
get: (...params: any[]) => {
stmt.bind(params);
const hasRow = stmt.step();
if (!hasRow) {
stmt.reset();
return undefined;
}
const columns = stmt.getColumnNames();
const values = stmt.get();
stmt.reset();
const result: any = {};
columns.forEach((col: string, idx: number) => {
result[col] = values[idx];
});
return result;
},
all: (...params: any[]) => {
stmt.bind(params);
const results: any[] = [];
while (stmt.step()) {
const columns = stmt.getColumnNames();
const values = stmt.get();
const result: any = {};
columns.forEach((col: string, idx: number) => {
result[col] = values[idx];
});
results.push(result);
}
stmt.reset();
return results;
},
finalize: () => {
stmt.free();
}
};
}
exec(sql: string) {
return this.db.exec(sql);
}
save() {
// Save to file if needed
if (this.filename !== ':memory:') {
try {
// Create parent directories if they don't exist
const dir = path.dirname(this.filename);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const data = this.db.export();
fs.writeFileSync(this.filename, Buffer.from(data));
} catch (error) {
console.error('❌ Could not save database to file:', (error as Error).message);
throw error;
}
}
}
close() {
// Save to file before closing
this.save();
this.db.close();
}
pragma(pragma: string, options?: any) {
try {
// SECURITY: Validate PRAGMA command against whitelist to prevent SQL injection
const validatedPragma = validatePragmaCommand(pragma);
// Execute validated PRAGMA
const result = this.db.exec(`PRAGMA ${validatedPragma}`);
return result[0]?.values[0]?.[0];
} catch (error) {
if (error instanceof ValidationError) {
console.error(`❌ Invalid PRAGMA command: ${error.message}`);
throw error;
}
throw error;
}
}
transaction(fn: () => any) {
// Return a function that executes the transaction when called
// This matches better-sqlite3 API where transaction() returns a callable function
return () => {
try {
this.db.exec('BEGIN TRANSACTION');
const result = fn();
this.db.exec('COMMIT');
return result;
} catch (error) {
this.db.exec('ROLLBACK');
throw error;
}
};
}
};
}
/**
* Create a database instance using sql.js
*/
export async function createDatabase(filename: string, options?: any): Promise<any> {
const DatabaseImpl = await getDatabaseImplementation();
return new DatabaseImpl(filename, options);
}
/**
* Get information about current database implementation
*/
export function getDatabaseInfo(): {
implementation: string;
isNative: boolean;
performance: 'high' | 'medium' | 'low';
requiresBuildTools: boolean;
} {
return {
implementation: 'sql.js (WASM)',
isNative: false,
performance: 'medium',
requiresBuildTools: false
};
}