@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.
package/README.md CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  ![NestJS Telescope Banner](assets/hero.png)
4
4
 
5
- A full-featured developer assistant and application monitoring suite for NestJS, inspired by Laravel Telescope. It monitors incoming HTTP requests, database queries, cache operations, background queue jobs, application events, outgoing mail, logs, unhandled exceptions, cron job schedules, and Redis commands.
5
+ A full-featured developer assistant and application monitoring suite for NestJS, inspired by Laravel Telescope. It monitors incoming HTTP requests, database queries, cache operations, background queue jobs, application events, outgoing mail, logs, unhandled exceptions, cron job schedules, Redis commands, CLI commands, ORM model lifecycle events, notifications, and authorization gate checks.
6
6
 
7
- All telemetry records are persisted to a database table and accessible via a modern, high-fidelity dark-themed single-page application dashboard.
7
+ All telemetry records are persisted to **MongoDB** (via Mongoose) for zero-impact storage that never touches your production database.
8
8
 
9
9
  ---
10
10
 
11
11
  ## Features
12
12
 
13
13
  - **HTTP Request Watcher**: Captures request methods, URLs, IP addresses, payloads, headers, durations, and response status/bodies.
14
- - **Database Query Watcher**: Hooks into TypeORM entity cycles (`INSERT`, `UPDATE`, `REMOVE`) to record query queries, parameters, and affected data.
14
+ - **HTTP Client Watcher**: Patches both Node.js `http`/`https` modules and **Axios** to capture all outbound HTTP calls with full request/response data and timing.
15
+ - **Database Query Watcher**: Hooks into TypeORM `DataSource.createQueryRunner` to record all SQL queries, parameters, durations, and success/failure status.
15
16
  - **Cache Watcher**: Wraps Cache Manager operations (`GET`, `SET`, `DEL`) to trace cache activity.
16
17
  - **Queue Watcher**: Tracks BullMQ background jobs and captures completion values and job failures.
17
18
  - **Event Watcher**: Intercepts `EventEmitter2` events and lists firing metrics.
@@ -20,6 +21,41 @@ All telemetry records are persisted to a database table and accessible via a mod
20
21
  - **Exception Watcher**: Hooks into HTTP streams to catch and log request failures, statuses, and stack traces.
21
22
  - **Schedule Watcher**: Monitors cron execution runtimes and statuses from `@nestjs/schedule`.
22
23
  - **Redis Watcher**: Hooks into `ioredis` to report Redis command durations and arguments.
24
+ - **Gate (Authorization) Watcher**: Intercepts HTTP requests to record authorization decisions — successful auth checks and failed auth/forbidden errors with user context and requested roles.
25
+ - **Command Watcher**: Records CLI command executions (startup commands like `start`, `build`, `test`, etc.) with arguments and PIDs.
26
+ - **Model Watcher**: Subscribes to **both TypeORM** entity lifecycle events (`INSERT`, `UPDATE`, `REMOVE`) and **Mongoose** document lifecycle (`save`, `deleteOne`) to record all ORM/ODM operations.
27
+ - **Notification Watcher**: Intercepts `EventEmitter2` events matching notification patterns (notification, mail, sms, push) and records them with channel and recipient details.
28
+
29
+ ---
30
+
31
+ ## Storage: MongoDB (Zero Production Impact)
32
+
33
+ Telescope stores its telemetry data in a **dedicated MongoDB collection** (`telescope_entries`), completely separate from your application's production database.
34
+
35
+ **Why MongoDB?**
36
+ - No schema migrations or entity registration in your app
37
+ - No foreign key constraints or connection pool contention
38
+ - Automatic TTL-based cleanup
39
+ - High write throughput for telemetry workloads
40
+ - Zero impact on your production database performance
41
+
42
+ ### MongoDB Connection
43
+
44
+ Telescope connects to MongoDB using the `MONGO_URI` environment variable, or falls back to `mongodb://localhost:27017/telescope`.
45
+
46
+ ```bash
47
+ # .env
48
+ MONGO_URI=mongodb://localhost:27017/telescope
49
+ ```
50
+
51
+ Or pass the URI directly in configuration:
52
+
53
+ ```typescript
54
+ TelescopeModule.forRoot({
55
+ mongoUri: 'mongodb://localhost:27017/telescope',
56
+ // ... other options
57
+ })
58
+ ```
23
59
 
