@ngn-net/nestjs-telescope 0.1.6 → 0.1.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.
Files changed (37) hide show
  1. package/README.md +42 -34
  2. package/dist/constants.d.ts +12 -3
  3. package/dist/constants.js +14 -5
  4. package/dist/controllers/telescope.controller.d.ts +5 -2
  5. package/dist/controllers/telescope.controller.js +41 -13
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.js +12 -0
  8. package/dist/interfaces/telescope-options.interface.d.ts +9 -3
  9. package/dist/storage/entities/telescope-entry.entity.js +34 -7
  10. package/dist/storage/telescope-repository.service.d.ts +17 -7
  11. package/dist/storage/telescope-repository.service.js +42 -17
  12. package/dist/telescope.module.d.ts +6 -2
  13. package/dist/telescope.module.js +148 -44
  14. package/dist/telescope.service.d.ts +21 -1
  15. package/dist/telescope.service.js +72 -22
  16. package/dist/ui/index.html +1635 -0
  17. package/dist/watchers/cache.watcher.d.ts +5 -4
  18. package/dist/watchers/cache.watcher.js +50 -34
  19. package/dist/watchers/event.watcher.d.ts +7 -3
  20. package/dist/watchers/event.watcher.js +22 -6
  21. package/dist/watchers/exception.watcher.js +7 -1
  22. package/dist/watchers/http-request.watcher.d.ts +3 -1
  23. package/dist/watchers/http-request.watcher.js +25 -8
  24. package/dist/watchers/log.watcher.d.ts +1 -0
  25. package/dist/watchers/log.watcher.js +22 -17
  26. package/dist/watchers/mail.watcher.d.ts +1 -1
  27. package/dist/watchers/mail.watcher.js +28 -56
  28. package/dist/watchers/query.watcher.d.ts +6 -3
  29. package/dist/watchers/query.watcher.js +36 -8
  30. package/dist/watchers/queue.watcher.d.ts +2 -4
  31. package/dist/watchers/queue.watcher.js +38 -32
  32. package/dist/watchers/redis.watcher.d.ts +3 -1
  33. package/dist/watchers/redis.watcher.js +16 -4
  34. package/dist/watchers/schedule.watcher.d.ts +8 -3
  35. package/dist/watchers/schedule.watcher.js +25 -11
  36. package/package.json +51 -19
  37. package/ui/index.html +602 -235
@@ -5,15 +5,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
8
11
  var TelescopeModule_1;
9
12
  Object.defineProperty(exports, "__esModule", { value: true });
10
13
  exports.TelescopeModule = void 0;
11
14
  // src/telescope.module.ts
12
15
  const common_1 = require("@nestjs/common");
13
- const cache_manager_1 = require("@nestjs/cache-manager");
16
+ const core_1 = require("@nestjs/core");
14
17
  const typeorm_1 = require("@nestjs/typeorm");
15
- const bullmq_1 = require("@nestjs/bullmq");
16
- const config_1 = require("@nestjs/config");
17
18
  const jwt_1 = require("@nestjs/jwt");
18
19
  const telescope_service_1 = require("./telescope.service");
19
20
  const telescope_controller_1 = require("./controllers/telescope.controller");
@@ -30,50 +31,152 @@ const exception_watcher_1 = require("./watchers/exception.watcher");
30
31
  const schedule_watcher_1 = require("./watchers/schedule.watcher");
31
32
  const redis_watcher_1 = require("./watchers/redis.watcher");
32
33
  const telescope_jwt_guard_1 = require("./guards/telescope-jwt.guard");
34
+ const constants_1 = require("./constants");
35
+ const entry_type_enum_1 = require("./enums/entry-type.enum");
33
36
  let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
