@nmakarov/cli-toolkit 0.16.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +9 -0
  2. package/dist/cli-runner.cjs +5335 -0
  3. package/dist/cli-runner.cjs.map +1 -0
  4. package/dist/cli-runner.js +5318 -0
  5. package/dist/cli-runner.js.map +1 -0
  6. package/dist/db.cjs +1 -1
  7. package/dist/db.cjs.map +1 -1
  8. package/dist/db.js +1 -1
  9. package/dist/db.js.map +1 -1
  10. package/dist/filedatabase.cjs +94 -13
  11. package/dist/filedatabase.cjs.map +1 -1
  12. package/dist/filedatabase.js +94 -13
  13. package/dist/filedatabase.js.map +1 -1
  14. package/dist/http-client2.cjs +1375 -11
  15. package/dist/http-client2.cjs.map +1 -1
  16. package/dist/http-client2.js +1365 -11
  17. package/dist/http-client2.js.map +1 -1
  18. package/dist/index.cjs +1639 -30
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +1608 -29
  21. package/dist/index.js.map +1 -1
  22. package/dist/init.cjs +25 -7
  23. package/dist/init.cjs.map +1 -1
  24. package/dist/init.js +25 -7
  25. package/dist/init.js.map +1 -1
  26. package/dist/logger.cjs +9 -6
  27. package/dist/logger.cjs.map +1 -1
  28. package/dist/logger.js +9 -6
  29. package/dist/logger.js.map +1 -1
  30. package/dist/mock-server.cjs +192 -343
  31. package/dist/mock-server.cjs.map +1 -1
  32. package/dist/mock-server.js +190 -343
  33. package/dist/mock-server.js.map +1 -1
  34. package/dist/params.cjs +16 -1
  35. package/dist/params.cjs.map +1 -1
  36. package/dist/params.js +16 -1
  37. package/dist/params.js.map +1 -1
  38. package/dist/tasks.cjs +2666 -0
  39. package/dist/tasks.cjs.map +1 -0
  40. package/dist/tasks.js +2600 -0
  41. package/dist/tasks.js.map +1 -0
  42. package/dist/utils.cjs +15 -2
  43. package/dist/utils.cjs.map +1 -1
  44. package/dist/utils.js +12 -1
  45. package/dist/utils.js.map +1 -1
  46. package/package.json +20 -5
  47. package/scripts/ssm/parse-cli.ts +35 -0
  48. package/scripts/ssm/ssm-admin.ts +151 -0
  49. package/scripts/ssm/ssm-pull.ts +150 -0
package/dist/db.cjs CHANGED
@@ -420,7 +420,7 @@ async function dbFindAndConnect(context, dbNameOrConnectionString) {
420
420
  dbConnectionString: "string",
421
421
  dbProfile: "boolean default false"
422
422
  };
423
- const paramsConfig = context.params.getAllForModule(defs);
423
+ const paramsConfig = context.params.getAll(defs);
424
424
  dbName = paramsConfig.dbName;
425
425
  dbConnectionString = paramsConfig.dbConnectionString;
426
426
  dbProfile = paramsConfig.dbProfile;
