@node-telescope/nestjs 0.1.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/dist/index.js ADDED
@@ -0,0 +1,795 @@
1
+ import { Injectable, Inject, Get, Post, Body, Delete, Param, Req, Query, Controller, UseGuards, Module, HttpException, HttpStatus } from '@nestjs/common';
2
+ import { APP_INTERCEPTOR } from '@nestjs/core';
3
+ import { EntryType, HeaderFilter, runWithContext, getContext, IncomingEntry, Telescope } from '@node-telescope/core';
4
+ export { Telescope } from '@node-telescope/core';
5
+ import { Observable } from 'rxjs';
6
+ import { tap } from 'rxjs/operators';
7
+ import { WebSocketServer } from 'ws';
8
+ import { createRequire } from 'module';
9
+ import { join, dirname } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { readFile } from 'fs/promises';
12
+
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
+ var __decorateClass = (decorators, target, key, kind) => {
16
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
17
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
18
+ if (decorator = decorators[i])
19
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
20
+ if (kind && result) __defProp(target, key, result);
21
+ return result;
22
+ };
23
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
24
+
25
+ // src/telescope.constants.ts
26
+ var TELESCOPE_OPTIONS = "TELESCOPE_OPTIONS";
27
+ var TELESCOPE_INSTANCE = "TELESCOPE_INSTANCE";
28
+ var MAX_RESPONSE_BODY_SIZE = 64 * 1024;
29
+ var TelescopeInterceptor = class {
30
+ constructor(telescope) {
31
+ this.telescope = telescope;
32
+ this.headerFilter = new HeaderFilter(this.telescope.config.hiddenRequestHeaders);
33
+ }
34
+ headerFilter;
35
+ intercept(context, next) {
36
+ if (context.getType() !== "http") {
37
+ return next.handle();
38
+ }
39
+ const httpCtx = context.switchToHttp();
40
+ const req = httpCtx.getRequest();
41
+ const res = httpCtx.getResponse();
42
+ const requestPath = req.path || req.url;
43
+ if (this.telescope.shouldIgnorePath(requestPath)) {
44
+ return next.handle();
45
+ }
46
+ if (!this.telescope.isRecording()) {
47
+ return next.handle();
48
+ }
49
+ return new Observable((subscriber) => {
50
+ runWithContext(() => {
51
+ const ctx = getContext();
52
+ const startTime = ctx?.startTime ?? performance.now();
53
+ const batchId = ctx?.batchId ?? "";
54
+ const chunks = [];
55
+ let responseBodySize = 0;
56
+ const originalWrite = res.write.bind(res);
57
+ const originalEnd = res.end.bind(res);
58
+ res.write = function telescopeWrite(chunk, ...args) {
59
+ try {
60
+ if (chunk && responseBodySize < MAX_RESPONSE_BODY_SIZE) {
61
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
62
+ responseBodySize += buf.length;
63
+ if (responseBodySize <= MAX_RESPONSE_BODY_SIZE) {
64
+ chunks.push(buf);
65
+ }
66
+ }
67
+ } catch {
68
+ }
69
+ return originalWrite(chunk, ...args);
70
+ };
71
+ res.end = function telescopeEnd(chunk, ...args) {
72
+ try {
73
+ if (chunk && responseBodySize < MAX_RESPONSE_BODY_SIZE) {
74
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
75
+ responseBodySize += buf.length;
76
+ if (responseBodySize <= MAX_RESPONSE_BODY_SIZE) {
77
+ chunks.push(buf);
78
+ }
79
+ }
80
+ } catch {
81
+ }
82
+ return originalEnd(chunk, ...args);
83
+ };
84
+ res.on("finish", () => {
85
+ try {
86
+ if (!this.telescope.watchers.has(EntryType.Request)) return;
87
+ const duration = performance.now() - startTime;
88
+ let responseBody = null;
89
+ try {
90
+ const raw = Buffer.concat(chunks).toString("utf-8");
91
+ const contentType = res.getHeader("content-type");
92
+ if (typeof contentType === "string" && contentType.includes("application/json")) {
93
+ responseBody = JSON.parse(raw);
94
+ } else {
95
+ responseBody = raw;
96
+ }
97
+ } catch {
98
+ responseBody = "[unparseable]";
99
+ }
100
+ const filteredHeaders = this.headerFilter.filter(
101
+ req.headers
102
+ );
103
+ const rawResponseHeaders = res.getHeaders();
104
+ const responseHeaders = {};
105
+ for (const [key, value] of Object.entries(rawResponseHeaders)) {
106
+ if (typeof value === "number") {
107
+ responseHeaders[key] = String(value);
108
+ } else {
109
+ responseHeaders[key] = value;
110
+ }
111
+ }
112
+ const entry = new IncomingEntry(EntryType.Request, {
113
+ method: req.method,
114
+ url: req.originalUrl || req.url,
115
+ path: req.path,
116
+ headers: filteredHeaders,
117
+ payload: req.body ?? null,
118
+ query: req.query ?? {},
119
+ ipAddress: req.ip || req.socket?.remoteAddress || "unknown",
120
+ responseStatus: res.statusCode,
121
+ responseHeaders,
122
+ response: responseBody,
123
+ duration: Math.round(duration * 100) / 100,
124
+ memory: process.memoryUsage().heapUsed
125
+ });
126
+ entry.setBatchId(batchId);
127
+ this.telescope.recordEntry(entry);
128
+ } catch (error) {
129
+ console.warn("[Telescope] Error recording request:", error);
130
+ }
131
+ });
132
+ next.handle().pipe(
133
+ tap({
134
+ error: () => {
135
+ }
136
+ })
137
+ ).subscribe(subscriber);
138
+ });
139
+ });
140
+ }
141
+ };
142
+ TelescopeInterceptor = __decorateClass([
143
+ Injectable(),
144
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE))
145
+ ], TelescopeInterceptor);
146
+ var TelescopeAuthGuard = class {
147
+ constructor(telescope) {
148
+ this.telescope = telescope;
149
+ }
150
+ async canActivate(context) {
151
+ const gate = this.telescope.config.gate;
152
+ if (!gate) {
153
+ return true;
154
+ }
155
+ try {
156
+ const request = context.switchToHttp().getRequest();
157
+ const result = await gate(request);
158
+ return !!result;
159
+ } catch (error) {
160
+ console.warn("[Telescope] Gate check error:", error);
161
+ return false;
162
+ }
163
+ }
164
+ };
165
+ TelescopeAuthGuard = __decorateClass([
166
+ Injectable(),
167
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE))
168
+ ], TelescopeAuthGuard);
169
+
170
+ // src/telescope.controller.ts
171
+ var ENTRY_TYPE_MAP = {
172
+ requests: EntryType.Request,
173
+ exceptions: EntryType.Exception,
174
+ queries: EntryType.Query,
175
+ logs: EntryType.Log,
176
+ models: EntryType.Model,
177
+ events: EntryType.Event,
178
+ jobs: EntryType.Job,
179
+ mail: EntryType.Mail,
180
+ notifications: EntryType.Notification,
181
+ cache: EntryType.Cache,
182
+ redis: EntryType.Redis,
183
+ gates: EntryType.Gate,
184
+ "http-client": EntryType.HttpClient,
185
+ commands: EntryType.Command,
186
+ schedule: EntryType.Schedule,
187
+ schedules: EntryType.Schedule,
188
+ dumps: EntryType.Dump,
189
+ batches: EntryType.Batch,
190
+ views: EntryType.View
191
+ };
192
+ var TelescopeController = class {
193
+ constructor(telescope) {
194
+ this.telescope = telescope;
195
+ }
196
+ getStatus() {
197
+ try {
198
+ return { recording: this.telescope.isRecording() };
199
+ } catch (error) {
200
+ console.warn("[Telescope] Status error:", error);
201
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
202
+ }
203
+ }
204
+ toggleStatus(body) {
205
+ try {
206
+ if (typeof body?.recording === "boolean") {
207
+ if (body.recording) {
208
+ this.telescope.resume();
209
+ } else {
210
+ this.telescope.pause();
211
+ }
212
+ }
213
+ return { recording: this.telescope.isRecording() };
214
+ } catch (error) {
215
+ console.warn("[Telescope] Status toggle error:", error);
216
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
217
+ }
218
+ }
219
+ async clearEntries() {
220
+ try {
221
+ const storage = this.telescope.getStorage();
222
+ if (!storage) {
223
+ throw new HttpException("Storage not available", HttpStatus.SERVICE_UNAVAILABLE);
224
+ }
225
+ await storage.truncate();
226
+ return { success: true };
227
+ } catch (error) {
228
+ if (error instanceof HttpException) throw error;
229
+ console.warn("[Telescope] Truncate error:", error);
230
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
231
+ }
232
+ }
233
+ async getBatchEntries(id) {
234
+ try {
235
+ const storage = this.telescope.getStorage();
236
+ if (!storage) {
237
+ throw new HttpException("Storage not available", HttpStatus.SERVICE_UNAVAILABLE);
238
+ }
239
+ const entry = await storage.find(id);
240
+ if (!entry) {
241
+ throw new HttpException("Entry not found", HttpStatus.NOT_FOUND);
242
+ }
243
+ const batchEntries = await storage.findByBatchId(entry.batchId);
244
+ return { entries: batchEntries };
245
+ } catch (error) {
246
+ if (error instanceof HttpException) throw error;
247
+ console.warn("[Telescope] Batch query error:", error);
248
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
249
+ }
250
+ }
251
+ async replayRequest(id, req) {
252
+ try {
253
+ const storage = this.telescope.getStorage();
254
+ if (!storage) {
255
+ throw new HttpException("Storage not available", HttpStatus.SERVICE_UNAVAILABLE);
256
+ }
257
+ const entry = await storage.find(id);
258
+ if (!entry) {
259
+ throw new HttpException("Entry not found", HttpStatus.NOT_FOUND);
260
+ }
261
+ if (entry.type !== EntryType.Request) {
262
+ throw new HttpException("Only request entries can be replayed", HttpStatus.BAD_REQUEST);
263
+ }
264
+ const content = entry.content;
265
+ const method = String(content["method"] ?? "GET").toUpperCase();
266
+ const path = String(content["path"] ?? content["url"] ?? "/");
267
+ const headers = content["headers"] ?? {};
268
+ const payload = content["payload"];
269
+ const host = req.get("host") ?? "localhost";
270
+ const protocol = req.protocol;
271
+ const targetUrl = `${protocol}://${host}${path}`;
272
+ const fetchOptions = {
273
+ method,
274
+ headers: { ...headers }
275
+ };
276
+ if (payload && method !== "GET" && method !== "HEAD") {
277
+ fetchOptions.body = typeof payload === "string" ? payload : JSON.stringify(payload);
278
+ if (!headers["content-type"]) {
279
+ fetchOptions.headers["content-type"] = "application/json";
280
+ }
281
+ }
282
+ const skipHeaders = ["host", "connection", "content-length", "transfer-encoding"];
283
+ for (const h of skipHeaders) {
284
+ delete fetchOptions.headers[h];
285
+ }
286
+ const response = await fetch(targetUrl, fetchOptions);
287
+ let responseBody;
288
+ const contentType = response.headers.get("content-type") ?? "";
289
+ if (contentType.includes("application/json")) {
290
+ responseBody = await response.json();
291
+ } else {
292
+ responseBody = await response.text();
293
+ }
294
+ return {
295
+ success: true,
296
+ replay: {
297
+ method,
298
+ url: targetUrl,
299
+ status: response.status,
300
+ headers: Object.fromEntries(response.headers.entries()),
301
+ body: responseBody
302
+ }
303
+ };
304
+ } catch (error) {
305
+ if (error instanceof HttpException) throw error;
306
+ console.warn("[Telescope] Replay error:", error);
307
+ const message = error instanceof Error ? error.message : "Replay failed";
308
+ throw new HttpException(message, HttpStatus.INTERNAL_SERVER_ERROR);
309
+ }
310
+ }
311
+ async listEntries(typeParam, before, take, tag, familyHash) {
312
+ try {
313
+ const storage = this.telescope.getStorage();
314
+ if (!storage) {
315
+ throw new HttpException("Storage not available", HttpStatus.SERVICE_UNAVAILABLE);
316
+ }
317
+ const entryType = ENTRY_TYPE_MAP[typeParam];
318
+ if (!entryType) {
319
+ throw new HttpException(`Unknown entry type: ${typeParam}`, HttpStatus.BAD_REQUEST);
320
+ }
321
+ const filter = {
322
+ type: entryType
323
+ };
324
+ if (before) filter.beforeId = before;
325
+ if (take) filter.take = Math.min(parseInt(take, 10) || 50, 100);
326
+ else filter.take = 50;
327
+ if (tag) filter.tag = tag;
328
+ if (familyHash) filter.familyHash = familyHash;
329
+ return await storage.query(filter);
330
+ } catch (error) {
331
+ if (error instanceof HttpException) throw error;
332
+ console.warn("[Telescope] Query error:", error);
333
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
334
+ }
335
+ }
336
+ async getEntry(typeParam, id) {
337
+ try {
338
+ const storage = this.telescope.getStorage();
339
+ if (!storage) {
340
+ throw new HttpException("Storage not available", HttpStatus.SERVICE_UNAVAILABLE);
341
+ }
342
+ const entry = await storage.find(id);
343
+ if (!entry) {
344
+ throw new HttpException("Entry not found", HttpStatus.NOT_FOUND);
345
+ }
346
+ const expectedType = ENTRY_TYPE_MAP[typeParam];
347
+ if (expectedType && entry.type !== expectedType) {
348
+ throw new HttpException("Entry not found", HttpStatus.NOT_FOUND);
349
+ }
350
+ let batch = [];
351
+ try {
352
+ const batchEntries = await storage.findByBatchId(entry.batchId);
353
+ batch = batchEntries.filter((e) => e.id !== entry.id);
354
+ } catch {
355
+ }
356
+ return { entry, batch };
357
+ } catch (error) {
358
+ if (error instanceof HttpException) throw error;
359
+ console.warn("[Telescope] Entry lookup error:", error);
360
+ throw new HttpException("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
361
+ }
362
+ }
363
+ };
364
+ __decorateClass([
365
+ Get("api/status")
366
+ ], TelescopeController.prototype, "getStatus", 1);
367
+ __decorateClass([
368
+ Post("api/status"),
369
+ __decorateParam(0, Body())
370
+ ], TelescopeController.prototype, "toggleStatus", 1);
371
+ __decorateClass([
372
+ Delete("api/entries")
373
+ ], TelescopeController.prototype, "clearEntries", 1);
374
+ __decorateClass([
375
+ Get("api/entries/:id/batch"),
376
+ __decorateParam(0, Param("id"))
377
+ ], TelescopeController.prototype, "getBatchEntries", 1);
378
+ __decorateClass([
379
+ Post("api/replay/:id"),
380
+ __decorateParam(0, Param("id")),
381
+ __decorateParam(1, Req())
382
+ ], TelescopeController.prototype, "replayRequest", 1);
383
+ __decorateClass([
384
+ Get("api/:type"),
385
+ __decorateParam(0, Param("type")),
386
+ __decorateParam(1, Query("before")),
387
+ __decorateParam(2, Query("take")),
388
+ __decorateParam(3, Query("tag")),
389
+ __decorateParam(4, Query("familyHash"))
390
+ ], TelescopeController.prototype, "listEntries", 1);
391
+ __decorateClass([
392
+ Get("api/:type/:id"),
393
+ __decorateParam(0, Param("type")),
394
+ __decorateParam(1, Param("id"))
395
+ ], TelescopeController.prototype, "getEntry", 1);
396
+ TelescopeController = __decorateClass([
397
+ Controller("__telescope"),
398
+ UseGuards(TelescopeAuthGuard),
399
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE))
400
+ ], TelescopeController);
401
+ var TelescopeGateway = class {
402
+ constructor(telescope, httpAdapterHost) {
403
+ this.telescope = telescope;
404
+ this.httpAdapterHost = httpAdapterHost;
405
+ }
406
+ wss = null;
407
+ entryHandler = null;
408
+ onModuleInit() {
409
+ try {
410
+ this.setupWebSocketServer();
411
+ } catch (error) {
412
+ console.warn("[Telescope] WebSocket setup error:", error);
413
+ }
414
+ }
415
+ async onModuleDestroy() {
416
+ try {
417
+ if (this.entryHandler) {
418
+ this.telescope.off("entry", this.entryHandler);
419
+ this.entryHandler = null;
420
+ }
421
+ if (this.wss) {
422
+ for (const client of this.wss.clients) {
423
+ try {
424
+ client.close();
425
+ } catch {
426
+ }
427
+ }
428
+ await new Promise((resolve) => {
429
+ this.wss.close(() => resolve());
430
+ });
431
+ this.wss = null;
432
+ }
433
+ } catch (error) {
434
+ console.warn("[Telescope] WebSocket cleanup error:", error);
435
+ }
436
+ }
437
+ setupWebSocketServer() {
438
+ const httpAdapter = this.httpAdapterHost?.httpAdapter;
439
+ if (!httpAdapter) {
440
+ console.warn("[Telescope] No HTTP adapter found \u2014 WebSocket server not started");
441
+ return;
442
+ }
443
+ const server = httpAdapter.getHttpServer();
444
+ if (!server) {
445
+ console.warn("[Telescope] No HTTP server found \u2014 WebSocket server not started");
446
+ return;
447
+ }
448
+ const telescopePath = this.telescope.config.path;
449
+ const wsPath = `${telescopePath}/ws`;
450
+ this.wss = new WebSocketServer({ noServer: true });
451
+ server.on("upgrade", (request, socket, head) => {
452
+ try {
453
+ const url = new URL(
454
+ request.url || "",
455
+ `http://${request.headers["host"] || "localhost"}`
456
+ );
457
+ if (url.pathname === wsPath) {
458
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
459
+ this.wss.emit("connection", ws, request);
460
+ });
461
+ }
462
+ } catch (error) {
463
+ console.warn("[Telescope] WebSocket upgrade error:", error);
464
+ try {
465
+ socket.destroy();
466
+ } catch {
467
+ }
468
+ }
469
+ });
470
+ this.wss.on("connection", (ws) => {
471
+ try {
472
+ ws.send(
473
+ JSON.stringify({ type: "connected", message: "Telescope WebSocket connected" })
474
+ );
475
+ } catch (error) {
476
+ console.warn("[Telescope] WebSocket welcome error:", error);
477
+ }
478
+ ws.on("error", (error) => {
479
+ console.warn("[Telescope] WebSocket client error:", error);
480
+ });
481
+ });
482
+ this.entryHandler = (entry) => {
483
+ try {
484
+ if (!this.wss || this.wss.clients.size === 0) return;
485
+ const message = JSON.stringify({ type: "entry", data: entry });
486
+ for (const client of this.wss.clients) {
487
+ if (client.readyState === 1) {
488
+ try {
489
+ client.send(message);
490
+ } catch {
491
+ }
492
+ }
493
+ }
494
+ } catch (error) {
495
+ console.warn("[Telescope] WebSocket broadcast error:", error);
496
+ }
497
+ };
498
+ this.telescope.on("entry", this.entryHandler);
499
+ }
500
+ };
501
+ TelescopeGateway = __decorateClass([
502
+ Injectable(),
503
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE))
504
+ ], TelescopeGateway);
505
+ var CSP_HEADER = [
506
+ "default-src 'self'",
507
+ "script-src 'self' 'unsafe-inline'",
508
+ "style-src 'self' 'unsafe-inline'",
509
+ "img-src 'self' data: blob:",
510
+ "font-src 'self' data:",
511
+ "connect-src 'self' ws: wss:"
512
+ ].join("; ");
513
+ var FALLBACK_HTML = `<!DOCTYPE html>
514
+ <html lang="en">
515
+ <head>
516
+ <meta charset="UTF-8">
517
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
518
+ <title>Telescope Dashboard</title>
519
+ <style>
520
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #1a1a2e; color: #e0e0e0; }
521
+ .container { text-align: center; max-width: 500px; padding: 2rem; }
522
+ h1 { color: #7c3aed; margin-bottom: 1rem; }
523
+ p { line-height: 1.6; color: #a0a0b0; }
524
+ code { background: #2a2a3e; padding: 0.2em 0.5em; border-radius: 4px; font-size: 0.9em; }
525
+ </style>
526
+ </head>
527
+ <body>
528
+ <div class="container">
529
+ <h1>Telescope Dashboard</h1>
530
+ <p>The dashboard package is not installed.</p>
531
+ <p>Install it with:</p>
532
+ <p><code>npm install @node-telescope/dashboard</code></p>
533
+ <p>The Telescope API is still operational at the <code>/api</code> endpoints.</p>
534
+ </div>
535
+ </body>
536
+ </html>`;
537
+ function resolveDashboardDist() {
538
+ try {
539
+ const require2 = createRequire(import.meta.url);
540
+ const pkgJsonPath = require2.resolve("@node-telescope/dashboard/package.json");
541
+ const packageRoot = dirname(pkgJsonPath);
542
+ const distDir = join(packageRoot, "dist");
543
+ if (existsSync(join(distDir, "index.html"))) {
544
+ return distDir;
545
+ }
546
+ if (existsSync(join(packageRoot, "index.html"))) {
547
+ return packageRoot;
548
+ }
549
+ return null;
550
+ } catch {
551
+ return null;
552
+ }
553
+ }
554
+ var TelescopeDashboardMiddleware = class {
555
+ constructor(telescope) {
556
+ this.telescope = telescope;
557
+ this.dashboardDist = resolveDashboardDist();
558
+ this.telescopePath = this.telescope.config.path;
559
+ }
560
+ dashboardDist;
561
+ telescopePath;
562
+ async use(req, res, next) {
563
+ try {
564
+ if (!req.path.startsWith(this.telescopePath)) {
565
+ next();
566
+ return;
567
+ }
568
+ const relativePath = req.path.slice(this.telescopePath.length);
569
+ if (relativePath.startsWith("/api")) {
570
+ next();
571
+ return;
572
+ }
573
+ res.setHeader("Content-Security-Policy", CSP_HEADER);
574
+ res.setHeader("X-Content-Type-Options", "nosniff");
575
+ res.setHeader("X-Frame-Options", "DENY");
576
+ if (!this.dashboardDist) {
577
+ res.status(200).type("html").send(FALLBACK_HTML);
578
+ return;
579
+ }
580
+ const filePath = relativePath || "/index.html";
581
+ if (filePath.includes(".") && !filePath.endsWith(".html")) {
582
+ const fullPath = join(this.dashboardDist, filePath);
583
+ if (existsSync(fullPath)) {
584
+ res.sendFile(fullPath);
585
+ return;
586
+ }
587
+ res.status(404).send("Not found");
588
+ return;
589
+ }
590
+ const indexPath = join(this.dashboardDist, "index.html");
591
+ const html = await readFile(indexPath, "utf-8");
592
+ res.type("html").send(html);
593
+ } catch (error) {
594
+ console.warn("[Telescope] Dashboard serve error:", error);
595
+ next();
596
+ }
597
+ }
598
+ };
599
+ TelescopeDashboardMiddleware = __decorateClass([
600
+ Injectable(),
601
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE))
602
+ ], TelescopeDashboardMiddleware);
603
+
604
+ // src/telescope.module.ts
605
+ async function autoResolveStorage(config) {
606
+ try {
607
+ const { SqliteStorage } = await import('@node-telescope/storage-sqlite');
608
+ const storage = new SqliteStorage(config.databasePath);
609
+ return storage;
610
+ } catch {
611
+ return null;
612
+ }
613
+ }
614
+ var TelescopeModule = class {
615
+ constructor(telescope, options) {
616
+ this.telescope = telescope;
617
+ this.options = options;
618
+ }
619
+ /**
620
+ * Register TelescopeModule synchronously with configuration options.
621
+ *
622
+ * Usage:
623
+ * ```ts
624
+ * TelescopeModule.forRoot({ enabled: true, path: '/__telescope' })
625
+ * ```
626
+ */
627
+ static forRoot(config = {}) {
628
+ const optionsProvider = {
629
+ provide: TELESCOPE_OPTIONS,
630
+ useValue: config
631
+ };
632
+ const telescopeProvider = {
633
+ provide: TELESCOPE_INSTANCE,
634
+ useFactory: () => {
635
+ const telescope = new Telescope(config);
636
+ if (config.storage) {
637
+ telescope.setStorage(config.storage);
638
+ }
639
+ return telescope;
640
+ }
641
+ };
642
+ return {
643
+ module: TelescopeModule,
644
+ global: true,
645
+ providers: [
646
+ optionsProvider,
647
+ telescopeProvider,
648
+ TelescopeAuthGuard,
649
+ TelescopeGateway,
650
+ TelescopeDashboardMiddleware,
651
+ {
652
+ provide: APP_INTERCEPTOR,
653
+ useClass: TelescopeInterceptor
654
+ }
655
+ ],
656
+ controllers: [TelescopeController],
657
+ exports: [TELESCOPE_INSTANCE, TELESCOPE_OPTIONS]
658
+ };
659
+ }
660
+ /**
661
+ * Register TelescopeModule asynchronously.
662
+ * Supports useFactory, useClass, and useExisting patterns.
663
+ *
664
+ * Usage:
665
+ * ```ts
666
+ * TelescopeModule.forRootAsync({
667
+ * useFactory: (configService) => ({
668
+ * enabled: configService.get('TELESCOPE_ENABLED'),
669
+ * }),
670
+ * inject: [ConfigService],
671
+ * })
672
+ * ```
673
+ */
674
+ static forRootAsync(options) {
675
+ const asyncProviders = TelescopeModule.createAsyncProviders(options);
676
+ const telescopeProvider = {
677
+ provide: TELESCOPE_INSTANCE,
678
+ useFactory: (config) => {
679
+ const telescope = new Telescope(config);
680
+ if (config.storage) {
681
+ telescope.setStorage(config.storage);
682
+ }
683
+ return telescope;
684
+ },
685
+ inject: [TELESCOPE_OPTIONS]
686
+ };
687
+ return {
688
+ module: TelescopeModule,
689
+ global: true,
690
+ imports: options.imports || [],
691
+ providers: [
692
+ ...asyncProviders,
693
+ telescopeProvider,
694
+ TelescopeAuthGuard,
695
+ TelescopeGateway,
696
+ TelescopeDashboardMiddleware,
697
+ {
698
+ provide: APP_INTERCEPTOR,
699
+ useClass: TelescopeInterceptor
700
+ }
701
+ ],
702
+ controllers: [TelescopeController],
703
+ exports: [TELESCOPE_INSTANCE, TELESCOPE_OPTIONS]
704
+ };
705
+ }
706
+ /**
707
+ * Configure middleware — mounts the dashboard middleware.
708
+ */
709
+ configure(consumer) {
710
+ const telescopePath = this.telescope.config.path;
711
+ consumer.apply(TelescopeDashboardMiddleware).forRoutes(`${telescopePath}`, `${telescopePath}/*`);
712
+ }
713
+ /**
714
+ * Lifecycle: start telescope when the module initializes.
715
+ * Auto-resolves storage if none was provided.
716
+ */
717
+ async onModuleInit() {
718
+ try {
719
+ if (!this.options.storage && !this.telescope.getStorage()) {
720
+ const storage = await autoResolveStorage(this.options);
721
+ if (storage) {
722
+ this.telescope.setStorage(storage);
723
+ } else {
724
+ console.warn(
725
+ "[Telescope] No storage configured. Install @node-telescope/storage-sqlite for automatic setup."
726
+ );
727
+ }
728
+ }
729
+ this.telescope.start();
730
+ } catch (error) {
731
+ console.warn("[Telescope] Module init error:", error);
732
+ }
733
+ }
734
+ /**
735
+ * Lifecycle: stop telescope when the module is destroyed.
736
+ */
737
+ async onModuleDestroy() {
738
+ try {
739
+ await this.telescope.stop();
740
+ } catch (error) {
741
+ console.warn("[Telescope] Module destroy error:", error);
742
+ }
743
+ }
744
+ /**
745
+ * Creates async providers for useFactory, useClass, or useExisting patterns.
746
+ */
747
+ static createAsyncProviders(options) {
748
+ if (options.useFactory) {
749
+ return [
750
+ {
751
+ provide: TELESCOPE_OPTIONS,
752
+ useFactory: options.useFactory,
753
+ inject: options.inject || []
754
+ }
755
+ ];
756
+ }
757
+ if (options.useClass) {
758
+ return [
759
+ {
760
+ provide: options.useClass,
761
+ useClass: options.useClass
762
+ },
763
+ {
764
+ provide: TELESCOPE_OPTIONS,
765
+ useFactory: async (factory) => factory.createTelescopeOptions(),
766
+ inject: [options.useClass]
767
+ }
768
+ ];
769
+ }
770
+ if (options.useExisting) {
771
+ return [
772
+ {
773
+ provide: TELESCOPE_OPTIONS,
774
+ useFactory: async (factory) => factory.createTelescopeOptions(),
775
+ inject: [options.useExisting]
776
+ }
777
+ ];
778
+ }
779
+ return [
780
+ {
781
+ provide: TELESCOPE_OPTIONS,
782
+ useValue: {}
783
+ }
784
+ ];
785
+ }
786
+ };
787
+ TelescopeModule = __decorateClass([
788
+ Module({}),
789
+ __decorateParam(0, Inject(TELESCOPE_INSTANCE)),
790
+ __decorateParam(1, Inject(TELESCOPE_OPTIONS))
791
+ ], TelescopeModule);
792
+
793
+ export { TELESCOPE_INSTANCE, TELESCOPE_OPTIONS, TelescopeAuthGuard, TelescopeController, TelescopeDashboardMiddleware, TelescopeGateway, TelescopeInterceptor, TelescopeModule };
794
+ //# sourceMappingURL=index.js.map
795
+ //# sourceMappingURL=index.js.map