@ngn-net/nestjs-telescope 0.2.6 → 0.2.8

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.
@@ -35,6 +35,7 @@ const log_watcher_1 = require("./watchers/log.watcher");
35
35
  const exception_watcher_1 = require("./watchers/exception.watcher");
36
36
  const schedule_watcher_1 = require("./watchers/schedule.watcher");
37
37
  const http_service_watcher_1 = require("./watchers/http-service.watcher");
38
+ const model_subscriber_1 = require("./watchers/model.subscriber");
38
39
  const redis_watcher_1 = require("./watchers/redis.watcher");
39
40
  const telescope_jwt_guard_1 = require("./guards/telescope-jwt.guard");
40
41
  const command_watcher_1 = require("./watchers/command.watcher");
@@ -246,6 +247,7 @@ let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
246
247
  }
247
248
  if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.MODEL)) {
248
249
  providers.push(model_watcher_1.ModelWatcher);
250
+ providers.push(model_subscriber_1.ModelSubscriber);
249
251
  }
250
252
  if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.NOTIFICATION)) {
251
253
  providers.push(notification_watcher_1.NotificationWatcher);
@@ -314,6 +316,11 @@ let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
314
316
  providers.push(redis_watcher_1.RedisWatcher);
315
317
  providers.push(http_client_watcher_1.HttpClientWatcher);
316
318
  providers.push(http_service_watcher_1.HttpServiceWatcher);
319
+ providers.push(command_watcher_1.CommandWatcher);
320
+ providers.push(model_watcher_1.ModelWatcher);
321
+ providers.push(model_subscriber_1.ModelSubscriber);
322
+ providers.push(notification_watcher_1.NotificationWatcher);
323
+ providers.push(gate_watcher_1.GateWatcher);
317
324
  // Optional modules/watchers if packages are present
318
325
  try {
319
326
  require('@nestjs/cache-manager');
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ngn-net/nestjs-telescope",
3
- "version": "0.2.6",
4
- "builtAt": "2026-06-08T12:28:43.444Z"
3
+ "version": "0.2.8",
4
+ "builtAt": "2026-06-08T12:41:45.083Z"
5
5
  }
@@ -1,13 +1,17 @@
1
- import { EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
1
+ import { EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent, DataSource } from 'typeorm';
2
+ import { OnModuleInit } from '@nestjs/common';
2
3
  import { TelescopeService } from '../telescope.service';
3
4
  /**
4
5
  * Subscribes to TypeORM entity lifecycle events and records them.
5
6
  * This watcher is automatically registered when TypeORM is present.
6
7
  */
7
- export declare class ModelSubscriber implements EntitySubscriberInterface {
8
+ export declare class ModelSubscriber implements EntitySubscriberInterface, OnModuleInit {
8
9
  private readonly telescope;
9
- constructor(telescope: TelescopeService);
10
+ private readonly dataSource?;
11
+ constructor(telescope: TelescopeService, dataSource?: DataSource | undefined);
12
+ onModuleInit(): void;
10
13
  listenTo(): ObjectConstructor;
14
+ private isTelescopeEntity;
11
15
  afterInsert(event: InsertEvent<any>): void;
12
16
  afterUpdate(event: UpdateEvent<any>): void;
13
17
  afterRemove(event: RemoveEvent<any>): void;
@@ -8,9 +8,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var ModelSubscriber_1;
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
16
  exports.ModelSubscriber = void 0;
13
- // src/watchers/model.subscriber.ts
14
17
  const typeorm_1 = require("typeorm");
15
18
  const common_1 = require("@nestjs/common");
16
19
  const telescope_service_1 = require("../telescope.service");
@@ -19,16 +22,38 @@ const entry_type_enum_1 = require("../enums/entry-type.enum");
19
22
  * Subscribes to TypeORM entity lifecycle events and records them.
20
23
  * This watcher is automatically registered when TypeORM is present.
21
24
  */
22
- let ModelSubscriber = class ModelSubscriber {
25
+ let ModelSubscriber = ModelSubscriber_1 = class ModelSubscriber {
23
26
  telescope;
24
- constructor(telescope) {
27
+ dataSource;
28
+ constructor(telescope, dataSource) {
25
29
  this.telescope = telescope;
30
+ this.dataSource = dataSource;
31
+ }
32
+ onModuleInit() {
33
+ if (this.dataSource) {
34
+ try {
35
+ const alreadyRegistered = this.dataSource.subscribers.some((s) => s instanceof ModelSubscriber_1);
36
+ if (!alreadyRegistered) {
37
+ this.dataSource.subscribers.push(this);
38
+ }
39
+ }
40
+ catch {
41
+ // Ignore registration errors
42
+ }
43
+ }
26
44
  }
27
45
  listenTo() {
28
46
  // Listen to all entities
29
47
  return Object;
30
48
  }
49
+ isTelescopeEntity(event) {
50
+ const name = event.metadata?.name;
51
+ const tableName = event.metadata?.tableName;
52
+ return name === 'TelescopeEntry' || tableName === 'telescope_entries';
53
+ }
31
54
  afterInsert(event) {
55
+ if (this.isTelescopeEntity(event))
56
+ return;
32
57
  this.telescope.record({
33
58
  type: entry_type_enum_1.EntryType.MODEL,
34
59
  content: {
@@ -40,6 +65,8 @@ let ModelSubscriber = class ModelSubscriber {
40
65
  }).catch(() => { });
41
66
  }
42
67
  afterUpdate(event) {
68
+ if (this.isTelescopeEntity(event))
69
+ return;
43
70
  this.telescope.record({
44
71
  type: entry_type_enum_1.EntryType.MODEL,
45
72
  content: {
@@ -52,6 +79,8 @@ let ModelSubscriber = class ModelSubscriber {
52
79
  }).catch(() => { });
53
80
  }
54
81
  afterRemove(event) {
82
+ if (this.isTelescopeEntity(event))
83
+ return;
55
84
  this.telescope.record({
56
85
  type: entry_type_enum_1.EntryType.MODEL,
57
86
  content: {
@@ -63,8 +92,10 @@ let ModelSubscriber = class ModelSubscriber {
63
92
  }
64
93
  };
65
94
  exports.ModelSubscriber = ModelSubscriber;
66
- exports.ModelSubscriber = ModelSubscriber = __decorate([
95
+ exports.ModelSubscriber = ModelSubscriber = ModelSubscriber_1 = __decorate([
67
96
  (0, typeorm_1.EventSubscriber)(),
68
97
  (0, common_1.Injectable)(),
69
- __metadata("design:paramtypes", [telescope_service_1.TelescopeService])
98
+ __param(1, (0, common_1.Optional)()),
99
+ __metadata("design:paramtypes", [telescope_service_1.TelescopeService,
100
+ typeorm_1.DataSource])
70
101
  ], ModelSubscriber);
@@ -1,13 +1,9 @@
1
1
  import { OnModuleInit } from '@nestjs/common';
2
- import { DataSource, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
2
+ import { DataSource } from 'typeorm';
3
3
  import { TelescopeService } from '../telescope.service';
4
4
  export declare class QueryWatcher implements OnModuleInit {
5
5
  private readonly telescope;
6
6
  private readonly dataSource?;
7
7
  constructor(telescope: TelescopeService, dataSource?: DataSource | undefined);
8
8
  onModuleInit(): void;
9
- afterInsert(event: InsertEvent<any>): void;
10
- afterUpdate(event: UpdateEvent<any>): void;
11
- afterRemove(event: RemoveEvent<any>): void;
12
- private capture;
13
9
  }
@@ -11,7 +11,6 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
- var QueryWatcher_1;
15
14
  Object.defineProperty(exports, "__esModule", { value: true });
16
15
  exports.QueryWatcher = void 0;
17
16
  // src/watchers/query.watcher.ts
@@ -19,7 +18,8 @@ const common_1 = require("@nestjs/common");
19
18
  const typeorm_1 = require("typeorm");
20
19
  const telescope_service_1 = require("../telescope.service");
21
20
  const entry_type_enum_1 = require("../enums/entry-type.enum");
22
- let QueryWatcher = QueryWatcher_1 = class QueryWatcher {
21
+ const isWrappedSymbol = Symbol('TelescopeQueryRunnerWrapped');
22
+ let QueryWatcher = class QueryWatcher {
23
23
  telescope;
24
24
  dataSource;
25
25
  constructor(telescope, dataSource) {
@@ -27,54 +27,141 @@ let QueryWatcher = QueryWatcher_1 = class QueryWatcher {
27
27
  this.dataSource = dataSource;
28
28
  }
29
29
  onModuleInit() {
30
- // Manually register this subscriber with the DataSource
31
- // The @EventSubscriber() decorator alone doesn't auto-register when the
32
- // subscriber is managed by NestJS DI rather than TypeORM's internal discovery
33
- if (this.dataSource) {
34
- try {
35
- // Avoid duplicate registration
36
- const alreadyRegistered = this.dataSource.subscribers.some((s) => s instanceof QueryWatcher_1);
37
- if (!alreadyRegistered) {
38
- this.dataSource.subscribers.push(this);
39
- }
30
+ if (!this.dataSource) {
31
+ return;
32
+ }
33
+ try {
34
+ const ds = this.dataSource;
35
+ if (ds[isWrappedSymbol]) {
36
+ return;
40
37
  }
41
- catch {
42
- // Ignore registration errors
38
+ ds[isWrappedSymbol] = true;
39
+ const originalCreateQueryRunner = ds.createQueryRunner;
40
+ if (typeof originalCreateQueryRunner !== 'function') {
41
+ return;
43
42
  }
43
+ const telescope = this.telescope;
44
+ ds.createQueryRunner = function (...args) {
45
+ const queryRunner = originalCreateQueryRunner.apply(this, args);
46
+ if (queryRunner && typeof queryRunner.query === 'function') {
47
+ const originalQuery = queryRunner.query;
48
+ queryRunner.query = async function (query, parameters, useAutocommit) {
49
+ // Skip tracking telescope's own queries to avoid infinite recursion/loops
50
+ if (!query || query.includes('telescope_entries') || query.includes('telescope_entry')) {
51
+ return originalQuery.call(this, query, parameters, useAutocommit);
52
+ }
53
+ const startTime = Date.now();
54
+ try {
55
+ const result = await originalQuery.call(this, query, parameters, useAutocommit);
56
+ const duration = Date.now() - startTime;
57
+ const { operation, entity } = parseSqlQuery(query);
58
+ const sanitizedParams = sanitizeParameters(parameters);
59
+ telescope.record({
60
+ type: entry_type_enum_1.EntryType.QUERY,
61
+ content: {
62
+ operation,
63
+ entity,
64
+ query,
65
+ parameters: sanitizedParams,
66
+ duration,
67
+ success: true,
68
+ connection: queryRunner.connection?.name || 'default',
69
+ },
70
+ }).catch(() => { });
71
+ return result;
72
+ }
73
+ catch (error) {
74
+ const duration = Date.now() - startTime;
75
+ const { operation, entity } = parseSqlQuery(query);
76
+ const sanitizedParams = sanitizeParameters(parameters);
77
+ telescope.record({
78
+ type: entry_type_enum_1.EntryType.QUERY,
79
+ content: {
80
+ operation,
81
+ entity,
82
+ query,
83
+ parameters: sanitizedParams,
84
+ duration,
85
+ success: false,
86
+ error: error instanceof Error ? error.message : String(error),
87
+ connection: queryRunner.connection?.name || 'default',
88
+ },
89
+ }).catch(() => { });
90
+ throw error;
91
+ }
92
+ };
93
+ }
94
+ return queryRunner;
95
+ };
96
+ }
97
+ catch {
98
+ // Ignore patching errors to protect app stability
44
99
  }
45
- }
46
- afterInsert(event) {
47
- this.capture(event, 'INSERT');
48
- }
49
- afterUpdate(event) {
50
- this.capture(event, 'UPDATE');
51
- }
52
- afterRemove(event) {
53
- this.capture(event, 'REMOVE');
54
- }
55
- capture(event, operation) {
56
- // Skip recording telescope's own entity changes to avoid infinite loops
57
- const tableName = event.metadata?.tableName;
58
- if (tableName === 'telescope_entries')
59
- return;
60
- this.telescope.record({
61
- type: entry_type_enum_1.EntryType.QUERY,
62
- content: {
63
- operation,
64
- entity: tableName || 'unknown',
65
- primaryKey: event.entity?.id || null,
66
- data: event.entity,
67
- query: event.query,
68
- parameters: event.parameters,
69
- },
70
- });
71
100
  }
72
101
  };
73
102
  exports.QueryWatcher = QueryWatcher;
74
- exports.QueryWatcher = QueryWatcher = QueryWatcher_1 = __decorate([
75
- (0, typeorm_1.EventSubscriber)(),
103
+ exports.QueryWatcher = QueryWatcher = __decorate([
76
104
  (0, common_1.Injectable)(),
77
105
  __param(1, (0, common_1.Optional)()),
78
106
  __metadata("design:paramtypes", [telescope_service_1.TelescopeService,
79
107
  typeorm_1.DataSource])
80
108
  ], QueryWatcher);
109
+ function parseSqlQuery(query) {
110
+ const trimmed = query.trim();
111
+ const matchOp = trimmed.match(/^([a-zA-Z]+)/);
112
+ const operation = matchOp ? matchOp[1].toUpperCase() : 'QUERY';
113
+ let entity = 'db';
114
+ try {
115
+ const lowerQuery = trimmed.toLowerCase();
116
+ if (lowerQuery.startsWith('select')) {
117
+ const fromMatch = trimmed.match(/from\s+([`"']?[a-zA-Z0-9_.-]+[`"']?)/i);
118
+ if (fromMatch) {
119
+ entity = fromMatch[1].replace(/[`"']/g, '');
120
+ }
121
+ }
122
+ else if (lowerQuery.startsWith('insert')) {
123
+ const intoMatch = trimmed.match(/into\s+([`"']?[a-zA-Z0-9_.-]+[`"']?)/i);
124
+ if (intoMatch) {
125
+ entity = intoMatch[1].replace(/[`"']/g, '');
126
+ }
127
+ }
128
+ else if (lowerQuery.startsWith('update')) {
129
+ const updateMatch = trimmed.match(/update\s+([`"']?[a-zA-Z0-9_.-]+[`"']?)/i);
130
+ if (updateMatch) {
131
+ entity = updateMatch[1].replace(/[`"']/g, '');
132
+ }
133
+ }
134
+ else if (lowerQuery.startsWith('delete')) {
135
+ const deleteMatch = trimmed.match(/from\s+([`"']?[a-zA-Z0-9_.-]+[`"']?)/i);
136
+ if (deleteMatch) {
137
+ entity = deleteMatch[1].replace(/[`"']/g, '');
138
+ }
139
+ }
140
+ }
141
+ catch {
142
+ // Keep 'db' as fallback
143
+ }
144
+ return { operation, entity };
145
+ }
146
+ function sanitizeParameters(params) {
147
+ if (!params || !Array.isArray(params))
148
+ return [];
149
+ return params.map((param) => {
150
+ if (typeof param === 'bigint') {
151
+ return param.toString();
152
+ }
153
+ if (param instanceof Date) {
154
+ return param.toISOString();
155
+ }
156
+ if (param && typeof param === 'object') {
157
+ try {
158
+ JSON.stringify(param);
159
+ return param;
160
+ }
161
+ catch {
162
+ return '[Unserializable Object]';
163
+ }
164
+ }
165
+ return param;
166
+ });
167
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngn-net/nestjs-telescope",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },