trickle-observe 0.2.94 → 0.2.96

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.
@@ -13,3 +13,21 @@
13
13
  * Called from observe-register when pg is required.
14
14
  */
15
15
  export declare function patchPg(pgModule: any, debug: boolean): void;
16
+ /**
17
+ * Patch mysql2 to capture queries.
18
+ */
19
+ export declare function patchMysql2(mysqlModule: any, debug: boolean): void;
20
+ /**
21
+ * Patch better-sqlite3 to capture queries.
22
+ */
23
+ export declare function patchBetterSqlite3(dbConstructor: any, debug: boolean): void;
24
+ /**
25
+ * Patch ioredis to capture Redis commands.
26
+ * Called from observe-register when ioredis is required.
27
+ */
28
+ export declare function patchIoredis(ioredisModule: any, debug: boolean): void;
29
+ /**
30
+ * Patch mongoose to capture MongoDB operations.
31
+ * Called from observe-register when mongoose is required.
32
+ */
33
+ export declare function patchMongoose(mongooseModule: any, debug: boolean): void;
@@ -44,6 +44,10 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  })();
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.patchPg = patchPg;
47
+ exports.patchMysql2 = patchMysql2;
48
+ exports.patchBetterSqlite3 = patchBetterSqlite3;
49
+ exports.patchIoredis = patchIoredis;
50
+ exports.patchMongoose = patchMongoose;
47
51
  const fs = __importStar(require("fs"));
48
52
  const path = __importStar(require("path"));
49
53
  let queriesFile = null;
@@ -197,3 +201,190 @@ function patchPg(pgModule, debug) {
197
201
  console.log('[trickle/db] PostgreSQL query tracing enabled');
198
202
  }
199
203
  }
