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.
@@ -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.
@@ -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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.107",
3
+ "version": "0.2.108",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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.
@@ -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');