37
+ moduleRef;
38
+ constructor(moduleRef) {
39
+ this.moduleRef = moduleRef;
40
+ }
41
+ onModuleInit() {
42
+ // Dynamic EventEmitter2 configuration
43
+ try {
44
+ const eventWatcher = this.moduleRef.get(event_watcher_1.EventWatcher, { strict: false });
45
+ const { EventEmitter2 } = require('@nestjs/event-emitter');
46
+ if (eventWatcher && EventEmitter2) {
47
+ const emitter = this.moduleRef.get(EventEmitter2, { strict: false });
48
+ if (emitter) {
49
+ eventWatcher.setEmitter(emitter);
50
+ }
51
+ }
52
+ }
53
+ catch (e) {
54
+ // EventEmitter2 or EventWatcher not registered/available
55
+ }
56
+ // Dynamic SchedulerRegistry configuration
57
+ try {
58
+ const scheduleWatcher = this.moduleRef.get(schedule_watcher_1.ScheduleWatcher, { strict: false });
59
+ const { SchedulerRegistry } = require('@nestjs/schedule');
60
+ if (scheduleWatcher && SchedulerRegistry) {
61
+ const scheduler = this.moduleRef.get(SchedulerRegistry, { strict: false });
62
+ if (scheduler) {
63
+ scheduleWatcher.setSchedulerRegistry(scheduler);
64
+ }
65
+ }
66
+ }
67
+ catch (e) {
68
+ // SchedulerRegistry or ScheduleWatcher not registered/available
69
+ }
70
+ }
34
71
  static forRoot(options = {}) {
72
+ const imports = [
73
+ typeorm_1.TypeOrmModule.forFeature([telescope_entry_entity_1.TelescopeEntry]),
74
+ jwt_1.JwtModule.register({
75
+ secret: options.jwtSecret || process.env.TELESCOPE_JWT_SECRET || constants_1.DEFAULT_JWT_SECRET,
76
+ signOptions: { expiresIn: '1d' },
77
+ }),
78
+ ];
79
+ const providers = [
80
+ {
81
+ provide: constants_1.TELESCOPE_OPTIONS,
82
+ useValue: options,
83
+ },
84
+ telescope_service_1.TelescopeService,
85
+ telescope_repository_service_1.TelescopeRepository,
86
+ telescope_jwt_guard_1.JwtAuthGuard,
87
+ ];
88
+ // Always registered watchers (no external peer dependencies needed)
89
+ const watchersToRegister = [
90
+ { type: entry_type_enum_1.EntryType.REQUEST, watcher: http_request_watcher_1.HttpRequestWatcher },
91
+ { type: entry_type_enum_1.EntryType.QUERY, watcher: query_watcher_1.QueryWatcher },
92
+ { type: entry_type_enum_1.EntryType.LOG, watcher: log_watcher_1.LogWatcher },
93
+ { type: entry_type_enum_1.EntryType.EXCEPTION, watcher: exception_watcher_1.ExceptionWatcher },
94
+ { type: entry_type_enum_1.EntryType.REDIS, watcher: redis_watcher_1.RedisWatcher },
95
+ ];
96
+ for (const item of watchersToRegister) {
97
+ if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(item.type)) {
98
+ providers.push(item.watcher);
99
+ }
100
+ }
101
+ // Optional dependencies checking:
102
+ // 1. CacheWatcher (@nestjs/cache-manager)
103
+ let hasCache = false;
104
+ try {
105
+ require('@nestjs/cache-manager');
106
+ hasCache = true;
107
+ }
108
+ catch { }
109
+ if (hasCache && (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.CACHE))) {
110
+ try {
111
+ const { CacheModule } = require('@nestjs/cache-manager');
112
+ if (CacheModule) {
113
+ imports.push(CacheModule.register({}));
114
+ providers.push(cache_watcher_1.CacheWatcher);
115
+ }
116
+ }
117
+ catch { }
118
+ }
119
+ // 2. QueueWatcher (@nestjs/bullmq & bullmq)
120
+ let hasBull = false;
121
+ try {
122
+ require('@nestjs/bullmq');
123
+ require('bullmq');
124
+ hasBull = true;
125
+ }
126
+ catch { }
127
+ if (hasBull && (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.JOB))) {
128
+ try {
129
+ const { BullModule } = require('@nestjs/bullmq');
130
+ if (BullModule) {
131
+ imports.push(BullModule.registerQueue({
132
+ name: 'telescope-queue',
133
+ connection: {
134
+ host: process.env.REDIS_HOST || '127.0.0.1',
135
+ port: Number(process.env.REDIS_PORT) || 6379,
136
+ },
137
+ }));
138
+ providers.push(queue_watcher_1.QueueWatcher);
139
+ }
140
+ }
141
+ catch { }
142
+ }
143
+ // 3. EventWatcher (@nestjs/event-emitter)
144
+ let hasEvents = false;
145
+ try {
146
+ require('@nestjs/event-emitter');
147
+ hasEvents = true;
148
+ }
149
+ catch { }
150
+ if (hasEvents && (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.EVENT))) {
151
+ providers.push(event_watcher_1.EventWatcher);
152
+ }
153
+ // 4. ScheduleWatcher (@nestjs/schedule)
154
+ let hasSchedule = false;
155
+ try {
156
+ require('@nestjs/schedule');
157
+ hasSchedule = true;
158
+ }
159
+ catch { }
160
+ if (hasSchedule && (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.SCHEDULED_TASK))) {
161
+ providers.push(schedule_watcher_1.ScheduleWatcher);
162
+ }
163
+ // 5. MailWatcher (nodemailer)
164
+ let hasNodemailer = false;
165
+ try {
166
+ require('nodemailer');
167
+ hasNodemailer = true;
168
+ }
169
+ catch { }
170
+ if (hasNodemailer && (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.MAIL))) {
171
+ providers.push(mail_watcher_1.MailWatcher);
172
+ }
173
+ // Dynamic Route Prefix Setup using Reflect metadata on TelescopeController
174
+ const path = options.path || 'telescope';
175
+ Reflect.defineMetadata('path', path, telescope_controller_1.TelescopeController);
35
176
  return {
36
177
  module: TelescopeModule_1,
37
- imports: [
38
- config_1.ConfigModule.forRoot({ isGlobal: true }),
39
- typeorm_1.TypeOrmModule.forFeature([telescope_entry_entity_1.TelescopeEntry]),
40
- cache_manager_1.CacheModule.register({}),
41
- bullmq_1.BullModule.registerQueue({
42
- name: 'telescope-queue',
43
- connection: {
44
- // connection settings are read from env
45
- host: process.env.REDIS_HOST || '127.0.0.1',
46
- port: Number(process.env.REDIS_PORT) || 6379,
47
- },
48
- }),
49
- jwt_1.JwtModule.registerAsync({
50
- imports: [config_1.ConfigModule],
51
- useFactory: async (config) => ({
52
- secret: options.jwtSecret || config.get('TELESCOPE_JWT_SECRET') || 'change-me',
53
- signOptions: { expiresIn: '1d' },
54
- }),
55
- inject: [config_1.ConfigService],
56
- }),
57
- ],
58
- providers: [
59
- {
60
- provide: 'TELESCOPE_OPTIONS',
61
- useValue: options,
62
- },
63
- telescope_service_1.TelescopeService,
64
- telescope_repository_service_1.TelescopeRepository,
65
- http_request_watcher_1.HttpRequestWatcher,
66
- query_watcher_1.QueryWatcher,
67
- cache_watcher_1.CacheWatcher,
68
- queue_watcher_1.QueueWatcher,
69
- event_watcher_1.EventWatcher,
70
- mail_watcher_1.MailWatcher,
71
- log_watcher_1.LogWatcher,
72
- exception_watcher_1.ExceptionWatcher,
73
- schedule_watcher_1.ScheduleWatcher,
74
- redis_watcher_1.RedisWatcher,
75
- telescope_jwt_guard_1.JwtAuthGuard,
76
- ],
178
+ imports,
179
+ providers,
77
180
  controllers: [telescope_controller_1.TelescopeController],
78
181
  exports: [telescope_service_1.TelescopeService, telescope_repository_service_1.TelescopeRepository],
79
182
  };