204
+ /**
205
+ * Patch mysql2 to capture queries.
206
+ */
207
+ function patchMysql2(mysqlModule, debug) {
208
+ debugMode = debug;
209
+ // Patch Connection.prototype.query and .execute
210
+ const Connection = mysqlModule.Connection;
211
+ if (!Connection || !Connection.prototype)
212
+ return;
213
+ for (const method of ['query', 'execute']) {
214
+ const original = Connection.prototype[method];
215
+ if (!original || original.__trickle_patched)
216
+ continue;
217
+ Connection.prototype[method] = function patchedMethod(...args) {
218
+ const startTime = performance.now();
219
+ let queryText = typeof args[0] === 'string' ? args[0] : args[0]?.sql || '';
220
+ const truncated = queryText.length > MAX_QUERY_LENGTH ? queryText.substring(0, MAX_QUERY_LENGTH) + '...' : queryText;
221
+ const result = original.apply(this, args);
222
+ if (result && typeof result.then === 'function') {
223
+ return result.then((res) => {
224
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
225
+ const rows = Array.isArray(res) ? res[0] : res;
226
+ writeQuery({
227
+ kind: 'query', query: truncated, durationMs,
228
+ rowCount: Array.isArray(rows) ? rows.length : 0,
229
+ columns: Array.isArray(rows) && rows[0] ? Object.keys(rows[0]) : undefined,
230
+ timestamp: Date.now(),
231
+ });
232
+ return res;
233
+ }, (err) => {
234
+ writeQuery({
235
+ kind: 'query', query: truncated,
236
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
237
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
238
+ });
239
+ throw err;
240
+ });
241
+ }
242
+ return result;
243
+ };
244
+ Connection.prototype[method].__trickle_patched = true;
245
+ }
246
+ if (debug)
247
+ console.log('[trickle/db] MySQL query tracing enabled');
248
+ }
249
+ /**
250
+ * Patch better-sqlite3 to capture queries.
251
+ */
252
+ function patchBetterSqlite3(dbConstructor, debug) {
253
+ debugMode = debug;
254
+ // better-sqlite3 returns a Database constructor — patch its prototype
255
+ const origPrepare = dbConstructor.prototype?.prepare;
256
+ if (!origPrepare || origPrepare.__trickle_patched)
257
+ return;
258
+ dbConstructor.prototype.prepare = function patchedPrepare(sql) {
259
+ const stmt = origPrepare.call(this, sql);
260
+ const truncated = sql.length > MAX_QUERY_LENGTH ? sql.substring(0, MAX_QUERY_LENGTH) + '...' : sql;
261
+ // Patch run, get, all methods on the statement
262
+ for (const method of ['run', 'get', 'all']) {
263
+ const origMethod = stmt[method];
264
+ if (!origMethod)
265
+ continue;
266
+ stmt[method] = function (...args) {
267
+ const startTime = performance.now();
268
+ try {
269
+ const result = origMethod.apply(this, args);
270
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
271
+ const rowCount = method === 'all' ? (Array.isArray(result) ? result.length : 0)
272
+ : method === 'get' ? (result ? 1 : 0)
273
+ : (result?.changes || 0);
274
+ writeQuery({
275
+ kind: 'query', query: truncated, durationMs, rowCount, timestamp: Date.now(),
276
+ });
277
+ return result;
278
+ }
279
+ catch (err) {
280
+ writeQuery({
281
+ kind: 'query', query: truncated,
282
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
283
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
284
+ });
285
+ throw err;
286
+ }
287
+ };
288
+ }
289
+ return stmt;
290
+ };
291
+ dbConstructor.prototype.prepare.__trickle_patched = true;
292
+ if (debug)
293
+ console.log('[trickle/db] SQLite query tracing enabled');
294
+ }
295
+ /**
296
+ * Patch ioredis to capture Redis commands.
297
+ * Called from observe-register when ioredis is required.
298
+ */
299
+ function patchIoredis(ioredisModule, debug) {
300
+ debugMode = debug;
301
+ const RedisClass = ioredisModule.default || ioredisModule;
302
+ const proto = RedisClass.prototype;
303
+ if (!proto || proto.sendCommand?.__trickle_patched)
304
+ return;
305
+ const origSendCommand = proto.sendCommand;
306
+ if (!origSendCommand)
307
+ return;
308
+ proto.sendCommand = function patchedSendCommand(command, ...rest) {
309
+ const cmdName = command?.name || 'UNKNOWN';
310
+ const cmdArgs = (command?.args || []).slice(0, 3).map((a) => typeof a === 'string' ? (a.length > 50 ? a.substring(0, 50) + '...' : a) : String(a).substring(0, 50));
311
+ const queryStr = `${cmdName.toUpperCase()} ${cmdArgs.join(' ')}`.trim();
312
+ const startTime = performance.now();
313
+ const result = origSendCommand.call(this, command, ...rest);
314
+ // ioredis returns a Promise
315
+ if (result && typeof result.then === 'function') {
316
+ result.then(() => {
317
+ writeQuery({
318
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
319
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
320
+ rowCount: 1, timestamp: Date.now(),
321
+ });
322
+ }, (err) => {
323
+ writeQuery({
324
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
325
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
326
+ rowCount: 0, error: err?.message?.substring(0, 200), timestamp: Date.now(),
327
+ });
328
+ });
329
+ }
330
+ return result;
331
+ };
332
+ proto.sendCommand.__trickle_patched = true;
333
+ if (debug)
334
+ console.log('[trickle/db] Redis (ioredis) query tracing enabled');
335
+ }
336
+ /**
337
+ * Patch mongoose to capture MongoDB operations.
338
+ * Called from observe-register when mongoose is required.
339
+ */
340
+ function patchMongoose(mongooseModule, debug) {
341
+ debugMode = debug;
342
+ const Model = mongooseModule.Model;
343
+ if (!Model || Model.__trickle_patched)
344
+ return;
345
+ const methodsToWrap = [
346
+ 'find', 'findOne', 'findById', 'findOneAndUpdate', 'findOneAndDelete',
347
+ 'create', 'insertMany', 'updateOne', 'updateMany',
348
+ 'deleteOne', 'deleteMany', 'countDocuments', 'aggregate',
349
+ ];
350
+ for (const method of methodsToWrap) {
351
+ const orig = Model[method];
352
+ if (!orig || orig.__trickle_patched)
353
+ continue;
354
+ Model[method] = function patchedMethod(...args) {
355
+ const collName = this.modelName || this.collection?.name || '?';
356
+ let filterStr = '';
357
+ if (args[0] && typeof args[0] === 'object') {
358
+ try {
359
+ filterStr = ' ' + JSON.stringify(args[0]).substring(0, 200);
360
+ }
361
+ catch { }
362
+ }
363
+ const queryStr = `db.${collName}.${method}(${filterStr.trim()})`;
364
+ const startTime = performance.now();
365
+ const result = orig.apply(this, args);
366
+ // Mongoose methods return Query objects (thenables) or Promises
367
+ if (result && typeof result.then === 'function') {
368
+ result.then((res) => {
369
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
370
+ const rowCount = Array.isArray(res) ? res.length : (res ? 1 : 0);
371
+ writeQuery({
372
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
373
+ durationMs, rowCount, timestamp: Date.now(),
374
+ });
375
+ }, (err) => {
376
+ writeQuery({
377
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
378
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
379
+ rowCount: 0, error: err?.message?.substring(0, 200), timestamp: Date.now(),
380
+ });
381
+ });
382
+ }
383
+ return result;
384
+ };
385
+ Model[method].__trickle_patched = true;
386
+ }
387
+ Model.__trickle_patched = true;
388
+ if (debug)
389
+ console.log('[trickle/db] MongoDB (mongoose) query tracing enabled');
390
+ }
@@ -1123,14 +1123,48 @@ if (enabled) {
1123
1123
  }
