@ngn-net/nestjs-telescope 0.2.5 → 0.2.7
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/telescope.module.js +7 -0
- package/dist/ui/manifest.json +2 -2
- package/dist/watchers/http-client.watcher.js +13 -0
- package/dist/watchers/model.subscriber.d.ts +6 -3
- package/dist/watchers/model.subscriber.js +25 -5
- package/dist/watchers/query.watcher.d.ts +1 -5
- package/dist/watchers/query.watcher.js +129 -42
- package/package.json +1 -1
package/dist/telescope.module.js
CHANGED
|
@@ -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');
|
package/dist/ui/manifest.json
CHANGED
|
@@ -67,6 +67,19 @@ let HttpClientWatcher = class HttpClientWatcher {
|
|
|
67
67
|
this.patched = true;
|
|
68
68
|
this.patch(http);
|
|
69
69
|
this.patch(https);
|
|
70
|
+
// Also patch follow-redirects if used by HTTP clients like Axios
|
|
71
|
+
try {
|
|
72
|
+
const followRedirects = require('follow-redirects');
|
|
73
|
+
if (followRedirects) {
|
|
74
|
+
if (followRedirects.http)
|
|
75
|
+
this.patch(followRedirects.http);
|
|
76
|
+
if (followRedirects.https)
|
|
77
|
+
this.patch(followRedirects.https);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// follow-redirects not available, skip
|
|
82
|
+
}
|
|
70
83
|
}
|
|
71
84
|
patch(module) {
|
|
72
85
|
try {
|
|
@@ -1,12 +1,15 @@
|
|
|
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
|
-
|
|
10
|
+
private readonly dataSource?;
|
|
11
|
+
constructor(telescope: TelescopeService, dataSource?: DataSource | undefined);
|
|
12
|
+
onModuleInit(): void;
|
|
10
13
|
listenTo(): ObjectConstructor;
|
|
11
14
|
afterInsert(event: InsertEvent<any>): void;
|
|
12
15
|
afterUpdate(event: UpdateEvent<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,10 +22,25 @@ 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
|
-
|
|
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
|
|
@@ -63,8 +81,10 @@ let ModelSubscriber = class ModelSubscriber {
|
|
|
63
81
|
}
|
|
64
82
|
};
|
|
65
83
|
exports.ModelSubscriber = ModelSubscriber;
|
|
66
|
-
exports.ModelSubscriber = ModelSubscriber = __decorate([
|
|
84
|
+
exports.ModelSubscriber = ModelSubscriber = ModelSubscriber_1 = __decorate([
|
|
67
85
|
(0, typeorm_1.EventSubscriber)(),
|
|
68
86
|
(0, common_1.Injectable)(),
|
|
69
|
-
|
|
87
|
+
__param(1, (0, common_1.Optional)()),
|
|
88
|
+
__metadata("design:paramtypes", [telescope_service_1.TelescopeService,
|
|
89
|
+
typeorm_1.DataSource])
|
|
70
90
|
], ModelSubscriber);
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
-
import { DataSource
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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 =
|
|
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
|
+
}
|