@@ -82,5 +185,6 @@ let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
82
185
  exports.TelescopeModule = TelescopeModule;
83
186
  exports.TelescopeModule = TelescopeModule = TelescopeModule_1 = __decorate([
84
187
  (0, common_1.Global)(),
85
- (0, common_1.Module)({})
188
+ (0, common_1.Module)({}),
189
+ __metadata("design:paramtypes", [core_1.ModuleRef])
86
190
  ], TelescopeModule);
@@ -2,11 +2,31 @@ import { OnModuleInit } from '@nestjs/common';
2
2
  import { TelescopeRepository } from './storage/telescope-repository.service';
3
3
  import { TelescopeEntry } from './storage/entities/telescope-entry.entity';
4
4
  import { EntryType } from './enums/entry-type.enum';
5
+ import { TelescopeOptions } from './interfaces/telescope-options.interface';
5
6
  export declare class TelescopeService implements OnModuleInit {
6
7
  private readonly repo;
7
8
  private readonly options?;
8
- constructor(repo: TelescopeRepository, options?: any | undefined);
9
+ private lastPruneTime;
10
+ private recording;
11
+ constructor(repo: TelescopeRepository, options?: TelescopeOptions | undefined);
9
12
  onModuleInit(): Promise<void>;
13
+ /**
14
+ * Check if a specific entry type is enabled in the options.
15
+ * If `enabledEntryTypes` is not configured, all types are enabled.
16
+ */
17
+ isEnabled(type: EntryType): boolean;
18
+ /**
19
+ * Check if a request path should be ignored.
20
+ */
21
+ shouldIgnorePath(path: string): boolean;
22
+ /**
23
+ * Check if a Redis command should be ignored.
24
+ */
25
+ shouldIgnoreCommand(command: string): boolean;
26
+ /**
27
+ * Record a telescope entry. Performs type-checking, deduplication guard,
28
+ * and throttled pruning.
29
+ */
10
30
  record(entry: Partial<TelescopeEntry> & {
11
31
  type: EntryType;
12
32
  }): Promise<void>;
@@ -16,47 +16,97 @@ exports.TelescopeService = void 0;
16
16
  // src/telescope.service.ts
17
17
  const common_1 = require("@nestjs/common");
18
18
  const telescope_repository_service_1 = require("./storage/telescope-repository.service");
19
+ const constants_1 = require("./constants");
19
20
  let TelescopeService = class TelescopeService {
20
21
  repo;
21
22
  options;
23
+ lastPruneTime = 0;
24
+ recording = false; // Guard against recursive recording
22
25
  constructor(repo, options) {
23
26
  this.repo = repo;
24
27
  this.options = options;
25
28
  }
26
29
  async onModuleInit() {
27
- // ensure database schema exists (TypeORM will sync)
30
+ // Module is initialized; TypeORM will handle schema sync
28
31
  }
32
+ /**
33
+ * Check if a specific entry type is enabled in the options.
34
+ * If `enabledEntryTypes` is not configured, all types are enabled.
35
+ */
36
+ isEnabled(type) {
37
+ if (!this.options?.enabledEntryTypes)
38
+ return true;
39
+ return this.options.enabledEntryTypes.includes(type);
40
+ }
41
+ /**
42
+ * Check if a request path should be ignored.
43
+ */
44
+ shouldIgnorePath(path) {
45
+ if (!path || !this.options?.ignorePaths)
46
+ return false;
47
+ const telescopePath = this.options.path || 'telescope';
48
+ // Always ignore telescope's own routes
49
+ if (path.startsWith(`/${telescopePath}`))
50
+ return true;
51
+ return this.options.ignorePaths.some((p) => path.startsWith(p));
52
+ }
53
+ /**
54
+ * Check if a Redis command should be ignored.
55
+ */
56
+ shouldIgnoreCommand(command) {
57
+ if (!command || !this.options?.ignoreCommands)
58
+ return false;
59
+ return this.options.ignoreCommands.some((c) => c.toLowerCase() === command.toLowerCase());
60
+ }
61
+ /**
62
+ * Record a telescope entry. Performs type-checking, deduplication guard,
63
+ * and throttled pruning.
64
+ */
29
65
  async record(entry) {
30
- // Check if the type is explicitly enabled (if enabledEntryTypes is configured)
31
- if (this.options?.enabledEntryTypes) {
32
- if (!this.options.enabledEntryTypes.includes(entry.type)) {
33
- return;
66
+ // Skip if this type is not enabled
67
+ if (!this.isEnabled(entry.type))
68
+ return;
69
+ // Guard against recursive calls (e.g., LogWatcher triggering another record)
70
+ if (this.recording)
71
+ return;
72
+ this.recording = true;
73
+ try {
74
+ const uuidStr = entry.uuid ?? `${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
75
+ const fullEntry = {
76
+ uuid: uuidStr,
77
+ type: entry.type,
78
+ content: entry.content ?? {},
79
+ recordedAt: entry.recordedAt ?? new Date(),
80
+ familyHash: entry.familyHash ?? null,
81
+ batchId: entry.batchId,
82
+ };
83
+ await this.repo.save(fullEntry);
84
+ // Throttled pruning: at most once every PRUNE_THROTTLE_MS
85
+ const now = Date.now();
86
+ if (now - this.lastPruneTime >= constants_1.PRUNE_THROTTLE_MS) {
87
+ this.lastPruneTime = now;
88
+ const maxEntries = this.options?.maxEntries ?? constants_1.DEFAULT_MAX_ENTRIES;
89
+ try {
90
+ await this.repo.prune(maxEntries);
91
+ }
92
+ catch {
93
+ // Ignore pruning errors
94
+ }
34
95
  }
35
96
  }
36
- const uuidStr = entry.uuid ?? `${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
37
- const fullEntry = {
38
- uuid: uuidStr,
39
- type: entry.type,
40
- content: entry.content ?? {},
41
- recordedAt: entry.recordedAt ?? new Date(),
42
- familyHash: entry.familyHash ?? null,
43
- batchId: entry.batchId,
44
- };
45
- await this.repo.save(fullEntry);
46
- // Run pruning to stay within the limit
47
- const maxEntries = this.options?.maxEntries ?? 1000;
48
- try {
49
- await this.repo.prune(maxEntries);
97
+ catch (err) {
98
+ // Gracefully catch all database/metadata errors to protect host app stability.
99
+ // Avoid using console.log here as it might trigger infinite recursion in LogWatcher.
50
100
  }
51
- catch (e) {
52
- // Ignore database errors during pruning
101
+ finally {
102
+ this.recording = false;
53
103
  }
54
104
  }
55
105
  };
56
106
  exports.TelescopeService = TelescopeService;
57
107
  exports.TelescopeService = TelescopeService = __decorate([
58
108
  (0, common_1.Injectable)(),
59
- __param(1, (0, common_1.Inject)('TELESCOPE_OPTIONS')),
109
+ __param(1, (0, common_1.Inject)(constants_1.TELESCOPE_OPTIONS)),
60
110
  __param(1, (0, common_1.Optional)()),
61
111
  __metadata("design:paramtypes", [telescope_repository_service_1.TelescopeRepository, Object])
62
112
  ], TelescopeService);