@ngn-net/nestjs-telescope 0.1.5 → 0.1.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.
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 +69 -23
  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
package/README.md CHANGED
@@ -23,7 +23,7 @@ All telemetry records are persisted to a database table and accessible via a mod
23
23
 
24
24
  ## Installation
25
25
 
26
- Install the package alongside its peer dependencies:
26
+ Install the package:
27
27
 
28
28
  ```bash
29
29
  npm install @ngn-net/nestjs-telescope @nestjs/typeorm typeorm @nestjs/jwt @nestjs/passport passport-jwt
@@ -33,6 +33,38 @@ Ensure you have TypeORM initialized in your NestJS application.
33
33
 
34
34
  ---
35
35
 
36
+ ## Optional & Peer Dependencies
37
+
38
+ 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:
39
+
40
+ | Watcher | Required Dependency | Enable Entry Type |
41
+ |---|---|---|
42
+ | **Cache Watcher** | `@nestjs/cache-manager` & `cache-manager` | `EntryType.CACHE` |
43
+ | **Queue Watcher** | `@nestjs/bullmq` & `bullmq` | `EntryType.JOB` |
44
+ | **Event Watcher** | `@nestjs/event-emitter` | `EntryType.EVENT` |
45
+ | **Schedule Watcher** | `@nestjs/schedule` | `EntryType.SCHEDULED_TASK` |
46
+ | **Mail Watcher** | `nodemailer` & `@types/nodemailer` | `EntryType.MAIL` |
47
+ | **Redis Watcher** | `ioredis` | `EntryType.REDIS` |
48
+
49
+ ---
50
+
51
+ ## Configuration Reference
52
+
53
+ You can pass configuration options into `TelescopeModule.forRoot(options)`:
54
+
55
+ | Option | Type | Default | Description |
56
+ |---|---|---|---|
57
+ | `path` | `string` | `'telescope'` | Base path for the UI and API. |
58
+ | `jwtSecret` | `string` | `'telescope-change-me'` | JWT secret used to sign UI authentication tokens. |
59
+ | `password` | `string` | `'password'` | Password for dashboard login (can also be set via `TELESCOPE_PASSWORD` env). |
60
+ | `maxEntries` | `number` | `1000` | Maximum number of entries to retain in the database before pruning. |
61
+ | `enabledEntryTypes` | `EntryType[]` | `all` | Specific entry types to record. If undefined, all types are recorded. |
62
+ | `ignorePaths` | `string[]` | `[]` | Specific request path prefixes to ignore (e.g. `['/health', '/metrics']`). |
63
+ | `ignoreCommands` | `string[]` | `[]` | Specific Redis command names to ignore (e.g. `['ping', 'info']`). |
64
+ | `uiMiddleware` | `any[]` | `[]` | Custom middleware to apply to Telescope routes. |
65
+
66
+ ---
67
+
36
68
  ## Usage
37
69
 
38
70
  Import the `TelescopeModule` in your root `AppModule`:
@@ -45,54 +77,29 @@ import { TelescopeModule, EntryType } from '@ngn-net/nestjs-telescope';
45
77
  @Module({
46
78
  imports: [
47
79
  TypeOrmModule.forRoot({
48
- type: 'postgres', // or mysql, sqlite, etc.
49
- host: 'localhost',
50
- port: 5432,
51
- username: 'db_user',
52
- password: 'db_password',
53
- database: 'app_db',
80
+ type: 'sqlite', // or postgres, mysql, etc.
81
+ database: 'app.db',
54
82
  autoLoadEntities: true,
55
- synchronize: true, // Telescope creates telescope_entries table automatically
83
+ synchronize: true, // Telescope automatically registers its own entity
56
84
  }),
57
85
  TelescopeModule.forRoot({
58
86
  path: 'telescope', // Access at /telescope
59
- jwtSecret: 'your-secret-key', // Secret key for authentication
60
- maxEntries: 1000, // Maximum entries to retain in database
61
- enabledEntryTypes: [
62
- EntryType.REQUEST,
63
- EntryType.QUERY,
64
- EntryType.CACHE,
65
- EntryType.JOB,
66
- EntryType.EVENT,
67
- EntryType.LOG,
68
- EntryType.EXCEPTION,
69
- EntryType.SCHEDULED_TASK,
70
- EntryType.REDIS,
71
- EntryType.MAIL,
72
- ],
87
+ password: 'my-secure-password',
88
+ maxEntries: 500,
89
+ ignorePaths: ['/health-check'],
73
90
  }),
74
91
  ],
75
92
  })
76
93
  export class AppModule {}
77
94
  ```