24
60
  ---
25
61
 
@@ -28,10 +64,32 @@ All telemetry records are persisted to a database table and accessible via a mod
28
64
  Install the package:
29
65
 
30
66
  ```bash
31
- npm install @ngn-net/nestjs-telescope @nestjs/typeorm typeorm @nestjs/jwt @nestjs/passport passport-jwt
67
+ npm install @ngn-net/nestjs-telescope @nestjs/mongoose mongoose @nestjs/jwt @nestjs/passport passport-jwt
32
68
  ```
33
69
 
34
- Ensure you have TypeORM initialized in your NestJS application.
70
+ Ensure you have `MongooseModule` initialized in your NestJS application.
71
+
72
+ ---
73
+
74
+ ## Quick Start
75
+
76
+ ```typescript
77
+ import { Module } from '@nestjs/common';
78
+ import { MongooseModule } from '@nestjs/mongoose';
79
+ import { TelescopeModule } from '@ngn-net/nestjs-telescope';
80
+
81
+ @Module({
82
+ imports: [
83
+ MongooseModule.forRoot(process.env.MONGO_URI || 'mongodb://localhost:27017/telescope'),
84
+ TelescopeModule.forRoot({
85
+ path: 'telescope',
86
+ jwtSecret: 'your-secret-key',
87
+ password: 'your-password',
88
+ }),
89
+ ],
90
+ })
91
+ export class AppModule {}
92
+ ```
35
93
 
36
94
  ---
37
95
 
@@ -39,7 +97,7 @@ Ensure you have TypeORM initialized in your NestJS application.
39
97
 
40
98
  All watchers are dynamically activated. If your application does not use certain modules, Telescope will safely boot without them. Simply install the peer dependencies for the watchers you want to enable:
41
99
 
42
- | Watcher | Required Dependency | Enable Entry Type |
100
+ | Watcher | Required Dependency | Entry Type |
43
101
  |---|---|---|
44
102
  | **Cache Watcher** | `@nestjs/cache-manager` & `cache-manager` | `EntryType.CACHE` |
45
103
  | **Queue Watcher** | `@nestjs/bullmq` & `bullmq` | `EntryType.JOB` |
@@ -48,6 +106,20 @@ All watchers are dynamically activated. If your application does not use certain
48
106
  | **Mail Watcher** | `nodemailer` & `@types/nodemailer` | `EntryType.MAIL` |
49
107
  | **Redis Watcher** | `ioredis` | `EntryType.REDIS` |
50
108
 
109
+ **Built-in watchers** (always available, no extra dependencies):
110
+
111
+ | Watcher | Entry Type | What It Captures |
112
+ |---|---|---|
113
+ | **HTTP Request** | `EntryType.REQUEST` | Incoming HTTP requests and responses |
114
+ | **HTTP Client** | `EntryType.HTTP_CLIENT` | Outbound HTTP calls (Node http/https + Axios) |
115
+ | **Query** | `EntryType.QUERY` | TypeORM SQL queries |
116
+ | **Log** | `EntryType.LOG` | Console log/warn/error/debug output |
117
+ | **Exception** | `EntryType.EXCEPTION` | Unhandled HTTP exceptions |
118
+ | **Gate** | `EntryType.GATE` | Authorization checks (success + failure) |
119
+ | **Command** | `EntryType.COMMAND` | CLI command executions |
120
+ | **Model** | `EntryType.MODEL` | TypeORM + Mongoose entity lifecycle events |
121
+ | **Notification** | `EntryType.NOTIFICATION` | Notification events via EventEmitter2 |
122
+
51
123
  ---
52
124
 
53
125
  ## Configuration Reference
@@ -59,36 +131,51 @@ You can pass configuration options into `TelescopeModule.forRoot(options)`:
59
131
  | `path` | `string` | `'telescope'` | Base path for the UI and API. |
60
132
  | `jwtSecret` | `string` | `'telescope-change-me'` | JWT secret used to sign UI authentication tokens. |
61
133
  | `password` | `string` | `'password'` | Password for dashboard login (can also be set via `TELESCOPE_PASSWORD` env). |
