Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
WatchConfigManager.ts8.74 kB
// ============================================================================ // WatchConfigManager - Neo4j CRUD for WatchConfig nodes // ============================================================================ import { randomUUID } from 'node:crypto'; import type { Driver } from 'neo4j-driver'; import type { WatchConfig, WatchConfigInput } from '../types/index.js'; export class WatchConfigManager { constructor(private driver: Driver) {} /** * Create a new watch configuration in Neo4j * * Stores watch configuration for file monitoring with indexing settings. * Used by FileWatchManager to persist watch state across restarts. * * @param input - Watch configuration parameters * @returns Created watch configuration with generated ID * * @example * const manager = new WatchConfigManager(driver); * const config = await manager.createWatch({ * path: '/Users/user/project/src', * host_path: '/Users/user/project/src', * recursive: true, * generate_embeddings: true * }); * console.log('Created watch:', config.id); */ async createWatch(input: WatchConfigInput): Promise<WatchConfig> { const session = this.driver.session(); try { const id = `watch-${Date.now()}-${randomUUID().substring(0, 8)}`; const now = new Date().toISOString(); const result = await session.run(` CREATE (w:WatchConfig:Node { id: $id, type: 'watchConfig', path: $path, host_path: $host_path, recursive: $recursive, debounce_ms: $debounce_ms, file_patterns: $file_patterns, ignore_patterns: $ignore_patterns, generate_embeddings: $generate_embeddings, status: 'active', added_date: $added_date, last_updated: $added_date, files_indexed: 0 }) RETURN w `, { id, path: input.path, host_path: input.host_path || null, recursive: input.recursive ?? true, debounce_ms: input.debounce_ms ?? 500, file_patterns: input.file_patterns ?? null, ignore_patterns: input.ignore_patterns ?? [], generate_embeddings: input.generate_embeddings ?? false, added_date: now }); const node = result.records[0].get('w'); return this.mapToWatchConfig(node.properties); } finally { await session.close(); } } /** * Get active watch configuration by path * * @param path - Directory path being watched * @returns Watch configuration or null if not found * * @example * const config = await manager.getByPath('/Users/user/project/src'); * if (config) { * console.log('Watch status:', config.status); * console.log('Files indexed:', config.files_indexed); * } */ async getByPath(path: string): Promise<WatchConfig | null> { const session = this.driver.session(); try { const result = await session.run(` MATCH (w:WatchConfig {path: $path, status: 'active'}) RETURN w `, { path }); if (result.records.length === 0) { return null; } const node = result.records[0].get('w'); return this.mapToWatchConfig(node.properties); } finally { await session.close(); } } /** * Get watch configuration by ID * * @param id - Watch configuration ID * @returns Watch configuration or null if not found * * @example * const config = await manager.getById('watch-1234-abcd'); */ async getById(id: string): Promise<WatchConfig | null> { const session = this.driver.session(); try { const result = await session.run(` MATCH (w:WatchConfig {id: $id}) RETURN w `, { id }); if (result.records.length === 0) { return null; } const node = result.records[0].get('w'); return this.mapToWatchConfig(node.properties); } finally { await session.close(); } } /** * List all watch configurations (active and inactive) * * @returns Array of all watch configurations, sorted by status and date * * @example * const configs = await manager.listAll(); * console.log('Total watches:', configs.length); * const active = configs.filter(c => c.status === 'active'); * console.log('Active:', active.length); */ async listAll(): Promise<WatchConfig[]> { const session = this.driver.session(); try { const result = await session.run(` MATCH (w:WatchConfig) RETURN w ORDER BY w.status DESC, w.added_date ASC `); // Ensure records is an array before mapping if (!result.records || !Array.isArray(result.records)) { return []; } return result.records.map(record => { const node = record.get('w'); return this.mapToWatchConfig(node.properties); }); } finally { await session.close(); } } /** * Reactivate an inactive watch configuration * * @param id - Watch configuration ID to reactivate * @throws {Error} If watch configuration not found * * @example * await manager.reactivate('watch-1234-abcd'); * console.log('Watch reactivated'); */ async reactivate(id: string): Promise<void> { const session = this.driver.session(); try { const result = await session.run(` MATCH (w:WatchConfig {id: $id}) SET w.status = 'active', w.error = null, w.last_updated = datetime() RETURN w `, { id }); if (result.records.length === 0) { throw new Error(`Watch configuration with ID ${id} not found`); } console.log(`✅ Reactivated watch config ${id} in database`); } finally { await session.close(); } } /** * Update watch statistics after indexing * * @param id - Watch configuration ID * @param filesIndexed - Total number of files indexed * * @example * await manager.updateStats('watch-1234-abcd', 150); */ async updateStats(id: string, filesIndexed: number): Promise<void> { const session = this.driver.session(); try { await session.run(` MATCH (w:WatchConfig {id: $id}) SET w.files_indexed = $filesIndexed, w.last_indexed = datetime(), w.last_updated = datetime() `, { id, filesIndexed }); } finally { await session.close(); } } /** * Mark watch as inactive with optional error * * @param id - Watch configuration ID * @param error - Optional error message * * @example * await manager.markInactive('watch-1234-abcd', 'Directory not found'); */ async markInactive(id: string, error?: string): Promise<void> { const session = this.driver.session(); try { await session.run(` MATCH (w:WatchConfig {id: $id}) SET w.status = 'inactive', w.error = $error, w.last_updated = datetime() `, { id, error: error || null }); } finally { await session.close(); } } /** * Delete watch configuration from database * * @param id - Watch configuration ID to delete * * @example * await manager.delete('watch-1234-abcd'); * console.log('Watch configuration deleted'); */ async delete(id: string): Promise<void> { const session = this.driver.session(); try { await session.run(` MATCH (w:WatchConfig {id: $id}) DETACH DELETE w `, { id }); } finally { await session.close(); } } /** * Map Neo4j properties to WatchConfig */ private mapToWatchConfig(props: any): WatchConfig { // Helper to convert Neo4j Integer to JS number const toNumber = (value: any): number => { if (value === null || value === undefined) return 0; if (typeof value === 'number') return value; if (typeof value === 'object' && 'toNumber' in value) { return value.toNumber(); } return 0; }; return { id: props.id, path: props.path, host_path: props.host_path, recursive: props.recursive, debounce_ms: props.debounce_ms, file_patterns: props.file_patterns, ignore_patterns: props.ignore_patterns || [], generate_embeddings: props.generate_embeddings || false, status: props.status, added_date: props.added_date, last_indexed: props.last_indexed, last_updated: props.last_updated, files_indexed: toNumber(props.files_indexed), error: props.error }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server