78
95
 
79
- ### Authentication Configuration
80
-
81
- To secure the Telescope dashboard, configure the dashboard access password in your environment variables:
82
-
83
- ```env
84
- TELESCOPE_PASSWORD=my-secure-password
85
- TELESCOPE_JWT_SECRET=super-secret-token
86
- ```
87
- *If not set, the password defaults to `password`.*
88
-
89
96
  ---
90
97
 
91
98
  ## Dashboard Access
92
99
 
93
100
  1. Run your NestJS application.
94
- 2. Open your browser and navigate to `http://localhost:3000/telescope`.
95
- 3. Enter the password configured via `TELESCOPE_PASSWORD`.
101
+ 2. Open your browser and navigate to `http://localhost:3000/telescope` (or your configured path).
102
+ 3. Enter the configured password (or defaults to `password`).
96
103
  4. Monitor requests, logs, queries, and errors in real-time.
97
104
 
98
105
  ---
@@ -108,3 +115,4 @@ To run Jest tests:
108
115
  ```bash
109
116
  npm run test
110
117
  ```
118
+
@@ -1,3 +1,12 @@
1
- export declare const TELESCOPE_PATH: string;
2
- export declare const TELESCOPE_JWT_SECRET: string;
3
- export declare const TELESCOPE_DB_CONNECTION: string;
1
+ /** Injection token for telescope configuration options */
2
+ export declare const TELESCOPE_OPTIONS = "TELESCOPE_OPTIONS";
3
+ /** Default base path for the Telescope UI & API */
4
+ export declare const DEFAULT_TELESCOPE_PATH = "telescope";
5
+ /** Default JWT secret (should always be overridden in production) */
6
+ export declare const DEFAULT_JWT_SECRET = "telescope-change-me";
7
+ /** Default maximum number of entries to retain */
8
+ export declare const DEFAULT_MAX_ENTRIES = 1000;
9
+ /** Default password for UI authentication */
10
+ export declare const DEFAULT_PASSWORD = "password";
11
+ /** Minimum interval (ms) between prune operations */
12
+ export declare const PRUNE_THROTTLE_MS = 60000;
package/dist/constants.js CHANGED
@@ -1,7 +1,16 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TELESCOPE_DB_CONNECTION = exports.TELESCOPE_JWT_SECRET = exports.TELESCOPE_PATH = void 0;
4
2
  // src/constants.ts
5
- exports.TELESCOPE_PATH = process.env.TELESCOPE_PATH || 'telescope';
6
- exports.TELESCOPE_JWT_SECRET = process.env.TELESCOPE_JWT_SECRET || 'change-me';
7
- exports.TELESCOPE_DB_CONNECTION = process.env.TELESCOPE_DB_CONNECTION || 'default';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.PRUNE_THROTTLE_MS = exports.DEFAULT_PASSWORD = exports.DEFAULT_MAX_ENTRIES = exports.DEFAULT_JWT_SECRET = exports.DEFAULT_TELESCOPE_PATH = exports.TELESCOPE_OPTIONS = void 0;
5
+ /** Injection token for telescope configuration options */
6
+ exports.TELESCOPE_OPTIONS = 'TELESCOPE_OPTIONS';
7
+ /** Default base path for the Telescope UI & API */
8
+ exports.DEFAULT_TELESCOPE_PATH = 'telescope';
9
+ /** Default JWT secret (should always be overridden in production) */
10
+ exports.DEFAULT_JWT_SECRET = 'telescope-change-me';
11
+ /** Default maximum number of entries to retain */
12
+ exports.DEFAULT_MAX_ENTRIES = 1000;
13
+ /** Default password for UI authentication */
14
+ exports.DEFAULT_PASSWORD = 'password';
15
+ /** Minimum interval (ms) between prune operations */
16
+ exports.PRUNE_THROTTLE_MS = 60_000;
@@ -1,14 +1,17 @@
1
1
  import { TelescopeRepository } from '../storage/telescope-repository.service';
