/**
* TOML configuration loader for HybridHub
* Handles loading and parsing of hybridhub.toml configuration files
*/
import fs from "fs";
import path from "path";
import TOML from "@iarna/toml";
import type {
TomlConfig,
DatabaseSourceConfig,
StorageSourceConfig,
DatabaseToolConfig,
StorageToolConfig,
} from "../types/config.js";
/**
* Find configuration file in standard locations
* @param customPath - Optional custom path from CLI argument
* @returns Path to config file or null if not found
*/
export function findConfigFile(customPath?: string): string | null {
// If custom path provided, use it directly
if (customPath) {
if (fs.existsSync(customPath)) {
return customPath;
}
console.error(`Warning: Specified config file not found: ${customPath}`);
return null;
}
// Standard locations to search for config file
const searchPaths = [
path.join(process.cwd(), "hybridhub.toml"),
path.join(process.cwd(), ".hybridhub.toml"),
path.join(process.cwd(), "config", "hybridhub.toml"),
];
for (const configPath of searchPaths) {
if (fs.existsSync(configPath)) {
return configPath;
}
}
return null;
}
/**
* Parse TOML configuration file
* @param configPath - Path to the configuration file
* @returns Parsed configuration object
*/
export function parseTomlFile(configPath: string): TomlConfig {
const content = fs.readFileSync(configPath, "utf-8");
const parsed = TOML.parse(content) as any;
const config: TomlConfig = {
databases: [],
storages: [],
database_tools: [],
storage_tools: [],
};
// Parse database sources
if (parsed.databases && Array.isArray(parsed.databases)) {
config.databases = parsed.databases.map((db: any) => ({
id: db.id,
type: db.type,
dsn: db.dsn,
host: db.host,
port: db.port,
database: db.database,
user: db.user,
password: db.password,
instanceName: db.instanceName,
readonly: db.readonly,
max_rows: db.max_rows,
connection_timeout: db.connection_timeout,
request_timeout: db.request_timeout,
ssh_host: db.ssh_host,
ssh_port: db.ssh_port,
ssh_user: db.ssh_user,
ssh_password: db.ssh_password,
ssh_key: db.ssh_key,
ssh_passphrase: db.ssh_passphrase,
} as DatabaseSourceConfig));
}
// Parse storage sources
if (parsed.storages && Array.isArray(parsed.storages)) {
config.storages = parsed.storages.map((storage: any) => ({
id: storage.id,
type: storage.type,
endpoint: storage.endpoint,
access_key: storage.access_key,
secret_key: storage.secret_key,
region: storage.region,
default_bucket: storage.default_bucket,
connection_timeout: storage.connection_timeout,
security_token: storage.security_token,
ssl: storage.ssl,
path_style: storage.path_style,
} as StorageSourceConfig));
}
// Parse database tools
if (parsed.database_tools && Array.isArray(parsed.database_tools)) {
config.database_tools = parsed.database_tools.map((tool: any) => ({
name: tool.name,
description: tool.description,
source: tool.source,
statement: tool.statement,
parameters: tool.parameters,
} as DatabaseToolConfig));
}
// Parse storage tools
if (parsed.storage_tools && Array.isArray(parsed.storage_tools)) {
config.storage_tools = parsed.storage_tools.map((tool: any) => ({
name: tool.name,
source: tool.source,
description: tool.description,
operation: tool.operation,
filter: tool.filter,
parameters: tool.parameters,
max_keys: tool.max_keys,
max_size: tool.max_size,
max_results: tool.max_results,
} as StorageToolConfig));
}
return config;
}
/**
* Load TOML configuration from file
* @param customPath - Optional custom path from CLI argument
* @returns Configuration object or null if not found
*/
export function loadTomlConfig(customPath?: string): TomlConfig | null {
const configPath = findConfigFile(customPath);
if (!configPath) {
return null;
}
console.error(`Loading configuration from: ${configPath}`);
try {
return parseTomlFile(configPath);
} catch (error) {
console.error(`Error parsing config file ${configPath}:`, error);
throw error;
}
}
/**
* Validate TOML configuration
* @param config - Configuration to validate
* @returns True if valid, throws error otherwise
*/
export function validateTomlConfig(config: TomlConfig): boolean {
const errors: string[] = [];
const sourceIds = new Set<string>();
// Validate database sources
if (config.databases) {
for (const db of config.databases) {
if (!db.id) {
errors.push("Database source missing required 'id' field");
} else if (sourceIds.has(db.id)) {
errors.push(`Duplicate source ID: ${db.id}`);
} else {
sourceIds.add(db.id);
}
// Must have either DSN or type + connection params
if (!db.dsn && !db.type) {
errors.push(`Database source '${db.id}' must have either 'dsn' or 'type' specified`);
}
}
}
// Validate storage sources
if (config.storages) {
for (const storage of config.storages) {
if (!storage.id) {
errors.push("Storage source missing required 'id' field");
} else if (sourceIds.has(storage.id)) {
errors.push(`Duplicate source ID: ${storage.id}`);
} else {
sourceIds.add(storage.id);
}
if (!storage.type) {
errors.push(`Storage source '${storage.id}' missing required 'type' field`);
}
if (!storage.endpoint) {
errors.push(`Storage source '${storage.id}' missing required 'endpoint' field`);
}
if (!storage.access_key) {
errors.push(`Storage source '${storage.id}' missing required 'access_key' field`);
}
if (!storage.secret_key) {
errors.push(`Storage source '${storage.id}' missing required 'secret_key' field`);
}
}
}
// Validate database tools
if (config.database_tools) {
for (const tool of config.database_tools) {
if (!tool.name) {
errors.push("Database tool missing required 'name' field");
}
if (!tool.source) {
errors.push(`Database tool '${tool.name}' missing required 'source' field`);
} else if (!sourceIds.has(tool.source)) {
errors.push(`Database tool '${tool.name}' references non-existent source '${tool.source}'`);
}
if (!tool.statement) {
errors.push(`Database tool '${tool.name}' missing required 'statement' field`);
}
}
}
// Validate storage tools
if (config.storage_tools) {
for (const tool of config.storage_tools) {
if (!tool.name) {
errors.push("Storage tool missing required 'name' field");
}
if (!tool.source) {
errors.push(`Storage tool '${tool.name}' missing required 'source' field`);
} else if (!sourceIds.has(tool.source)) {
errors.push(`Storage tool '${tool.name}' references non-existent source '${tool.source}'`);
}
}
}
if (errors.length > 0) {
throw new Error(`Configuration validation failed:\n${errors.join("\n")}`);
}
return true;
}