Skip to main content
Glama
IBM

IBM i MCP Server

Official
by IBM
duckDBConnectionManager.ts8.19 kB
/** * @fileoverview Manages DuckDB instance and connection lifecycle. * @module services/duck-db/duckDBConnectionManager */ import * as duckdb from "@duckdb/node-api"; import { JsonRpcErrorCode, McpError } from "@/types-global/errors.js"; import { ErrorHandler, logger, RequestContext, requestContextService, } from "@/utils/index.js"; import { DuckDBServiceConfig } from "./types.js"; const DEFAULT_DB_PATH = ":memory:"; export class DuckDBConnectionManager { private dbInstance: duckdb.DuckDBInstance | null = null; private dbConnection: duckdb.DuckDBConnection | null = null; private isInitialized = false; private currentConfig: DuckDBServiceConfig | null = null; /** * Initializes the DuckDB instance and connection. * @param {DuckDBServiceConfig} [config] - Configuration for the service. * @returns {Promise<void>} */ public async initialize(config?: DuckDBServiceConfig): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBConnectionManager.initialize", initialData: config, }); if (this.isInitialized) { logger.warning( context, "DuckDBConnectionManager already initialized. Close first to re-initialize.", ); // Potentially compare new config with old config if re-init with changes is desired return; } this.currentConfig = config || {}; return ErrorHandler.tryCatch( async () => { const dbPath = this.currentConfig?.dbPath || DEFAULT_DB_PATH; const launchConfig = this.currentConfig?.launchConfig; logger.info( context, `Initializing DuckDB instance with path: ${dbPath}`, ); // Pass launchConfig directly to the create method // The exact structure for launchConfig might need verification against @duckdb/node-api docs // Assuming it takes an object similar to what's in DuckDBServiceConfig this.dbInstance = await duckdb.DuckDBInstance.create( dbPath, launchConfig || {}, ); this.dbConnection = await this.dbInstance.connect(); logger.info(context, "DuckDB instance and connection created."); // Set isInitialized to true now that core components are ready this.isInitialized = true; // If launchConfig was successfully passed to create(), this block might no longer be needed // or might be adjusted for settings that *can* be applied post-connection. // For now, let's comment it out to avoid the original error. // if (launchConfig) { // for (const [key, value] of Object.entries(launchConfig)) { // // Ensure connection is available for run // if (!this.dbConnection) { // throw new McpError( // BaseErrorCode.INTERNAL_ERROR, // "Connection not available for applying launch config", // context, // ); // } // await this.dbConnection.run( // `SET ${key}='${String(value).replace(/'/g, "''")}';`, // ); // } // logger.info({ // ...context, // launchConfig, // }, "Applied launch configuration via SET commands."); // } if ( this.currentConfig?.extensions && this.currentConfig.extensions.length > 0 ) { logger.info( { ...context, extensions: this.currentConfig.extensions, }, "Loading extensions...", ); for (const extName of this.currentConfig.extensions) { await this.loadExtension(extName, context); } } // this.isInitialized = true; // Moved earlier logger.info( context, "DuckDBConnectionManager initialized successfully.", ); }, { operation: "DuckDBConnectionManager.initialize", context, input: config, errorCode: JsonRpcErrorCode.InitializationFailed, critical: true, }, ); } /** * Installs and loads a DuckDB extension. * @param {string} extensionName - The name of the extension. * @param {RequestContext} parentContext - The parent request context. * @returns {Promise<void>} */ public async loadExtension( extensionName: string, parentContext: RequestContext, ): Promise<void> { this.ensureInitialized(parentContext); const context = requestContextService.createRequestContext({ operation: "DuckDBConnectionManager.loadExtension", initialData: { extensionName }, parentContext, }); return ErrorHandler.tryCatch( async () => { logger.info(context, `Installing extension: ${extensionName}`); await this.dbConnection!.run( `INSTALL '${extensionName.replace(/'/g, "''")}'`, ); logger.info(context, `Loading extension: ${extensionName}`); await this.dbConnection!.run( `LOAD '${extensionName.replace(/'/g, "''")}'`, ); logger.info( context, `Extension ${extensionName} installed and loaded.`, ); }, { operation: "DuckDBConnectionManager.loadExtension", context, input: { extensionName }, errorCode: JsonRpcErrorCode.InternalError, }, ); } /** * Closes the DuckDB connection and instance. * @returns {Promise<void>} */ public async close(): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBConnectionManager.close", }); if (!this.isInitialized) { logger.warning( context, "DuckDBConnectionManager not initialized, nothing to close.", ); return; } return ErrorHandler.tryCatch( async () => { if (this.dbConnection) { this.dbConnection.closeSync(); // Use synchronous closeSync this.dbConnection = null; logger.info(context, "DuckDB connection closed."); } if (this.dbInstance) { this.dbInstance.closeSync(); // Use synchronous closeSync this.dbInstance = null; logger.info(context, "DuckDB instance closed."); } this.isInitialized = false; this.currentConfig = null; logger.info(context, "DuckDBConnectionManager closed successfully."); }, { operation: "DuckDBConnectionManager.close", context, errorCode: JsonRpcErrorCode.InternalError, }, ); } /** * Ensures that the service is initialized. * @param {RequestContext} context - The request context for error reporting. * @throws {McpError} If the service is not initialized. */ public ensureInitialized(context: RequestContext): void { if (!this.isInitialized || !this.dbConnection || !this.dbInstance) { throw new McpError( JsonRpcErrorCode.ServiceUnavailable, "DuckDBConnectionManager is not initialized. Call initialize() first.", context, ); } } /** * Gets the underlying DuckDB connection object. * @returns {duckdb.DuckDBConnection} * @throws {McpError} If the service is not initialized. */ public getConnection(): duckdb.DuckDBConnection { const context = requestContextService.createRequestContext({ operation: "DuckDBConnectionManager.getConnection", }); this.ensureInitialized(context); return this.dbConnection!; } /** * Gets the underlying DuckDB instance object. * @returns {duckdb.DuckDBInstance} * @throws {McpError} If the service is not initialized. */ public getInstance(): duckdb.DuckDBInstance { const context = requestContextService.createRequestContext({ operation: "DuckDBConnectionManager.getInstance", }); this.ensureInitialized(context); return this.dbInstance!; } /** * Checks if the service is currently initialized. * @returns {boolean} True if initialized, false otherwise. */ public get isServiceInitialized(): boolean { return this.isInitialized; } }

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/IBM/ibmi-mcp-server'

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