@ngn-net/nestjs-telescope 0.2.14 → 0.3.0

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.
@@ -11,14 +11,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var ModelWatcher_1;
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.ModelWatcher = void 0;
14
- // src/watchers/model.watcher.ts
15
14
  const common_1 = require("@nestjs/common");
16
15
  const telescope_service_1 = require("../telescope.service");
17
16
  const entry_type_enum_1 = require("../enums/entry-type.enum");
18
- /**
19
- * Placeholder watcher for ORM model events (e.g., TypeORM, Mongoose, Sequelize).
20
- * Real implementation would subscribe to lifecycle hooks and record create/update/delete.
21
- */
22
17
  let ModelWatcher = ModelWatcher_1 = class ModelWatcher {
23
18
  telescope;
24
19
  logger = new common_1.Logger(ModelWatcher_1.name);
@@ -26,18 +21,51 @@ let ModelWatcher = ModelWatcher_1 = class ModelWatcher {
26
21
  this.telescope = telescope;
27
22
  }
28
23
  onModuleInit() {
29
- // Dummy entry to prove watcher registration.
30
- this.telescope
31
- .record({
32
- type: entry_type_enum_1.EntryType.MODEL,
33
- content: {
34
- model: 'module:init',
35
- action: 'init',
36
- timestamp: new Date().toISOString(),
37
- },
38
- })
39
- .catch(() => { });
40
- this.logger.debug('ModelWatcher initialized dummy entry recorded');
24
+ this.tryHookMongoose();
25
+ }
26
+ tryHookMongoose() {
27
+ try {
28
+ const mongoose = require('mongoose');
29
+ if (!mongoose || !mongoose.Schema)
30
+ return;
31
+ const originalSave = mongoose.Document.prototype.save;
32
+ if (!originalSave)
33
+ return;
34
+ const self = this;
35
+ mongoose.Document.prototype.save = async function (...args) {
36
+ const isNew = this.isNew;
37
+ const operation = isNew ? 'insert' : 'update';
38
+ const entity = this.constructor?.modelName || 'Unknown';
39
+ const result = await originalSave.apply(this, args);
40
+ self.telescope.record({
41
+ type: entry_type_enum_1.EntryType.MODEL,
42
+ content: {
43
+ action: operation,
44
+ entity,
45
+ primaryKey: this._id?.toString() || null,
46
+ data: isNew ? this.toObject() : { modified: true },
47
+ },
48
+ }).catch(() => { });
49
+ return result;
50
+ };
51
+ const originalRemove = mongoose.Document.prototype.deleteOne;
52
+ if (originalRemove) {
53
+ mongoose.Document.prototype.deleteOne = async function (...args) {
54
+ const entity = this.constructor?.modelName || 'Unknown';
55
+ const result = await originalRemove.apply(this, args);
56
+ self.telescope.record({
57
+ type: entry_type_enum_1.EntryType.MODEL,
58
+ content: {
59
+ action: 'remove',
60
+ entity,
61
+ primaryKey: this._id?.toString() || null,
62
+ },
63
+ }).catch(() => { });
64
+ return result;
65
+ };
66
+ }
67
+ }
68
+ catch { }
41
69
  }
42
70
  };
43
71
  exports.ModelWatcher = ModelWatcher;
@@ -1,13 +1,9 @@
1
1
  import { OnModuleInit } from '@nestjs/common';
2
2
  import { TelescopeService } from '../telescope.service';
3
- /**
4
- * Placeholder watcher for notification events.
5
- * In a full implementation you would hook into NestJS's EventEmitter2 or any
6
- * custom notification system and log the payload.
7
- */
8
3
  export declare class NotificationWatcher implements OnModuleInit {
9
4
  private readonly telescope;
10
5
  private readonly logger;
11
6
  constructor(telescope: TelescopeService);
12
7
  onModuleInit(): void;
8
+ private tryHookEventEmitter;
13
9
  }
