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