trickle-observe 0.2.107 → 0.2.108
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.
- package/dist/db-observer.d.ts +11 -0
- package/dist/db-observer.js +150 -0
- package/dist/observe-register.js +18 -0
- package/package.json +1 -1
- package/src/db-observer.ts +160 -0
- package/src/observe-register.ts +18 -0
package/dist/db-observer.d.ts
CHANGED
|
@@ -43,6 +43,17 @@ export declare function patchDrizzle(drizzleModule: any, debug: boolean): void;
|
|
|
43
43
|
* Knex emits 'query' and 'query-response' events on the knex instance.
|
|
44
44
|
*/
|
|
45
45
|
export declare function patchKnex(knexModule: any, debug: boolean): void;
|
|
46
|
+
/**
|
|
47
|
+
* Patch TypeORM to capture queries via its Logger interface.
|
|
48
|
+
* TypeORM DataSource accepts a `logger` option. We wrap createConnection/DataSource
|
|
49
|
+
* to inject a custom logger that captures all queries.
|
|
50
|
+
*/
|
|
51
|
+
export declare function patchTypeORM(typeormModule: any, debug: boolean): void;
|
|
52
|
+
/**
|
|
53
|
+
* Patch Sequelize to capture queries via its logging option.
|
|
54
|
+
* Sequelize accepts a `logging` function in its constructor options.
|
|
55
|
+
*/
|
|
56
|
+
export declare function patchSequelize(sequelizeModule: any, debug: boolean): void;
|
|
46
57
|
/**
|
|
47
58
|
* Patch mongoose to capture MongoDB operations.
|
|
48
59
|
* Called from observe-register when mongoose is required.
|
package/dist/db-observer.js
CHANGED
|
@@ -50,6 +50,8 @@ exports.patchIoredis = patchIoredis;
|
|
|
50
50
|
exports.patchPrisma = patchPrisma;
|
|
51
51
|
exports.patchDrizzle = patchDrizzle;
|
|
52
52
|
exports.patchKnex = patchKnex;
|
|
53
|
+
exports.patchTypeORM = patchTypeORM;
|
|
54
|
+
exports.patchSequelize = patchSequelize;
|
|
53
55
|
exports.patchMongoose = patchMongoose;
|
|
54
56
|
const fs = __importStar(require("fs"));
|
|
55
57
|
const path = __importStar(require("path"));
|
|
@@ -523,6 +525,154 @@ function patchKnex(knexModule, debug) {
|
|
|
523
525
|
if (debug)
|
|
524
526
|
console.log('[trickle/db] Knex query tracing enabled');
|
|
525
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Patch TypeORM to capture queries via its Logger interface.
|
|
530
|
+
* TypeORM DataSource accepts a `logger` option. We wrap createConnection/DataSource
|
|
531
|
+
* to inject a custom logger that captures all queries.
|
|
532
|
+
*/
|
|
533
|
+
function patchTypeORM(typeormModule, debug) {
|
|
534
|
+
debugMode = debug;
|
|
535
|
+
// Patch DataSource constructor (TypeORM 0.3+)
|
|
536
|
+
const DataSource = typeormModule.DataSource;
|
|
537
|
+
if (DataSource && !DataSource.__trickle_patched) {
|
|
538
|
+
const OrigDataSource = DataSource;
|
|
539
|
+
typeormModule.DataSource = function PatchedDataSource(options) {
|
|
540
|
+
// Inject custom logger
|
|
541
|
+
if (!options._trickle_injected) {
|
|
542
|
+
options._trickle_injected = true;
|
|
543
|
+
const origLogger = options.logger;
|
|
544
|
+
options.logger = {
|
|
545
|
+
logQuery(query, parameters) {
|
|
546
|
+
writeQuery({
|
|
547
|
+
kind: 'query',
|
|
548
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
549
|
+
params: parameters?.slice(0, 5),
|
|
550
|
+
durationMs: 0,
|
|
551
|
+
rowCount: 0,
|
|
552
|
+
timestamp: Date.now(),
|
|
553
|
+
});
|
|
554
|
+
if (origLogger && typeof origLogger === 'object' && 'logQuery' in origLogger) {
|
|
555
|
+
origLogger.logQuery(query, parameters);
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
logQueryError(error, query, parameters) {
|
|
559
|
+
writeQuery({
|
|
560
|
+
kind: 'query',
|
|
561
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
562
|
+
params: parameters?.slice(0, 5),
|
|
563
|
+
durationMs: 0,
|
|
564
|
+
rowCount: 0,
|
|
565
|
+
error: error.substring(0, 200),
|
|
566
|
+
timestamp: Date.now(),
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
logQuerySlow(time, query, parameters) {
|
|
570
|
+
writeQuery({
|
|
571
|
+
kind: 'query',
|
|
572
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
573
|
+
params: parameters?.slice(0, 5),
|
|
574
|
+
durationMs: time,
|
|
575
|
+
rowCount: 0,
|
|
576
|
+
timestamp: Date.now(),
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
logSchemaBuild() { },
|
|
580
|
+
logMigration() { },
|
|
581
|
+
log() { },
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return new OrigDataSource(options);
|
|
585
|
+
};
|
|
586
|
+
// Copy statics
|
|
587
|
+
Object.setPrototypeOf(typeormModule.DataSource, OrigDataSource);
|
|
588
|
+
typeormModule.DataSource.prototype = OrigDataSource.prototype;
|
|
589
|
+
typeormModule.DataSource.__trickle_patched = true;
|
|
590
|
+
}
|
|
591
|
+
// Also patch createConnection (TypeORM 0.2)
|
|
592
|
+
if (typeormModule.createConnection && !typeormModule.createConnection.__trickle_patched) {
|
|
593
|
+
const origCreate = typeormModule.createConnection;
|
|
594
|
+
typeormModule.createConnection = function patchedCreateConnection(options) {
|
|
595
|
+
if (options && typeof options === 'object' && !options.logging) {
|
|
596
|
+
options.logging = true;
|
|
597
|
+
}
|
|
598
|
+
return origCreate(options);
|
|
599
|
+
};
|
|
600
|
+
typeormModule.createConnection.__trickle_patched = true;
|
|
601
|
+
}
|
|
602
|
+
if (debug)
|
|
603
|
+
console.log('[trickle/db] TypeORM query tracing enabled');
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Patch Sequelize to capture queries via its logging option.
|
|
607
|
+
* Sequelize accepts a `logging` function in its constructor options.
|
|
608
|
+
*/
|
|
609
|
+
function patchSequelize(sequelizeModule, debug) {
|
|
610
|
+
debugMode = debug;
|
|
611
|
+
const Sequelize = sequelizeModule.Sequelize || sequelizeModule.default || sequelizeModule;
|
|
612
|
+
if (!Sequelize || typeof Sequelize !== 'function' || Sequelize.__trickle_patched)
|
|
613
|
+
return;
|
|
614
|
+
const origConstructor = Sequelize;
|
|
615
|
+
const patchedSequelize = function (...args) {
|
|
616
|
+
// Sequelize(uri, options) or Sequelize(database, user, pass, options)
|
|
617
|
+
let options;
|
|
618
|
+
if (args.length >= 4) {
|
|
619
|
+
options = args[3] = args[3] || {};
|
|
620
|
+
}
|
|
621
|
+
else if (args.length >= 2 && typeof args[1] === 'object') {
|
|
622
|
+
options = args[1];
|
|
623
|
+
}
|
|
624
|
+
else if (args.length === 1 && typeof args[0] === 'object') {
|
|
625
|
+
options = args[0];
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
options = {};
|
|
629
|
+
args.push(options);
|
|
630
|
+
}
|
|
631
|
+
// Wrap the logging function
|
|
632
|
+
const origLogging = options.logging;
|
|
633
|
+
options.logging = (sql, timing) => {
|
|
634
|
+
const queryText = typeof sql === 'string' ? sql : String(sql);
|
|
635
|
+
// Sequelize prepends "Executed (default): " or "Executing (default): " to queries
|
|
636
|
+
const cleanQuery = queryText.replace(/^Execut(?:ed|ing) \([^)]*\):\s*/, '');
|
|
637
|
+
const durationMs = typeof timing === 'number' ? timing : 0;
|
|
638
|
+
writeQuery({
|
|
639
|
+
kind: 'query',
|
|
640
|
+
query: cleanQuery.substring(0, MAX_QUERY_LENGTH),
|
|
641
|
+
durationMs,
|
|
642
|
+
rowCount: 0,
|
|
643
|
+
timestamp: Date.now(),
|
|
644
|
+
});
|
|
645
|
+
// Call original logger
|
|
646
|
+
if (typeof origLogging === 'function') {
|
|
647
|
+
origLogging(sql, timing);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
options.benchmark = true; // Enable timing in log
|
|
651
|
+
// Call original constructor
|
|
652
|
+
if (new.target) {
|
|
653
|
+
return new origConstructor(...args);
|
|
654
|
+
}
|
|
655
|
+
return origConstructor.apply(this, args);
|
|
656
|
+
};
|
|
657
|
+
// Copy prototype and statics
|
|
658
|
+
patchedSequelize.prototype = origConstructor.prototype;
|
|
659
|
+
Object.setPrototypeOf(patchedSequelize, origConstructor);
|
|
660
|
+
for (const key of Object.getOwnPropertyNames(origConstructor)) {
|
|
661
|
+
if (key !== 'prototype' && key !== 'length' && key !== 'name') {
|
|
662
|
+
try {
|
|
663
|
+
Object.defineProperty(patchedSequelize, key, Object.getOwnPropertyDescriptor(origConstructor, key));
|
|
664
|
+
}
|
|
665
|
+
catch { }
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Replace in module exports
|
|
669
|
+
if (sequelizeModule.Sequelize) {
|
|
670
|
+
sequelizeModule.Sequelize = patchedSequelize;
|
|
671
|
+
}
|
|
672
|
+
patchedSequelize.__trickle_patched = true;
|
|
673
|
+
if (debug)
|
|
674
|
+
console.log('[trickle/db] Sequelize query tracing enabled');
|
|
675
|
+
}
|
|
526
676
|
/**
|
|
527
677
|
* Patch mongoose to capture MongoDB operations.
|
|
528
678
|
* Called from observe-register when mongoose is required.
|
package/dist/observe-register.js
CHANGED
|
@@ -1230,6 +1230,24 @@ if (enabled) {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
catch { /* not critical */ }
|
|
1232
1232
|
}
|
|
1233
|
+
// TypeORM
|
|
1234
|
+
if (request === 'typeorm' && !expressPatched.has('typeorm')) {
|
|
1235
|
+
expressPatched.add('typeorm');
|
|
1236
|
+
try {
|
|
1237
|
+
const { patchTypeORM } = require(path_1.default.join(__dirname, 'db-observer.js'));
|
|
1238
|
+
patchTypeORM(exports, debug);
|
|
1239
|
+
}
|
|
1240
|
+
catch { /* not critical */ }
|
|
1241
|
+
}
|
|
1242
|
+
// Sequelize
|
|
1243
|
+
if (request === 'sequelize' && !expressPatched.has('sequelize')) {
|
|
1244
|
+
expressPatched.add('sequelize');
|
|
1245
|
+
try {
|
|
1246
|
+
const { patchSequelize } = require(path_1.default.join(__dirname, 'db-observer.js'));
|
|
1247
|
+
patchSequelize(exports, debug);
|
|
1248
|
+
}
|
|
1249
|
+
catch { /* not critical */ }
|
|
1250
|
+
}
|
|
1233
1251
|
// Redis (ioredis)
|
|
1234
1252
|
if (request === 'ioredis' && !expressPatched.has('ioredis')) {
|
|
1235
1253
|
expressPatched.add('ioredis');
|
package/package.json
CHANGED
package/src/db-observer.ts
CHANGED
|
@@ -533,6 +533,166 @@ export function patchKnex(knexModule: any, debug: boolean): void {
|
|
|
533
533
|
if (debug) console.log('[trickle/db] Knex query tracing enabled');
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Patch TypeORM to capture queries via its Logger interface.
|
|
538
|
+
* TypeORM DataSource accepts a `logger` option. We wrap createConnection/DataSource
|
|
539
|
+
* to inject a custom logger that captures all queries.
|
|
540
|
+
*/
|
|
541
|
+
export function patchTypeORM(typeormModule: any, debug: boolean): void {
|
|
542
|
+
debugMode = debug;
|
|
543
|
+
|
|
544
|
+
// Patch DataSource constructor (TypeORM 0.3+)
|
|
545
|
+
const DataSource = typeormModule.DataSource;
|
|
546
|
+
if (DataSource && !DataSource.__trickle_patched) {
|
|
547
|
+
const OrigDataSource = DataSource;
|
|
548
|
+
|
|
549
|
+
typeormModule.DataSource = function PatchedDataSource(options: any) {
|
|
550
|
+
// Inject custom logger
|
|
551
|
+
if (!options._trickle_injected) {
|
|
552
|
+
options._trickle_injected = true;
|
|
553
|
+
const origLogger = options.logger;
|
|
554
|
+
|
|
555
|
+
options.logger = {
|
|
556
|
+
logQuery(query: string, parameters?: any[]) {
|
|
557
|
+
writeQuery({
|
|
558
|
+
kind: 'query',
|
|
559
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
560
|
+
params: parameters?.slice(0, 5),
|
|
561
|
+
durationMs: 0,
|
|
562
|
+
rowCount: 0,
|
|
563
|
+
timestamp: Date.now(),
|
|
564
|
+
});
|
|
565
|
+
if (origLogger && typeof origLogger === 'object' && 'logQuery' in origLogger) {
|
|
566
|
+
origLogger.logQuery(query, parameters);
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
logQueryError(error: string, query: string, parameters?: any[]) {
|
|
570
|
+
writeQuery({
|
|
571
|
+
kind: 'query',
|
|
572
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
573
|
+
params: parameters?.slice(0, 5),
|
|
574
|
+
durationMs: 0,
|
|
575
|
+
rowCount: 0,
|
|
576
|
+
error: error.substring(0, 200),
|
|
577
|
+
timestamp: Date.now(),
|
|
578
|
+
});
|
|
579
|
+
},
|
|
580
|
+
logQuerySlow(time: number, query: string, parameters?: any[]) {
|
|
581
|
+
writeQuery({
|
|
582
|
+
kind: 'query',
|
|
583
|
+
query: query.substring(0, MAX_QUERY_LENGTH),
|
|
584
|
+
params: parameters?.slice(0, 5),
|
|
585
|
+
durationMs: time,
|
|
586
|
+
rowCount: 0,
|
|
587
|
+
timestamp: Date.now(),
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
logSchemaBuild() {},
|
|
591
|
+
logMigration() {},
|
|
592
|
+
log() {},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return new OrigDataSource(options);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// Copy statics
|
|
600
|
+
Object.setPrototypeOf(typeormModule.DataSource, OrigDataSource);
|
|
601
|
+
typeormModule.DataSource.prototype = OrigDataSource.prototype;
|
|
602
|
+
typeormModule.DataSource.__trickle_patched = true;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Also patch createConnection (TypeORM 0.2)
|
|
606
|
+
if (typeormModule.createConnection && !(typeormModule.createConnection as any).__trickle_patched) {
|
|
607
|
+
const origCreate = typeormModule.createConnection;
|
|
608
|
+
typeormModule.createConnection = function patchedCreateConnection(options: any) {
|
|
609
|
+
if (options && typeof options === 'object' && !options.logging) {
|
|
610
|
+
options.logging = true;
|
|
611
|
+
}
|
|
612
|
+
return origCreate(options);
|
|
613
|
+
};
|
|
614
|
+
(typeormModule.createConnection as any).__trickle_patched = true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (debug) console.log('[trickle/db] TypeORM query tracing enabled');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Patch Sequelize to capture queries via its logging option.
|
|
622
|
+
* Sequelize accepts a `logging` function in its constructor options.
|
|
623
|
+
*/
|
|
624
|
+
export function patchSequelize(sequelizeModule: any, debug: boolean): void {
|
|
625
|
+
debugMode = debug;
|
|
626
|
+
|
|
627
|
+
const Sequelize = sequelizeModule.Sequelize || sequelizeModule.default || sequelizeModule;
|
|
628
|
+
if (!Sequelize || typeof Sequelize !== 'function' || (Sequelize as any).__trickle_patched) return;
|
|
629
|
+
|
|
630
|
+
const origConstructor = Sequelize;
|
|
631
|
+
|
|
632
|
+
const patchedSequelize = function (this: any, ...args: any[]) {
|
|
633
|
+
// Sequelize(uri, options) or Sequelize(database, user, pass, options)
|
|
634
|
+
let options: any;
|
|
635
|
+
if (args.length >= 4) {
|
|
636
|
+
options = args[3] = args[3] || {};
|
|
637
|
+
} else if (args.length >= 2 && typeof args[1] === 'object') {
|
|
638
|
+
options = args[1];
|
|
639
|
+
} else if (args.length === 1 && typeof args[0] === 'object') {
|
|
640
|
+
options = args[0];
|
|
641
|
+
} else {
|
|
642
|
+
options = {};
|
|
643
|
+
args.push(options);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Wrap the logging function
|
|
647
|
+
const origLogging = options.logging;
|
|
648
|
+
options.logging = (sql: string, timing?: any) => {
|
|
649
|
+
const queryText = typeof sql === 'string' ? sql : String(sql);
|
|
650
|
+
// Sequelize prepends "Executed (default): " or "Executing (default): " to queries
|
|
651
|
+
const cleanQuery = queryText.replace(/^Execut(?:ed|ing) \([^)]*\):\s*/, '');
|
|
652
|
+
const durationMs = typeof timing === 'number' ? timing : 0;
|
|
653
|
+
|
|
654
|
+
writeQuery({
|
|
655
|
+
kind: 'query',
|
|
656
|
+
query: cleanQuery.substring(0, MAX_QUERY_LENGTH),
|
|
657
|
+
durationMs,
|
|
658
|
+
rowCount: 0,
|
|
659
|
+
timestamp: Date.now(),
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Call original logger
|
|
663
|
+
if (typeof origLogging === 'function') {
|
|
664
|
+
origLogging(sql, timing);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
options.benchmark = true; // Enable timing in log
|
|
668
|
+
|
|
669
|
+
// Call original constructor
|
|
670
|
+
if (new.target) {
|
|
671
|
+
return new origConstructor(...args);
|
|
672
|
+
}
|
|
673
|
+
return origConstructor.apply(this, args);
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// Copy prototype and statics
|
|
677
|
+
patchedSequelize.prototype = origConstructor.prototype;
|
|
678
|
+
Object.setPrototypeOf(patchedSequelize, origConstructor);
|
|
679
|
+
for (const key of Object.getOwnPropertyNames(origConstructor)) {
|
|
680
|
+
if (key !== 'prototype' && key !== 'length' && key !== 'name') {
|
|
681
|
+
try {
|
|
682
|
+
Object.defineProperty(patchedSequelize, key, Object.getOwnPropertyDescriptor(origConstructor, key)!);
|
|
683
|
+
} catch {}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Replace in module exports
|
|
688
|
+
if (sequelizeModule.Sequelize) {
|
|
689
|
+
sequelizeModule.Sequelize = patchedSequelize;
|
|
690
|
+
}
|
|
691
|
+
(patchedSequelize as any).__trickle_patched = true;
|
|
692
|
+
|
|
693
|
+
if (debug) console.log('[trickle/db] Sequelize query tracing enabled');
|
|
694
|
+
}
|
|
695
|
+
|
|
536
696
|
/**
|
|
537
697
|
* Patch mongoose to capture MongoDB operations.
|
|
538
698
|
* Called from observe-register when mongoose is required.
|
package/src/observe-register.ts
CHANGED
|
@@ -1215,6 +1215,24 @@ if (enabled) {
|
|
|
1215
1215
|
} catch { /* not critical */ }
|
|
1216
1216
|
}
|
|
1217
1217
|
|
|
1218
|
+
// TypeORM
|
|
1219
|
+
if (request === 'typeorm' && !expressPatched.has('typeorm')) {
|
|
1220
|
+
expressPatched.add('typeorm');
|
|
1221
|
+
try {
|
|
1222
|
+
const { patchTypeORM } = require(path.join(__dirname, 'db-observer.js'));
|
|
1223
|
+
patchTypeORM(exports, debug);
|
|
1224
|
+
} catch { /* not critical */ }
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Sequelize
|
|
1228
|
+
if (request === 'sequelize' && !expressPatched.has('sequelize')) {
|
|
1229
|
+
expressPatched.add('sequelize');
|
|
1230
|
+
try {
|
|
1231
|
+
const { patchSequelize } = require(path.join(__dirname, 'db-observer.js'));
|
|
1232
|
+
patchSequelize(exports, debug);
|
|
1233
|
+
} catch { /* not critical */ }
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1218
1236
|
// Redis (ioredis)
|
|
1219
1237
|
if (request === 'ioredis' && !expressPatched.has('ioredis')) {
|
|
1220
1238
|
expressPatched.add('ioredis');
|