1124
1124
  catch { /* fall through to normal processing */ }
1125
1125
  }
1126
- // ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
1126
+ // ── Database auto-detection: patch database drivers to capture SQL queries ──
1127
1127
  if (request === 'pg' && !expressPatched.has('pg')) {
1128
1128
  expressPatched.add('pg');
1129
1129
  try {
1130
1130
  const { patchPg } = require(path_1.default.join(__dirname, 'db-observer.js'));
1131
1131
  patchPg(exports, debug);
1132
1132
  }
1133
- catch { /* pg observer not critical */ }
1133
+ catch { /* not critical */ }
1134
+ }
1135
+ if ((request === 'mysql2' || request === 'mysql2/promise') && !expressPatched.has('mysql2')) {
1136
+ expressPatched.add('mysql2');
1137
+ try {
1138
+ const { patchMysql2 } = require(path_1.default.join(__dirname, 'db-observer.js'));
1139
+ patchMysql2(exports, debug);
1140
+ }
1141
+ catch { /* not critical */ }
1142
+ }
1143
+ if (request === 'better-sqlite3' && !expressPatched.has('better-sqlite3')) {
1144
+ expressPatched.add('better-sqlite3');
1145
+ try {
1146
+ const { patchBetterSqlite3 } = require(path_1.default.join(__dirname, 'db-observer.js'));
1147
+ patchBetterSqlite3(exports, debug);
1148
+ }
1149
+ catch { /* not critical */ }
1150
+ }
1151
+ // Redis (ioredis)
1152
+ if (request === 'ioredis' && !expressPatched.has('ioredis')) {
1153
+ expressPatched.add('ioredis');
1154
+ try {
1155
+ const { patchIoredis } = require(path_1.default.join(__dirname, 'db-observer.js'));
1156
+ patchIoredis(exports, debug);
1157
+ }
1158
+ catch { /* not critical */ }
1159
+ }
1160
+ // MongoDB (mongoose)
1161
+ if (request === 'mongoose' && !expressPatched.has('mongoose')) {
1162
+ expressPatched.add('mongoose');
1163
+ try {
1164
+ const { patchMongoose } = require(path_1.default.join(__dirname, 'db-observer.js'));
1165
+ patchMongoose(exports, debug);
1166
+ }
1167
+ catch { /* not critical */ }
1134
1168
  }
