pocketsmith-mcp
Version:
MCP server for managing budgets via PocketSmith API
150 lines (149 loc) • 6.19 kB
JavaScript
/**
* @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;
}
}