62
- | `maxEntries` | `number` | `1000` | Maximum number of entries to retain in the database before pruning. |
134
+ | `maxEntries` | `number` | `1000` | Maximum number of entries to retain before pruning. |
63
135
  | `enabledEntryTypes` | `EntryType[]` | `all` | Specific entry types to record. If undefined, all types are recorded. |
64
- | `ignorePaths` | `string[]` | `[]` | Specific request path prefixes to ignore (e.g. `['/health', '/metrics']`). |
65
- | `ignoreCommands` | `string[]` | `[]` | Specific Redis command names to ignore (e.g. `['ping', 'info']`). |
136
+ | `ignorePaths` | `string[]` | `[]` | Request path prefixes to ignore (e.g. `['/health', '/metrics']`). |
137
+ | `ignoreCommands` | `string[]` | `[]` | Redis command names to ignore (e.g. `['ping', 'info']`). |
66
138
  | `uiMiddleware` | `any[]` | `[]` | Custom middleware to apply to Telescope routes. |
139
+ | `enableDashboard` | `boolean` | `true` | Enable/disable the dashboard UI. |
140
+ | `mongoUri` | `string` | `'mongodb://localhost:27017/telescope'` | MongoDB connection URI. |
67
141
 
68
142
  ---
69
143
 
70
144
  ## Usage
71
145
 
72
- Import the `TelescopeModule` in your root `AppModule`:
146
+ ### Synchronous Configuration
73
147
 
74
148
  ```typescript
75
149
  import { Module } from '@nestjs/common';
76
- import { TypeOrmModule } from '@nestjs/typeorm';
150
+ import { MongooseModule } from '@nestjs/mongoose';
77
151
  import { TelescopeModule, EntryType } from '@ngn-net/nestjs-telescope';
78
152
 
79
153
  @Module({
80
154
  imports: [
81
- TypeOrmModule.forRoot({
82
- type: 'sqlite', // or postgres, mysql, etc.
83
- database: 'app.db',
84
- autoLoadEntities: true,
85
- synchronize: true, // Telescope automatically registers its own entity
86
- }),
155
+ MongooseModule.forRoot(process.env.MONGO_URI || 'mongodb://localhost:27017/myapp'),
87
156
  TelescopeModule.forRoot({
88
- path: 'telescope', // Access at /telescope
157
+ path: 'telescope',
89
158
  password: 'my-secure-password',
159
+ jwtSecret: 'my-jwt-secret',
90
160
  maxEntries: 500,
91
161
  ignorePaths: ['/health-check'],
162
+ enabledEntryTypes: [
163
+ EntryType.REQUEST,
164
+ EntryType.HTTP_CLIENT,
165
+ EntryType.QUERY,
166
+ EntryType.CACHE,
167
+ EntryType.JOB,
168
+ EntryType.EVENT,
169
+ EntryType.LOG,
170
+ EntryType.EXCEPTION,
171
+ EntryType.SCHEDULED_TASK,
172
+ EntryType.REDIS,
173
+ EntryType.MAIL,
174
+ EntryType.GATE,
175
+ EntryType.COMMAND,
176
+ EntryType.MODEL,
177
+ EntryType.NOTIFICATION,
178
+ ],
92
179
  }),
93
180
  ],
94
181
  })
@@ -97,8 +184,6 @@ export class AppModule {}
97
184
 
98
185
  ### Async Configuration
99
186
 
100
- You can also register `TelescopeModule` asynchronously using a factory (e.g. inject `ConfigService`):
101
-
102
187
  ```typescript
103
188
  import { Module } from '@nestjs/common';
104
189
  import { ConfigModule, ConfigService } from '@nestjs/config';
@@ -133,6 +218,47 @@ export class AppModule {}
133
218
 
134
219
  ---
135
220
 