1135
1169
  // Resolve to absolute path for dedup — do this FIRST since bundlers like
1136
1170
  // tsx/esbuild may use path aliases (e.g., @config/env) that don't start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.94",
3
+ "version": "0.2.96",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -183,3 +183,211 @@ export function patchPg(pgModule: any, debug: boolean): void {
183
183
  console.log('[trickle/db] PostgreSQL query tracing enabled');
184
184
  }
185
185
  }
186
+
187
+ /**
188
+ * Patch mysql2 to capture queries.
189
+ */
190
+ export function patchMysql2(mysqlModule: any, debug: boolean): void {
191
+ debugMode = debug;
192
+
193
+ // Patch Connection.prototype.query and .execute
194
+ const Connection = mysqlModule.Connection;
195
+ if (!Connection || !Connection.prototype) return;
196
+
197
+ for (const method of ['query', 'execute'] as const) {
198
+ const original = Connection.prototype[method];
199
+ if (!original || (original as any).__trickle_patched) continue;
200
+
201
+ Connection.prototype[method] = function patchedMethod(...args: any[]): any {
202
+ const startTime = performance.now();
203
+ let queryText = typeof args[0] === 'string' ? args[0] : args[0]?.sql || '';
204
+ const truncated = queryText.length > MAX_QUERY_LENGTH ? queryText.substring(0, MAX_QUERY_LENGTH) + '...' : queryText;
205
+
206
+ const result = original.apply(this, args);
207
+ if (result && typeof result.then === 'function') {
208
+ return result.then(
209
+ (res: any) => {
210
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
211
+ const rows = Array.isArray(res) ? res[0] : res;
212
+ writeQuery({
213
+ kind: 'query', query: truncated, durationMs,
214
+ rowCount: Array.isArray(rows) ? rows.length : 0,
215
+ columns: Array.isArray(rows) && rows[0] ? Object.keys(rows[0]) : undefined,
216
+ timestamp: Date.now(),
217
+ });
218
+ return res;
219
+ },
220
+ (err: any) => {
221
+ writeQuery({
222
+ kind: 'query', query: truncated,
223
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
224
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
225
+ });
226
+ throw err;
227
+ },
228
+ );
229
+ }
230
+ return result;
231
+ };
232
+ (Connection.prototype[method] as any).__trickle_patched = true;
233
+ }
234
+
235
+ if (debug) console.log('[trickle/db] MySQL query tracing enabled');
236
+ }
237
+
238
+ /**
239
+ * Patch better-sqlite3 to capture queries.
240
+ */
241
+ export function patchBetterSqlite3(dbConstructor: any, debug: boolean): void {
242
+ debugMode = debug;
243
+
244
+ // better-sqlite3 returns a Database constructor — patch its prototype
245
+ const origPrepare = dbConstructor.prototype?.prepare;
246
+ if (!origPrepare || (origPrepare as any).__trickle_patched) return;
247
+
248
+ dbConstructor.prototype.prepare = function patchedPrepare(sql: string): any {
249
+ const stmt = origPrepare.call(this, sql);
250
+ const truncated = sql.length > MAX_QUERY_LENGTH ? sql.substring(0, MAX_QUERY_LENGTH) + '...' : sql;
251
+
252
+ // Patch run, get, all methods on the statement
253
+ for (const method of ['run', 'get', 'all'] as const) {
254
+ const origMethod = stmt[method];
255
+ if (!origMethod) continue;
256
+ stmt[method] = function (...args: any[]): any {
257
+ const startTime = performance.now();
258
+ try {
259
+ const result = origMethod.apply(this, args);
260
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
261
+ const rowCount = method === 'all' ? (Array.isArray(result) ? result.length : 0)
262
+ : method === 'get' ? (result ? 1 : 0)
263
+ : (result?.changes || 0);
264
+ writeQuery({
265
+ kind: 'query', query: truncated, durationMs, rowCount, timestamp: Date.now(),
266
+ });
267
+ return result;
268
+ } catch (err: any) {
269
+ writeQuery({
270
+ kind: 'query', query: truncated,
271
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
272
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
273
+ });
274
+ throw err;
275
+ }
276
+ };
277
+ }
278
+ return stmt;
279
+ };
280
+ (dbConstructor.prototype.prepare as any).__trickle_patched = true;
281
+
282
+ if (debug) console.log('[trickle/db] SQLite query tracing enabled');
283
+ }
284
+
285
+ /**
286
+ * Patch ioredis to capture Redis commands.
287
+ * Called from observe-register when ioredis is required.
288
+ */
289
+ export function patchIoredis(ioredisModule: any, debug: boolean): void {
290
+ debugMode = debug;
291
+
292
+ const RedisClass = ioredisModule.default || ioredisModule;
293
+ const proto = RedisClass.prototype;
294
+ if (!proto || (proto.sendCommand as any)?.__trickle_patched) return;
295
+
296
+ const origSendCommand = proto.sendCommand;
297
+ if (!origSendCommand) return;
298
+
299
+ proto.sendCommand = function patchedSendCommand(command: any, ...rest: any[]): any {
300
+ const cmdName = command?.name || 'UNKNOWN';
301
+ const cmdArgs = (command?.args || []).slice(0, 3).map((a: any) =>
302
+ typeof a === 'string' ? (a.length > 50 ? a.substring(0, 50) + '...' : a) : String(a).substring(0, 50)
303
+ );
304
+ const queryStr = `${cmdName.toUpperCase()} ${cmdArgs.join(' ')}`.trim();
305
+
306
+ const startTime = performance.now();
307
+ const result = origSendCommand.call(this, command, ...rest);
308
+
309
+ // ioredis returns a Promise
310
+ if (result && typeof result.then === 'function') {
311
+ result.then(
312
+ () => {
313
+ writeQuery({
314
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
315
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
316
+ rowCount: 1, timestamp: Date.now(),
317
+ });
318
+ },
319
+ (err: any) => {
320
+ writeQuery({
321
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
322
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
323
+ rowCount: 0, error: err?.message?.substring(0, 200), timestamp: Date.now(),
324
+ });
325
+ }
326
+ );
327
+ }
328
+ return result;
329
+ };
330
+ (proto.sendCommand as any).__trickle_patched = true;
331
+
332
+ if (debug) console.log('[trickle/db] Redis (ioredis) query tracing enabled');
333
+ }
334
+
335
+ /**
336
+ * Patch mongoose to capture MongoDB operations.
337
+ * Called from observe-register when mongoose is required.
338
+ */
339
+ export function patchMongoose(mongooseModule: any, debug: boolean): void {
340
+ debugMode = debug;
341
+
342
+ const Model = mongooseModule.Model;
343
+ if (!Model || (Model as any).__trickle_patched) return;
344
+
345
+ const methodsToWrap = [
346
+ 'find', 'findOne', 'findById', 'findOneAndUpdate', 'findOneAndDelete',
347
+ 'create', 'insertMany', 'updateOne', 'updateMany',
348
+ 'deleteOne', 'deleteMany', 'countDocuments', 'aggregate',
349
+ ];
350
+
351
+ for (const method of methodsToWrap) {
352
+ const orig = Model[method];
353
+ if (!orig || (orig as any).__trickle_patched) continue;
354
+
355
+ Model[method] = function patchedMethod(this: any, ...args: any[]): any {
356
+ const collName = this.modelName || this.collection?.name || '?';
357
+ let filterStr = '';
358
+ if (args[0] && typeof args[0] === 'object') {
359
+ try { filterStr = ' ' + JSON.stringify(args[0]).substring(0, 200); } catch {}
360
+ }
361
+ const queryStr = `db.${collName}.${method}(${filterStr.trim()})`;
362
+
363
+ const startTime = performance.now();
364
+ const result = orig.apply(this, args);
365
+
366
+ // Mongoose methods return Query objects (thenables) or Promises
367
+ if (result && typeof result.then === 'function') {
368
+ result.then(
369
+ (res: any) => {
370
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
371
+ const rowCount = Array.isArray(res) ? res.length : (res ? 1 : 0);
372
+ writeQuery({
373
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
374
+ durationMs, rowCount, timestamp: Date.now(),
375
+ });
376
+ },
377
+ (err: any) => {
378
+ writeQuery({
379
+ kind: 'query', query: queryStr.substring(0, MAX_QUERY_LENGTH),
380
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
381
+ rowCount: 0, error: err?.message?.substring(0, 200), timestamp: Date.now(),
382
+ });
383
+ }
384
+ );
385
+ }
386
+ return result;
387
+ };
388
+ (Model[method] as any).__trickle_patched = true;
389
+ }
390
+ (Model as any).__trickle_patched = true;
391
+
392
+ if (debug) console.log('[trickle/db] MongoDB (mongoose) query tracing enabled');
393
+ }
@@ -1113,13 +1113,45 @@ if (enabled) {
1113
1113
  } catch { /* fall through to normal processing */ }
