{"version":3,"file":"db.cjs","names":["getConfig","SpanStatusCode"],"sources":["../src/db.ts"],"sourcesContent":["/**\n * Database Instrumentation Helpers\n *\n * Optional import: Not included in main bundle\n * Import from: 'autotel/db'\n *\n * Provides functional utilities for database query instrumentation.\n * Works with Prisma, Drizzle, TypeORM, raw SQL, and more.\n *\n * @example\n * ```typescript\n * import { instrumentDatabase } from 'autotel/db'\n *\n * const db = drizzle(pool)\n * instrumentDatabase(db, { dbSystem: 'postgresql', dbName: 'myapp' })\n *\n * // Now all queries are automatically trace\n * await db.select().from(users)\n * ```\n */\n\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { getConfig } from './config';\n\n/**\n * Helper: Trace a single database query\n *\n * @example\n * ```typescript\n * import { tracebQuery } from 'autotel/db'\n *\n * const users = await tracebQuery(\n *   'postgresql',\n *   'SELECT',\n *   () => db.query('SELECT * FROM users WHERE active = true')\n * )\n * ```\n */\nexport async function tracebQuery<T>(\n  dbSystem: string,\n  operation: string,\n  fn: () => Promise<T>,\n  attributes?: Record<string, string | number>,\n): Promise<T> {\n  const config = getConfig();\n  const tracer = config.tracer;\n\n  const spanName = `${dbSystem}.${operation}`;\n\n  return tracer.startActiveSpan(spanName, async (span) => {\n    const startTime = performance.now();\n\n    try {\n      span.setAttributes({\n        'db.system': dbSystem,\n        'db.operation': operation,\n        ...attributes,\n      });\n\n      const result = await fn();\n\n      const duration = performance.now() - startTime;\n      span.setStatus({ code: SpanStatusCode.OK });\n      span.setAttribute('db.duration_ms', duration);\n\n      if (Array.isArray(result)) {\n        span.setAttribute('db.result_count', result.length);\n      }\n\n      return result;\n    } catch (error) {\n      const duration = performance.now() - startTime;\n\n      span.setStatus({\n        code: SpanStatusCode.ERROR,\n        message: error instanceof Error ? error.message : 'Unknown error',\n      });\n\n      span.setAttributes({\n        'db.duration_ms': duration,\n        'error.type':\n          error instanceof Error ? error.constructor.name : 'Unknown',\n        'error.message':\n          error instanceof Error ? error.message : 'Unknown error',\n      });\n\n      throw error;\n    } finally {\n      span.end();\n    }\n  });\n}\n\n// Helper functions\n\nfunction inferDbOperation(methodName: string): string {\n  const lower = methodName.toLowerCase();\n  if (lower.includes('find') || lower.includes('get') || lower.includes('list'))\n    return 'SELECT';\n  if (lower.includes('create') || lower.includes('insert')) return 'INSERT';\n  if (lower.includes('update') || lower.includes('modify')) return 'UPDATE';\n  if (lower.includes('delete') || lower.includes('remove')) return 'DELETE';\n  if (lower.includes('count')) return 'COUNT';\n  return 'QUERY';\n}\n\nfunction inferTableName(methodName: string): string | undefined {\n  // Extract table name from method patterns like:\n  // findUser -> user\n  // listUsers -> users\n  // createOrder -> order\n\n  const patterns = [\n    /find([A-Z][a-zA-Z]+)/,\n    /get([A-Z][a-zA-Z]+)/,\n    /list([A-Z][a-zA-Z]+)/,\n    /create([A-Z][a-zA-Z]+)/,\n    /update([A-Z][a-zA-Z]+)/,\n    /delete([A-Z][a-zA-Z]+)/,\n    /remove([A-Z][a-zA-Z]+)/,\n  ];\n\n  for (const pattern of patterns) {\n    const match = methodName.match(pattern);\n    if (match && match[1]) {\n      return match[1].toLowerCase();\n    }\n  }\n\n  return undefined;\n}\n\nfunction sanitizeSqlQuery(query: string): string {\n  // Remove string literals and sensitive values (PII, credentials, etc.)\n  // Preserves query structure for debugging while protecting data\n  return query\n    .replaceAll(/'[^']*'/g, \"'?'\")\n    .replaceAll(/\"[^\"]*\"/g, '\"?\"')\n    .replaceAll(/\\b\\d+\\b/g, '?') // Replace literal numbers\n    .trim();\n}\n\n/**\n * Common database operation metrics\n */\nexport const DB_OPERATIONS = {\n  SELECT: 'SELECT',\n  INSERT: 'INSERT',\n  UPDATE: 'UPDATE',\n  DELETE: 'DELETE',\n  COUNT: 'COUNT',\n  AGGREGATE: 'AGGREGATE',\n} as const;\n\n/**\n * Common database systems\n */\nexport const DB_SYSTEMS = {\n  POSTGRESQL: 'postgresql',\n  MYSQL: 'mysql',\n  MONGODB: 'mongodb',\n  REDIS: 'redis',\n  SQLITE: 'sqlite',\n  MSSQL: 'mssql',\n} as const;\n\n// Symbol for idempotency - prevents double-instrumentation\nconst INSTRUMENTED_SYMBOL = Symbol.for('autotel.db.instrumented');\n\n/**\n * Options for instrumentDatabase\n */\nexport interface InstrumentDatabaseOptions {\n  /** Database system (e.g., 'postgresql', 'mysql') */\n  dbSystem: string;\n  /** Database name (optional) */\n  dbName?: string;\n  /** Method names to instrument (if not provided, instruments common patterns) */\n  methods?: string[];\n  /** Method names to skip */\n  skipMethods?: string[];\n  /** Sanitize queries (remove sensitive data) - default: true */\n  sanitizeQuery?: boolean;\n  /** Slow query threshold in milliseconds - default: 1000ms */\n  slowQueryThresholdMs?: number;\n}\n\n/**\n * Instrument a database client instance with OpenTelemetry tracing\n *\n * This is a function-based alternative to @DbInstrumented decorator.\n * Modifies the client in-place and returns it (idempotent - safe to call multiple times).\n *\n * Inspired by otel-drizzle and other otel instrumentation packages.\n *\n * @example Drizzle ORM\n * ```typescript\n * import { drizzle } from 'drizzle-orm/node-postgres'\n * import { instrumentDatabase } from 'autotel/db'\n *\n * const db = drizzle(pool)\n * instrumentDatabase(db, { dbSystem: 'postgresql', dbName: 'myapp' })\n *\n * // Now all db queries are automatically trace\n * await db.select().from(users)\n * ```\n *\n * @example Prisma\n * ```typescript\n * import { PrismaClient } from '@prisma/client'\n * import { instrumentDatabase } from 'autotel/db'\n *\n * const prisma = new PrismaClient()\n * instrumentDatabase(prisma, {\n *   dbSystem: 'postgresql',\n *   methods: ['findMany', 'findUnique', 'create', 'update', 'delete']\n * })\n *\n * // All specified methods are trace\n * await prisma.user.findMany()\n * ```\n *\n * @example Generic database client\n * ```typescript\n * import { instrumentDatabase } from 'autotel/db'\n *\n * const db = createDatabaseClient()\n * instrumentDatabase(db, {\n *   dbSystem: 'mongodb',\n *   methods: ['find', 'findOne', 'insertOne', 'updateOne', 'deleteOne']\n * })\n * ```\n */\nexport function instrumentDatabase<T extends object>(\n  client: T,\n  options: InstrumentDatabaseOptions,\n): T {\n  // Idempotency check - if already instrumented, return as-is\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  if ((client as any)[INSTRUMENTED_SYMBOL]) {\n    return client;\n  }\n\n  const {\n    dbSystem,\n    dbName,\n    methods,\n    skipMethods = [],\n    sanitizeQuery = true,\n    slowQueryThresholdMs = 1000,\n  } = options;\n\n  const config = getConfig();\n  const tracer = config.tracer;\n\n  // Determine which methods to instrument\n  const methodsToInstrument = methods || extractDatabaseMethods(client);\n  const skipSet = new Set(skipMethods);\n\n  for (const methodName of methodsToInstrument) {\n    if (skipSet.has(methodName)) continue;\n    if (methodName.startsWith('_')) continue; // Skip private methods\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const method = (client as any)[methodName];\n    if (typeof method !== 'function') continue;\n\n    // Preserve the original method\n    const originalMethod = method;\n\n    // Wrap the method\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    (client as any)[methodName] = async function (this: T, ...args: any[]) {\n      const operation = inferDbOperation(methodName);\n      const table = inferTableName(methodName);\n\n      const spanName = table\n        ? `${dbSystem}.${operation} ${table}`\n        : `${dbSystem}.${operation}`;\n\n      return tracer.startActiveSpan(spanName, async (span) => {\n        const startTime = performance.now();\n\n        try {\n          span.setAttributes({\n            'db.system': dbSystem,\n            'db.operation': operation,\n          });\n\n          if (dbName) {\n            span.setAttribute('db.name', dbName);\n          }\n\n          if (table) {\n            span.setAttribute('db.sql.table', table);\n          }\n\n          // Try to extract query from arguments (common patterns)\n          const query = extractQueryFromArgs(args);\n          if (query) {\n            span.setAttribute(\n              'db.statement',\n              sanitizeQuery ? sanitizeSqlQuery(query) : query,\n            );\n          }\n\n          // Execute original method\n          const result = await originalMethod.apply(this, args);\n\n          const duration = performance.now() - startTime;\n\n          span.setStatus({ code: SpanStatusCode.OK });\n          span.setAttributes({\n            'db.duration_ms': duration,\n          });\n\n          // Mark slow queries\n          if (duration > slowQueryThresholdMs) {\n            span.setAttribute('db.slow_query', true);\n            span.setAttribute(\n              'db.slow_query_threshold_ms',\n              slowQueryThresholdMs,\n            );\n          }\n\n          // Track result count if it's an array\n          if (Array.isArray(result)) {\n            span.setAttribute('db.result_count', result.length);\n          }\n\n          return result;\n        } catch (error) {\n          const duration = performance.now() - startTime;\n\n          span.setStatus({\n            code: SpanStatusCode.ERROR,\n            message: error instanceof Error ? error.message : 'Unknown error',\n          });\n\n          span.setAttributes({\n            'db.duration_ms': duration,\n            'error.type':\n              error instanceof Error ? error.constructor.name : 'Unknown',\n            'error.message':\n              error instanceof Error ? error.message : 'Unknown error',\n          });\n\n          span.recordException(\n            error instanceof Error ? error : new Error(String(error)),\n          );\n\n          throw error;\n        } finally {\n          span.end();\n        }\n      });\n    };\n\n    // Preserve function name\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    Object.defineProperty((client as any)[methodName], 'name', {\n      value: methodName,\n      configurable: true,\n    });\n  }\n\n  // Mark as instrumented\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  (client as any)[INSTRUMENTED_SYMBOL] = true;\n\n  return client;\n}\n\n/**\n * Extract method names from a database client that should be instrumented\n */\nfunction extractDatabaseMethods(client: object): string[] {\n  const methods: string[] = [];\n  const proto = Object.getPrototypeOf(client);\n\n  // Get own methods\n  for (const key of Object.getOwnPropertyNames(client)) {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    if (typeof (client as any)[key] === 'function' && !key.startsWith('_')) {\n      methods.push(key);\n    }\n  }\n\n  // Get prototype methods\n  if (proto) {\n    for (const key of Object.getOwnPropertyNames(proto)) {\n      if (\n        typeof proto[key] === 'function' &&\n        !key.startsWith('_') &&\n        key !== 'constructor'\n      ) {\n        methods.push(key);\n      }\n    }\n  }\n\n  return [...new Set(methods)]; // Deduplicate\n}\n\n/**\n * Try to extract SQL query from common argument patterns\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction extractQueryFromArgs(args: any[]): string | undefined {\n  if (args.length === 0) return undefined;\n\n  const firstArg = args[0];\n\n  // String query (raw SQL)\n  if (typeof firstArg === 'string') {\n    return firstArg;\n  }\n\n  // Object with sql property\n  if (firstArg && typeof firstArg === 'object') {\n    if ('sql' in firstArg && typeof firstArg.sql === 'string') {\n      return firstArg.sql;\n    }\n    // PostgreSQL-style query object\n    if ('text' in firstArg && typeof firstArg.text === 'string') {\n      return firstArg.text;\n    }\n    // Query builder pattern\n    if ('toQuery' in firstArg && typeof firstArg.toQuery === 'function') {\n      try {\n        const queryResult = firstArg.toQuery();\n        if (typeof queryResult === 'string') return queryResult;\n        if (\n          queryResult &&\n          typeof queryResult === 'object' &&\n          'sql' in queryResult\n        ) {\n          return queryResult.sql as string;\n        }\n      } catch {\n        // Ignore errors from toQuery()\n      }\n    }\n  }\n\n  return undefined;\n}\n"],"mappings":"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"}