221
+ ## Entry Types
222
+
223
+ All available entry types for filtering and configuration:
224
+
225
+ ```typescript
226
+ import { EntryType } from '@ngn-net/nestjs-telescope';
227
+
228
+ EntryType.REQUEST // Incoming HTTP requests
229
+ EntryType.HTTP_CLIENT // Outbound HTTP calls (http/https/Axios)
230
+ EntryType.QUERY // Database queries (TypeORM)
231
+ EntryType.CACHE // Cache operations
232
+ EntryType.JOB // Background queue jobs (BullMQ)
233
+ EntryType.EVENT // Application events (EventEmitter2)
234
+ EntryType.MAIL // Outgoing emails
235
+ EntryType.LOG // Console log output
236
+ EntryType.EXCEPTION // Unhandled exceptions
237
+ EntryType.SCHEDULED_TASK // Cron/scheduled task executions
238
+ EntryType.REDIS // Redis commands
239
+ EntryType.GATE // Authorization gate checks
240
+ EntryType.COMMAND // CLI command executions
241
+ EntryType.MODEL // ORM model lifecycle events
242
+ EntryType.NOTIFICATION // Notification events
243
+ EntryType.DUMP // Debug dump output
244
+ ```
245
+
246
+ ---
247
+
248
+ ## API Endpoints
249
+
250
+ All endpoints are prefixed with your configured `path` (default: `/telescope`).
251
+
252
+ | Method | Endpoint | Description |
253
+ |---|---|---|
254
+ | `POST` | `/api/login` | Authenticate with password, returns JWT |
255
+ | `GET` | `/api/stats` | Get entry counts by type |
256
+ | `GET` | `/api/entries` | Query entries with filters (`type`, `search`, `batchId`, `page`, `perPage`) |
257
+ | `GET` | `/api/entries/:uuid` | Get a single entry by UUID |
258
+ | `DELETE` | `/api/entries` | Clear all entries |
259
+
260
+ ---
261
+
136
262
  ## Development & Test
137
263
 
138
264
  To build the package:
@@ -145,3 +271,25 @@ To run Jest tests:
145
271
  npm run test