1114
1114
  }
1115
1115
 
1116
- // ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
1116
+ // ── Database auto-detection: patch database drivers to capture SQL queries ──
1117
1117
  if (request === 'pg' && !expressPatched.has('pg')) {
1118
1118
  expressPatched.add('pg');
1119
1119
  try {
1120
1120
  const { patchPg } = require(path.join(__dirname, 'db-observer.js'));
1121
1121
  patchPg(exports, debug);
1122
- } catch { /* pg observer not critical */ }
1122
+ } catch { /* not critical */ }
1123
+ }
1124
+ if ((request === 'mysql2' || request === 'mysql2/promise') && !expressPatched.has('mysql2')) {
1125
+ expressPatched.add('mysql2');
1126
+ try {
1127
+ const { patchMysql2 } = require(path.join(__dirname, 'db-observer.js'));
1128
+ patchMysql2(exports, debug);
1129
+ } catch { /* not critical */ }
1130
+ }
1131
+ if (request === 'better-sqlite3' && !expressPatched.has('better-sqlite3')) {
1132
+ expressPatched.add('better-sqlite3');
1133
+ try {
1134
+ const { patchBetterSqlite3 } = require(path.join(__dirname, 'db-observer.js'));
1135
+ patchBetterSqlite3(exports, debug);
1136
+ } catch { /* not critical */ }
1137
+ }
1138
+
1139
+ // Redis (ioredis)
1140
+ if (request === 'ioredis' && !expressPatched.has('ioredis')) {
1141
+ expressPatched.add('ioredis');
1142
+ try {
1143
+ const { patchIoredis } = require(path.join(__dirname, 'db-observer.js'));
1144
+ patchIoredis(exports, debug);
1145
+ } catch { /* not critical */ }
1146
+ }
1147
+
1148
+ // MongoDB (mongoose)
1149
+ if (request === 'mongoose' && !expressPatched.has('mongoose')) {
1150
+ expressPatched.add('mongoose');
1151
+ try {
1152
+ const { patchMongoose } = require(path.join(__dirname, 'db-observer.js'));
1153
+ patchMongoose(exports, debug);
1154
+ } catch { /* not critical */ }
1123
1155
  }
1124
1156
 
1125
1157
  // Resolve to absolute path for dedup — do this FIRST since bundlers like