2
2
  import { JwtService } from '@nestjs/jwt';
3
+ import { TelescopeOptions } from '../interfaces/telescope-options.interface';
3
4
  import { Request, Response } from 'express';
4
5
  export declare class TelescopeController {
5
6
  private readonly repo;
6
7
  private readonly jwtService;
7
- constructor(repo: TelescopeRepository, jwtService: JwtService);
8
+ private readonly options;
9
+ constructor(repo: TelescopeRepository, jwtService: JwtService, options: TelescopeOptions);
8
10
  login(body: any): Promise<{
9
11
  token: string;
10
12
  }>;
11
- getEntries(type?: string, search?: string, limit?: string): Promise<import("..").TelescopeEntry[]>;
13
+ getStats(): Promise<Record<string, number>>;
14
+ getEntries(type?: string, search?: string, page?: string, perPage?: string, limit?: string): Promise<import("../storage/telescope-repository.service").PaginatedResult>;
12
15
  getEntry(uuid: string): Promise<import("..").TelescopeEntry>;
13
16
  clearEntries(): Promise<{
14
17
  success: boolean;
@@ -24,12 +24,14 @@ const fs_1 = require("fs");
24
24
  let TelescopeController = class TelescopeController {
25
25
  repo;
26
26
  jwtService;
27
- constructor(repo, jwtService) {
27
+ options;
28
+ constructor(repo, jwtService, options) {
28
29
  this.repo = repo;
29
30
  this.jwtService = jwtService;
31
+ this.options = options;
30
32
  }
31
33
  async login(body) {
32
- const password = process.env.TELESCOPE_PASSWORD || 'password';
34
+ const password = this.options?.password || process.env.TELESCOPE_PASSWORD || 'password';
33
35
  if (body.password === password) {
34
36
  const payload = { username: 'telescope' };
35
37
  return {
@@ -38,9 +40,19 @@ let TelescopeController = class TelescopeController {
38
40
  }
39
41
  throw new common_1.UnauthorizedException('Invalid credentials');
40
42
  }
41
- async getEntries(type, search, limit) {
42
- const limitNum = limit ? parseInt(limit, 10) : 100;
43
- return await this.repo.queryEntries({ type, search, limit: limitNum });
43
+ async getStats() {
44
+ return await this.repo.countByType();
45
+ }
46
+ async getEntries(type, search, page, perPage, limit) {
47
+ const pageNum = page ? parseInt(page, 10) : 1;
48
+ const limitNum = limit ? parseInt(limit, 10) : undefined;
49
+ const perPageNum = perPage ? parseInt(perPage, 10) : (limitNum || 50);
50
+ return await this.repo.queryEntries({
51
+ type,
52
+ search,
53
+ page: pageNum,
54
+ limit: perPageNum,
55
+ });
44
56
  }
45
57
  async getEntry(uuid) {
46
58
  const entry = await this.repo.findByUuid(uuid);
@@ -56,9 +68,10 @@ let TelescopeController = class TelescopeController {
56
68
  // Fallback wildcard to serve static assets and UI dashboard
57
69
  serveStatic(req, res) {
58
70
  const rawPath = req.path;
59
- // Strip TELESCOPE_PATH prefix to find the asset inside the ui directory
71
+ const basePath = this.options?.path || constants_1.DEFAULT_TELESCOPE_PATH;
72
+ // Strip basePath prefix to find the asset inside the ui directory
60
73
  // e.g. /telescope/assets/main.js -> /assets/main.js
61
- let assetPath = rawPath.replace(new RegExp(`^/${constants_1.TELESCOPE_PATH}`), '');
74
+ let assetPath = rawPath.replace(new RegExp(`^/${basePath}`), '');
62
75
  if (assetPath.startsWith('/')) {
63
76
  assetPath = assetPath.substring(1);
64
77
  }
@@ -66,12 +79,17 @@ let TelescopeController = class TelescopeController {
66
79
  if (!assetPath || !assetPath.includes('.')) {
67
80
  assetPath = 'index.html';
68
81
  }
69
- const filePath = (0, path_1.join)(__dirname, '../../ui', assetPath);
82
+ // Locate UI directory in either source (dev) or dist (prod) mode
83
+ let uiDir = (0, path_1.join)(__dirname, '../../ui');
84
+ if (!(0, fs_1.existsSync)((0, path_1.join)(uiDir, 'index.html'))) {
85
+ uiDir = (0, path_1.join)(__dirname, '../ui');
86
+ }
87
+ const filePath = (0, path_1.join)(uiDir, assetPath);
70
88
  if ((0, fs_1.existsSync)(filePath)) {
71
89
  return res.sendFile(filePath);
72
90
  }
73
91
  // Fall back to index.html
74
- const fallbackPath = (0, path_1.join)(__dirname, '../../ui/index.html');
92
+ const fallbackPath = (0, path_1.join)(uiDir, 'index.html');
75
93
  if ((0, fs_1.existsSync)(fallbackPath)) {
76
94
  return res.sendFile(fallbackPath);
77
95
  }
@@ -86,14 +104,23 @@ __decorate([
86
104
  __metadata("design:paramtypes", [Object]),
87
105
  __metadata("design:returntype", Promise)
88
106
  ], TelescopeController.prototype, "login", null);
107
+ __decorate([
108
+ (0, common_1.UseGuards)(telescope_jwt_guard_1.JwtAuthGuard),
109
+ (0, common_1.Get)('api/stats'),
110
+ __metadata("design:type", Function),
111
+ __metadata("design:paramtypes", []),
112
+ __metadata("design:returntype", Promise)
113
+ ], TelescopeController.prototype, "getStats", null);
89
114
  __decorate([
90
115
  (0, common_1.UseGuards)(telescope_jwt_guard_1.JwtAuthGuard),
91
116
  (0, common_1.Get)('api/entries'),
92
117
  __param(0, (0, common_1.Query)('type')),
93
118
  __param(1, (0, common_1.Query)('search')),
94
- __param(2, (0, common_1.Query)('limit')),
119
+ __param(2, (0, common_1.Query)('page')),
120
+ __param(3, (0, common_1.Query)('perPage')),
121
+ __param(4, (0, common_1.Query)('limit')),
95
122
  __metadata("design:type", Function),
96
- __metadata("design:paramtypes", [String, String, String]),
123
+ __metadata("design:paramtypes", [String, String, String, String, String]),
97
124
  __metadata("design:returntype", Promise)
98
125
  ], TelescopeController.prototype, "getEntries", null);
99
126
  __decorate([
@@ -120,7 +147,8 @@ __decorate([
120
147
  __metadata("design:returntype", void 0)
121
148
  ], TelescopeController.prototype, "serveStatic", null);
122
149
  exports.TelescopeController = TelescopeController = __decorate([
123
- (0, common_1.Controller)(constants_1.TELESCOPE_PATH),
150
+ (0, common_1.Controller)(),
151
+ __param(2, (0, common_1.Inject)(constants_1.TELESCOPE_OPTIONS)),
124
152
  __metadata("design:paramtypes", [telescope_repository_service_1.TelescopeRepository,
125
- jwt_1.JwtService])
153
+ jwt_1.JwtService, Object])
126
154
  ], TelescopeController);
package/dist/index.d.ts CHANGED
@@ -6,3 +6,14 @@ export * from './enums/entry-type.enum';
6
6
  export * from './interfaces/telescope-options.interface';
7
7
  export * from './interfaces/entry.interface';
8
8
  export * from './guards/telescope-jwt.guard';
9
+ export * from './constants';
10
+ export * from './watchers/http-request.watcher';
11
+ export * from './watchers/query.watcher';
12
+ export * from './watchers/cache.watcher';
13
+ export * from './watchers/queue.watcher';
14
+ export * from './watchers/event.watcher';
15
+ export * from './watchers/mail.watcher';
16
+ export * from './watchers/log.watcher';
17
+ export * from './watchers/exception.watcher';
18
+ export * from './watchers/schedule.watcher';
19
+ export * from './watchers/redis.watcher';
package/dist/index.js CHANGED
@@ -23,3 +23,15 @@ __exportStar(require("./enums/entry-type.enum"), exports);
23
23
  __exportStar(require("./interfaces/telescope-options.interface"), exports);
24
24
  __exportStar(require("./interfaces/entry.interface"), exports);
25
25
  __exportStar(require("./guards/telescope-jwt.guard"), exports);
26
+ __exportStar(require("./constants"), exports);
27
+ // Export all watchers for custom usage/extension
28
+ __exportStar(require("./watchers/http-request.watcher"), exports);
29
+ __exportStar(require("./watchers/query.watcher"), exports);
30
+ __exportStar(require("./watchers/cache.watcher"), exports);
31
+ __exportStar(require("./watchers/queue.watcher"), exports);
32
+ __exportStar(require("./watchers/event.watcher"), exports);
33
+ __exportStar(require("./watchers/mail.watcher"), exports);
34
+ __exportStar(require("./watchers/log.watcher"), exports);
35
+ __exportStar(require("./watchers/exception.watcher"), exports);
36
+ __exportStar(require("./watchers/schedule.watcher"), exports);
37
+ __exportStar(require("./watchers/redis.watcher"), exports);
@@ -1,13 +1,19 @@
1
1
  import { EntryType } from '../enums/entry-type.enum';
2
2
  export interface TelescopeOptions {
3
- /** Base path for UI (default: /telescope) */
3
+ /** Base path for UI and API (default: 'telescope') */
4
4
  path?: string;
5
5
  /** JWT secret used for UI authentication */
6
6
  jwtSecret?: string;
7
- /** Which entry types to enable */
7
+ /** Password for dashboard login (default: 'password', or env TELESCOPE_PASSWORD) */
8
+ password?: string;
9
+ /** Which entry types to enable (default: all) */
8
10
  enabledEntryTypes?: EntryType[];
9
- /** Max number of entries to retain (pruning) */
11
+ /** Max number of entries to retain in the database (default: 1000) */
10
12
  maxEntries?: number;
13
+ /** Request paths to ignore (e.g., ['/health', '/telescope']) */
14
+ ignorePaths?: string[];
15
+ /** Redis commands to ignore (e.g., ['info', 'ping']) */
16
+ ignoreCommands?: string[];
11
17
  /** Additional middleware to apply to UI routes */
12
18
  uiMiddleware?: any[];
13
19
  }
@@ -13,6 +13,30 @@ exports.TelescopeEntry = void 0;
13
13
  // src/storage/entities/telescope-entry.entity.ts
14
14
  const typeorm_1 = require("typeorm");
15
15
  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
+ };
16
40
  let TelescopeEntry = class TelescopeEntry {
17
41
  id;
18
42
  uuid;
@@ -28,29 +52,32 @@ __decorate([
28
52
  __metadata("design:type", Number)
29
53
  ], TelescopeEntry.prototype, "id", void 0);
30
54
  __decorate([
31
- (0, typeorm_1.Column)({ type: 'uuid' }),
55
+ (0, typeorm_1.Column)({ type: 'varchar', length: 64 }),
56
+ (0, typeorm_1.Index)({ unique: true }),
32
57
  __metadata("design:type", String)
33
58
  ], TelescopeEntry.prototype, "uuid", void 0);
34
59
  __decorate([
35
- (0, typeorm_1.Column)({ nullable: true }),
60
+ (0, typeorm_1.Column)({ type: 'varchar', length: 64, nullable: true }),
36
61
  __metadata("design:type", String)
37
62
  ], TelescopeEntry.prototype, "batchId", void 0);
38
63
  __decorate([
39
- (0, typeorm_1.Column)({ type: 'enum', enum: entry_type_enum_1.EntryType }),
64
+ (0, typeorm_1.Column)({ type: 'varchar', length: 50 }),
40
65
  __metadata("design:type", String)
41
66
  ], TelescopeEntry.prototype, "type", void 0);
42
67
  __decorate([
43
- (0, typeorm_1.Column)({ type: 'jsonb' }),
68
+ (0, typeorm_1.Column)({ type: 'text', transformer: JsonTransformer }),
44
69
  __metadata("design:type", Object)
45
70
  ], TelescopeEntry.prototype, "content", void 0);
46
71
  __decorate([
47
- (0, typeorm_1.CreateDateColumn)({ type: 'timestamp' }),
72
+ (0, typeorm_1.CreateDateColumn)(),
48
73
  __metadata("design:type", Date)
49
74
  ], TelescopeEntry.prototype, "recordedAt", void 0);
50
75
  __decorate([
51
- (0, typeorm_1.Column)({ nullable: true }),
76
+ (0, typeorm_1.Column)({ type: 'varchar', length: 64, nullable: true }),
52
77
  __metadata("design:type", String)
53
78
  ], TelescopeEntry.prototype, "familyHash", void 0);
54
79
  exports.TelescopeEntry = TelescopeEntry = __decorate([
55
- (0, typeorm_1.Entity)({ name: 'telescope_entries' })
80
+ (0, typeorm_1.Entity)({ name: 'telescope_entries' }),
81
+ (0, typeorm_1.Index)(['type']),
82
+ (0, typeorm_1.Index)(['recordedAt'])
56
83
  ], TelescopeEntry);
@@ -1,6 +1,19 @@
1
- import { Repository, DeleteResult } from 'typeorm';
1
+ import { Repository } from 'typeorm';
2
2
  import { TelescopeEntry } from './entities/telescope-entry.entity';
3
3
  import { EntryType } from '../enums/entry-type.enum';
4
+ export interface QueryFilters {
5
+ type?: string;
6
+ search?: string;
7
+ limit?: number;
8
+ page?: number;
9
+ }
10
+ export interface PaginatedResult {
11
+ data: TelescopeEntry[];
12
+ total: number;
13
+ page: number;
14
+ perPage: number;
15
+ totalPages: number;
16
+ }
4
17
  export declare class TelescopeRepository {
5
18
  private readonly entryRepo;
6
19
  constructor(entryRepo: Repository<TelescopeEntry>);
@@ -8,11 +21,8 @@ export declare class TelescopeRepository {
8
21
  findAll(limit?: number): Promise<TelescopeEntry[]>;
9
22
  findByType(type: EntryType, limit?: number): Promise<TelescopeEntry[]>;
10
23
  prune(maxEntries: number): Promise<void>;
11
- queryEntries(filters: {
12
- type?: string;
13
- search?: string;
14
- limit?: number;
15
- }): Promise<TelescopeEntry[]>;
24
+ queryEntries(filters: QueryFilters): Promise<PaginatedResult>;
16
25
  findByUuid(uuid: string): Promise<TelescopeEntry | null>;
17
- clearAll(): Promise<DeleteResult>;
26
+ clearAll(): Promise<import("typeorm").DeleteResult>;
27
+ countByType(): Promise<Record<string, number>>;
18
28
  }
@@ -38,29 +38,41 @@ let TelescopeRepository = class TelescopeRepository {
38
38
  if (count <= maxEntries)
39
39
  return;
40
40
  const excess = count - maxEntries;
41
- const oldest = await this.entryRepo.find({ order: { recordedAt: 'ASC' }, take: excess });
42
- const ids = oldest.map(e => e.id);
43
- await this.entryRepo.delete(ids);
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
+ });
47
+ if (oldest.length > 0) {
48
+ const ids = oldest.map((e) => e.id);
49
+ await this.entryRepo.delete(ids);
50
+ }
44
51
  }
45
52
  async queryEntries(filters) {
46
- const limit = filters.limit || 100;
47
- const findOptions = {
48
- order: { recordedAt: 'DESC' },
49
- take: limit,
50
- };
53
+ const perPage = Math.min(filters.limit || 50, 200);
54
+ const page = Math.max(filters.page || 1, 1);
55
+ const skip = (page - 1) * perPage;
56
+ const qb = this.entryRepo.createQueryBuilder('entry');
57
+ qb.orderBy('entry.recordedAt', 'DESC');
51
58
  if (filters.type) {
52
- findOptions.where = { type: filters.type };
59
+ qb.andWhere('entry.type = :type', { type: filters.type });
53
60
  }
54
- let entries = await this.entryRepo.find(findOptions);
61
+ // For search, do in-memory filtering since LIKE on JSON text varies per DB
55
62
  if (filters.search) {
56
- const searchLower = filters.search.toLowerCase();
57
- entries = entries.filter(entry => {
58
- const uuidMatch = entry.uuid.toLowerCase().includes(searchLower);
59
- const contentMatch = JSON.stringify(entry.content).toLowerCase().includes(searchLower);
60
- return uuidMatch || contentMatch;
61
- });
63
+ const searchLower = `%${filters.search.toLowerCase()}%`;
64
+ // Try database-level search first (works on most DBs)
65
+ qb.andWhere('(LOWER(entry.uuid) LIKE :search OR LOWER(entry.content) LIKE :search)', { search: searchLower });
62
66
  }
63
- return entries;
67
+ const total = await qb.getCount();
68
+ const data = await qb.skip(skip).take(perPage).getMany();
69
+ return {
70
+ data,
71
+ total,
72
+ page,
73
+ perPage,
74
+ totalPages: Math.ceil(total / perPage),
75
+ };
64
76
  }
65
77
  async findByUuid(uuid) {
66
78
  return await this.entryRepo.findOne({ where: { uuid } });
@@ -68,6 +80,19 @@ let TelescopeRepository = class TelescopeRepository {
68
80
  async clearAll() {
69
81
  return await this.entryRepo.delete({});
70
82
  }
83
+ async countByType() {
84
+ const results = await this.entryRepo
85
+ .createQueryBuilder('entry')
86
+ .select('entry.type', 'type')
87
+ .addSelect('COUNT(*)', 'count')
88
+ .groupBy('entry.type')
89
+ .getRawMany();
90
+ const counts = {};
91
+ for (const row of results) {
92
+ counts[row.type] = parseInt(row.count, 10);
93
+ }
94
+ return counts;
95
+ }
71
96
  };
72
97
  exports.TelescopeRepository = TelescopeRepository;
73
98
  exports.TelescopeRepository = TelescopeRepository = __decorate([
@@ -1,4 +1,8 @@
1
- import { DynamicModule } from '@nestjs/common';
2
- export declare class TelescopeModule {
1
+ import { DynamicModule, OnModuleInit } from '@nestjs/common';
2
+ import { ModuleRef } from '@nestjs/core';
3
+ export declare class TelescopeModule implements OnModuleInit {
4
+ private readonly moduleRef;
5
+ constructor(moduleRef: ModuleRef);
6
+ onModuleInit(): void;
3
7
  static forRoot(options?: any): DynamicModule;
4
8
  }