autotel
Version:
Write Once, Observe Anywhere
1 lines • 20.1 kB
Source Map (JSON)
{"version":3,"sources":["../src/db.ts"],"names":["getConfig","SpanStatusCode"],"mappings":";;;;;;;AAsCA,eAAsB,WAAA,CACpB,QAAA,EACA,SAAA,EACA,EAAA,EACA,UAAA,EACY;AACZ,EAAA,MAAM,SAASA,2BAAA,EAAU;AACzB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA;AAEtB,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AAEzC,EAAA,OAAO,MAAA,CAAO,eAAA,CAAgB,QAAA,EAAU,OAAO,IAAA,KAAS;AACtD,IAAA,MAAM,SAAA,GAAY,YAAY,GAAA,EAAI;AAElC,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,aAAA,CAAc;AAAA,QACjB,WAAA,EAAa,QAAA;AAAA,QACb,cAAA,EAAgB,SAAA;AAAA,QAChB,GAAG;AAAA,OACJ,CAAA;AAED,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AAExB,MAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,EAAI,GAAI,SAAA;AACrC,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMC,kBAAA,CAAe,IAAI,CAAA;AAC1C,MAAA,IAAA,CAAK,YAAA,CAAa,kBAAkB,QAAQ,CAAA;AAE5C,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AACzB,QAAA,IAAA,CAAK,YAAA,CAAa,iBAAA,EAAmB,MAAA,CAAO,MAAM,CAAA;AAAA,MACpD;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,EAAI,GAAI,SAAA;AAErC,MAAA,IAAA,CAAK,SAAA,CAAU;AAAA,QACb,MAAMA,kBAAA,CAAe,KAAA;AAAA,QACrB,OAAA,EAAS,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,OACnD,CAAA;AAED,MAAA,IAAA,CAAK,aAAA,CAAc;AAAA,QACjB,gBAAA,EAAkB,QAAA;AAAA,QAClB,YAAA,EACE,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,YAAY,IAAA,GAAO,SAAA;AAAA,QACpD,eAAA,EACE,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,OAC5C,CAAA;AAED,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,EACF,CAAC,CAAA;AACH;AAIA,SAAS,iBAAiB,UAAA,EAA4B;AACpD,EAAA,MAAM,KAAA,GAAQ,WAAW,WAAA,EAAY;AACrC,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IAAK,KAAA,CAAM,SAAS,KAAK,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAC1E,IAAA,OAAO,QAAA;AACT,EAAA,IAAI,KAAA,CAAM,SAAS,QAAQ,CAAA,IAAK,MAAM,QAAA,CAAS,QAAQ,GAAG,OAAO,QAAA;AACjE,EAAA,IAAI,KAAA,CAAM,SAAS,QAAQ,CAAA,IAAK,MAAM,QAAA,CAAS,QAAQ,GAAG,OAAO,QAAA;AACjE,EAAA,IAAI,KAAA,CAAM,SAAS,QAAQ,CAAA,IAAK,MAAM,QAAA,CAAS,QAAQ,GAAG,OAAO,QAAA;AACjE,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,EAAG,OAAO,OAAA;AACpC,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,eAAe,UAAA,EAAwC;AAM9D,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,sBAAA;AAAA,IACA,qBAAA;AAAA,IACA,sBAAA;AAAA,IACA,wBAAA;AAAA,IACA,wBAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,OAAO,CAAA;AACtC,IAAA,IAAI,KAAA,IAAS,KAAA,CAAM,CAAC,CAAA,EAAG;AACrB,MAAA,OAAO,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,EAAY;AAAA,IAC9B;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,iBAAiB,KAAA,EAAuB;AAG/C,EAAA,OAAO,KAAA,CACJ,UAAA,CAAW,UAAA,EAAY,KAAK,CAAA,CAC5B,UAAA,CAAW,UAAA,EAAY,KAAK,CAAA,CAC5B,UAAA,CAAW,UAAA,EAAY,GAAG,EAC1B,IAAA,EAAK;AACV;AAKO,IAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,QAAA;AAAA,EACR,KAAA,EAAO,OAAA;AAAA,EACP,SAAA,EAAW;AACb;AAKO,IAAM,UAAA,GAAa;AAAA,EACxB,UAAA,EAAY,YAAA;AAAA,EACZ,KAAA,EAAO,OAAA;AAAA,EACP,OAAA,EAAS,SAAA;AAAA,EACT,KAAA,EAAO,OAAA;AAAA,EACP,MAAA,EAAQ,QAAA;AAAA,EACR,KAAA,EAAO;AACT;AAGA,IAAM,mBAAA,mBAAsB,MAAA,CAAO,GAAA,CAAI,yBAAyB,CAAA;AAkEzD,SAAS,kBAAA,CACd,QACA,OAAA,EACG;AAGH,EAAA,IAAK,MAAA,CAAe,mBAAmB,CAAA,EAAG;AACxC,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,cAAc,EAAC;AAAA,IACf,aAAA,GAAgB,IAAA;AAAA,IAChB,oBAAA,GAAuB;AAAA,GACzB,GAAI,OAAA;AAEJ,EAAA,MAAM,SAASD,2BAAA,EAAU;AACzB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA;AAGtB,EAAA,MAAM,mBAAA,GAAsB,OAAA,IAAW,sBAAA,CAAuB,MAAM,CAAA;AACpE,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,WAAW,CAAA;AAEnC,EAAA,KAAA,MAAW,cAAc,mBAAA,EAAqB;AAC5C,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA,EAAG;AAC7B,IAAA,IAAI,UAAA,CAAW,UAAA,CAAW,GAAG,CAAA,EAAG;AAGhC,IAAA,MAAM,MAAA,GAAU,OAAe,UAAU,CAAA;AACzC,IAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAGlC,IAAA,MAAM,cAAA,GAAiB,MAAA;AAIvB,IAAC,MAAA,CAAe,UAAU,CAAA,GAAI,eAAA,GAA4B,IAAA,EAAa;AACrE,MAAA,MAAM,SAAA,GAAY,iBAAiB,UAAU,CAAA;AAC7C,MAAA,MAAM,KAAA,GAAQ,eAAe,UAAU,CAAA;AAEvC,MAAA,MAAM,QAAA,GAAW,KAAA,GACb,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,GACjC,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AAE5B,MAAA,OAAO,MAAA,CAAO,eAAA,CAAgB,QAAA,EAAU,OAAO,IAAA,KAAS;AACtD,QAAA,MAAM,SAAA,GAAY,YAAY,GAAA,EAAI;AAElC,QAAA,IAAI;AACF,UAAA,IAAA,CAAK,aAAA,CAAc;AAAA,YACjB,WAAA,EAAa,QAAA;AAAA,YACb,cAAA,EAAgB;AAAA,WACjB,CAAA;AAED,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,IAAA,CAAK,YAAA,CAAa,WAAW,MAAM,CAAA;AAAA,UACrC;AAEA,UAAA,IAAI,KAAA,EAAO;AACT,YAAA,IAAA,CAAK,YAAA,CAAa,gBAAgB,KAAK,CAAA;AAAA,UACzC;AAGA,UAAA,MAAM,KAAA,GAAQ,qBAAqB,IAAI,CAAA;AACvC,UAAA,IAAI,KAAA,EAAO;AACT,YAAA,IAAA,CAAK,YAAA;AAAA,cACH,cAAA;AAAA,cACA,aAAA,GAAgB,gBAAA,CAAiB,KAAK,CAAA,GAAI;AAAA,aAC5C;AAAA,UACF;AAGA,UAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,KAAA,CAAM,MAAM,IAAI,CAAA;AAEpD,UAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,EAAI,GAAI,SAAA;AAErC,UAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMC,kBAAA,CAAe,IAAI,CAAA;AAC1C,UAAA,IAAA,CAAK,aAAA,CAAc;AAAA,YACjB,gBAAA,EAAkB;AAAA,WACnB,CAAA;AAGD,UAAA,IAAI,WAAW,oBAAA,EAAsB;AACnC,YAAA,IAAA,CAAK,YAAA,CAAa,iBAAiB,IAAI,CAAA;AACvC,YAAA,IAAA,CAAK,YAAA;AAAA,cACH,4BAAA;AAAA,cACA;AAAA,aACF;AAAA,UACF;AAGA,UAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AACzB,YAAA,IAAA,CAAK,YAAA,CAAa,iBAAA,EAAmB,MAAA,CAAO,MAAM,CAAA;AAAA,UACpD;AAEA,UAAA,OAAO,MAAA;AAAA,QACT,SAAS,KAAA,EAAO;AACd,UAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,EAAI,GAAI,SAAA;AAErC,UAAA,IAAA,CAAK,SAAA,CAAU;AAAA,YACb,MAAMA,kBAAA,CAAe,KAAA;AAAA,YACrB,OAAA,EAAS,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,WACnD,CAAA;AAED,UAAA,IAAA,CAAK,aAAA,CAAc;AAAA,YACjB,gBAAA,EAAkB,QAAA;AAAA,YAClB,YAAA,EACE,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,YAAY,IAAA,GAAO,SAAA;AAAA,YACpD,eAAA,EACE,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,WAC5C,CAAA;AAED,UAAA,IAAA,CAAK,eAAA;AAAA,YACH,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC;AAAA,WAC1D;AAEA,UAAA,MAAM,KAAA;AAAA,QACR,CAAA,SAAE;AACA,UAAA,IAAA,CAAK,GAAA,EAAI;AAAA,QACX;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAA;AAIA,IAAA,MAAA,CAAO,cAAA,CAAgB,MAAA,CAAe,UAAU,CAAA,EAAG,MAAA,EAAQ;AAAA,MACzD,KAAA,EAAO,UAAA;AAAA,MACP,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH;AAIA,EAAC,MAAA,CAAe,mBAAmB,CAAA,GAAI,IAAA;AAEvC,EAAA,OAAO,MAAA;AACT;AAKA,SAAS,uBAAuB,MAAA,EAA0B;AACxD,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,cAAA,CAAe,MAAM,CAAA;AAG1C,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,mBAAA,CAAoB,MAAM,CAAA,EAAG;AAEpD,IAAA,IAAI,OAAQ,OAAe,GAAG,CAAA,KAAM,cAAc,CAAC,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACtE,MAAA,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,IAClB;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,mBAAA,CAAoB,KAAK,CAAA,EAAG;AACnD,MAAA,IACE,OAAO,KAAA,CAAM,GAAG,CAAA,KAAM,UAAA,IACtB,CAAC,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,IACnB,GAAA,KAAQ,aAAA,EACR;AACA,QAAA,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,CAAC,GAAG,IAAI,GAAA,CAAI,OAAO,CAAC,CAAA;AAC7B;AAMA,SAAS,qBAAqB,IAAA,EAAiC;AAC7D,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AAE9B,EAAA,MAAM,QAAA,GAAW,KAAK,CAAC,CAAA;AAGvB,EAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,IAAA,OAAO,QAAA;AAAA,EACT;AAGA,EAAA,IAAI,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC5C,IAAA,IAAI,KAAA,IAAS,QAAA,IAAY,OAAO,QAAA,CAAS,QAAQ,QAAA,EAAU;AACzD,MAAA,OAAO,QAAA,CAAS,GAAA;AAAA,IAClB;AAEA,IAAA,IAAI,MAAA,IAAU,QAAA,IAAY,OAAO,QAAA,CAAS,SAAS,QAAA,EAAU;AAC3D,MAAA,OAAO,QAAA,CAAS,IAAA;AAAA,IAClB;AAEA,IAAA,IAAI,SAAA,IAAa,QAAA,IAAY,OAAO,QAAA,CAAS,YAAY,UAAA,EAAY;AACnE,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAc,SAAS,OAAA,EAAQ;AACrC,QAAA,IAAI,OAAO,WAAA,KAAgB,QAAA,EAAU,OAAO,WAAA;AAC5C,QAAA,IACE,WAAA,IACA,OAAO,WAAA,KAAgB,QAAA,IACvB,SAAS,WAAA,EACT;AACA,UAAA,OAAO,WAAA,CAAY,GAAA;AAAA,QACrB;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT","file":"db.cjs","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"]}