package/dist/db.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db.ts","../src/db/index.ts","../src/errors.ts"],"sourcesContent":["// Re-export Db module\nexport * from \"./db/index.js\";\n\n","/**\n * Database Client - Low-level SQL database operations\n *\n * Wraps Knex.js with connection management, profiling, and utility functions.\n * The instance can be used directly like a Knex instance: db('table').select()\n */\n\nimport knex, { Knex } from 'knex';\nimport { ParamError } from '../errors.js';\nimport type { Context } from '../init/types.js';\nimport type { DbConfig, DbOptions, DatabaseClient, QueryLogEntry, DbInstance } from './types.js';\n\n/**\n * Database client wrapper around Knex\n * \n * Usage:\n * ```typescript\n * const db = new Db({\n * connectionString: 'postgresql://user:pass@host:5432/dbname',\n * testConnection: true,\n * profile: false\n * });\n * await db.connect();\n * \n * // Use like Knex:\n * const users = await db('users').select('*');\n * await db('posts').insert({ title: 'Hello' });\n * ```\n */\nexport class Db {\n private knexInstance: Knex | null = null;\n private config: Required<DbConfig>;\n private logger: any;\n private queriesLog: QueryLogEntry[] = [];\n private isConnected: boolean = false;\n\n /**\n * Constructor - accepts config object\n * Use dbInit() function to initialize with Context\n */\n constructor(config: DbConfig) {\n if (!config.connectionString) {\n throw new ParamError('Db: connectionString is required');\n }\n\n this.config = {\n testConnection: true,\n profile: false,\n pool: { min: 2, max: 10 },\n acquireConnectionTimeout: 10000,\n ssl: { rejectUnauthorized: false },\n logger: console,\n name: 'default',\n ...config,\n };\n\n this.logger = this.config.logger;\n\n // Create a callable function wrapper that forwards to the instance\n // This allows db('table') to work like Knex\n const instance = this;\n const callableWrapper = function(...args: any[]) {\n // This function body is never executed - the Proxy apply trap handles calls\n // But we need a function to make the Proxy apply trap work\n throw new Error('This should never be called directly');\n };\n \n // Store instance reference on the wrapper for Proxy access\n (callableWrapper as any)._instance = instance;\n\n // Create a Proxy that makes the wrapper callable and forwards property access\n return new Proxy(callableWrapper, {\n // Intercept function calls: db('table')\n apply: (target, thisArg, argumentsList) => {\n const inst = (target as any)._instance;\n if (!inst.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n // Forward the call to the Knex instance (Knex instances are callable)\n return (inst.knexInstance as any)(...argumentsList);\n },\n // Intercept property access: db.schema, db.raw, etc.\n get: (target, prop) => {\n // Allow access to _instance for internal use\n if (prop === '_instance') {\n return (target as any)._instance;\n }\n \n const instance = (target as any)._instance;\n \n // List of our own methods that should NOT be forwarded to Knex\n const ownMethods = [\n 'connect',\n 'disconnect',\n 'testConnection',\n 'tableExists',\n 'getQueryLog',\n 'getKnex',\n 'isConnectedToDb',\n 'getErrorMessage',\n 'detectClient',\n 'attachProfiler',\n ];\n \n // Always return our own methods first (before checking Knex)\n if (prop in instance) {\n const value = (instance as any)[prop];\n // If it's one of our own methods, return it bound to instance\n if (typeof value === 'function' && ownMethods.includes(prop as string)) {\n return value.bind(instance);\n }\n // If it's a non-function property, return it\n if (typeof value !== 'function') {\n return value;\n }\n }\n \n // If we have a Knex instance, forward to it for everything else\n if (instance.knexInstance) {\n const knexProp = (instance.knexInstance as any)[prop];\n if (typeof knexProp === 'function') {\n // Bind methods to the Knex instance\n return knexProp.bind(instance.knexInstance);\n }\n return knexProp;\n }\n \n // Return our own methods that aren't in the ownMethods list (shouldn't happen, but fallback)\n if (prop in instance) {\n const method = (instance as any)[prop];\n if (typeof method === 'function') {\n return method.bind(instance);\n }\n return method;\n }\n \n // Property doesn't exist\n return undefined;\n },\n }) as any;\n }\n\n /**\n * Detect database client type from connection string\n */\n private detectClient(connectionString: string): DatabaseClient | null {\n if (connectionString.match(/^postgresql/)) {\n return 'pg';\n }\n if (connectionString.match(/^mysql/)) {\n return 'mysql2';\n }\n return null;\n }\n\n /**\n * Connect to the database\n */\n async connect(): Promise<void> {\n if (this.isConnected && this.knexInstance) {\n this.logger.warn?.('[Db] Already connected');\n return;\n }\n\n const client = this.detectClient(this.config.connectionString);\n if (!client) {\n throw new ParamError(\n `Db: Cannot determine client type from connection string. Expected postgresql:// or mysql://`\n );\n }\n\n try {\n // Force IPv4 only (disable IPv6) by setting family: 4 in connection config\n // Knex accepts connection as string or object; we wrap string to add family option\n const connectionConfig = {\n connectionString: this.config.connectionString,\n family: 4, // Force IPv4 only (disable IPv6)\n };\n\n this.knexInstance = knex({\n client,\n connection: connectionConfig,\n pool: this.config.pool,\n acquireConnectionTimeout: this.config.acquireConnectionTimeout,\n ...(this.config.ssl && { ssl: this.config.ssl }),\n } as any);\n\n // Attach profiler if enabled\n if (this.config.profile) {\n this.attachProfiler();\n }\n\n // Test connection if requested\n if (this.config.testConnection) {\n await this.testConnection();\n }\n\n this.isConnected = true;\n this.logger.debug?.(`[Db] Connected to database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n // If testConnection already threw a ParamError, preserve its message\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = this.getErrorMessage(error);\n throw new ParamError(`Db: Connection failed - ${errorMsg}`);\n }\n }\n\n /**\n * Disconnect from the database\n */\n async disconnect(): Promise<void> {\n if (!this.knexInstance) {\n return;\n }\n\n try {\n await this.knexInstance.destroy();\n this.knexInstance = null;\n this.isConnected = false;\n this.queriesLog = [];\n this.logger.debug?.(`[Db] Disconnected from database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);\n throw error;\n }\n }\n\n /**\n * Extract error message from various error types\n */\n private getErrorMessage(error: any): string {\n // Handle AggregateError (can contain multiple errors)\n if (error instanceof AggregateError) {\n const errors = error.errors || [];\n \n // If all errors are the same type (e.g., ECONNREFUSED for different IPs), show a consolidated message\n if (errors.length > 0) {\n const firstError = errors[0];\n const firstErrorMsg = firstError instanceof Error ? firstError.message : String(firstError);\n \n // Check if all errors are similar (same error code/type, different addresses)\n const allSimilar = errors.every((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Extract error code (e.g., \"ECONNREFUSED\") from message\n const codeMatch = msg.match(/^(\\w+)\\s/);\n const firstCodeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n return codeMatch && firstCodeMatch && codeMatch[1] === firstCodeMatch[1];\n });\n \n if (allSimilar && errors.length > 1) {\n // Extract addresses/IPs from error messages\n const addresses = errors.map((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Try to extract address (e.g., \"::1:5432\" or \"127.0.0.1:5432\")\n const addrMatch = msg.match(/([:\\d.]+:\\d+)/);\n return addrMatch ? addrMatch[1] : null;\n }).filter(Boolean);\n \n if (addresses.length > 0) {\n // Show consolidated message with all addresses\n const codeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n const code = codeMatch ? codeMatch[1] : 'Connection error';\n return `${code} (tried: ${addresses.join(', ')})`;\n }\n }\n \n // Fallback: show all errors but deduplicate identical messages\n const uniqueMessages = [...new Set(errors.map((e: any) => {\n return e instanceof Error ? e.message : String(e);\n }))];\n \n if (uniqueMessages.length === 1) {\n return uniqueMessages[0];\n }\n \n return uniqueMessages.join('; ');\n }\n \n return error.message || 'Multiple errors occurred';\n }\n \n // Handle standard Error objects\n if (error instanceof Error) {\n // Check for common database error properties (code is often present on Node.js errors)\n const errorWithCode = error as Error & { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${error.message || String(error)}`;\n }\n return error.message || String(error);\n }\n \n // Handle string errors\n if (typeof error === 'string') {\n return error;\n }\n \n // Handle objects with message property\n if (error?.message) {\n const msg = String(error.message);\n const errorWithCode = error as { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${msg}`;\n }\n return msg;\n }\n \n // Fallback: try to stringify the error\n return String(error) || 'Unknown error';\n }\n\n /**\n * Test database connection\n */\n async testConnection(): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n const result = await this.knexInstance.raw('SELECT 2+3 AS result');\n const isOk = result.rows?.[0]?.result === 5 || result[0]?.[0]?.result === 5;\n this.logger.debug?.(`[Db] Connection test: ${isOk ? 'OK' : 'FAILED'}`);\n return isOk;\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Connection test failed: ${errorMsg}`);\n throw new ParamError(`Db: Connection test failed - ${errorMsg}`);\n }\n }\n\n /**\n * Attach query profiler to log all queries\n */\n private attachProfiler(): void {\n if (!this.knexInstance) {\n return;\n }\n\n this.queriesLog = [];\n (this.knexInstance as any).queriesLog = this.queriesLog;\n\n this.knexInstance.on('query', (query: any) => {\n query.__startTime = process.hrtime();\n });\n\n this.knexInstance.on('query-response', (response: any, query: any) => {\n const [seconds, nanoseconds] = process.hrtime(query.__startTime);\n const executionTimeMs = ((seconds * 1000) + (nanoseconds / 1e6)).toFixed(2);\n\n const logEntry: QueryLogEntry = {\n sql: query.sql,\n bindings: query.bindings || [],\n executionTimeMs,\n };\n\n this.queriesLog.push(logEntry);\n this.logger.debug?.(`[Db] Query: ${query.sql} | Duration: ${executionTimeMs}ms`);\n });\n\n this.knexInstance.on('query-error', (error: Error, query: any) => {\n this.logger.error?.(`[Db] Query failed: ${query.sql}`, error);\n });\n }\n\n /**\n * Get query log (only available if profiling is enabled)\n */\n getQueryLog(): QueryLogEntry[] {\n return [...this.queriesLog];\n }\n\n /**\n * Check if a table exists\n */\n async tableExists(tableName: string): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n return await this.knexInstance.schema.hasTable(tableName);\n } catch (error: any) {\n this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);\n throw error;\n }\n }\n\n /**\n * Get the underlying Knex instance (for advanced usage)\n */\n getKnex(): Knex {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n return this.knexInstance;\n }\n\n /**\n * Get connection status\n */\n isConnectedToDb(): boolean {\n return this.isConnected && this.knexInstance !== null;\n }\n\n /**\n * Initialize Db with context (connects and registers disconnect cleanup).\n * Params are read via getAllForModule(\"db\", defs). Same as dbInit(context, dbNameOrConnectionString).\n */\n static async init(context: Context, dbNameOrConnectionString?: string): Promise<Db> {\n return dbFindAndConnect(context, dbNameOrConnectionString);\n }\n}\n\n/**\n * Helper function to capitalize first letter of a string\n */\nfunction capitalizeFirstLetter(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Connect to database - creates Db instance, connects, registers cleanup, attaches profiler\n * \n * This is a dedicated connect function that handles:\n * - Creating Db instance with proper configuration\n * - Connecting to database\n * - Registering cleanup function\n * - Attaching profiler if needed\n * - Better error handling\n */\nexport async function dbConnect(\n context: Context,\n connectionString: string,\n name?: string,\n dbProfile?: boolean\n): Promise<Db> {\n // Get configuration from params\n const defs = {\n testDbConnection: 'boolean default true',\n name: 'string',\n poolMin: 'number default 2',\n poolMax: 'number default 10',\n acquireConnectionTimeout: 'number default 10000',\n sslRejectUnauthorized: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n\n // Create config\n const config: DbConfig = {\n connectionString,\n name: paramsConfig.name || name || 'default',\n testConnection: paramsConfig.testDbConnection,\n profile: dbProfile ?? false,\n pool: {\n min: paramsConfig.poolMin,\n max: paramsConfig.poolMax,\n },\n acquireConnectionTimeout: paramsConfig.acquireConnectionTimeout,\n ssl: {\n rejectUnauthorized: paramsConfig.sslRejectUnauthorized,\n },\n logger: context.logger,\n };\n \n try {\n // Create Db instance\n const db = new Db(config);\n \n // Register cleanup function\n context.registerCleanup(async () => {\n await db.disconnect();\n context.logger.debug(`[Db] instance \"${name || connectionString}\" destroyed`);\n });\n \n // Connect to database (profiler is attached automatically if config.profile is true)\n await db.connect();\n \n context.logger.debug(`[Db] instance \"${name || connectionString}\" initialized`);\n \n return db;\n } catch (error: any) {\n // Better error handling\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n throw new ParamError(`[Db] connect error: ${errorMsg}`);\n }\n}\n\n/**\n * Find and connect to database - resolves database name or connection string\n * \n * This function handles:\n * - Direct connection string (postgresql://... or mysql://...)\n * - Database name/label that gets resolved to dbConnectionString${CapitalizedName}\n * - Reading from params if no second parameter provided\n */\nexport async function dbFindAndConnect(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n let dbName: string | undefined;\n let dbConnectionString: string | undefined;\n let dbProfile: boolean | undefined;\n \n // If second parameter is provided\n if (dbNameOrConnectionString) {\n // Check if it looks like a connection string (postgresql:// or mysql://)\n if (dbNameOrConnectionString.match(/^(postgresql|mysql):\\/\\/[^\\s]+:[^\\s]+@[^\\s]+:\\d+\\/[^\\s]+$/)) {\n dbName = undefined;\n dbConnectionString = dbNameOrConnectionString;\n } else {\n // Treat it as a database name/label\n dbName = dbNameOrConnectionString;\n }\n } else {\n // No second parameter - read from params\n const defs = {\n dbName: 'string',\n dbConnectionString: 'string',\n dbProfile: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n dbName = paramsConfig.dbName;\n dbConnectionString = paramsConfig.dbConnectionString;\n dbProfile = paramsConfig.dbProfile;\n }\n \n // Validate that we have either dbName or dbConnectionString\n if (!dbName && !dbConnectionString) {\n throw new ParamError('Db: either dbName or dbConnectionString must be specified');\n }\n \n // If dbName is provided, resolve it to connection string\n if (dbName) {\n const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;\n dbConnectionString = await context.params.get(paramName, 'string');\n if (!dbConnectionString) {\n throw new ParamError(\n `Db: cannot find dbConnectionString for dbName=\"${dbName}\" (looked for param \"${paramName}\")`\n );\n }\n }\n \n // Connect using dedicated connect function\n const db = await dbConnect(context, dbConnectionString!, dbName, dbProfile);\n \n // Test connection (connect() already tests if testConnection is true, but we can test explicitly here too)\n // The test is already done in db.connect() if config.testConnection is true\n \n return db;\n}\n\n/**\n * Initialize Db instance with context (auto-connects)\n * \n * This is the standard \"init\" function that auto-initializes the DB component.\n * It calls dbFindAndConnect, optionally passing a second parameter.\n * \n * Usage:\n * ```typescript\n * // Auto-initialize from params:\n * const db = await dbInit(context);\n * \n * // Or with database name/label:\n * const db = await dbInit(context, 'local');\n * \n * // Or with direct connection string:\n * const db = await dbInit(context, 'postgresql://user:pass@host:5432/dbname');\n * ```\n */\nexport async function dbInit(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n return await dbFindAndConnect(context, dbNameOrConnectionString);\n}\n\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":["instance","knex"]}
1
+ {"version":3,"sources":["../src/db.ts","../src/db/index.ts","../src/errors.ts"],"sourcesContent":["// Re-export Db module\nexport * from \"./db/index.js\";\n\n","/**\n * Database Client - Low-level SQL database operations\n *\n * Wraps Knex.js with connection management, profiling, and utility functions.\n * The instance can be used directly like a Knex instance: db('table').select()\n */\n\nimport knex, { Knex } from 'knex';\nimport { ParamError } from '../errors.js';\nimport type { Context } from '../init/types.js';\nimport type { DbConfig, DbOptions, DatabaseClient, QueryLogEntry, DbInstance } from './types.js';\n\n/**\n * Database client wrapper around Knex\n * \n * Usage:\n * ```typescript\n * const db = new Db({\n * connectionString: 'postgresql://user:pass@host:5432/dbname',\n * testConnection: true,\n * profile: false\n * });\n * await db.connect();\n * \n * // Use like Knex:\n * const users = await db('users').select('*');\n * await db('posts').insert({ title: 'Hello' });\n * ```\n */\nexport class Db {\n private knexInstance: Knex | null = null;\n private config: Required<DbConfig>;\n private logger: any;\n private queriesLog: QueryLogEntry[] = [];\n private isConnected: boolean = false;\n\n /**\n * Constructor - accepts config object\n * Use dbInit() function to initialize with Context\n */\n constructor(config: DbConfig) {\n if (!config.connectionString) {\n throw new ParamError('Db: connectionString is required');\n }\n\n this.config = {\n testConnection: true,\n profile: false,\n pool: { min: 2, max: 10 },\n acquireConnectionTimeout: 10000,\n ssl: { rejectUnauthorized: false },\n logger: console,\n name: 'default',\n ...config,\n };\n\n this.logger = this.config.logger;\n\n // Create a callable function wrapper that forwards to the instance\n // This allows db('table') to work like Knex\n const instance = this;\n const callableWrapper = function(...args: any[]) {\n // This function body is never executed - the Proxy apply trap handles calls\n // But we need a function to make the Proxy apply trap work\n throw new Error('This should never be called directly');\n };\n \n // Store instance reference on the wrapper for Proxy access\n (callableWrapper as any)._instance = instance;\n\n // Create a Proxy that makes the wrapper callable and forwards property access\n return new Proxy(callableWrapper, {\n // Intercept function calls: db('table')\n apply: (target, thisArg, argumentsList) => {\n const inst = (target as any)._instance;\n if (!inst.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n // Forward the call to the Knex instance (Knex instances are callable)\n return (inst.knexInstance as any)(...argumentsList);\n },\n // Intercept property access: db.schema, db.raw, etc.\n get: (target, prop) => {\n // Allow access to _instance for internal use\n if (prop === '_instance') {\n return (target as any)._instance;\n }\n \n const instance = (target as any)._instance;\n \n // List of our own methods that should NOT be forwarded to Knex\n const ownMethods = [\n 'connect',\n 'disconnect',\n 'testConnection',\n 'tableExists',\n 'getQueryLog',\n 'getKnex',\n 'isConnectedToDb',\n 'getErrorMessage',\n 'detectClient',\n 'attachProfiler',\n ];\n \n // Always return our own methods first (before checking Knex)\n if (prop in instance) {\n const value = (instance as any)[prop];\n // If it's one of our own methods, return it bound to instance\n if (typeof value === 'function' && ownMethods.includes(prop as string)) {\n return value.bind(instance);\n }\n // If it's a non-function property, return it\n if (typeof value !== 'function') {\n return value;\n }\n }\n \n // If we have a Knex instance, forward to it for everything else\n if (instance.knexInstance) {\n const knexProp = (instance.knexInstance as any)[prop];\n if (typeof knexProp === 'function') {\n // Bind methods to the Knex instance\n return knexProp.bind(instance.knexInstance);\n }\n return knexProp;\n }\n \n // Return our own methods that aren't in the ownMethods list (shouldn't happen, but fallback)\n if (prop in instance) {\n const method = (instance as any)[prop];\n if (typeof method === 'function') {\n return method.bind(instance);\n }\n return method;\n }\n \n // Property doesn't exist\n return undefined;\n },\n }) as any;\n }\n\n /**\n * Detect database client type from connection string\n */\n private detectClient(connectionString: string): DatabaseClient | null {\n if (connectionString.match(/^postgresql/)) {\n return 'pg';\n }\n if (connectionString.match(/^mysql/)) {\n return 'mysql2';\n }\n return null;\n }\n\n /**\n * Connect to the database\n */\n async connect(): Promise<void> {\n if (this.isConnected && this.knexInstance) {\n this.logger.warn?.('[Db] Already connected');\n return;\n }\n\n const client = this.detectClient(this.config.connectionString);\n if (!client) {\n throw new ParamError(\n `Db: Cannot determine client type from connection string. Expected postgresql:// or mysql://`\n );\n }\n\n try {\n // Force IPv4 only (disable IPv6) by setting family: 4 in connection config\n // Knex accepts connection as string or object; we wrap string to add family option\n const connectionConfig = {\n connectionString: this.config.connectionString,\n family: 4, // Force IPv4 only (disable IPv6)\n };\n\n this.knexInstance = knex({\n client,\n connection: connectionConfig,\n pool: this.config.pool,\n acquireConnectionTimeout: this.config.acquireConnectionTimeout,\n ...(this.config.ssl && { ssl: this.config.ssl }),\n } as any);\n\n // Attach profiler if enabled\n if (this.config.profile) {\n this.attachProfiler();\n }\n\n // Test connection if requested\n if (this.config.testConnection) {\n await this.testConnection();\n }\n\n this.isConnected = true;\n this.logger.debug?.(`[Db] Connected to database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n // If testConnection already threw a ParamError, preserve its message\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = this.getErrorMessage(error);\n throw new ParamError(`Db: Connection failed - ${errorMsg}`);\n }\n }\n\n /**\n * Disconnect from the database\n */\n async disconnect(): Promise<void> {\n if (!this.knexInstance) {\n return;\n }\n\n try {\n await this.knexInstance.destroy();\n this.knexInstance = null;\n this.isConnected = false;\n this.queriesLog = [];\n this.logger.debug?.(`[Db] Disconnected from database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);\n throw error;\n }\n }\n\n /**\n * Extract error message from various error types\n */\n private getErrorMessage(error: any): string {\n // Handle AggregateError (can contain multiple errors)\n if (error instanceof AggregateError) {\n const errors = error.errors || [];\n \n // If all errors are the same type (e.g., ECONNREFUSED for different IPs), show a consolidated message\n if (errors.length > 0) {\n const firstError = errors[0];\n const firstErrorMsg = firstError instanceof Error ? firstError.message : String(firstError);\n \n // Check if all errors are similar (same error code/type, different addresses)\n const allSimilar = errors.every((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Extract error code (e.g., \"ECONNREFUSED\") from message\n const codeMatch = msg.match(/^(\\w+)\\s/);\n const firstCodeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n return codeMatch && firstCodeMatch && codeMatch[1] === firstCodeMatch[1];\n });\n \n if (allSimilar && errors.length > 1) {\n // Extract addresses/IPs from error messages\n const addresses = errors.map((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Try to extract address (e.g., \"::1:5432\" or \"127.0.0.1:5432\")\n const addrMatch = msg.match(/([:\\d.]+:\\d+)/);\n return addrMatch ? addrMatch[1] : null;\n }).filter(Boolean);\n \n if (addresses.length > 0) {\n // Show consolidated message with all addresses\n const codeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n const code = codeMatch ? codeMatch[1] : 'Connection error';\n return `${code} (tried: ${addresses.join(', ')})`;\n }\n }\n \n // Fallback: show all errors but deduplicate identical messages\n const uniqueMessages = [...new Set(errors.map((e: any) => {\n return e instanceof Error ? e.message : String(e);\n }))];\n \n if (uniqueMessages.length === 1) {\n return uniqueMessages[0];\n }\n \n return uniqueMessages.join('; ');\n }\n \n return error.message || 'Multiple errors occurred';\n }\n \n // Handle standard Error objects\n if (error instanceof Error) {\n // Check for common database error properties (code is often present on Node.js errors)\n const errorWithCode = error as Error & { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${error.message || String(error)}`;\n }\n return error.message || String(error);\n }\n \n // Handle string errors\n if (typeof error === 'string') {\n return error;\n }\n \n // Handle objects with message property\n if (error?.message) {\n const msg = String(error.message);\n const errorWithCode = error as { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${msg}`;\n }\n return msg;\n }\n \n // Fallback: try to stringify the error\n return String(error) || 'Unknown error';\n }\n\n /**\n * Test database connection\n */\n async testConnection(): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n const result = await this.knexInstance.raw('SELECT 2+3 AS result');\n const isOk = result.rows?.[0]?.result === 5 || result[0]?.[0]?.result === 5;\n this.logger.debug?.(`[Db] Connection test: ${isOk ? 'OK' : 'FAILED'}`);\n return isOk;\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Connection test failed: ${errorMsg}`);\n throw new ParamError(`Db: Connection test failed - ${errorMsg}`);\n }\n }\n\n /**\n * Attach query profiler to log all queries\n */\n attachProfiler(): void {\n if (!this.knexInstance) {\n return;\n }\n\n this.queriesLog = [];\n (this.knexInstance as any).queriesLog = this.queriesLog;\n\n this.knexInstance.on('query', (query: any) => {\n query.__startTime = process.hrtime();\n });\n\n this.knexInstance.on('query-response', (response: any, query: any) => {\n const [seconds, nanoseconds] = process.hrtime(query.__startTime);\n const executionTimeMs = ((seconds * 1000) + (nanoseconds / 1e6)).toFixed(2);\n\n const logEntry: QueryLogEntry = {\n sql: query.sql,\n bindings: query.bindings || [],\n executionTimeMs,\n };\n\n this.queriesLog.push(logEntry);\n this.logger.debug?.(`[Db] Query: ${query.sql} | Duration: ${executionTimeMs}ms`);\n });\n\n this.knexInstance.on('query-error', (error: Error, query: any) => {\n this.logger.error?.(`[Db] Query failed: ${query.sql}`, error);\n });\n }\n\n /**\n * Get query log (only available if profiling is enabled)\n */\n getQueryLog(): QueryLogEntry[] {\n return [...this.queriesLog];\n }\n\n /**\n * Check if a table exists\n */\n async tableExists(tableName: string): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n return await this.knexInstance.schema.hasTable(tableName);\n } catch (error: any) {\n this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);\n throw error;\n }\n }\n\n /**\n * Get the underlying Knex instance (for advanced usage)\n */\n getKnex(): Knex {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n return this.knexInstance;\n }\n\n /**\n * Get connection status\n */\n isConnectedToDb(): boolean {\n return this.isConnected && this.knexInstance !== null;\n }\n\n /**\n * Initialize Db with context (connects and registers disconnect cleanup).\n * Params are read via getAllForModule(\"db\", defs). Same as dbInit(context, dbNameOrConnectionString).\n */\n static async init(context: Context, dbNameOrConnectionString?: string): Promise<Db> {\n return dbFindAndConnect(context, dbNameOrConnectionString);\n }\n}\n\n/**\n * Helper function to capitalize first letter of a string\n */\nfunction capitalizeFirstLetter(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Connect to database - creates Db instance, connects, registers cleanup, attaches profiler\n * \n * This is a dedicated connect function that handles:\n * - Creating Db instance with proper configuration\n * - Connecting to database\n * - Registering cleanup function\n * - Attaching profiler if needed\n * - Better error handling\n */\nexport async function dbConnect(\n context: Context,\n connectionString: string,\n name?: string,\n dbProfile?: boolean\n): Promise<Db> {\n // Get configuration from params\n const defs = {\n testDbConnection: 'boolean default true',\n name: 'string',\n poolMin: 'number default 2',\n poolMax: 'number default 10',\n acquireConnectionTimeout: 'number default 10000',\n sslRejectUnauthorized: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n\n // Create config\n const config: DbConfig = {\n connectionString,\n name: paramsConfig.name || name || 'default',\n testConnection: paramsConfig.testDbConnection,\n profile: dbProfile ?? false,\n pool: {\n min: paramsConfig.poolMin,\n max: paramsConfig.poolMax,\n },\n acquireConnectionTimeout: paramsConfig.acquireConnectionTimeout,\n ssl: {\n rejectUnauthorized: paramsConfig.sslRejectUnauthorized,\n },\n logger: context.logger,\n };\n \n try {\n // Create Db instance\n const db = new Db(config);\n \n // Register cleanup function\n context.registerCleanup(async () => {\n await db.disconnect();\n context.logger.debug(`[Db] instance \"${name || connectionString}\" destroyed`);\n });\n \n // Connect to database (profiler is attached automatically if config.profile is true)\n await db.connect();\n \n context.logger.debug(`[Db] instance \"${name || connectionString}\" initialized`);\n \n return db;\n } catch (error: any) {\n // Better error handling\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n throw new ParamError(`[Db] connect error: ${errorMsg}`);\n }\n}\n\n/**\n * Find and connect to database - resolves database name or connection string\n * \n * This function handles:\n * - Direct connection string (postgresql://... or mysql://...)\n * - Database name/label that gets resolved to dbConnectionString${CapitalizedName}\n * - Reading from params if no second parameter provided\n */\nexport async function dbFindAndConnect(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n let dbName: string | undefined;\n let dbConnectionString: string | undefined;\n let dbProfile: boolean | undefined;\n \n // If second parameter is provided\n if (dbNameOrConnectionString) {\n // Check if it looks like a connection string (postgresql:// or mysql://)\n if (dbNameOrConnectionString.match(/^(postgresql|mysql):\\/\\/[^\\s]+:[^\\s]+@[^\\s]+:\\d+\\/[^\\s]+$/)) {\n dbName = undefined;\n dbConnectionString = dbNameOrConnectionString;\n } else {\n // Treat it as a database name/label\n dbName = dbNameOrConnectionString;\n }\n } else {\n // No second parameter - read from params\n const defs = {\n dbName: 'string',\n dbConnectionString: 'string',\n dbProfile: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAll(defs);\n dbName = paramsConfig.dbName;\n dbConnectionString = paramsConfig.dbConnectionString;\n dbProfile = paramsConfig.dbProfile;\n }\n \n // Validate that we have either dbName or dbConnectionString\n if (!dbName && !dbConnectionString) {\n throw new ParamError('Db: either dbName or dbConnectionString must be specified');\n }\n \n // If dbName is provided, resolve it to connection string\n if (dbName) {\n const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;\n dbConnectionString = await context.params.get(paramName, 'string');\n if (!dbConnectionString) {\n throw new ParamError(\n `Db: cannot find dbConnectionString for dbName=\"${dbName}\" (looked for param \"${paramName}\")`\n );\n }\n }\n \n // Connect using dedicated connect function\n // context.logger.notice(`[Db] connecting to database \"${dbConnectionString}\"`);\n // TODO: figure out the output of \"--showUsedParams\" in the case of the DB - stuff gets to \"script\" section that doesn't belong there.\n const db = await dbConnect(context, dbConnectionString!, dbName, dbProfile);\n \n // Test connection (connect() already tests if testConnection is true, but we can test explicitly here too)\n // The test is already done in db.connect() if config.testConnection is true\n \n return db;\n}\n\n/**\n * Initialize Db instance with context (auto-connects)\n * \n * This is the standard \"init\" function that auto-initializes the DB component.\n * It calls dbFindAndConnect, optionally passing a second parameter.\n * \n * Usage:\n * ```typescript\n * // Auto-initialize from params:\n * const db = await dbInit(context);\n * \n * // Or with database name/label:\n * const db = await dbInit(context, 'local');\n * \n * // Or with direct connection string:\n * const db = await dbInit(context, 'postgresql://user:pass@host:5432/dbname');\n * ```\n */\nexport async function dbInit(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n return await dbFindAndConnect(context, dbNameOrConnectionString);\n}\n\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":["instance","knex"]}
package/dist/db.js CHANGED
@@ -381,7 +381,7 @@ async function dbFindAndConnect(context, dbNameOrConnectionString) {
381
381
  dbConnectionString: "string",
382
382
  dbProfile: "boolean default false"
383
383
  };
384
- const paramsConfig = context.params.getAllForModule(defs);
384
+ const paramsConfig = context.params.getAll(defs);
385
385
  dbName = paramsConfig.dbName;
386
386
  dbConnectionString = paramsConfig.dbConnectionString;
387
387
  dbProfile = paramsConfig.dbProfile;
package/dist/db.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db/index.ts","../src/errors.ts"],"sourcesContent":["/**\n * Database Client - Low-level SQL database operations\n *\n * Wraps Knex.js with connection management, profiling, and utility functions.\n * The instance can be used directly like a Knex instance: db('table').select()\n */\n\nimport knex, { Knex } from 'knex';\nimport { ParamError } from '../errors.js';\nimport type { Context } from '../init/types.js';\nimport type { DbConfig, DbOptions, DatabaseClient, QueryLogEntry, DbInstance } from './types.js';\n\n/**\n * Database client wrapper around Knex\n * \n * Usage:\n * ```typescript\n * const db = new Db({\n * connectionString: 'postgresql://user:pass@host:5432/dbname',\n * testConnection: true,\n * profile: false\n * });\n * await db.connect();\n * \n * // Use like Knex:\n * const users = await db('users').select('*');\n * await db('posts').insert({ title: 'Hello' });\n * ```\n */\nexport class Db {\n private knexInstance: Knex | null = null;\n private config: Required<DbConfig>;\n private logger: any;\n private queriesLog: QueryLogEntry[] = [];\n private isConnected: boolean = false;\n\n /**\n * Constructor - accepts config object\n * Use dbInit() function to initialize with Context\n */\n constructor(config: DbConfig) {\n if (!config.connectionString) {\n throw new ParamError('Db: connectionString is required');\n }\n\n this.config = {\n testConnection: true,\n profile: false,\n pool: { min: 2, max: 10 },\n acquireConnectionTimeout: 10000,\n ssl: { rejectUnauthorized: false },\n logger: console,\n name: 'default',\n ...config,\n };\n\n this.logger = this.config.logger;\n\n // Create a callable function wrapper that forwards to the instance\n // This allows db('table') to work like Knex\n const instance = this;\n const callableWrapper = function(...args: any[]) {\n // This function body is never executed - the Proxy apply trap handles calls\n // But we need a function to make the Proxy apply trap work\n throw new Error('This should never be called directly');\n };\n \n // Store instance reference on the wrapper for Proxy access\n (callableWrapper as any)._instance = instance;\n\n // Create a Proxy that makes the wrapper callable and forwards property access\n return new Proxy(callableWrapper, {\n // Intercept function calls: db('table')\n apply: (target, thisArg, argumentsList) => {\n const inst = (target as any)._instance;\n if (!inst.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n // Forward the call to the Knex instance (Knex instances are callable)\n return (inst.knexInstance as any)(...argumentsList);\n },\n // Intercept property access: db.schema, db.raw, etc.\n get: (target, prop) => {\n // Allow access to _instance for internal use\n if (prop === '_instance') {\n return (target as any)._instance;\n }\n \n const instance = (target as any)._instance;\n \n // List of our own methods that should NOT be forwarded to Knex\n const ownMethods = [\n 'connect',\n 'disconnect',\n 'testConnection',\n 'tableExists',\n 'getQueryLog',\n 'getKnex',\n 'isConnectedToDb',\n 'getErrorMessage',\n 'detectClient',\n 'attachProfiler',\n ];\n \n // Always return our own methods first (before checking Knex)\n if (prop in instance) {\n const value = (instance as any)[prop];\n // If it's one of our own methods, return it bound to instance\n if (typeof value === 'function' && ownMethods.includes(prop as string)) {\n return value.bind(instance);\n }\n // If it's a non-function property, return it\n if (typeof value !== 'function') {\n return value;\n }\n }\n \n // If we have a Knex instance, forward to it for everything else\n if (instance.knexInstance) {\n const knexProp = (instance.knexInstance as any)[prop];\n if (typeof knexProp === 'function') {\n // Bind methods to the Knex instance\n return knexProp.bind(instance.knexInstance);\n }\n return knexProp;\n }\n \n // Return our own methods that aren't in the ownMethods list (shouldn't happen, but fallback)\n if (prop in instance) {\n const method = (instance as any)[prop];\n if (typeof method === 'function') {\n return method.bind(instance);\n }\n return method;\n }\n \n // Property doesn't exist\n return undefined;\n },\n }) as any;\n }\n\n /**\n * Detect database client type from connection string\n */\n private detectClient(connectionString: string): DatabaseClient | null {\n if (connectionString.match(/^postgresql/)) {\n return 'pg';\n }\n if (connectionString.match(/^mysql/)) {\n return 'mysql2';\n }\n return null;\n }\n\n /**\n * Connect to the database\n */\n async connect(): Promise<void> {\n if (this.isConnected && this.knexInstance) {\n this.logger.warn?.('[Db] Already connected');\n return;\n }\n\n const client = this.detectClient(this.config.connectionString);\n if (!client) {\n throw new ParamError(\n `Db: Cannot determine client type from connection string. Expected postgresql:// or mysql://`\n );\n }\n\n try {\n // Force IPv4 only (disable IPv6) by setting family: 4 in connection config\n // Knex accepts connection as string or object; we wrap string to add family option\n const connectionConfig = {\n connectionString: this.config.connectionString,\n family: 4, // Force IPv4 only (disable IPv6)\n };\n\n this.knexInstance = knex({\n client,\n connection: connectionConfig,\n pool: this.config.pool,\n acquireConnectionTimeout: this.config.acquireConnectionTimeout,\n ...(this.config.ssl && { ssl: this.config.ssl }),\n } as any);\n\n // Attach profiler if enabled\n if (this.config.profile) {\n this.attachProfiler();\n }\n\n // Test connection if requested\n if (this.config.testConnection) {\n await this.testConnection();\n }\n\n this.isConnected = true;\n this.logger.debug?.(`[Db] Connected to database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n // If testConnection already threw a ParamError, preserve its message\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = this.getErrorMessage(error);\n throw new ParamError(`Db: Connection failed - ${errorMsg}`);\n }\n }\n\n /**\n * Disconnect from the database\n */\n async disconnect(): Promise<void> {\n if (!this.knexInstance) {\n return;\n }\n\n try {\n await this.knexInstance.destroy();\n this.knexInstance = null;\n this.isConnected = false;\n this.queriesLog = [];\n this.logger.debug?.(`[Db] Disconnected from database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);\n throw error;\n }\n }\n\n /**\n * Extract error message from various error types\n */\n private getErrorMessage(error: any): string {\n // Handle AggregateError (can contain multiple errors)\n if (error instanceof AggregateError) {\n const errors = error.errors || [];\n \n // If all errors are the same type (e.g., ECONNREFUSED for different IPs), show a consolidated message\n if (errors.length > 0) {\n const firstError = errors[0];\n const firstErrorMsg = firstError instanceof Error ? firstError.message : String(firstError);\n \n // Check if all errors are similar (same error code/type, different addresses)\n const allSimilar = errors.every((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Extract error code (e.g., \"ECONNREFUSED\") from message\n const codeMatch = msg.match(/^(\\w+)\\s/);\n const firstCodeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n return codeMatch && firstCodeMatch && codeMatch[1] === firstCodeMatch[1];\n });\n \n if (allSimilar && errors.length > 1) {\n // Extract addresses/IPs from error messages\n const addresses = errors.map((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Try to extract address (e.g., \"::1:5432\" or \"127.0.0.1:5432\")\n const addrMatch = msg.match(/([:\\d.]+:\\d+)/);\n return addrMatch ? addrMatch[1] : null;\n }).filter(Boolean);\n \n if (addresses.length > 0) {\n // Show consolidated message with all addresses\n const codeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n const code = codeMatch ? codeMatch[1] : 'Connection error';\n return `${code} (tried: ${addresses.join(', ')})`;\n }\n }\n \n // Fallback: show all errors but deduplicate identical messages\n const uniqueMessages = [...new Set(errors.map((e: any) => {\n return e instanceof Error ? e.message : String(e);\n }))];\n \n if (uniqueMessages.length === 1) {\n return uniqueMessages[0];\n }\n \n return uniqueMessages.join('; ');\n }\n \n return error.message || 'Multiple errors occurred';\n }\n \n // Handle standard Error objects\n if (error instanceof Error) {\n // Check for common database error properties (code is often present on Node.js errors)\n const errorWithCode = error as Error & { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${error.message || String(error)}`;\n }\n return error.message || String(error);\n }\n \n // Handle string errors\n if (typeof error === 'string') {\n return error;\n }\n \n // Handle objects with message property\n if (error?.message) {\n const msg = String(error.message);\n const errorWithCode = error as { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${msg}`;\n }\n return msg;\n }\n \n // Fallback: try to stringify the error\n return String(error) || 'Unknown error';\n }\n\n /**\n * Test database connection\n */\n async testConnection(): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n const result = await this.knexInstance.raw('SELECT 2+3 AS result');\n const isOk = result.rows?.[0]?.result === 5 || result[0]?.[0]?.result === 5;\n this.logger.debug?.(`[Db] Connection test: ${isOk ? 'OK' : 'FAILED'}`);\n return isOk;\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Connection test failed: ${errorMsg}`);\n throw new ParamError(`Db: Connection test failed - ${errorMsg}`);\n }\n }\n\n /**\n * Attach query profiler to log all queries\n */\n private attachProfiler(): void {\n if (!this.knexInstance) {\n return;\n }\n\n this.queriesLog = [];\n (this.knexInstance as any).queriesLog = this.queriesLog;\n\n this.knexInstance.on('query', (query: any) => {\n query.__startTime = process.hrtime();\n });\n\n this.knexInstance.on('query-response', (response: any, query: any) => {\n const [seconds, nanoseconds] = process.hrtime(query.__startTime);\n const executionTimeMs = ((seconds * 1000) + (nanoseconds / 1e6)).toFixed(2);\n\n const logEntry: QueryLogEntry = {\n sql: query.sql,\n bindings: query.bindings || [],\n executionTimeMs,\n };\n\n this.queriesLog.push(logEntry);\n this.logger.debug?.(`[Db] Query: ${query.sql} | Duration: ${executionTimeMs}ms`);\n });\n\n this.knexInstance.on('query-error', (error: Error, query: any) => {\n this.logger.error?.(`[Db] Query failed: ${query.sql}`, error);\n });\n }\n\n /**\n * Get query log (only available if profiling is enabled)\n */\n getQueryLog(): QueryLogEntry[] {\n return [...this.queriesLog];\n }\n\n /**\n * Check if a table exists\n */\n async tableExists(tableName: string): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n return await this.knexInstance.schema.hasTable(tableName);\n } catch (error: any) {\n this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);\n throw error;\n }\n }\n\n /**\n * Get the underlying Knex instance (for advanced usage)\n */\n getKnex(): Knex {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n return this.knexInstance;\n }\n\n /**\n * Get connection status\n */\n isConnectedToDb(): boolean {\n return this.isConnected && this.knexInstance !== null;\n }\n\n /**\n * Initialize Db with context (connects and registers disconnect cleanup).\n * Params are read via getAllForModule(\"db\", defs). Same as dbInit(context, dbNameOrConnectionString).\n */\n static async init(context: Context, dbNameOrConnectionString?: string): Promise<Db> {\n return dbFindAndConnect(context, dbNameOrConnectionString);\n }\n}\n\n/**\n * Helper function to capitalize first letter of a string\n */\nfunction capitalizeFirstLetter(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Connect to database - creates Db instance, connects, registers cleanup, attaches profiler\n * \n * This is a dedicated connect function that handles:\n * - Creating Db instance with proper configuration\n * - Connecting to database\n * - Registering cleanup function\n * - Attaching profiler if needed\n * - Better error handling\n */\nexport async function dbConnect(\n context: Context,\n connectionString: string,\n name?: string,\n dbProfile?: boolean\n): Promise<Db> {\n // Get configuration from params\n const defs = {\n testDbConnection: 'boolean default true',\n name: 'string',\n poolMin: 'number default 2',\n poolMax: 'number default 10',\n acquireConnectionTimeout: 'number default 10000',\n sslRejectUnauthorized: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n\n // Create config\n const config: DbConfig = {\n connectionString,\n name: paramsConfig.name || name || 'default',\n testConnection: paramsConfig.testDbConnection,\n profile: dbProfile ?? false,\n pool: {\n min: paramsConfig.poolMin,\n max: paramsConfig.poolMax,\n },\n acquireConnectionTimeout: paramsConfig.acquireConnectionTimeout,\n ssl: {\n rejectUnauthorized: paramsConfig.sslRejectUnauthorized,\n },\n logger: context.logger,\n };\n \n try {\n // Create Db instance\n const db = new Db(config);\n \n // Register cleanup function\n context.registerCleanup(async () => {\n await db.disconnect();\n context.logger.debug(`[Db] instance \"${name || connectionString}\" destroyed`);\n });\n \n // Connect to database (profiler is attached automatically if config.profile is true)\n await db.connect();\n \n context.logger.debug(`[Db] instance \"${name || connectionString}\" initialized`);\n \n return db;\n } catch (error: any) {\n // Better error handling\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n throw new ParamError(`[Db] connect error: ${errorMsg}`);\n }\n}\n\n/**\n * Find and connect to database - resolves database name or connection string\n * \n * This function handles:\n * - Direct connection string (postgresql://... or mysql://...)\n * - Database name/label that gets resolved to dbConnectionString${CapitalizedName}\n * - Reading from params if no second parameter provided\n */\nexport async function dbFindAndConnect(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n let dbName: string | undefined;\n let dbConnectionString: string | undefined;\n let dbProfile: boolean | undefined;\n \n // If second parameter is provided\n if (dbNameOrConnectionString) {\n // Check if it looks like a connection string (postgresql:// or mysql://)\n if (dbNameOrConnectionString.match(/^(postgresql|mysql):\\/\\/[^\\s]+:[^\\s]+@[^\\s]+:\\d+\\/[^\\s]+$/)) {\n dbName = undefined;\n dbConnectionString = dbNameOrConnectionString;\n } else {\n // Treat it as a database name/label\n dbName = dbNameOrConnectionString;\n }\n } else {\n // No second parameter - read from params\n const defs = {\n dbName: 'string',\n dbConnectionString: 'string',\n dbProfile: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n dbName = paramsConfig.dbName;\n dbConnectionString = paramsConfig.dbConnectionString;\n dbProfile = paramsConfig.dbProfile;\n }\n \n // Validate that we have either dbName or dbConnectionString\n if (!dbName && !dbConnectionString) {\n throw new ParamError('Db: either dbName or dbConnectionString must be specified');\n }\n \n // If dbName is provided, resolve it to connection string\n if (dbName) {\n const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;\n dbConnectionString = await context.params.get(paramName, 'string');\n if (!dbConnectionString) {\n throw new ParamError(\n `Db: cannot find dbConnectionString for dbName=\"${dbName}\" (looked for param \"${paramName}\")`\n );\n }\n }\n \n // Connect using dedicated connect function\n const db = await dbConnect(context, dbConnectionString!, dbName, dbProfile);\n \n // Test connection (connect() already tests if testConnection is true, but we can test explicitly here too)\n // The test is already done in db.connect() if config.testConnection is true\n \n return db;\n}\n\n/**\n * Initialize Db instance with context (auto-connects)\n * \n * This is the standard \"init\" function that auto-initializes the DB component.\n * It calls dbFindAndConnect, optionally passing a second parameter.\n * \n * Usage:\n * ```typescript\n * // Auto-initialize from params:\n * const db = await dbInit(context);\n * \n * // Or with database name/label:\n * const db = await dbInit(context, 'local');\n * \n * // Or with direct connection string:\n * const db = await dbInit(context, 'postgresql://user:pass@host:5432/dbname');\n * ```\n */\nexport async function dbInit(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n return await dbFindAndConnect(context, dbNameOrConnectionString);\n}\n\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":["instance"]}
1
+ {"version":3,"sources":["../src/db/index.ts","../src/errors.ts"],"sourcesContent":["/**\n * Database Client - Low-level SQL database operations\n *\n * Wraps Knex.js with connection management, profiling, and utility functions.\n * The instance can be used directly like a Knex instance: db('table').select()\n */\n\nimport knex, { Knex } from 'knex';\nimport { ParamError } from '../errors.js';\nimport type { Context } from '../init/types.js';\nimport type { DbConfig, DbOptions, DatabaseClient, QueryLogEntry, DbInstance } from './types.js';\n\n/**\n * Database client wrapper around Knex\n * \n * Usage:\n * ```typescript\n * const db = new Db({\n * connectionString: 'postgresql://user:pass@host:5432/dbname',\n * testConnection: true,\n * profile: false\n * });\n * await db.connect();\n * \n * // Use like Knex:\n * const users = await db('users').select('*');\n * await db('posts').insert({ title: 'Hello' });\n * ```\n */\nexport class Db {\n private knexInstance: Knex | null = null;\n private config: Required<DbConfig>;\n private logger: any;\n private queriesLog: QueryLogEntry[] = [];\n private isConnected: boolean = false;\n\n /**\n * Constructor - accepts config object\n * Use dbInit() function to initialize with Context\n */\n constructor(config: DbConfig) {\n if (!config.connectionString) {\n throw new ParamError('Db: connectionString is required');\n }\n\n this.config = {\n testConnection: true,\n profile: false,\n pool: { min: 2, max: 10 },\n acquireConnectionTimeout: 10000,\n ssl: { rejectUnauthorized: false },\n logger: console,\n name: 'default',\n ...config,\n };\n\n this.logger = this.config.logger;\n\n // Create a callable function wrapper that forwards to the instance\n // This allows db('table') to work like Knex\n const instance = this;\n const callableWrapper = function(...args: any[]) {\n // This function body is never executed - the Proxy apply trap handles calls\n // But we need a function to make the Proxy apply trap work\n throw new Error('This should never be called directly');\n };\n \n // Store instance reference on the wrapper for Proxy access\n (callableWrapper as any)._instance = instance;\n\n // Create a Proxy that makes the wrapper callable and forwards property access\n return new Proxy(callableWrapper, {\n // Intercept function calls: db('table')\n apply: (target, thisArg, argumentsList) => {\n const inst = (target as any)._instance;\n if (!inst.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n // Forward the call to the Knex instance (Knex instances are callable)\n return (inst.knexInstance as any)(...argumentsList);\n },\n // Intercept property access: db.schema, db.raw, etc.\n get: (target, prop) => {\n // Allow access to _instance for internal use\n if (prop === '_instance') {\n return (target as any)._instance;\n }\n \n const instance = (target as any)._instance;\n \n // List of our own methods that should NOT be forwarded to Knex\n const ownMethods = [\n 'connect',\n 'disconnect',\n 'testConnection',\n 'tableExists',\n 'getQueryLog',\n 'getKnex',\n 'isConnectedToDb',\n 'getErrorMessage',\n 'detectClient',\n 'attachProfiler',\n ];\n \n // Always return our own methods first (before checking Knex)\n if (prop in instance) {\n const value = (instance as any)[prop];\n // If it's one of our own methods, return it bound to instance\n if (typeof value === 'function' && ownMethods.includes(prop as string)) {\n return value.bind(instance);\n }\n // If it's a non-function property, return it\n if (typeof value !== 'function') {\n return value;\n }\n }\n \n // If we have a Knex instance, forward to it for everything else\n if (instance.knexInstance) {\n const knexProp = (instance.knexInstance as any)[prop];\n if (typeof knexProp === 'function') {\n // Bind methods to the Knex instance\n return knexProp.bind(instance.knexInstance);\n }\n return knexProp;\n }\n \n // Return our own methods that aren't in the ownMethods list (shouldn't happen, but fallback)\n if (prop in instance) {\n const method = (instance as any)[prop];\n if (typeof method === 'function') {\n return method.bind(instance);\n }\n return method;\n }\n \n // Property doesn't exist\n return undefined;\n },\n }) as any;\n }\n\n /**\n * Detect database client type from connection string\n */\n private detectClient(connectionString: string): DatabaseClient | null {\n if (connectionString.match(/^postgresql/)) {\n return 'pg';\n }\n if (connectionString.match(/^mysql/)) {\n return 'mysql2';\n }\n return null;\n }\n\n /**\n * Connect to the database\n */\n async connect(): Promise<void> {\n if (this.isConnected && this.knexInstance) {\n this.logger.warn?.('[Db] Already connected');\n return;\n }\n\n const client = this.detectClient(this.config.connectionString);\n if (!client) {\n throw new ParamError(\n `Db: Cannot determine client type from connection string. Expected postgresql:// or mysql://`\n );\n }\n\n try {\n // Force IPv4 only (disable IPv6) by setting family: 4 in connection config\n // Knex accepts connection as string or object; we wrap string to add family option\n const connectionConfig = {\n connectionString: this.config.connectionString,\n family: 4, // Force IPv4 only (disable IPv6)\n };\n\n this.knexInstance = knex({\n client,\n connection: connectionConfig,\n pool: this.config.pool,\n acquireConnectionTimeout: this.config.acquireConnectionTimeout,\n ...(this.config.ssl && { ssl: this.config.ssl }),\n } as any);\n\n // Attach profiler if enabled\n if (this.config.profile) {\n this.attachProfiler();\n }\n\n // Test connection if requested\n if (this.config.testConnection) {\n await this.testConnection();\n }\n\n this.isConnected = true;\n this.logger.debug?.(`[Db] Connected to database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n // If testConnection already threw a ParamError, preserve its message\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = this.getErrorMessage(error);\n throw new ParamError(`Db: Connection failed - ${errorMsg}`);\n }\n }\n\n /**\n * Disconnect from the database\n */\n async disconnect(): Promise<void> {\n if (!this.knexInstance) {\n return;\n }\n\n try {\n await this.knexInstance.destroy();\n this.knexInstance = null;\n this.isConnected = false;\n this.queriesLog = [];\n this.logger.debug?.(`[Db] Disconnected from database \"${this.config.name || this.config.connectionString}\"`);\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);\n throw error;\n }\n }\n\n /**\n * Extract error message from various error types\n */\n private getErrorMessage(error: any): string {\n // Handle AggregateError (can contain multiple errors)\n if (error instanceof AggregateError) {\n const errors = error.errors || [];\n \n // If all errors are the same type (e.g., ECONNREFUSED for different IPs), show a consolidated message\n if (errors.length > 0) {\n const firstError = errors[0];\n const firstErrorMsg = firstError instanceof Error ? firstError.message : String(firstError);\n \n // Check if all errors are similar (same error code/type, different addresses)\n const allSimilar = errors.every((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Extract error code (e.g., \"ECONNREFUSED\") from message\n const codeMatch = msg.match(/^(\\w+)\\s/);\n const firstCodeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n return codeMatch && firstCodeMatch && codeMatch[1] === firstCodeMatch[1];\n });\n \n if (allSimilar && errors.length > 1) {\n // Extract addresses/IPs from error messages\n const addresses = errors.map((e: any) => {\n const msg = e instanceof Error ? e.message : String(e);\n // Try to extract address (e.g., \"::1:5432\" or \"127.0.0.1:5432\")\n const addrMatch = msg.match(/([:\\d.]+:\\d+)/);\n return addrMatch ? addrMatch[1] : null;\n }).filter(Boolean);\n \n if (addresses.length > 0) {\n // Show consolidated message with all addresses\n const codeMatch = firstErrorMsg.match(/^(\\w+)\\s/);\n const code = codeMatch ? codeMatch[1] : 'Connection error';\n return `${code} (tried: ${addresses.join(', ')})`;\n }\n }\n \n // Fallback: show all errors but deduplicate identical messages\n const uniqueMessages = [...new Set(errors.map((e: any) => {\n return e instanceof Error ? e.message : String(e);\n }))];\n \n if (uniqueMessages.length === 1) {\n return uniqueMessages[0];\n }\n \n return uniqueMessages.join('; ');\n }\n \n return error.message || 'Multiple errors occurred';\n }\n \n // Handle standard Error objects\n if (error instanceof Error) {\n // Check for common database error properties (code is often present on Node.js errors)\n const errorWithCode = error as Error & { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${error.message || String(error)}`;\n }\n return error.message || String(error);\n }\n \n // Handle string errors\n if (typeof error === 'string') {\n return error;\n }\n \n // Handle objects with message property\n if (error?.message) {\n const msg = String(error.message);\n const errorWithCode = error as { code?: string };\n if (errorWithCode.code) {\n return `${errorWithCode.code}: ${msg}`;\n }\n return msg;\n }\n \n // Fallback: try to stringify the error\n return String(error) || 'Unknown error';\n }\n\n /**\n * Test database connection\n */\n async testConnection(): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n const result = await this.knexInstance.raw('SELECT 2+3 AS result');\n const isOk = result.rows?.[0]?.result === 5 || result[0]?.[0]?.result === 5;\n this.logger.debug?.(`[Db] Connection test: ${isOk ? 'OK' : 'FAILED'}`);\n return isOk;\n } catch (error: any) {\n const errorMsg = this.getErrorMessage(error);\n this.logger.error?.(`[Db] Connection test failed: ${errorMsg}`);\n throw new ParamError(`Db: Connection test failed - ${errorMsg}`);\n }\n }\n\n /**\n * Attach query profiler to log all queries\n */\n attachProfiler(): void {\n if (!this.knexInstance) {\n return;\n }\n\n this.queriesLog = [];\n (this.knexInstance as any).queriesLog = this.queriesLog;\n\n this.knexInstance.on('query', (query: any) => {\n query.__startTime = process.hrtime();\n });\n\n this.knexInstance.on('query-response', (response: any, query: any) => {\n const [seconds, nanoseconds] = process.hrtime(query.__startTime);\n const executionTimeMs = ((seconds * 1000) + (nanoseconds / 1e6)).toFixed(2);\n\n const logEntry: QueryLogEntry = {\n sql: query.sql,\n bindings: query.bindings || [],\n executionTimeMs,\n };\n\n this.queriesLog.push(logEntry);\n this.logger.debug?.(`[Db] Query: ${query.sql} | Duration: ${executionTimeMs}ms`);\n });\n\n this.knexInstance.on('query-error', (error: Error, query: any) => {\n this.logger.error?.(`[Db] Query failed: ${query.sql}`, error);\n });\n }\n\n /**\n * Get query log (only available if profiling is enabled)\n */\n getQueryLog(): QueryLogEntry[] {\n return [...this.queriesLog];\n }\n\n /**\n * Check if a table exists\n */\n async tableExists(tableName: string): Promise<boolean> {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n\n try {\n return await this.knexInstance.schema.hasTable(tableName);\n } catch (error: any) {\n this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);\n throw error;\n }\n }\n\n /**\n * Get the underlying Knex instance (for advanced usage)\n */\n getKnex(): Knex {\n if (!this.knexInstance) {\n throw new Error('Db: Not connected. Call connect() first.');\n }\n return this.knexInstance;\n }\n\n /**\n * Get connection status\n */\n isConnectedToDb(): boolean {\n return this.isConnected && this.knexInstance !== null;\n }\n\n /**\n * Initialize Db with context (connects and registers disconnect cleanup).\n * Params are read via getAllForModule(\"db\", defs). Same as dbInit(context, dbNameOrConnectionString).\n */\n static async init(context: Context, dbNameOrConnectionString?: string): Promise<Db> {\n return dbFindAndConnect(context, dbNameOrConnectionString);\n }\n}\n\n/**\n * Helper function to capitalize first letter of a string\n */\nfunction capitalizeFirstLetter(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Connect to database - creates Db instance, connects, registers cleanup, attaches profiler\n * \n * This is a dedicated connect function that handles:\n * - Creating Db instance with proper configuration\n * - Connecting to database\n * - Registering cleanup function\n * - Attaching profiler if needed\n * - Better error handling\n */\nexport async function dbConnect(\n context: Context,\n connectionString: string,\n name?: string,\n dbProfile?: boolean\n): Promise<Db> {\n // Get configuration from params\n const defs = {\n testDbConnection: 'boolean default true',\n name: 'string',\n poolMin: 'number default 2',\n poolMax: 'number default 10',\n acquireConnectionTimeout: 'number default 10000',\n sslRejectUnauthorized: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAllForModule(defs);\n\n // Create config\n const config: DbConfig = {\n connectionString,\n name: paramsConfig.name || name || 'default',\n testConnection: paramsConfig.testDbConnection,\n profile: dbProfile ?? false,\n pool: {\n min: paramsConfig.poolMin,\n max: paramsConfig.poolMax,\n },\n acquireConnectionTimeout: paramsConfig.acquireConnectionTimeout,\n ssl: {\n rejectUnauthorized: paramsConfig.sslRejectUnauthorized,\n },\n logger: context.logger,\n };\n \n try {\n // Create Db instance\n const db = new Db(config);\n \n // Register cleanup function\n context.registerCleanup(async () => {\n await db.disconnect();\n context.logger.debug(`[Db] instance \"${name || connectionString}\" destroyed`);\n });\n \n // Connect to database (profiler is attached automatically if config.profile is true)\n await db.connect();\n \n context.logger.debug(`[Db] instance \"${name || connectionString}\" initialized`);\n \n return db;\n } catch (error: any) {\n // Better error handling\n if (error instanceof ParamError) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n throw new ParamError(`[Db] connect error: ${errorMsg}`);\n }\n}\n\n/**\n * Find and connect to database - resolves database name or connection string\n * \n * This function handles:\n * - Direct connection string (postgresql://... or mysql://...)\n * - Database name/label that gets resolved to dbConnectionString${CapitalizedName}\n * - Reading from params if no second parameter provided\n */\nexport async function dbFindAndConnect(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n let dbName: string | undefined;\n let dbConnectionString: string | undefined;\n let dbProfile: boolean | undefined;\n \n // If second parameter is provided\n if (dbNameOrConnectionString) {\n // Check if it looks like a connection string (postgresql:// or mysql://)\n if (dbNameOrConnectionString.match(/^(postgresql|mysql):\\/\\/[^\\s]+:[^\\s]+@[^\\s]+:\\d+\\/[^\\s]+$/)) {\n dbName = undefined;\n dbConnectionString = dbNameOrConnectionString;\n } else {\n // Treat it as a database name/label\n dbName = dbNameOrConnectionString;\n }\n } else {\n // No second parameter - read from params\n const defs = {\n dbName: 'string',\n dbConnectionString: 'string',\n dbProfile: 'boolean default false',\n };\n \n const paramsConfig = context.params.getAll(defs);\n dbName = paramsConfig.dbName;\n dbConnectionString = paramsConfig.dbConnectionString;\n dbProfile = paramsConfig.dbProfile;\n }\n \n // Validate that we have either dbName or dbConnectionString\n if (!dbName && !dbConnectionString) {\n throw new ParamError('Db: either dbName or dbConnectionString must be specified');\n }\n \n // If dbName is provided, resolve it to connection string\n if (dbName) {\n const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;\n dbConnectionString = await context.params.get(paramName, 'string');\n if (!dbConnectionString) {\n throw new ParamError(\n `Db: cannot find dbConnectionString for dbName=\"${dbName}\" (looked for param \"${paramName}\")`\n );\n }\n }\n \n // Connect using dedicated connect function\n // context.logger.notice(`[Db] connecting to database \"${dbConnectionString}\"`);\n // TODO: figure out the output of \"--showUsedParams\" in the case of the DB - stuff gets to \"script\" section that doesn't belong there.\n const db = await dbConnect(context, dbConnectionString!, dbName, dbProfile);\n \n // Test connection (connect() already tests if testConnection is true, but we can test explicitly here too)\n // The test is already done in db.connect() if config.testConnection is true\n \n return db;\n}\n\n/**\n * Initialize Db instance with context (auto-connects)\n * \n * This is the standard \"init\" function that auto-initializes the DB component.\n * It calls dbFindAndConnect, optionally passing a second parameter.\n * \n * Usage:\n * ```typescript\n * // Auto-initialize from params:\n * const db = await dbInit(context);\n * \n * // Or with database name/label:\n * const db = await dbInit(context, 'local');\n * \n * // Or with direct connection string:\n * const db = await dbInit(context, 'postgresql://user:pass@host:5432/dbname');\n * ```\n */\nexport async function dbInit(\n context: Context,\n dbNameOrConnectionString?: string\n): Promise<Db> {\n return await dbFindAndConnect(context, dbNameOrConnectionString);\n}\n\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message: string) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n"],"mappings":"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","names":["instance"]}
@@ -366,7 +366,7 @@ var FileDatabase = class _FileDatabase {
366
366
  const versions = await this.getVersions();
367
367
  while (versions.length > this.maxVersions) {
368
368
  const versionToDelete = import_path3.default.resolve(this.getDestinationPath(), versions.shift());
369
- this.logger.debug?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
369
+ this.logger.silly?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
370
370
  await import_fs3.default.promises.rm(versionToDelete, { recursive: true, force: true });
371
371
  }
372
372
  return versionName;
@@ -644,7 +644,7 @@ var FileDatabase = class _FileDatabase {
644
644
  };
645
645
  this.metadata.files.push(fileEntry);
646
646
  this.lastFileData = null;
647
- this.logger.debug?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
647
+ this.logger.silly?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
648
648
  }
649
649
  /**
650
650
  * Figure out what data to write and which file to use (for pagination)
@@ -677,7 +677,7 @@ var FileDatabase = class _FileDatabase {
677
677
  const filesBeforeCreate = this.metadata.files.length;
678
678
  this.makeNewFile();
679
679
  newlyCreatedFileIndex = filesBeforeCreate;
680
- this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
680
+ this.logger.silly?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
681
681
  } else if (this.metadata.files.length === 0) {
682
682
  this.makeNewFile();
683
683
  }
@@ -726,7 +726,7 @@ var FileDatabase = class _FileDatabase {
726
726
  dataLeftOver = null;
727
727
  }
728
728
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
729
- this.logger.debug?.(
729
+ this.logger.silly?.(
730
730
  `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
731
731
  );
732
732
  return { dataToWrite, dataLeftOver, fileName };
@@ -781,7 +781,7 @@ var FileDatabase = class _FileDatabase {
781
781
  this.metadata.modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
782
782
  this.metadata.dataType = detectDataType(dataToWrite);
783
783
  this.metadata.totalRecords = this.metadata.files.reduce((sum, file) => sum + (file.recordsCount || 0), 0);
784
- this.logger.debug?.(
784
+ this.logger.silly?.(
785
785
  `[FileDatabase] Updated metadata for file ${currentFile.fileName}: recordsCount=${recordsCount}, totalRecords=${this.metadata.totalRecords}`
786
786
  );
787
787
  }
@@ -805,7 +805,7 @@ var FileDatabase = class _FileDatabase {
805
805
  }
806
806
  try {
807
807
  await import_fs3.default.promises.writeFile(filePath, serializedData, "utf8");
808
- this.logger.debug?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
808
+ this.logger.silly?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
809
809
  } catch (error) {
810
810
  throw new FileDatabaseError(`Failed to write file ${filePath}: ${error.message}`);
811
811
  }
@@ -887,7 +887,8 @@ var FileDatabase = class _FileDatabase {
887
887
  this.useMetadata = format.hasMetadata;
888
888
  }
889
889
  if (this.useMetadata) {
890
- const metadataPath = import_path3.default.join(this.getDestinationPath(), "metadata.json");
890
+ const destPath = this.getDestinationPath();
891
+ const metadataPath = import_path3.default.join(destPath, "metadata.json");
891
892
  if (import_fs3.default.existsSync(metadataPath)) {
892
893
  try {
893
894
  const rawData = await import_fs3.default.promises.readFile(metadataPath, "utf8");
@@ -901,7 +902,9 @@ var FileDatabase = class _FileDatabase {
901
902
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
902
903
  }
903
904
  } else {
904
- throw new FileDatabaseError("[FileDatabase] No metadata found in non-versioned mode");
905
+ throw new FileDatabaseError(
906
+ `[FileDatabase] No metadata found in non-versioned mode. Looked for: ${metadataPath} (table path: ${destPath})`
907
+ );
905
908
  }
906
909
  } else {
907
910
  this.metadata = await this.figureMetadataFromVersionFiles("");
@@ -918,6 +921,13 @@ var FileDatabase = class _FileDatabase {
918
921
  * Write data to the file database
919
922
  */
920
923
  async write(data, options = {}) {
924
+ if (options.filename) {
925
+ const destPath2 = this.getDestinationPath();
926
+ await ensurePath(destPath2);
927
+ const filePath = import_path3.default.join(destPath2, options.filename);
928
+ await this.safeWrite(filePath, data);
929
+ return;
930
+ }
921
931
  if (options.forceNewVersion && !this.versioned) {
922
932
  throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
923
933
  }
@@ -941,17 +951,17 @@ var FileDatabase = class _FileDatabase {
941
951
  });
942
952
  if (matches) {
943
953
  targetFileIndex = i;
944
- this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
954
+ this.logger.silly?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
945
955
  break;
946
956
  } else {
947
- this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
957
+ this.logger.silly?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
948
958
  }
949
959
  }
950
960
  if (targetFileIndex === null) {
951
- this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
961
+ this.logger.silly?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
952
962
  }
953
963
  } else {
954
- this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
964
+ this.logger.silly?.(`[FileDatabase] No custom metadata provided, will create new file`);
955
965
  }
956
966
  if (targetFileIndex !== null) {
957
967
  const targetFile = this.metadata.files[targetFileIndex];
@@ -980,7 +990,17 @@ var FileDatabase = class _FileDatabase {
980
990
  * Read data from the file database
981
991
  */
982
992
  async read(options = {}) {
983
- const { version, nextPage = false, pageSize } = options;
993
+ const { version, nextPage = false, pageSize, filename } = options;
994
+ if (filename) {
995
+ const destPath = this.getDestinationPath(version);
996
+ const filePath = import_path3.default.join(destPath, filename);
997
+ try {
998
+ const rawData = await import_fs3.default.promises.readFile(filePath, "utf8");
999
+ return JSON.parse(rawData);
1000
+ } catch (error) {
1001
+ throw new FileDatabaseError(`Failed to read file ${filename}: ${error.message}`);
1002
+ }
1003
+ }
984
1004
  await this.prepare({ read: true, version });
985
1005
  const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
986
1006
  if (isNonPaginatedData) {
@@ -1061,6 +1081,67 @@ var FileDatabase = class _FileDatabase {
1061
1081
  this.currentRecord = 0;
1062
1082
  this.hasReadFirstPage = false;
1063
1083
  }
1084
+ /**
1085
+ * List filenames in the table directory.
1086
+ * For catalog/key-value usage (files written with { filename }).
1087
+ * Returns data file names (.json, .txt, .xml) excluding metadata.json.
1088
+ */
1089
+ async listFilenames() {
1090
+ const destPath = this.versioned && this.currentVersion ? import_path3.default.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1091
+ try {
1092
+ const entries = await import_fs3.default.promises.readdir(destPath, { withFileTypes: true });
1093
+ return entries.filter((e) => e.isFile() && e.name !== "metadata.json" && /\.(json|txt|xml)$/i.test(e.name)).map((e) => e.name);
1094
+ } catch (err) {
1095
+ if (err?.code === "ENOENT") return [];
1096
+ throw new FileDatabaseError(`Failed to list files: ${err.message}`);
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Remove a file from the table directory (catalog mode).
1101
+ * Use with listFilenames() to manage individual files.
1102
+ */
1103
+ async removeFile(filename) {
1104
+ const destPath = this.versioned && this.currentVersion ? import_path3.default.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1105
+ const filePath = import_path3.default.join(destPath, filename);
1106
+ try {
1107
+ await import_fs3.default.promises.unlink(filePath);
1108
+ } catch (err) {
1109
+ if (err?.code === "ENOENT") return;
1110
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Remove a file and its metadata entry (non-versioned mode with useMetadata).
1115
+ * Use with findData() to get fileName, then call removeFileEntry to delete.
1116
+ */
1117
+ async removeFileEntry(filename) {
1118
+ if (this.versioned) {
1119
+ throw new FileDatabaseError("removeFileEntry is only supported in non-versioned mode");
1120
+ }
1121
+ await this.prepare({ read: true });
1122
+ const idx = this.metadata.files.findIndex((f) => f.fileName === filename);
1123
+ if (idx === -1) {
1124
+ throw new FileDatabaseError(`File entry ${filename} not found in metadata`);
1125
+ }
1126
+ const entry = this.metadata.files[idx];
1127
+ const recordsCount = entry.recordsCount || 0;
1128
+ this.metadata.files.splice(idx, 1);
1129
+ this.metadata.totalRecords = Math.max(0, (this.metadata.totalRecords || 0) - recordsCount);
1130
+ const destPath = this.getDestinationPath();
1131
+ const filePath = import_path3.default.join(destPath, filename);
1132
+ try {
1133
+ await import_fs3.default.promises.unlink(filePath);
1134
+ } catch (err) {
1135
+ if (err?.code === "ENOENT") {
1136
+ this.logger.warn?.(`[FileDatabase] File ${filename} already missing on disk`);
1137
+ } else {
1138
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1139
+ }
1140
+ }
1141
+ if (this.useMetadata) {
1142
+ await this.saveVersionMetadata(this.metadata);
1143
+ }
1144
+ }
1064
1145
  /**
1065
1146
  * Set file-level synopsis calculation function
1066
1147
  */