claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
183 lines • 6.21 kB
JavaScript
/**
* Graph Edge Writer — ADR-130 Phase 1
*
* Provides a minimal interface for inserting rows into the graph_edges
* sql.js table defined by MEMORY_SCHEMA_V3.
*
* This module is intentionally thin: it opens the shared sql.js SQLite db
* (the same file used by memory-initializer storeEntry), ensures the
* graph_edges table exists, and returns a better-sqlite3-compatible
* prepared-statement interface.
*
* The module is designed for fire-and-forget callers — every public function
* suppresses errors internally so callers never need try/catch.
*
* @module v3/cli/memory/graph-edge-writer
*/
import * as path from 'path';
import * as fs from 'fs';
import * as crypto from 'crypto';
import { getMemoryRoot } from './memory-initializer.js';
import { encodeEmbedding } from './embedding-quantization.js';
// ============================================================================
// Lazy-cached sql.js db handle
// ============================================================================
let _db = null;
let _dbPath = '';
let _dbInitializing = false;
/**
* Return the sql.js Database instance for graph_edges writes.
* Creates the graph_edges table if it is absent (idempotent).
* Returns null if sql.js is not available or db cannot be opened.
*/
export async function getBridgeDb(customDbPath) {
const dbPath = customDbPath ?? path.join(getMemoryRoot(), 'memory.db');
if (_db && _dbPath === dbPath)
return _db;
if (_dbInitializing)
return null;
_dbInitializing = true;
try {
if (!fs.existsSync(dbPath))
return null;
const initSqlJs = (await import('sql.js')).default;
const SQL = await initSqlJs();
const fileBuffer = fs.readFileSync(dbPath);
const db = new SQL.Database(fileBuffer);
// Ensure graph_edges table exists (in case this is an older DB that
// predates ADR-130 Phase 1 schema migration).
db.run(`
CREATE TABLE IF NOT EXISTS graph_edges (
id TEXT PRIMARY KEY,
source_id TEXT NOT NULL,
target_id TEXT NOT NULL,
relation TEXT NOT NULL,
weight REAL DEFAULT 1.0,
confidence REAL DEFAULT 1.0,
decay_rate REAL DEFAULT 0.0,
last_reinforced TEXT,
witness_id TEXT,
embedding_ref TEXT,
metadata TEXT,
created_at TEXT NOT NULL
)
`);
db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges (relation)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_graph_edges_reinforced ON graph_edges (last_reinforced)`);
_db = db;
_dbPath = dbPath;
return db;
}
catch {
return null;
}
finally {
_dbInitializing = false;
}
}
/**
* Persist the in-memory sql.js database back to disk.
* Called after each write to keep the file consistent.
*/
async function flushDb(db) {
try {
if (!_dbPath)
return;
const data = db.export();
fs.writeFileSync(_dbPath, Buffer.from(data));
}
catch { /* non-fatal */ }
}
/**
* Insert a single edge into graph_edges.
* Fire-and-forget — errors are suppressed.
* Returns true if the write succeeded, false otherwise.
*/
export async function insertGraphEdge(input) {
try {
const db = await getBridgeDb(input.dbPath);
if (!db)
return false;
const id = `edge-${crypto.randomUUID()}`;
const createdAt = new Date().toISOString();
let embeddingRef = null;
if (input.embedding && input.embedding.length > 0) {
embeddingRef = encodeEmbedding(input.embedding);
}
const metaStr = input.metadata ? JSON.stringify(input.metadata) : null;
db.run(`INSERT OR IGNORE INTO graph_edges
(id, source_id, target_id, relation, weight, confidence, decay_rate,
last_reinforced, witness_id, embedding_ref, metadata, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
id,
input.sourceId,
input.targetId,
input.relation,
input.weight ?? 1.0,
input.confidence ?? 1.0,
input.decayRate ?? 0.0,
input.lastReinforced ?? null,
input.witnessId ?? null,
embeddingRef,
metaStr,
createdAt,
]);
await flushDb(db);
return true;
}
catch {
return false;
}
}
/**
* Query graph_edges by source_id.
* Returns rows or empty array on error.
*/
export async function queryEdgesBySource(sourceId, relation, dbPath) {
try {
const db = await getBridgeDb(dbPath);
if (!db)
return [];
const sql = relation
? `SELECT id, source_id, target_id, relation, weight FROM graph_edges WHERE source_id = ? AND relation = ? LIMIT 1000`
: `SELECT id, source_id, target_id, relation, weight FROM graph_edges WHERE source_id = ? LIMIT 1000`;
const args = relation ? [sourceId, relation] : [sourceId];
const result = db.exec(sql, args);
if (!result?.[0])
return [];
const cols = result[0].columns;
return result[0].values.map((row) => {
const obj = {};
cols.forEach((c, i) => { obj[c] = row[i]; });
return obj;
});
}
catch {
return [];
}
}
/**
* Count rows in graph_edges (for test assertions).
*/
export async function countGraphEdges(dbPath) {
try {
const db = await getBridgeDb(dbPath);
if (!db)
return 0;
const result = db.exec(`SELECT COUNT(*) FROM graph_edges`);
return result?.[0]?.values?.[0]?.[0] ?? 0;
}
catch {
return 0;
}
}
/**
* Reset the cached db handle (for tests that need a fresh DB).
*/
export function _resetBridgeDb() {
_db = null;
_dbPath = '';
}
//# sourceMappingURL=graph-edge-writer.js.map