@@ -11,15 +11,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var NotificationWatcher_1;
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.NotificationWatcher = void 0;
14
- // src/watchers/notification.watcher.ts
15
14
  const common_1 = require("@nestjs/common");
16
15
  const telescope_service_1 = require("../telescope.service");
17
16
  const entry_type_enum_1 = require("../enums/entry-type.enum");
18
- /**
19
- * Placeholder watcher for notification events.
20
- * In a full implementation you would hook into NestJS's EventEmitter2 or any
21
- * custom notification system and log the payload.
22
- */
23
17
  let NotificationWatcher = NotificationWatcher_1 = class NotificationWatcher {
24
18
  telescope;
25
19
  logger = new common_1.Logger(NotificationWatcher_1.name);
@@ -27,18 +21,41 @@ let NotificationWatcher = NotificationWatcher_1 = class NotificationWatcher {
27
21
  this.telescope = telescope;
28
22
  }
29
23
  onModuleInit() {
30
- // Record a dummy notification on module init.
31
- this.telescope
32
- .record({
33
- type: entry_type_enum_1.EntryType.NOTIFICATION,
34
- content: {
35
- notification: 'module:init',
36
- payload: {},
37
- timestamp: new Date().toISOString(),
38
- },
39
- })
40
- .catch(() => { });
41
- this.logger.debug('NotificationWatcher initialized dummy entry recorded');
24
+ this.tryHookEventEmitter();
25
+ }
26
+ tryHookEventEmitter() {
27
+ try {
28
+ const { EventEmitter2 } = require('@nestjs/event-emitter');
29
+ if (!EventEmitter2)
30
+ return;
31
+ const originalEmit = EventEmitter2.prototype.emit;
32
+ const self = this;
33
+ EventEmitter2.prototype.emit = function (event, ...args) {
34
+ const eventName = Array.isArray(event) ? event.join('.') : event;
35
+ if (eventName.startsWith('telescope.') || eventName === 'notifications.send') {
36
+ return originalEmit.apply(this, [event, ...args]);
37
+ }
38
+ const isNotification = eventName.includes('notification') ||
39
+ eventName.includes('mail') ||
40
+ eventName.includes('sms') ||
41
+ eventName.includes('push');
42
+ if (isNotification) {
43
+ const payload = args[0] || {};
44
+ self.telescope.record({
45
+ type: entry_type_enum_1.EntryType.NOTIFICATION,
46
+ content: {
47
+ notification: eventName,
48
+ channel: payload.channel || eventName.split('.')[0],
49
+ recipient: payload.to || payload.recipient || null,
50
+ payload,
51
+ timestamp: new Date().toISOString(),
52
+ },
53
+ }).catch(() => { });
54
+ }
55
+ return originalEmit.apply(this, [event, ...args]);
56
+ };
57
+ }
58
+ catch { }
42
59
  }
43
60
  };
44
61
  exports.NotificationWatcher = NotificationWatcher;
@@ -5,10 +5,9 @@ export declare class ScheduleWatcher implements OnApplicationBootstrap {
5
5
  private schedulerRegistry;
6
6
  constructor(telescope: TelescopeService);
7
7
  onApplicationBootstrap(): void;
8
- /**
9
- * Set the scheduler registry reference. Called from the module setup
10
- * when @nestjs/schedule is available.
11
- */
12
8
  setSchedulerRegistry(registry: any): void;
9
+ private tryAutoDetectCronJobs;
10
+ private hookCronJobsMap;
13
11
  private hookIntoCronJobs;
12
+ private hookCronJob;
14
13
  }
@@ -10,7 +10,6 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ScheduleWatcher = void 0;
13
- // src/watchers/schedule.watcher.ts
14
13
  const common_1 = require("@nestjs/common");
15
14
  const telescope_service_1 = require("../telescope.service");
16
15
  const entry_type_enum_1 = require("../enums/entry-type.enum");
@@ -21,82 +20,102 @@ let ScheduleWatcher = class ScheduleWatcher {
21
20
  this.telescope = telescope;
22
21
  }
