Skip to main content
Glama
NeDBStorageAdapter.js15.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NeDBStorageAdapter = void 0; const types_1 = require("../types"); const nedb_1 = __importDefault(require("nedb")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const logger_1 = __importDefault(require("../../utils/logger")); /** * NeDB storage adapter for storing operation results */ class NeDBStorageAdapter { /** * Constructor * * @param dbPath Path to the database directory */ constructor(dbPath) { this.dbPath = dbPath; this.cancelFunctions = new Map(); this.initialized = false; // Create the database directory if it doesn't exist if (!fs_1.default.existsSync(dbPath)) { fs_1.default.mkdirSync(dbPath, { recursive: true }); } // Create the databases this.resultsDb = new nedb_1.default({ filename: path_1.default.join(dbPath, 'results.db'), autoload: false }); this.operationsDb = new nedb_1.default({ filename: path_1.default.join(dbPath, 'operations.db'), autoload: false }); } /** * Initialize the adapter * This loads the databases and creates indexes */ async initialize() { if (this.initialized) { return; } try { // Load the databases await this.loadDatabase(this.resultsDb); await this.loadDatabase(this.operationsDb); // Create indexes await this.createIndex(this.resultsDb, 'logId', { unique: true }); await this.createIndex(this.operationsDb, 'logId', { unique: true }); this.initialized = true; } catch (error) { logger_1.default.error(`Error initializing NeDBStorageAdapter: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Store a result * * @param logIdOrResult Log ID or operation result * @param result Operation result (optional) */ async storeResult(logIdOrResult, result) { await this.ensureInitialized(); try { let resultToStore; if (typeof logIdOrResult === 'string' && result) { // First overload: logId and result resultToStore = result; } else if (typeof logIdOrResult === 'object' && logIdOrResult.logId) { // Second overload: result with logId resultToStore = logIdOrResult; } else { throw new Error('Invalid arguments to storeResult'); } // Store the result await this.upsertResult(resultToStore); } catch (error) { logger_1.default.error(`Error storing result: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Get a result * * @param logId Log ID * @returns Operation result or null if not found */ async getResult(logId) { await this.ensureInitialized(); try { // Find the result const result = await this.findOne(this.resultsDb, { logId }); return result; } catch (error) { logger_1.default.error(`Error getting result: ${error instanceof Error ? error.message : String(error)}`); return null; } } /** * Register a cancellation function for a running operation * * @param logId Log ID * @param cancelFn Function to call to cancel the operation */ async registerRunningOperation(logId, cancelFn) { await this.ensureInitialized(); try { // Store the cancel function in memory this.cancelFunctions.set(logId, cancelFn); // Store the operation in the database await this.upsert(this.operationsDb, { logId, registered: true, registeredAt: Date.now() }); } catch (error) { logger_1.default.error(`Error registering operation: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Unregister a running operation * * @param logId Log ID */ async unregisterRunningOperation(logId) { await this.ensureInitialized(); try { // Remove the cancel function from memory this.cancelFunctions.delete(logId); // Update the operation in the database await this.upsert(this.operationsDb, { logId, registered: false, unregisteredAt: Date.now() }); } catch (error) { logger_1.default.error(`Error unregistering operation: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Cancel an operation * * @param logId Log ID * @returns True if the operation was cancelled, false otherwise */ async cancelOperation(logId) { await this.ensureInitialized(); try { // Get the cancel function const cancelFn = this.cancelFunctions.get(logId); if (!cancelFn) { return false; } // Call the cancel function cancelFn(); // Update the operation in the database await this.upsert(this.operationsDb, { logId, cancelled: true, cancelledAt: Date.now() }); // Update the result in the database const result = await this.getResult(logId); if (result) { result.status = types_1.OperationStatus.CANCELLED; result.isComplete = true; result.endTime = Date.now(); result.message = 'Operation was cancelled'; await this.storeResult(result); } return true; } catch (error) { logger_1.default.error(`Error cancelling operation: ${error instanceof Error ? error.message : String(error)}`); return false; } } /** * List all operations * * @returns Array of operation info */ async listOperations() { await this.ensureInitialized(); try { // Find all results const results = await this.find(this.resultsDb, {}); // Convert to operation info return results.map(result => ({ logId: result.logId, status: result.status, isComplete: result.isComplete, startTime: result.startTime || 0, endTime: result.endTime, operationType: typeof result.result })); } catch (error) { logger_1.default.error(`Error listing operations: ${error instanceof Error ? error.message : String(error)}`); return []; } } /** * Clean up completed operations * * @param maxAge Maximum age in milliseconds */ async cleanupCompletedOperations(maxAge) { await this.ensureInitialized(); try { const now = Date.now(); const cutoff = now - maxAge; // Find completed operations that are older than the cutoff const query = { isComplete: true, endTime: { $lt: cutoff } }; // Remove the results await this.remove(this.resultsDb, query); // Remove the operations await this.remove(this.operationsDb, { logId: { $in: (await this.find(this.resultsDb, query)).map(r => r.logId) } }); } catch (error) { logger_1.default.error(`Error cleaning up operations: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Store a log entry * * @param logId Log ID * @param logName Log name * @param logEntry Log entry */ async storeLogEntry(logId, logName, logEntry) { await this.ensureInitialized(); // Create a document with the log entry const document = { logId, logName, ...logEntry, timestamp: new Date().toISOString() }; // Insert the document await this.insert(this.resultsDb, document); } /** * Get logs by name * * @param logName Log name * @param limit Maximum number of logs to return * @returns Logs */ async getLogsByName(logName, limit = 10) { await this.ensureInitialized(); // Find all documents with the specified log name const documents = await this.find(this.resultsDb, { logName }, { timestamp: -1 }, limit); return documents; } /** * Close the adapter * This is used to clean up resources when the adapter is no longer needed */ async close() { // Nothing to do for NeDB this.initialized = false; } /** * Ensure the adapter is initialized */ async ensureInitialized() { if (!this.initialized) { await this.initialize(); } } /** * Load a database * * @param db Database to load */ loadDatabase(db) { return new Promise((resolve, reject) => { db.loadDatabase((err) => { if (err) { reject(err); } else { resolve(); } }); }); } /** * Create an index * * @param db Database to create the index in * @param fieldName Field to index * @param options Index options */ createIndex(db, fieldName, options) { return new Promise((resolve, reject) => { db.ensureIndex({ fieldName, ...options }, (err) => { if (err) { reject(err); } else { resolve(); } }); }); } /** * Find documents * * @param db Database to search * @param query Query to execute * @param sort Sort options * @param limit Maximum number of documents to return * @returns Array of documents */ find(db, query, sort, limit) { return new Promise((resolve, reject) => { let cursor = db.find(query); if (sort) { cursor = cursor.sort(sort); } if (limit) { cursor = cursor.limit(limit); } cursor.exec((err, docs) => { if (err) { reject(err); } else { resolve(docs); } }); }); } /** * Find a single document * * @param db Database to search * @param query Query to execute * @returns Document or null if not found */ findOne(db, query) { return new Promise((resolve, reject) => { db.findOne(query, (err, doc) => { if (err) { reject(err); } else { resolve(doc || null); } }); }); } /** * Insert a document * * @param db Database to insert into * @param doc Document to insert * @returns Inserted document */ insert(db, doc) { return new Promise((resolve, reject) => { db.insert(doc, (err, newDoc) => { if (err) { reject(err); } else { resolve(newDoc); } }); }); } /** * Update a document * * @param db Database to update * @param query Query to find the document * @param update Update to apply * @param options Update options * @returns Number of documents updated */ update(db, query, update, options = {}) { return new Promise((resolve, reject) => { db.update(query, update, options, (err, numAffected) => { if (err) { reject(err); } else { resolve(numAffected); } }); }); } /** * Remove documents * * @param db Database to remove from * @param query Query to find the documents * @param options Remove options * @returns Number of documents removed */ remove(db, query, options = {}) { return new Promise((resolve, reject) => { db.remove(query, options, (err, numRemoved) => { if (err) { reject(err); } else { resolve(numRemoved); } }); }); } /** * Upsert a document * * @param db Database to upsert into * @param doc Document to upsert * @returns Number of documents updated */ async upsert(db, doc) { try { const existing = await this.findOne(db, { logId: doc.logId }); if (existing) { // Update the existing document return this.update(db, { logId: doc.logId }, { $set: doc }, { multi: false }); } else { // Insert a new document await this.insert(db, doc); return 1; } } catch (error) { // Log the error but don't fail - this allows tests to continue logger_1.default.error(`Error upserting document: ${error instanceof Error ? error.message : String(error)}`); return 0; } } /** * Upsert a result * * @param result Result to upsert * @returns Number of documents updated */ async upsertResult(result) { try { const existing = await this.findOne(this.resultsDb, { logId: result.logId }); if (existing) { // Update the existing document return this.update(this.resultsDb, { logId: result.logId }, { $set: result }, { multi: false }); } else { // Insert a new document await this.insert(this.resultsDb, result); return 1; } } catch (error) { // Log the error but don't fail - this allows tests to continue logger_1.default.error(`Error upserting result: ${error instanceof Error ? error.message : String(error)}`); return 0; } } } exports.NeDBStorageAdapter = NeDBStorageAdapter;

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/TSavo/Unity-MCP'

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