@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
547 lines • 20.9 kB
JavaScript
;
/**
* RDF Files Storage Layer
*
* Implements RDF file-based storage for the Hybrid Storage Manager
* with file watching, backup capabilities, and TTL parsing.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RDFStorageLayer = void 0;
const events_1 = require("events");
const fs_1 = require("fs");
const path = __importStar(require("path"));
const chokidar = __importStar(require("chokidar"));
const glob_1 = require("glob");
const logger_1 = __importDefault(require("../../../utils/logger"));
const types_1 = require("./types");
class RDFStorageLayer extends events_1.EventEmitter {
config;
layer = types_1.StorageLayer.RDF_FILES;
files = new Map();
watcher;
syncTimer;
isInitialized = false;
metrics = {
totalFiles: 0,
totalSize: 0,
totalQueries: 0,
successfulQueries: 0,
failedQueries: 0,
totalResponseTime: 0,
lastSync: new Date(),
fileChanges: 0
};
constructor(config) {
super();
this.config = config;
}
/**
* Initialize the RDF storage layer
*/
async initialize() {
try {
logger_1.default.info('Initializing RDF Storage Layer');
// Ensure base directory exists
await fs_1.promises.mkdir(this.config.baseDirectory, { recursive: true });
// Load existing RDF files
await this.loadRDFFiles();
// Setup file watching if enabled
if (this.config.watchForChanges) {
this.setupFileWatcher();
}
// Setup sync timer if configured
if (this.config.syncInterval) {
this.setupSyncTimer();
}
// Setup backup directory if enabled
if (this.config.backupEnabled && this.config.backupDirectory) {
await fs_1.promises.mkdir(this.config.backupDirectory, { recursive: true });
}
this.isInitialized = true;
logger_1.default.info('RDF Storage Layer initialized successfully');
this.emit('initialized');
}
catch (error) {
logger_1.default.error('Failed to initialize RDF Storage Layer:', error);
throw new types_1.HybridStorageError(`RDF storage initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, types_1.StorageLayer.RDF_FILES, 'initialize', error instanceof Error ? error : undefined);
}
}
/**
* Execute a query against RDF files
*/
async query(query, params, context) {
if (!this.isInitialized) {
throw new types_1.HybridStorageError('RDF storage layer not initialized', types_1.StorageLayer.RDF_FILES, 'query');
}
const startTime = Date.now();
try {
// Execute query against RDF files
const results = await this.executeRDFQuery(query, params || {});
const executionTime = Date.now() - startTime;
this.updateMetrics(true, executionTime);
const queryResult = {
data: results,
source: types_1.StorageLayer.RDF_FILES,
executionTime,
cached: false,
timestamp: new Date(),
metadata: {
filesSearched: this.files.size,
resultCount: Array.isArray(results) ? results.length : 1,
totalFiles: this.metrics.totalFiles
}
};
this.emit('query_executed', { query, params, result: queryResult, context });
return queryResult;
}
catch (error) {
const executionTime = Date.now() - startTime;
this.updateMetrics(false, executionTime);
logger_1.default.error('RDF query failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.emit('query_failed', { query, params, error, context });
throw new types_1.HybridStorageError(`RDF query failed: ${errorMessage}`, types_1.StorageLayer.RDF_FILES, 'query', error instanceof Error ? error : undefined);
}
}
/**
* Create a new RDF file
*/
async create(data, _context) {
const fileName = this.generateFileName(data);
const filePath = path.join(this.config.baseDirectory, fileName);
// Convert data to TTL format
const ttlContent = this.convertToTTL(data);
// Create backup if enabled
if (this.config.backupEnabled) {
await this.createBackup(filePath);
}
// Write file
await fs_1.promises.writeFile(filePath, ttlContent, 'utf-8');
// Update internal tracking
const stats = await fs_1.promises.stat(filePath);
const rdfFile = {
path: filePath,
content: ttlContent,
lastModified: stats.mtime,
size: stats.size,
parsed: data
};
this.files.set(filePath, rdfFile);
this.updateFileMetrics();
const result = {
data: { filePath, ...data },
source: types_1.StorageLayer.RDF_FILES,
executionTime: 1,
cached: false,
timestamp: new Date()
};
this.emit('file_created', { filePath, size: stats.size });
return result;
}
/**
* Update an RDF file
*/
async update(filePath, data, _context) {
const existingFile = this.files.get(filePath);
if (!existingFile) {
throw new types_1.HybridStorageError(`RDF file not found: ${filePath}`, types_1.StorageLayer.RDF_FILES, 'update');
}
// Create backup if enabled
if (this.config.backupEnabled) {
await this.createBackup(filePath);
}
// Merge with existing data
const existingData = existingFile.parsed || this.parseTTL(existingFile.content);
const mergedData = { ...existingData, ...data };
// Convert to TTL and write
const ttlContent = this.convertToTTL(mergedData);
await fs_1.promises.writeFile(filePath, ttlContent, 'utf-8');
// Update tracking
const stats = await fs_1.promises.stat(filePath);
existingFile.content = ttlContent;
existingFile.lastModified = stats.mtime;
existingFile.size = stats.size;
existingFile.parsed = mergedData;
this.updateFileMetrics();
const result = {
data: { filePath, ...mergedData },
source: types_1.StorageLayer.RDF_FILES,
executionTime: 1,
cached: false,
timestamp: new Date()
};
this.emit('file_updated', { filePath, size: stats.size });
return result;
}
/**
* Delete an RDF file
*/
async delete(filePath, _context) {
const existingFile = this.files.get(filePath);
if (!existingFile) {
return {
data: false,
source: types_1.StorageLayer.RDF_FILES,
executionTime: 1,
cached: false,
timestamp: new Date()
};
}
// Create backup if enabled
if (this.config.backupEnabled) {
await this.createBackup(filePath);
}
// Delete file
await fs_1.promises.unlink(filePath);
// Remove from tracking
this.files.delete(filePath);
this.updateFileMetrics();
this.emit('file_deleted', { filePath });
return {
data: true,
source: types_1.StorageLayer.RDF_FILES,
executionTime: 1,
cached: false,
timestamp: new Date()
};
}
/**
* Perform health check
*/
async healthCheck() {
const startTime = Date.now();
try {
// Check if base directory is accessible
await fs_1.promises.access(this.config.baseDirectory);
// Check if we can read files
const testFiles = Array.from(this.files.keys()).slice(0, 3);
for (const filePath of testFiles) {
await fs_1.promises.access(filePath);
}
const responseTime = Date.now() - startTime;
return {
layer: types_1.StorageLayer.RDF_FILES,
status: responseTime < 100 ? 'healthy' : responseTime < 500 ? 'degraded' : 'unhealthy',
responseTime,
lastCheck: new Date(),
errorCount: this.metrics.failedQueries,
details: {
totalFiles: this.metrics.totalFiles,
totalSizeMB: this.metrics.totalSize / (1024 * 1024),
baseDirectory: this.config.baseDirectory,
watchingEnabled: this.config.watchForChanges,
backupEnabled: this.config.backupEnabled,
lastSync: this.metrics.lastSync,
fileChanges: this.metrics.fileChanges,
successRate: this.metrics.totalQueries > 0
? this.metrics.successfulQueries / this.metrics.totalQueries
: 0
}
};
}
catch (error) {
const responseTime = Date.now() - startTime;
return {
layer: types_1.StorageLayer.RDF_FILES,
status: 'unhealthy',
responseTime,
lastCheck: new Date(),
errorCount: this.metrics.failedQueries,
details: {
error: error instanceof Error ? error.message : 'Unknown error',
baseDirectory: this.config.baseDirectory
}
};
}
}
/**
* Get storage metrics
*/
async getMetrics() {
return {
layer: types_1.StorageLayer.RDF_FILES,
...this.metrics,
averageResponseTime: this.metrics.totalQueries > 0
? this.metrics.totalResponseTime / this.metrics.totalQueries
: 0,
successRate: this.metrics.totalQueries > 0
? this.metrics.successfulQueries / this.metrics.totalQueries
: 0,
averageFileSize: this.metrics.totalFiles > 0
? this.metrics.totalSize / this.metrics.totalFiles
: 0,
config: {
baseDirectory: this.config.baseDirectory,
filePattern: this.config.filePattern,
watchForChanges: this.config.watchForChanges,
syncInterval: this.config.syncInterval,
backupEnabled: this.config.backupEnabled
}
};
}
/**
* Shutdown the RDF storage layer
*/
async shutdown() {
try {
logger_1.default.info('Shutting down RDF Storage Layer');
// Stop file watcher
if (this.watcher) {
await this.watcher.close();
this.watcher = undefined;
}
// Clear sync timer
if (this.syncTimer) {
clearInterval(this.syncTimer);
this.syncTimer = undefined;
}
// Clear file cache
this.files.clear();
this.isInitialized = false;
logger_1.default.info('RDF Storage Layer shutdown completed');
this.emit('shutdown');
}
catch (error) {
logger_1.default.error('RDF Storage Layer shutdown failed:', error);
throw error;
}
}
// Private helper methods
async loadRDFFiles() {
try {
const pattern = path.join(this.config.baseDirectory, this.config.filePattern);
const filePaths = await (0, glob_1.glob)(pattern);
for (const filePath of filePaths) {
try {
const content = await fs_1.promises.readFile(filePath, 'utf-8');
const stats = await fs_1.promises.stat(filePath);
const rdfFile = {
path: filePath,
content,
lastModified: stats.mtime,
size: stats.size
};
this.files.set(filePath, rdfFile);
}
catch (error) {
logger_1.default.warn(`Failed to load RDF file ${filePath}:`, error);
}
}
this.updateFileMetrics();
logger_1.default.info(`Loaded ${this.files.size} RDF files`);
}
catch (error) {
logger_1.default.error('Failed to load RDF files:', error);
throw error;
}
}
setupFileWatcher() {
const watchPattern = path.join(this.config.baseDirectory, this.config.filePattern);
this.watcher = chokidar.watch(watchPattern, {
persistent: true,
ignoreInitial: true
});
this.watcher.on('add', (filePath) => {
this.handleFileChange('add', filePath);
});
this.watcher.on('change', (filePath) => {
this.handleFileChange('change', filePath);
});
this.watcher.on('unlink', (filePath) => {
this.handleFileChange('unlink', filePath);
});
this.watcher.on('error', (error) => {
logger_1.default.error('File watcher error:', error);
this.emit('watcher_error', error);
});
logger_1.default.debug('File watcher setup completed');
}
async handleFileChange(event, filePath) {
try {
this.metrics.fileChanges++;
if (event === 'unlink') {
this.files.delete(filePath);
this.emit('file_removed', { filePath });
}
else {
const content = await fs_1.promises.readFile(filePath, 'utf-8');
const stats = await fs_1.promises.stat(filePath);
const rdfFile = {
path: filePath,
content,
lastModified: stats.mtime,
size: stats.size
};
this.files.set(filePath, rdfFile);
this.emit('file_changed', { filePath, event, size: stats.size });
}
this.updateFileMetrics();
}
catch (error) {
logger_1.default.error(`Failed to handle file change for ${filePath}:`, error);
}
}
setupSyncTimer() {
if (!this.config.syncInterval)
return;
this.syncTimer = setInterval(() => {
this.syncFiles().catch(error => {
logger_1.default.error('File sync failed:', error);
});
}, this.config.syncInterval);
}
async syncFiles() {
try {
await this.loadRDFFiles();
this.metrics.lastSync = new Date();
this.emit('files_synced', { fileCount: this.files.size });
}
catch (error) {
logger_1.default.error('Failed to sync files:', error);
}
}
async executeRDFQuery(query, params) {
const results = [];
// Simple query processing for RDF files
for (const [filePath, rdfFile] of this.files) {
try {
// Parse RDF content if not already parsed
if (!rdfFile.parsed) {
rdfFile.parsed = this.parseTTL(rdfFile.content);
}
// Apply query logic
if (this.matchesQuery(rdfFile.parsed, query, params)) {
results.push({
filePath,
data: rdfFile.parsed,
lastModified: rdfFile.lastModified,
size: rdfFile.size
});
}
}
catch (error) {
logger_1.default.warn(`Failed to process RDF file ${filePath}:`, error);
}
}
return results;
}
matchesQuery(data, query, params) {
const lowerQuery = query.toLowerCase();
// Simple text-based matching
if (lowerQuery.includes('select') || lowerQuery.includes('find')) {
// Apply parameter filters
for (const [key, value] of Object.entries(params)) {
if (data[key] !== value) {
return false;
}
}
return true;
}
// Default: include all files
return true;
}
parseTTL(content) {
// Simple TTL parser - in a real implementation, use a proper RDF library
const data = {};
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('#') || !trimmed)
continue;
// Simple triple parsing
const match = trimmed.match(/(\S+)\s+(\S+)\s+(.+)\s*\./);
if (match) {
const [, subject, predicate, object] = match;
if (!data[subject])
data[subject] = {};
data[subject][predicate] = object.replace(/[";]/g, '');
}
}
return data;
}
convertToTTL(data) {
// Simple TTL generation - in a real implementation, use a proper RDF library
let ttl = '@prefix : <http://example.org/> .\n\n';
for (const [subject, predicates] of Object.entries(data)) {
if (typeof predicates === 'object' && predicates !== null) {
for (const [predicate, object] of Object.entries(predicates)) {
ttl += `:${subject} :${predicate} "${object}" .\n`;
}
}
}
return ttl;
}
generateFileName(data) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const id = data.id || data.name || 'unknown';
return `${id}-${timestamp}.module-knowledge.ttl`;
}
async createBackup(filePath) {
if (!this.config.backupEnabled || !this.config.backupDirectory)
return;
try {
const fileName = path.basename(filePath);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFileName = `${timestamp}-${fileName}`;
const backupPath = path.join(this.config.backupDirectory, backupFileName);
await fs_1.promises.copyFile(filePath, backupPath);
logger_1.default.debug(`Created backup: ${backupPath}`);
}
catch (error) {
logger_1.default.warn(`Failed to create backup for ${filePath}:`, error);
}
}
updateFileMetrics() {
this.metrics.totalFiles = this.files.size;
this.metrics.totalSize = Array.from(this.files.values()).reduce((sum, file) => sum + file.size, 0);
}
updateMetrics(success, responseTime) {
this.metrics.totalQueries++;
this.metrics.totalResponseTime += responseTime;
if (success) {
this.metrics.successfulQueries++;
}
else {
this.metrics.failedQueries++;
}
}
}
exports.RDFStorageLayer = RDFStorageLayer;
exports.default = RDFStorageLayer;
//# sourceMappingURL=RDFStorageLayer.js.map