146
272
  ```
147
273
 
274
+ ---
275
+
276
+ ## Migration from v0.2.x
277
+
278
+ If upgrading from v0.2.x (which used TypeORM/SQL):
279
+
280
+ 1. Install the new dependencies:
281
+ ```bash
282
+ npm install @ngn-net/nestjs-telescope @nestjs/mongoose mongoose
283
+ ```
284
+
285
+ 2. Add `MongooseModule.forRoot()` to your root module (if not already present).
286
+
287
+ 3. Remove the old TypeORM-based telescope entity — Telescope now manages its own MongoDB collection automatically.
288
+
289
+ 4. The old `telescope_entries` SQL table can be dropped after migration.
290
+
291
+ ---
292
+
293
+ ## License
294
+
295
+ MIT
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // src/index.ts
18
17
  __exportStar(require("./telescope.module"), exports);
19
18
  __exportStar(require("./telescope.service"), exports);
20
19
  __exportStar(require("./storage/telescope-repository.service"), exports);
@@ -25,7 +24,6 @@ __exportStar(require("./interfaces/entry.interface"), exports);
25
24
  __exportStar(require("./guards/telescope-jwt.guard"), exports);
26
25
  __exportStar(require("./constants"), exports);
27
26
  __exportStar(require("./dashboard.provider"), exports);
28
- // Export all watchers for custom usage/extension
29
27
  __exportStar(require("./watchers/command.watcher"), exports);
30
28
  __exportStar(require("./watchers/model.watcher"), exports);
31
29
  __exportStar(require("./watchers/notification.watcher"), exports);
@@ -1,6 +1,8 @@
1
+ import { Document, HydratedDocument } from 'mongoose';
1
2
  import { EntryType } from '../../enums/entry-type.enum';
3
+ export type TelescopeEntryDocument = HydratedDocument<TelescopeEntry>;
2
4
  export declare class TelescopeEntry {
3
- id: number;
5
+ _id?: any;
4
6
  uuid: string;
5
7
  batchId?: string;
6
8
  type: EntryType;
@@ -8,3 +10,12 @@ export declare class TelescopeEntry {
8
10
  recordedAt: Date;
9
11
  familyHash?: string;
10
12
  }
13
+ export declare const TelescopeEntrySchema: import("mongoose").Schema<TelescopeEntry, import("mongoose").Model<TelescopeEntry, any, any, any, Document<unknown, any, TelescopeEntry, any, {}> & TelescopeEntry & {
14
+ _id: import("mongoose").Types.ObjectId;
15
+ } & {
16
+ __v: number;
17
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, TelescopeEntry, Document<unknown, {}, import("mongoose").FlatRecord<TelescopeEntry>, {}, import("mongoose").DefaultSchemaOptions> & import("mongoose").FlatRecord<TelescopeEntry> & {
18
+ _id: import("mongoose").Types.ObjectId;
19
+ } & {
20
+ __v: number;
21
+ }>;
@@ -9,36 +9,11 @@ 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
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.TelescopeEntry = void 0;
13
- // src/storage/entities/telescope-entry.entity.ts
14
- const typeorm_1 = require("typeorm");
12
+ exports.TelescopeEntrySchema = exports.TelescopeEntry = void 0;
13
+ const mongoose_1 = require("@nestjs/mongoose");
15
14
  const entry_type_enum_1 = require("../../enums/entry-type.enum");
16
- /**
17
- * Custom ValueTransformer that serializes objects to JSON strings and back.
18
- * This allows the `content` column to work on all databases (SQLite, MySQL, PostgreSQL)
19
- * by storing JSON as plain text instead of requiring a `jsonb` column type.
20
- */
21
- const JsonTransformer = {
22
- to(value) {
23
- if (value === null || value === undefined)
24
- return '{}';
25
- return typeof value === 'string' ? value : JSON.stringify(value);
26
- },
27
- from(value) {
28
- if (value === null || value === undefined)
29
- return {};
30
- if (typeof value === 'object')
31
- return value; // Already parsed (some drivers do this)
32
- try {
33
- return JSON.parse(value);
34
- }
35
- catch {
36
- return {};
37
- }
38
- },
39
- };
40
15
  let TelescopeEntry = class TelescopeEntry {
41
- id;
16
+ _id;
42
17
  uuid;
43
18
  batchId;
44
19
  type;
@@ -48,36 +23,30 @@ let TelescopeEntry = class TelescopeEntry {
48
23
  };
49
24
  exports.TelescopeEntry = TelescopeEntry;
50
25
  __decorate([
51
- (0, typeorm_1.PrimaryGeneratedColumn)(),
52
- __metadata("design:type", Number)
53
- ], TelescopeEntry.prototype, "id", void 0);
54
- __decorate([
55
- (0, typeorm_1.Column)({ type: 'varchar', length: 64 }),
56
- (0, typeorm_1.Index)({ unique: true }),
26
+ (0, mongoose_1.Prop)({ type: String, required: true, unique: true, index: true }),
57
27
  __metadata("design:type", String)
58
28
  ], TelescopeEntry.prototype, "uuid", void 0);
59
29
  __decorate([
60
- (0, typeorm_1.Column)({ type: 'varchar', length: 64, nullable: true }),
30
+ (0, mongoose_1.Prop)({ type: String, index: true, default: null }),
61
31
  __metadata("design:type", String)
62
32
  ], TelescopeEntry.prototype, "batchId", void 0);
63
33
  __decorate([
64
- (0, typeorm_1.Column)({ type: 'varchar', length: 50 }),
34
+ (0, mongoose_1.Prop)({ type: String, required: true, index: true }),
65
35
  __metadata("design:type", String)
66
36
  ], TelescopeEntry.prototype, "type", void 0);
67
37
  __decorate([
68
- (0, typeorm_1.Column)({ type: 'text', transformer: JsonTransformer }),
38
+ (0, mongoose_1.Prop)({ type: Object, default: {} }),
69
39
  __metadata("design:type", Object)
70
40
  ], TelescopeEntry.prototype, "content", void 0);
71
41
  __decorate([
72
- (0, typeorm_1.CreateDateColumn)(),
42
+ (0, mongoose_1.Prop)({ type: Date, default: () => new Date(), index: true }),
73
43
  __metadata("design:type", Date)
74
44
  ], TelescopeEntry.prototype, "recordedAt", void 0);
75
45
  __decorate([
76
- (0, typeorm_1.Column)({ type: 'varchar', length: 64, nullable: true }),
46
+ (0, mongoose_1.Prop)({ type: String, default: null }),
77
47
  __metadata("design:type", String)
78
48
  ], TelescopeEntry.prototype, "familyHash", void 0);
79
49
  exports.TelescopeEntry = TelescopeEntry = __decorate([
80
- (0, typeorm_1.Entity)({ name: 'telescope_entries' }),
81
- (0, typeorm_1.Index)(['type']),
82
- (0, typeorm_1.Index)(['recordedAt'])
50
+ (0, mongoose_1.Schema)({ collection: 'telescope_entries', timestamps: false })
83
51
  ], TelescopeEntry);
52
+ exports.TelescopeEntrySchema = mongoose_1.SchemaFactory.createForClass(TelescopeEntry);
@@ -1,5 +1,5 @@
1
- import { Repository } from 'typeorm';
2
- import { TelescopeEntry } from './entities/telescope-entry.entity';
1
+ import { Model } from 'mongoose';
2
+ import { TelescopeEntry, TelescopeEntryDocument } from './entities/telescope-entry.entity';
3
3
  import { EntryType } from '../enums/entry-type.enum';
4
4
  export interface QueryFilters {
5
5
  type?: string;
@@ -16,14 +16,14 @@ export interface PaginatedResult {
16
16
  totalPages: number;
17
17
  }
18
18
  export declare class TelescopeRepository {
19
- private readonly entryRepo;
20
- constructor(entryRepo: Repository<TelescopeEntry>);
19
+ private readonly entryModel;
20
+ constructor(entryModel: Model<TelescopeEntryDocument>);
21
21
  save(entry: Partial<TelescopeEntry>): Promise<TelescopeEntry>;
22
22
  findAll(limit?: number): Promise<TelescopeEntry[]>;
23
23
  findByType(type: EntryType, limit?: number): Promise<TelescopeEntry[]>;
24
24
  prune(maxEntries: number): Promise<void>;
25
25
  queryEntries(filters: QueryFilters): Promise<PaginatedResult>;
26
26
  findByUuid(uuid: string): Promise<TelescopeEntry | null>;
27
- clearAll(): Promise<import("typeorm").DeleteResult>;
27
+ clearAll(): Promise<import("mongodb").DeleteResult>;
28
28
  countByType(): Promise<Record<string, number>>;
29
29
  }
@@ -13,62 +13,61 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.TelescopeRepository = void 0;
16
- // src/storage/telescope-repository.service.ts
17
16
  const common_1 = require("@nestjs/common");
18
- const typeorm_1 = require("@nestjs/typeorm");
19
- const typeorm_2 = require("typeorm");
17
+ const mongoose_1 = require("@nestjs/mongoose");
18
+ const mongoose_2 = require("mongoose");
20
19
  const telescope_entry_entity_1 = require("./entities/telescope-entry.entity");
21
20
  let TelescopeRepository = class TelescopeRepository {
22
- entryRepo;
23
- constructor(entryRepo) {
24
- this.entryRepo = entryRepo;
21
+ entryModel;
22
+ constructor(entryModel) {
23
+ this.entryModel = entryModel;
25
24
  }
26
25
  async save(entry) {
27
- const ent = this.entryRepo.create(entry);
28
- return await this.entryRepo.save(ent);
26
+ const doc = new this.entryModel(entry);
27
+ return await doc.save();
29
28
  }
30
29
  async findAll(limit = 100) {
31
- return await this.entryRepo.find({ order: { recordedAt: 'DESC' }, take: limit });
30
+ return await this.entryModel.find().sort({ recordedAt: -1 }).limit(limit).lean();
32
31
  }
33
32
  async findByType(type, limit = 100) {
34
- return await this.entryRepo.find({ where: { type }, order: { recordedAt: 'DESC' }, take: limit });
33
+ return await this.entryModel.find({ type }).sort({ recordedAt: -1 }).limit(limit).lean();
35
34
  }
36
35
  async prune(maxEntries) {
37
- const count = await this.entryRepo.count();
36
+ const count = await this.entryModel.countDocuments();
38
37
  if (count <= maxEntries)
39
38
  return;
40
39
  const excess = count - maxEntries;
41
- // Use a single query to find and delete the oldest entries
42
- const oldest = await this.entryRepo.find({
43
- order: { recordedAt: 'ASC' },
44
- take: excess,
45
- select: ['id'],
46
- });
40
+ const oldest = await this.entryModel.find().sort({ recordedAt: 1 }).limit(excess).select('_id').lean();
47
41
  if (oldest.length > 0) {
48
- const ids = oldest.map((e) => e.id);
49
- await this.entryRepo.delete(ids);
42
+ const ids = oldest.map((e) => e._id);
43
+ await this.entryModel.deleteMany({ _id: { $in: ids } });
50
44
  }
51
45
  }
52
46
  async queryEntries(filters) {
53
47
  const perPage = Math.min(filters.limit || 50, 200);
54
48
  const page = Math.max(filters.page || 1, 1);
55
49
  const skip = (page - 1) * perPage;
56
- const qb = this.entryRepo.createQueryBuilder('entry');
57
- qb.orderBy('entry.recordedAt', 'DESC');
50
+ const query = {};
58
51
  if (filters.type) {
59
- qb.andWhere('entry.type = :type', { type: filters.type });
52
+ query.type = filters.type;
60
53
  }
61
54
  if (filters.batchId) {
62
- qb.andWhere('entry.batchId = :batchId', { batchId: filters.batchId });
55
+ query.batchId = filters.batchId;
63
56
  }
64
- // For search, do in-memory filtering since LIKE on JSON text varies per DB
65
57
  if (filters.search) {
66
- const searchLower = `%${filters.search.toLowerCase()}%`;
67
- // Try database-level search first (works on most DBs)
68
- qb.andWhere('(LOWER(entry.uuid) LIKE :search OR LOWER(entry.content) LIKE :search)', { search: searchLower });
58
+ const searchRegex = { $regex: filters.search, $options: 'i' };
59
+ query.$or = [
60
+ { uuid: searchRegex },
61
+ { type: searchRegex },
62
+ ];
69
63
  }
70
- const total = await qb.getCount();
71
- const data = await qb.skip(skip).take(perPage).getMany();
64
+ const total = await this.entryModel.countDocuments(query);
65
+ const data = await this.entryModel
66
+ .find(query)
67
+ .sort({ recordedAt: -1 })
68
+ .skip(skip)
69
+ .limit(perPage)
70
+ .lean();
72
71
  return {
73
72
  data,
74
73
  total,
@@ -78,24 +77,18 @@ let TelescopeRepository = class TelescopeRepository {
78
77
  };
79
78
  }
80
79
  async findByUuid(uuid) {
81
- return await this.entryRepo.findOne({ where: { uuid } });
80
+ return await this.entryModel.findOne({ uuid }).lean();
82
81
  }
83
82
  async clearAll() {
84
- return await this.entryRepo
85
- .createQueryBuilder()
86
- .delete()
87
- .execute();
83
+ return await this.entryModel.deleteMany({});
88
84
  }
89
85
  async countByType() {
90
- const results = await this.entryRepo
91
- .createQueryBuilder('entry')
92
- .select('entry.type', 'type')
93
- .addSelect('COUNT(*)', 'count')
94
- .groupBy('entry.type')
95
- .getRawMany();
86
+ const results = await this.entryModel.aggregate([
87
+ { $group: { _id: '$type', count: { $sum: 1 } } },
88
+ ]);
96
89
  const counts = {};
97
90
  for (const row of results) {
98
- counts[row.type] = parseInt(row.count, 10);
91
+ counts[row._id] = row.count;
99
92
  }
100
93
  return counts;
101
94
  }
@@ -103,6 +96,6 @@ let TelescopeRepository = class TelescopeRepository {
103
96
  exports.TelescopeRepository = TelescopeRepository;
104
97
  exports.TelescopeRepository = TelescopeRepository = __decorate([
105
98
  (0, common_1.Injectable)(),
106
- __param(0, (0, typeorm_1.InjectRepository)(telescope_entry_entity_1.TelescopeEntry)),
107
- __metadata("design:paramtypes", [typeorm_2.Repository])
99
+ __param(0, (0, mongoose_1.InjectModel)(telescope_entry_entity_1.TelescopeEntry.name)),
100
+ __metadata("design:paramtypes", [mongoose_2.Model])
108
101
  ], TelescopeRepository);