UNPKG

pocketsmith-mcp

Version:

MCP server for managing budgets via PocketSmith API

150 lines (149 loc) 6.19 kB
/** * @fileoverview Implements the DuckDB service for interacting with a DuckDB database. * @module services/duck-db/duckDBService */ import { BaseErrorCode, McpError } from "../../types-global/errors.js"; import { ErrorHandler, logger, requestContextService, } from "../../utils/index.js"; import { DuckDBConnectionManager } from "./duckDBConnectionManager.js"; import { DuckDBQueryExecutor } from "./duckDBQueryExecutor.js"; export class DuckDBService { constructor() { this.queryExecutor = null; this.isInitialized = false; this.connectionManager = new DuckDBConnectionManager(); } async initialize(config) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.initialize", initialData: config, }); if (this.isInitialized) { logger.warning("DuckDBService already initialized. Close first to re-initialize.", context); return; } return ErrorHandler.tryCatch(async () => { await this.connectionManager.initialize(config); const connection = this.connectionManager.getConnection(); this.queryExecutor = new DuckDBQueryExecutor(connection); this.isInitialized = true; logger.info("DuckDBService initialized successfully.", context); }, { operation: "DuckDBService.initialize", context, input: config, errorCode: BaseErrorCode.INITIALIZATION_FAILED, critical: true, }); } ensureInitialized(context) { this.connectionManager.ensureInitialized(context); // Delegate to manager if (!this.queryExecutor) { // This check is mostly for type safety, as connectionManager.ensureInitialized should cover it throw new McpError(BaseErrorCode.SERVICE_NOT_INITIALIZED, "DuckDBQueryExecutor not available. DuckDBService may not be fully initialized.", context); } } validateParams(params, context) { if (params === undefined) { return undefined; } if (Array.isArray(params)) { return params; } throw new McpError(BaseErrorCode.INVALID_INPUT, "DuckDB service only supports array-style parameters, not named objects.", context); } async run(sql, params) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.run", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor.run(sql, validatedParams); } async query(sql, params) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.query", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor.query(sql, validatedParams); } async stream(sql, params) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.stream", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor.stream(sql, validatedParams); } async prepare(sql) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.prepare", initialData: { sql }, }); this.ensureInitialized(context); return this.queryExecutor.prepare(sql); } async beginTransaction() { const context = requestContextService.createRequestContext({ operation: "DuckDBService.beginTransaction", }); this.ensureInitialized(context); return this.queryExecutor.beginTransaction(); } async commitTransaction() { const context = requestContextService.createRequestContext({ operation: "DuckDBService.commitTransaction", }); this.ensureInitialized(context); return this.queryExecutor.commitTransaction(); } async rollbackTransaction() { const context = requestContextService.createRequestContext({ operation: "DuckDBService.rollbackTransaction", }); this.ensureInitialized(context); return this.queryExecutor.rollbackTransaction(); } async loadExtension(extensionName) { const context = requestContextService.createRequestContext({ operation: "DuckDBService.loadExtension", initialData: { extensionName }, }); // ensureInitialized is implicitly called by connectionManager.loadExtension // if we call ensureInitialized here, it uses the service's context, // but loadExtension in manager will create its own. // It's better to let the manager handle its own initialization checks. return this.connectionManager.loadExtension(extensionName, context); } async close() { const context = requestContextService.createRequestContext({ operation: "DuckDBService.close", }); // No need to check this.isInitialized here, connectionManager.close() handles it. return ErrorHandler.tryCatch(async () => { await this.connectionManager.close(); this.queryExecutor = null; this.isInitialized = false; logger.info("DuckDBService closed successfully.", context); }, { operation: "DuckDBService.close", context, errorCode: BaseErrorCode.SHUTDOWN_ERROR, }); } getRawConnection() { if (this.connectionManager.isServiceInitialized) { return this.connectionManager.getConnection(); } return null; } getRawInstance() { if (this.connectionManager.isServiceInitialized) { return this.connectionManager.getInstance(); } return null; } }