@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 +168 -20
- package/dist/index.js +0 -2
- package/dist/storage/entities/telescope-entry.entity.d.ts +12 -1
- package/dist/storage/entities/telescope-entry.entity.js +11 -42
- package/dist/storage/telescope-repository.service.d.ts +5 -5
- package/dist/storage/telescope-repository.service.js +36 -43
- package/dist/telescope.module.js +10 -30
- package/dist/telescope.service.js +1 -1
- package/dist/ui/index.html +28 -1
- package/dist/ui/manifest.json +2 -2
- package/dist/watchers/command.watcher.d.ts +1 -5
- package/dist/watchers/command.watcher.js +19 -18
- package/dist/watchers/gate.watcher.d.ts +4 -9
- package/dist/watchers/gate.watcher.js +39 -22
- package/dist/watchers/http-client.watcher.d.ts +1 -0
- package/dist/watchers/http-client.watcher.js +58 -16
- package/dist/watchers/http-request.watcher.js +2 -0
- package/dist/watchers/model.subscriber.d.ts +0 -4
- package/dist/watchers/model.subscriber.js +1 -8
- package/dist/watchers/model.watcher.d.ts +1 -4
- package/dist/watchers/model.watcher.js +45 -17
- package/dist/watchers/notification.watcher.d.ts +1 -5
- package/dist/watchers/notification.watcher.js +35 -18
- package/dist/watchers/schedule.watcher.d.ts +3 -4
- package/dist/watchers/schedule.watcher.js +79 -60
- package/package.json +4 -2
- package/ui/index.html +28 -1
package/README.md
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
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
|
|
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
|
|
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
|
-
- **
|
|
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/
|
|
67
|
+
npm install @ngn-net/nestjs-telescope @nestjs/mongoose mongoose @nestjs/jwt @nestjs/passport passport-jwt
|
|
32
68
|
```
|
|
33
69
|
|
|
34
|
-
Ensure you have
|
|
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 |
|
|
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
|
|
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[]` | `[]` |
|
|
65
|
-
| `ignoreCommands` | `string[]` | `[]` |
|
|
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
|
-
|
|
146
|
+
### Synchronous Configuration
|
|
73
147
|
|
|
74
148
|
```typescript
|
|
75
149
|
import { Module } from '@nestjs/common';
|
|
76
|
-
import {
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 {
|
|
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
|
|
20
|
-
constructor(
|
|
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("
|
|
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
|
|
19
|
-
const
|
|
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
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
this.
|
|
21
|
+
entryModel;
|
|
22
|
+
constructor(entryModel) {
|
|
23
|
+
this.entryModel = entryModel;
|
|
25
24
|
}
|
|
26
25
|
async save(entry) {
|
|
27
|
-
const
|
|
28
|
-
return await
|
|
26
|
+
const doc = new this.entryModel(entry);
|
|
27
|
+
return await doc.save();
|
|
29
28
|
}
|
|
30
29
|
async findAll(limit = 100) {
|
|
31
|
-
return await this.
|
|
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.
|
|
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.
|
|
36
|
+
const count = await this.entryModel.countDocuments();
|
|
38
37
|
if (count <= maxEntries)
|
|
39
38
|
return;
|
|
40
39
|
const excess = count - maxEntries;
|
|
41
|
-
|
|
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.
|
|
49
|
-
await this.
|
|
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
|
|
57
|
-
qb.orderBy('entry.recordedAt', 'DESC');
|
|
50
|
+
const query = {};
|
|
58
51
|
if (filters.type) {
|
|
59
|
-
|
|
52
|
+
query.type = filters.type;
|
|
60
53
|
}
|
|
61
54
|
if (filters.batchId) {
|
|
62
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
const searchRegex = { $regex: filters.search, $options: 'i' };
|
|
59
|
+
query.$or = [
|
|
60
|
+
{ uuid: searchRegex },
|
|
61
|
+
{ type: searchRegex },
|
|
62
|
+
];
|
|
69
63
|
}
|
|
70
|
-
const total = await
|
|
71
|
-
const data = await
|
|
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.
|
|
80
|
+
return await this.entryModel.findOne({ uuid }).lean();
|
|
82
81
|
}
|
|
83
82
|
async clearAll() {
|
|
84
|
-
return await this.
|
|
85
|
-
.createQueryBuilder()
|
|
86
|
-
.delete()
|
|
87
|
-
.execute();
|
|
83
|
+
return await this.entryModel.deleteMany({});
|
|
88
84
|
}
|
|
89
85
|
async countByType() {
|
|
90
|
-
const results = await this.
|
|
91
|
-
|
|
92
|
-
|
|
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.
|
|
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,
|
|
107
|
-
__metadata("design:paramtypes", [
|
|
99
|
+
__param(0, (0, mongoose_1.InjectModel)(telescope_entry_entity_1.TelescopeEntry.name)),
|
|
100
|
+
__metadata("design:paramtypes", [mongoose_2.Model])
|
|
108
101
|
], TelescopeRepository);
|