23
22
  onApplicationBootstrap() {
24
- // Dynamically resolve SchedulerRegistry to avoid hard dependency
25
23
  try {
26
24
  const schedule = require('@nestjs/schedule');
27
- // SchedulerRegistry is a provider, we can't inject it here
28
- // This watcher works when setSchedulerRegistry() is called from the module
29
- }
30
- catch {
31
- // @nestjs/schedule not installed
32
25
  }
26
+ catch { }
33
27
  if (this.schedulerRegistry) {
34
28
  this.hookIntoCronJobs();
35
29
  }
30
+ else {
31
+ this.tryAutoDetectCronJobs();
32
+ }
36
33
  }
37
- /**
38
- * Set the scheduler registry reference. Called from the module setup
39
- * when @nestjs/schedule is available.
40
- */
41
34
  setSchedulerRegistry(registry) {
42
35
  this.schedulerRegistry = registry;
43
36
  this.hookIntoCronJobs();
44
37
  }
38
+ tryAutoDetectCronJobs() {
39
+ try {
40
+ const { SchedulerRegistry } = require('@nestjs/schedule');
41
+ if (!SchedulerRegistry)
42
+ return;
43
+ const self = this;
44
+ const originalGetCronJobs = SchedulerRegistry.prototype.getCronJobs;
45
+ if (originalGetCronJobs) {
46
+ SchedulerRegistry.prototype.getCronJobs = function () {
47
+ const jobs = originalGetCronJobs.call(this);
48
+ self.hookCronJobsMap(jobs);
49
+ return jobs;
50
+ };
51
+ }
52
+ }
53
+ catch { }
54
+ }
55
+ hookCronJobsMap(cronJobs) {
56
+ if (!cronJobs || typeof cronJobs.forEach !== 'function')
57
+ return;
58
+ cronJobs.forEach((job, name) => {
59
+ this.hookCronJob(job, name);
60
+ });
61
+ }
45
62
  hookIntoCronJobs() {
46
63
  if (!this.schedulerRegistry)
47
64
  return;
48
65
  try {
49
66
  const cronJobs = this.schedulerRegistry.getCronJobs();
50
- cronJobs.forEach((job, name) => {
51
- const originalCallbacks = job.callbacks || [];
52
- if (originalCallbacks.length > 0) {
53
- job.callbacks = originalCallbacks.map((cb) => {
54
- return async (...args) => {
55
- const start = Date.now();
56
- this.telescope.record({
57
- type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
58
- content: {
59
- name,
60
- status: 'running',
61
- cronTime: job.cronTime?.toString(),
62
- },
63
- });
64
- try {
65
- const res = await cb(...args);
66
- const duration = Date.now() - start;
67
- this.telescope.record({
68
- type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
69
- content: {
70
- name,
71
- status: 'completed',
72
- duration,
73
- cronTime: job.cronTime?.toString(),
74
- },
75
- });
76
- return res;
77
- }
78
- catch (err) {
79
- const duration = Date.now() - start;
80
- this.telescope.record({
81
- type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
82
- content: {
83
- name,
84
- status: 'failed',
85
- duration,
86
- error: err.message || String(err),
87
- cronTime: job.cronTime?.toString(),
88
- },
89
- });
90
- throw err;
91
- }
92
- };
93
- });
94
- }
95
- });
96
- }
97
- catch {
98
- // Ignore if schedule registry lookup fails
67
+ this.hookCronJobsMap(cronJobs);
99
68
  }
69
+ catch { }
70
+ }
71
+ hookCronJob(job, name) {
72
+ if (!job || job.__telescopeHooked)
73
+ return;
74
+ job.__telescopeHooked = true;
75
+ const originalCallback = job.callback;
76
+ if (!originalCallback)
77
+ return;
78
+ const self = this;
79
+ job.callback = async (...args) => {
80
+ const start = Date.now();
81
+ self.telescope.record({
82
+ type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
83
+ content: {
84
+ name,
85
+ status: 'running',
86
+ cronTime: job.cronTime?.toString(),
87
+ startedAt: new Date().toISOString(),
88
+ },
89
+ });
90
+ try {
91
+ const res = await originalCallback(...args);
92
+ const duration = Date.now() - start;
93
+ self.telescope.record({
94
+ type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
95
+ content: {
96
+ name,
97
+ status: 'completed',
98
+ duration,
99
+ cronTime: job.cronTime?.toString(),
100
+ },
101
+ });
102
+ return res;
103
+ }
104
+ catch (err) {
105
+ const duration = Date.now() - start;
106
+ self.telescope.record({
107
+ type: entry_type_enum_1.EntryType.SCHEDULED_TASK,
108
+ content: {
109
+ name,
110
+ status: 'failed',
111
+ duration,
112
+ error: err.message || String(err),
113
+ cronTime: job.cronTime?.toString(),
114
+ },
115
+ });
116
+ throw err;
117
+ }
118
+ };
100
119
  }
