UNPKG

mcp-ts-template

Version:

A production-grade TypeScript template for building robust Model Context Protocol (MCP) servers, featuring built-in observability with OpenTelemetry, advanced error handling, comprehensive utilities, and a modular architecture.

194 lines 9.02 kB
/** * @fileoverview Example script to demonstrate usage of the DuckDBService. * Creates a sample database, table, inserts data, queries it, and logs results. * Database files will be stored in the './duckdata/' directory. * @module storage/duckdbExample */ import * as fs from "fs"; import * as path from "path"; import { config } from "../config/index.js"; // Added config import import { DuckDBService } from "../services/duck-db/duckDBService.js"; import { BaseErrorCode } from "../types-global/errors.js"; import { ErrorHandler, idGenerator, // Added idGenerator import logger, requestContextService, } from "../utils/index.js"; const DUCKDB_DATA_DIR = path.resolve(process.cwd(), "duckdata"); const DUCKDB_FILE_PATH = path.join(DUCKDB_DATA_DIR, "example.db"); /** * Ensures that the directory for storing DuckDB files exists. * @param {RequestContext} context - The request context for logging. */ function ensureDataDirectoryExists(context) { if (!fs.existsSync(DUCKDB_DATA_DIR)) { logger.info(`Data directory ${DUCKDB_DATA_DIR} does not exist. Creating...`, context); try { fs.mkdirSync(DUCKDB_DATA_DIR, { recursive: true }); logger.info(`Data directory ${DUCKDB_DATA_DIR} created.`, context); } catch (error) { logger.error(`Failed to create data directory ${DUCKDB_DATA_DIR}`, error, context); // Re-throw as a critical error if directory creation fails throw new Error(`Could not create DuckDB data directory: ${error instanceof Error ? error.message : String(error)}`); } } else { logger.debug(`Data directory ${DUCKDB_DATA_DIR} already exists.`, context); } // Ensure a fresh database file for the example by deleting it if it exists. // This allows launchConfig settings like custom_user_agent to be applied on each run. if (fs.existsSync(DUCKDB_FILE_PATH)) { logger.info(`Existing DuckDB file ${DUCKDB_FILE_PATH} found. Deleting for a fresh example run...`, context); try { fs.unlinkSync(DUCKDB_FILE_PATH); logger.info(`Successfully deleted ${DUCKDB_FILE_PATH}.`, context); } catch (error) { logger.error(`Failed to delete existing DuckDB file ${DUCKDB_FILE_PATH}`, error, context); // Re-throw as a critical error if deletion fails, as it will likely cause subsequent errors throw new Error(`Could not delete existing DuckDB file: ${error instanceof Error ? error.message : String(error)}`); } } } async function runDuckDBExample() { const operation = "runDuckDBExample"; const context = requestContextService.createRequestContext({ operation }); logger.notice("Starting DuckDB example script...", context); ensureDataDirectoryExists(context); const service = new DuckDBService(); const config = { dbPath: DUCKDB_FILE_PATH, extensions: ["json"], // Example: include an extension launchConfig: { custom_user_agent: "DuckDBExampleScript/1.0" }, }; try { logger.info(`Initializing DuckDBService with path: ${config.dbPath}`, context); await service.initialize(config); logger.info("DuckDBService initialized.", context); // Create a table const createTableSql = ` CREATE TABLE IF NOT EXISTS users ( id VARCHAR(6) PRIMARY KEY, name VARCHAR NOT NULL, email VARCHAR, createdAt TIMESTAMP DEFAULT current_timestamp ); `; logger.info("Creating 'users' table...", { ...context, sql: createTableSql, }); await service.run(createTableSql); logger.info("'users' table created or already exists.", context); // Insert data const usersToInsert = [ { name: "Alice Wonderland", email: "alice@example.com" }, { name: "Bob The Builder", email: "bob@example.com" }, { name: "Charlie Chaplin", email: "charlie@example.com" }, ].map((user) => ({ id: idGenerator.generateRandomString(6), // Generate 6-digit alphanumeric ID directly ...user, })); logger.info("Inserting data into 'users' table...", { ...context, users: usersToInsert.length, }); for (const user of usersToInsert) { // Check if user already exists to prevent primary key constraint errors on re-runs const existingUser = await service.query("SELECT id FROM users WHERE id = ?", [user.id]); if (existingUser.rowCount === 0) { await service.run("INSERT INTO users (id, name, email) VALUES (?, ?, ?)", [user.id, user.name, user.email]); logger.debug(`Inserted user with ID: ${user.id}`, context); } else { logger.debug(`User with ID: ${user.id} already exists. Skipping insertion.`, context); } } logger.info("Data insertion complete.", context); // Query data const querySql = "SELECT id, name, email, createdAt FROM users ORDER BY id;"; logger.info("Querying 'users' table...", { ...context, sql: querySql }); const result = await service.query(querySql); logger.notice("Query Results:", { ...context, rowCount: result.rowCount, columnNames: result.columnNames, }); result.rows.forEach((row, index) => { logger.info(`Row ${index + 1}:`, { ...context, rowData: row }); }); // Example of using an extension function (json) // Use the ID of the first inserted user for the query if (usersToInsert.length > 0) { const firstUser = usersToInsert[0]; if (!firstUser) { logger.warning("Could not get the first user for JSON query example.", context); return; } const firstUserId = firstUser.id; const jsonQuerySql = "SELECT json_object('id', id, 'name', name, 'email', email) AS user_json FROM users WHERE id = ?;"; // Added email to json_object logger.info("Querying with JSON extension function for a specific user...", { ...context, sql: jsonQuerySql, userId: firstUserId, }); const jsonResult = await service.query(jsonQuerySql, [firstUserId]); if (jsonResult.rowCount > 0) { logger.info("JSON Query Result:", { ...context, jsonData: jsonResult.rows[0], }); } else { logger.warning(`Could not find user with ID ${firstUserId} for JSON query example.`, context); // Changed warn to warning } } else { logger.info("Skipping JSON query example as no users were inserted.", context); } } catch (error) { // ErrorHandler.tryCatch is used within the service, so errors should be McpError // If an error occurs outside service calls (e.g. directory creation), it might be a standard Error logger.error("An error occurred in the DuckDB example script", error, { ...context, isMcpError: error instanceof Object && "errorCode" in error, // Basic check }); } finally { logger.info("Closing DuckDBService...", context); // Wrap close in its own tryCatch as it might also throw try { await service.close(); logger.info("DuckDBService closed.", context); } catch (closeError) { logger.error("Failed to close DuckDBService", closeError, context); } } logger.notice("DuckDB example script finished.", context); } // Self-executing async function (async () => { // Initialize the logger before any other operations // Ensure config.logLevel is correctly typed as McpLogLevel for the initialize method. await logger.initialize(config.logLevel); // Setup a global error handler for unhandled rejections or exceptions // specific to this script's execution context. const scriptContext = requestContextService.createRequestContext({ operation: "DuckDBExampleScript.main", }); try { await ErrorHandler.tryCatch(runDuckDBExample, { operation: "runDuckDBExample.mainExecution", context: scriptContext, errorCode: BaseErrorCode.INTERNAL_ERROR, // Changed from SCRIPT_EXECUTION_ERROR critical: true, // If the main example fails, it's critical for the script }); } catch (e) { // This catch is for errors that ErrorHandler.tryCatch itself might rethrow // or if ErrorHandler is bypassed. logger.crit("Unhandled critical error in DuckDB example script execution.", e, scriptContext); process.exit(1); // Exit with error code } })(); //# sourceMappingURL=duckdbExample.js.map