101
120
  };
102
121
  exports.ScheduleWatcher = ScheduleWatcher;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@ngn-net/nestjs-telescope",
3
- "version": "0.2.14",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "Fullfeatured monitoring package for NestJS (inspired by Laravel Telescope)",
7
+ "description": "Full-featured monitoring package for NestJS (inspired by Laravel Telescope) — uses MongoDB for zero-impact storage",
8
8
  "author": "Generated by Antigravity",
9
9
  "license": "MIT",
10
10
  "main": "dist/index.js",
@@ -21,9 +21,11 @@
21
21
  "prepare": "npm run build"
22
22
  },
23
23
  "dependencies": {
24
+ "@nestjs/mongoose": "^11",
24
25
  "@nestjs/serve-static": "^5.0.5",
25
26
  "class-transformer": "^0.5",
26
27
  "class-validator": "^0.14",
28
+ "mongoose": "^8",
27
29
  "passport-jwt": "^4",
28
30
  "reflect-metadata": "^0.2"
29
31
  },
package/ui/index.html CHANGED
@@ -1644,7 +1644,34 @@
1644
1644
  {content.user && (
1645
1645
  <div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
1646
1646
  <div className="key-val-label" style={{ color: '#10b981', fontWeight: '600' }}>Authenticated User</div>
1647
- <pre className="json-code" style={{ borderColor: 'rgba(16, 185, 129, 0.3)', color: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.03)' }}>{JSON.stringify(content.user, null, 2)}</pre>
1647
+ <div className="json-code" style={{ borderColor: 'rgba(16, 185, 129, 0.3)', color: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.03)', padding: '12px', overflow: 'hidden' }}>
1648
+ <table style={{ width: '100%', borderCollapse: 'collapse', margin: 0 }}>
1649
+ <tbody>
1650
+ {Object.entries(content.user).map(([key, value]) => {
1651
+ let displayKey = key;
1652
+ if (key === 'id' || key === 'sub' || key === 'userId') displayKey = 'ID';
1653
+ else if (key === 'email') displayKey = 'Email Address';
1654
+ else displayKey = key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1').trim();
1655
+
1656
+ let displayValue = String(value);
1657
+ if (typeof value === 'object' && value !== null) {
1658
+ displayValue = JSON.stringify(value);
1659
+ }
1660
+
1661
+ return (
1662
+ <tr key={key} style={{ borderBottom: '1px solid rgba(16, 185, 129, 0.1)', backgroundColor: 'transparent' }}>
1663
+ <td style={{ padding: '8px', width: '200px', fontWeight: '600', color: '#10b981', borderBottom: 'none', textTransform: 'none', letterSpacing: 'normal', backgroundColor: 'transparent' }}>
1664
+ {displayKey}
1665
+ </td>
1666
+ <td style={{ padding: '8px', color: '#e2e8f0', borderBottom: 'none' }}>
1667
+ {displayValue}
1668
+ </td>
1669
+ </tr>
1670
+ );
1671
+ })}
1672
+ </tbody>
1673
+ </table>
1674
+ </div>
1648
1675
  </div>
1649
1676
  )}
1650
1677
  </React.Fragment>