@geekmidas/telescope 0.0.1

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 (103) hide show
  1. package/README.md +521 -0
  2. package/dist/Telescope-B3Wd82yk.cjs +602 -0
  3. package/dist/Telescope-B3Wd82yk.cjs.map +1 -0
  4. package/dist/Telescope-C5dyDYYB.d.cts +133 -0
  5. package/dist/Telescope-D-uoZB6b.mjs +596 -0
  6. package/dist/Telescope-D-uoZB6b.mjs.map +1 -0
  7. package/dist/Telescope-DyIWgh9-.d.mts +133 -0
  8. package/dist/Telescope.cjs +3 -0
  9. package/dist/Telescope.d.cts +3 -0
  10. package/dist/Telescope.d.mts +3 -0
  11. package/dist/Telescope.mjs +3 -0
  12. package/dist/chunk-CUT6urMc.cjs +30 -0
  13. package/dist/index.cjs +5 -0
  14. package/dist/index.d.cts +4 -0
  15. package/dist/index.d.mts +4 -0
  16. package/dist/index.mjs +4 -0
  17. package/dist/logger/console.cjs +161 -0
  18. package/dist/logger/console.cjs.map +1 -0
  19. package/dist/logger/console.d.cts +109 -0
  20. package/dist/logger/console.d.mts +109 -0
  21. package/dist/logger/console.mjs +159 -0
  22. package/dist/logger/console.mjs.map +1 -0
  23. package/dist/logger/pino.cjs +118 -0
  24. package/dist/logger/pino.cjs.map +1 -0
  25. package/dist/logger/pino.d.cts +89 -0
  26. package/dist/logger/pino.d.mts +89 -0
  27. package/dist/logger/pino.mjs +116 -0
  28. package/dist/logger/pino.mjs.map +1 -0
  29. package/dist/memory-9-B9WACq.cjs +110 -0
  30. package/dist/memory-9-B9WACq.cjs.map +1 -0
  31. package/dist/memory-Cm0eevCS.d.mts +38 -0
  32. package/dist/memory-DiP1a-pp.d.cts +38 -0
  33. package/dist/memory-SdN5vtG9.mjs +104 -0
  34. package/dist/memory-SdN5vtG9.mjs.map +1 -0
  35. package/dist/server/hono.cjs +180 -0
  36. package/dist/server/hono.cjs.map +1 -0
  37. package/dist/server/hono.d.cts +26 -0
  38. package/dist/server/hono.d.mts +26 -0
  39. package/dist/server/hono.mjs +176 -0
  40. package/dist/server/hono.mjs.map +1 -0
  41. package/dist/storage/kysely.cjs +336 -0
  42. package/dist/storage/kysely.cjs.map +1 -0
  43. package/dist/storage/kysely.d.cts +161 -0
  44. package/dist/storage/kysely.d.mts +161 -0
  45. package/dist/storage/kysely.mjs +334 -0
  46. package/dist/storage/kysely.mjs.map +1 -0
  47. package/dist/storage/memory.cjs +3 -0
  48. package/dist/storage/memory.d.cts +3 -0
  49. package/dist/storage/memory.d.mts +3 -0
  50. package/dist/storage/memory.mjs +3 -0
  51. package/dist/types-BGDhFv4R.d.cts +170 -0
  52. package/dist/types-CZbzz8kx.d.mts +170 -0
  53. package/dist/types.cjs +0 -0
  54. package/dist/types.d.cts +2 -0
  55. package/dist/types.d.mts +2 -0
  56. package/dist/types.mjs +0 -0
  57. package/dist/ui-assets-D6-8TAr_.mjs +30 -0
  58. package/dist/ui-assets-D6-8TAr_.mjs.map +1 -0
  59. package/dist/ui-assets-ulevVble.cjs +48 -0
  60. package/dist/ui-assets-ulevVble.cjs.map +1 -0
  61. package/dist/ui-assets.cjs +5 -0
  62. package/dist/ui-assets.d.cts +12 -0
  63. package/dist/ui-assets.d.mts +12 -0
  64. package/dist/ui-assets.mjs +3 -0
  65. package/package.json +83 -0
  66. package/scripts/embed-ui.ts +90 -0
  67. package/src/Telescope.ts +714 -0
  68. package/src/__tests__/Telescope.spec.ts +356 -0
  69. package/src/index.ts +23 -0
  70. package/src/logger/__tests__/console.spec.ts +266 -0
  71. package/src/logger/__tests__/pino.spec.ts +217 -0
  72. package/src/logger/console.ts +230 -0
  73. package/src/logger/pino.ts +191 -0
  74. package/src/server/__tests__/hono.spec.ts +340 -0
  75. package/src/server/hono.ts +247 -0
  76. package/src/storage/__tests__/kysely.spec.ts +715 -0
  77. package/src/storage/__tests__/memory.spec.ts +411 -0
  78. package/src/storage/kysely.ts +572 -0
  79. package/src/storage/memory.ts +168 -0
  80. package/src/types.ts +188 -0
  81. package/src/ui-assets.ts +40 -0
  82. package/ui/index.html +12 -0
  83. package/ui/node_modules/.bin/browserslist +21 -0
  84. package/ui/node_modules/.bin/jiti +21 -0
  85. package/ui/node_modules/.bin/terser +21 -0
  86. package/ui/node_modules/.bin/tsc +21 -0
  87. package/ui/node_modules/.bin/tsserver +21 -0
  88. package/ui/node_modules/.bin/tsx +21 -0
  89. package/ui/node_modules/.bin/vite +21 -0
  90. package/ui/package.json +24 -0
  91. package/ui/src/App.tsx +342 -0
  92. package/ui/src/api.ts +75 -0
  93. package/ui/src/components/ExceptionDetail.tsx +100 -0
  94. package/ui/src/components/LogDetail.tsx +91 -0
  95. package/ui/src/components/RequestDetail.tsx +143 -0
  96. package/ui/src/main.tsx +10 -0
  97. package/ui/src/styles.css +10 -0
  98. package/ui/src/types.ts +63 -0
  99. package/ui/src/vite-env.d.ts +1 -0
  100. package/ui/src/vite-plugin-gkm-config.ts +54 -0
  101. package/ui/tsconfig.json +20 -0
  102. package/ui/tsconfig.tsbuildinfo +14 -0
  103. package/ui/vite.config.ts +13 -0
@@ -0,0 +1,602 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const nanoid = require_chunk.__toESM(require("nanoid"));
3
+
4
+ //#region src/Telescope.ts
5
+ /**
6
+ * Framework-agnostic Telescope class for debugging and monitoring applications.
7
+ * Use framework-specific adapters (e.g., @geekmidas/telescope/hono) for integration.
8
+ */
9
+ var Telescope = class {
10
+ storage;
11
+ options;
12
+ wsClients = /* @__PURE__ */ new Set();
13
+ pruneInterval;
14
+ constructor(options) {
15
+ this.storage = options.storage;
16
+ this.options = this.normalizeOptions(options);
17
+ if (this.options.pruneAfterHours) {
18
+ const intervalMs = 60 * 60 * 1e3;
19
+ this.pruneInterval = setInterval(() => {
20
+ this.autoPrune().catch(console.error);
21
+ }, intervalMs);
22
+ }
23
+ }
24
+ /**
25
+ * Record a request entry
26
+ */
27
+ async recordRequest(entry) {
28
+ if (!this.options.enabled) return "";
29
+ const id = (0, nanoid.nanoid)();
30
+ const fullEntry = {
31
+ ...entry,
32
+ id,
33
+ timestamp: /* @__PURE__ */ new Date()
34
+ };
35
+ await this.storage.saveRequest(fullEntry);
36
+ this.broadcast({
37
+ type: "request",
38
+ payload: fullEntry,
39
+ timestamp: Date.now()
40
+ });
41
+ return id;
42
+ }
43
+ /**
44
+ * Log entry input for batch operations
45
+ */
46
+ async saveLogEntries(entries) {
47
+ if (this.storage.saveLogs) await this.storage.saveLogs(entries);
48
+ else await Promise.all(entries.map((entry) => this.storage.saveLog(entry)));
49
+ for (const entry of entries) this.broadcast({
50
+ type: "log",
51
+ payload: entry,
52
+ timestamp: Date.now()
53
+ });
54
+ }
55
+ /**
56
+ * Record log entries in batch.
57
+ * More efficient than individual calls for database storage.
58
+ *
59
+ * @example
60
+ * await telescope.log([
61
+ * { level: 'info', message: 'Request started' },
62
+ * { level: 'debug', message: 'Processing...', context: { step: 1 } },
63
+ * ]);
64
+ */
65
+ async log(entries) {
66
+ if (!this.options.enabled || entries.length === 0) return;
67
+ const timestamp = /* @__PURE__ */ new Date();
68
+ const logEntries = entries.map((e) => ({
69
+ id: (0, nanoid.nanoid)(),
70
+ level: e.level,
71
+ message: e.message,
72
+ context: e.context,
73
+ requestId: e.requestId,
74
+ timestamp
75
+ }));
76
+ await this.saveLogEntries(logEntries);
77
+ }
78
+ /**
79
+ * Log a debug message
80
+ */
81
+ async debug(message, context, requestId) {
82
+ if (!this.options.enabled) return;
83
+ const entry = {
84
+ id: (0, nanoid.nanoid)(),
85
+ level: "debug",
86
+ message,
87
+ context,
88
+ requestId,
89
+ timestamp: /* @__PURE__ */ new Date()
90
+ };
91
+ await this.storage.saveLog(entry);
92
+ this.broadcast({
93
+ type: "log",
94
+ payload: entry,
95
+ timestamp: Date.now()
96
+ });
97
+ }
98
+ /**
99
+ * Log an info message
100
+ */
101
+ async info(message, context, requestId) {
102
+ if (!this.options.enabled) return;
103
+ const entry = {
104
+ id: (0, nanoid.nanoid)(),
105
+ level: "info",
106
+ message,
107
+ context,
108
+ requestId,
109
+ timestamp: /* @__PURE__ */ new Date()
110
+ };
111
+ await this.storage.saveLog(entry);
112
+ this.broadcast({
113
+ type: "log",
114
+ payload: entry,
115
+ timestamp: Date.now()
116
+ });
117
+ }
118
+ /**
119
+ * Log a warning message
120
+ */
121
+ async warn(message, context, requestId) {
122
+ if (!this.options.enabled) return;
123
+ const entry = {
124
+ id: (0, nanoid.nanoid)(),
125
+ level: "warn",
126
+ message,
127
+ context,
128
+ requestId,
129
+ timestamp: /* @__PURE__ */ new Date()
130
+ };
131
+ await this.storage.saveLog(entry);
132
+ this.broadcast({
133
+ type: "log",
134
+ payload: entry,
135
+ timestamp: Date.now()
136
+ });
137
+ }
138
+ /**
139
+ * Log an error message
140
+ */
141
+ async error(message, context, requestId) {
142
+ if (!this.options.enabled) return;
143
+ const entry = {
144
+ id: (0, nanoid.nanoid)(),
145
+ level: "error",
146
+ message,
147
+ context,
148
+ requestId,
149
+ timestamp: /* @__PURE__ */ new Date()
150
+ };
151
+ await this.storage.saveLog(entry);
152
+ this.broadcast({
153
+ type: "log",
154
+ payload: entry,
155
+ timestamp: Date.now()
156
+ });
157
+ }
158
+ /**
159
+ * Record an exception
160
+ */
161
+ async exception(error, requestId) {
162
+ if (!this.options.enabled) return;
163
+ const stack = this.parseStack(error.stack || "");
164
+ const entry = {
165
+ id: (0, nanoid.nanoid)(),
166
+ name: error.name,
167
+ message: error.message,
168
+ stack,
169
+ requestId,
170
+ timestamp: /* @__PURE__ */ new Date(),
171
+ handled: false
172
+ };
173
+ await this.storage.saveException(entry);
174
+ this.broadcast({
175
+ type: "exception",
176
+ payload: entry,
177
+ timestamp: Date.now()
178
+ });
179
+ }
180
+ /**
181
+ * Get requests from storage
182
+ */
183
+ async getRequests(options) {
184
+ return this.storage.getRequests(options);
185
+ }
186
+ /**
187
+ * Get a single request by ID
188
+ */
189
+ async getRequest(id) {
190
+ return this.storage.getRequest(id);
191
+ }
192
+ /**
193
+ * Get exceptions from storage
194
+ */
195
+ async getExceptions(options) {
196
+ return this.storage.getExceptions(options);
197
+ }
198
+ /**
199
+ * Get a single exception by ID
200
+ */
201
+ async getException(id) {
202
+ return this.storage.getException(id);
203
+ }
204
+ /**
205
+ * Get logs from storage
206
+ */
207
+ async getLogs(options) {
208
+ return this.storage.getLogs(options);
209
+ }
210
+ /**
211
+ * Get storage statistics
212
+ */
213
+ async getStats() {
214
+ return this.storage.getStats();
215
+ }
216
+ /**
217
+ * Add a WebSocket client for real-time updates
218
+ */
219
+ addWsClient(ws) {
220
+ this.wsClients.add(ws);
221
+ this.broadcast({
222
+ type: "connected",
223
+ payload: { clientCount: this.wsClients.size },
224
+ timestamp: Date.now()
225
+ });
226
+ }
227
+ /**
228
+ * Remove a WebSocket client
229
+ */
230
+ removeWsClient(ws) {
231
+ this.wsClients.delete(ws);
232
+ }
233
+ /**
234
+ * Broadcast an event to all connected WebSocket clients
235
+ */
236
+ broadcast(event) {
237
+ const data = JSON.stringify(event);
238
+ for (const client of this.wsClients) try {
239
+ client.send(data);
240
+ } catch {
241
+ this.wsClients.delete(client);
242
+ }
243
+ }
244
+ /**
245
+ * Manually prune old entries
246
+ */
247
+ async prune(olderThan) {
248
+ return this.storage.prune(olderThan);
249
+ }
250
+ /**
251
+ * Clean up resources
252
+ */
253
+ destroy() {
254
+ if (this.pruneInterval) clearInterval(this.pruneInterval);
255
+ this.wsClients.clear();
256
+ }
257
+ /**
258
+ * Get the telescope path
259
+ */
260
+ get path() {
261
+ return this.options.path;
262
+ }
263
+ /**
264
+ * Check if telescope is enabled
265
+ */
266
+ get enabled() {
267
+ return this.options.enabled;
268
+ }
269
+ /**
270
+ * Check if body recording is enabled
271
+ */
272
+ get recordBody() {
273
+ return this.options.recordBody;
274
+ }
275
+ /**
276
+ * Get max body size
277
+ */
278
+ get maxBodySize() {
279
+ return this.options.maxBodySize;
280
+ }
281
+ /**
282
+ * Check if a path should be ignored
283
+ */
284
+ shouldIgnore(path) {
285
+ if (path.startsWith(this.options.path)) return true;
286
+ return this.options.ignorePatterns.some((pattern) => {
287
+ if (pattern.includes("*")) {
288
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
289
+ return regex.test(path);
290
+ }
291
+ return path.startsWith(pattern);
292
+ });
293
+ }
294
+ /**
295
+ * Get the dashboard HTML
296
+ */
297
+ getDashboardHtml() {
298
+ return `<!DOCTYPE html>
299
+ <html lang="en">
300
+ <head>
301
+ <meta charset="UTF-8">
302
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
303
+ <title>Telescope</title>
304
+ <style>
305
+ * { box-sizing: border-box; margin: 0; padding: 0; }
306
+ body {
307
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
308
+ background: #0f0f23;
309
+ color: #e0e0e0;
310
+ min-height: 100vh;
311
+ }
312
+ .container {
313
+ max-width: 1400px;
314
+ margin: 0 auto;
315
+ padding: 24px;
316
+ }
317
+ header {
318
+ display: flex;
319
+ justify-content: space-between;
320
+ align-items: center;
321
+ margin-bottom: 24px;
322
+ padding-bottom: 16px;
323
+ border-bottom: 1px solid #333;
324
+ }
325
+ h1 {
326
+ font-size: 24px;
327
+ font-weight: 600;
328
+ display: flex;
329
+ align-items: center;
330
+ gap: 8px;
331
+ }
332
+ h1::before {
333
+ content: '';
334
+ width: 8px;
335
+ height: 8px;
336
+ background: #10b981;
337
+ border-radius: 50%;
338
+ }
339
+ .stats {
340
+ display: flex;
341
+ gap: 24px;
342
+ font-size: 14px;
343
+ color: #888;
344
+ }
345
+ .stat-value {
346
+ color: #fff;
347
+ font-weight: 500;
348
+ }
349
+ nav {
350
+ display: flex;
351
+ gap: 8px;
352
+ margin-bottom: 24px;
353
+ }
354
+ nav a {
355
+ padding: 8px 16px;
356
+ background: #1a1a3e;
357
+ border-radius: 6px;
358
+ color: #e0e0e0;
359
+ text-decoration: none;
360
+ font-size: 14px;
361
+ transition: background 0.2s;
362
+ }
363
+ nav a:hover, nav a.active { background: #2a2a5e; }
364
+ .panel {
365
+ background: #1a1a3e;
366
+ border-radius: 8px;
367
+ overflow: hidden;
368
+ }
369
+ .entry {
370
+ display: grid;
371
+ grid-template-columns: 70px 1fr 100px 80px;
372
+ gap: 16px;
373
+ padding: 12px 16px;
374
+ border-bottom: 1px solid #252550;
375
+ align-items: center;
376
+ cursor: pointer;
377
+ transition: background 0.2s;
378
+ }
379
+ .entry:hover { background: #252550; }
380
+ .method {
381
+ font-weight: 600;
382
+ font-size: 12px;
383
+ padding: 4px 8px;
384
+ border-radius: 4px;
385
+ text-align: center;
386
+ }
387
+ .GET { background: #10b981; color: #fff; }
388
+ .POST { background: #3b82f6; color: #fff; }
389
+ .PUT { background: #f59e0b; color: #fff; }
390
+ .PATCH { background: #8b5cf6; color: #fff; }
391
+ .DELETE { background: #ef4444; color: #fff; }
392
+ .path { font-family: monospace; font-size: 13px; }
393
+ .status { font-family: monospace; }
394
+ .status-2xx { color: #10b981; }
395
+ .status-3xx { color: #3b82f6; }
396
+ .status-4xx { color: #f59e0b; }
397
+ .status-5xx { color: #ef4444; }
398
+ .duration { color: #888; font-size: 13px; }
399
+ .empty {
400
+ padding: 48px;
401
+ text-align: center;
402
+ color: #666;
403
+ }
404
+ #entries { max-height: calc(100vh - 200px); overflow-y: auto; }
405
+ </style>
406
+ </head>
407
+ <body>
408
+ <div class="container">
409
+ <header>
410
+ <h1>Telescope</h1>
411
+ <div class="stats">
412
+ <span>Requests: <span class="stat-value" id="request-count">-</span></span>
413
+ <span>Exceptions: <span class="stat-value" id="exception-count">-</span></span>
414
+ <span>Logs: <span class="stat-value" id="log-count">-</span></span>
415
+ </div>
416
+ </header>
417
+
418
+ <nav>
419
+ <a href="#" class="active" data-view="requests">Requests</a>
420
+ <a href="#" data-view="exceptions">Exceptions</a>
421
+ <a href="#" data-view="logs">Logs</a>
422
+ </nav>
423
+
424
+ <div class="panel">
425
+ <div id="entries"></div>
426
+ </div>
427
+ </div>
428
+
429
+ <script>
430
+ let currentView = 'requests';
431
+ const basePath = window.location.pathname.replace(/\\/$/, '');
432
+
433
+ async function fetchStats() {
434
+ try {
435
+ const res = await fetch(basePath + '/api/stats');
436
+ const stats = await res.json();
437
+ document.getElementById('request-count').textContent = stats.requests;
438
+ document.getElementById('exception-count').textContent = stats.exceptions;
439
+ document.getElementById('log-count').textContent = stats.logs;
440
+ } catch (e) {
441
+ console.error('Failed to fetch stats:', e);
442
+ }
443
+ }
444
+
445
+ async function fetchData(type) {
446
+ try {
447
+ const res = await fetch(basePath + '/api/' + type);
448
+ return await res.json();
449
+ } catch (e) {
450
+ console.error('Failed to fetch ' + type + ':', e);
451
+ return [];
452
+ }
453
+ }
454
+
455
+ function renderRequests(requests) {
456
+ const container = document.getElementById('entries');
457
+ if (requests.length === 0) {
458
+ container.innerHTML = '<div class="empty">No requests recorded yet</div>';
459
+ return;
460
+ }
461
+ container.innerHTML = requests.map(r => \`
462
+ <div class="entry">
463
+ <span class="method \${r.method}">\${r.method}</span>
464
+ <span class="path">\${r.path}</span>
465
+ <span class="status status-\${Math.floor(r.status/100)}xx">\${r.status}</span>
466
+ <span class="duration">\${r.duration.toFixed(1)}ms</span>
467
+ </div>
468
+ \`).join('');
469
+ }
470
+
471
+ function renderExceptions(exceptions) {
472
+ const container = document.getElementById('entries');
473
+ if (exceptions.length === 0) {
474
+ container.innerHTML = '<div class="empty">No exceptions recorded yet</div>';
475
+ return;
476
+ }
477
+ container.innerHTML = exceptions.map(e => \`
478
+ <div class="entry" style="grid-template-columns: 1fr 200px;">
479
+ <div>
480
+ <div style="color: #ef4444; font-weight: 500;">\${e.name}</div>
481
+ <div style="font-size: 13px; color: #888; margin-top: 4px;">\${e.message}</div>
482
+ </div>
483
+ <span class="duration">\${new Date(e.timestamp).toLocaleTimeString()}</span>
484
+ </div>
485
+ \`).join('');
486
+ }
487
+
488
+ function renderLogs(logs) {
489
+ const container = document.getElementById('entries');
490
+ if (logs.length === 0) {
491
+ container.innerHTML = '<div class="empty">No logs recorded yet</div>';
492
+ return;
493
+ }
494
+ const levelColors = { debug: '#888', info: '#3b82f6', warn: '#f59e0b', error: '#ef4444' };
495
+ container.innerHTML = logs.map(l => \`
496
+ <div class="entry" style="grid-template-columns: 60px 1fr 100px;">
497
+ <span style="color: \${levelColors[l.level]}; font-size: 12px; text-transform: uppercase;">\${l.level}</span>
498
+ <span style="font-family: monospace; font-size: 13px;">\${l.message}</span>
499
+ <span class="duration">\${new Date(l.timestamp).toLocaleTimeString()}</span>
500
+ </div>
501
+ \`).join('');
502
+ }
503
+
504
+ async function loadView(view) {
505
+ currentView = view;
506
+ document.querySelectorAll('nav a').forEach(a => {
507
+ a.classList.toggle('active', a.dataset.view === view);
508
+ });
509
+
510
+ const data = await fetchData(view);
511
+ if (view === 'requests') renderRequests(data);
512
+ else if (view === 'exceptions') renderExceptions(data);
513
+ else if (view === 'logs') renderLogs(data);
514
+ }
515
+
516
+ // Navigation
517
+ document.querySelectorAll('nav a').forEach(a => {
518
+ a.addEventListener('click', (e) => {
519
+ e.preventDefault();
520
+ loadView(a.dataset.view);
521
+ });
522
+ });
523
+
524
+ // WebSocket for real-time updates
525
+ function connectWs() {
526
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
527
+ const ws = new WebSocket(protocol + '//' + location.host + basePath + '/ws');
528
+
529
+ ws.onmessage = (event) => {
530
+ const msg = JSON.parse(event.data);
531
+ if (msg.type === currentView.slice(0, -1) ||
532
+ (msg.type === 'request' && currentView === 'requests') ||
533
+ (msg.type === 'exception' && currentView === 'exceptions') ||
534
+ (msg.type === 'log' && currentView === 'logs')) {
535
+ loadView(currentView);
536
+ }
537
+ fetchStats();
538
+ };
539
+
540
+ ws.onclose = () => {
541
+ setTimeout(connectWs, 1000);
542
+ };
543
+ }
544
+
545
+ // Initial load
546
+ fetchStats();
547
+ loadView('requests');
548
+ connectWs();
549
+ </script>
550
+ </body>
551
+ </html>`;
552
+ }
553
+ normalizeOptions(options) {
554
+ return {
555
+ storage: options.storage,
556
+ enabled: options.enabled ?? true,
557
+ path: options.path ?? "/__telescope",
558
+ recordBody: options.recordBody ?? true,
559
+ maxBodySize: options.maxBodySize ?? 64 * 1024,
560
+ ignorePatterns: options.ignorePatterns ?? [],
561
+ pruneAfterHours: options.pruneAfterHours
562
+ };
563
+ }
564
+ parseStack(stack) {
565
+ const lines = stack.split("\n").slice(1);
566
+ const frames = [];
567
+ for (const line of lines) {
568
+ const match = line.match(/at\s+(.+?)\s+\((.+):(\d+):(\d+)\)/) || line.match(/at\s+(.+):(\d+):(\d+)/);
569
+ if (match) {
570
+ if (match.length === 5) frames.push({
571
+ function: match[1],
572
+ file: match[2],
573
+ line: parseInt(match[3], 10),
574
+ column: parseInt(match[4], 10),
575
+ isApp: !match[2].includes("node_modules")
576
+ });
577
+ else if (match.length === 4) frames.push({
578
+ function: "<anonymous>",
579
+ file: match[1],
580
+ line: parseInt(match[2], 10),
581
+ column: parseInt(match[3], 10),
582
+ isApp: !match[1].includes("node_modules")
583
+ });
584
+ }
585
+ }
586
+ return frames;
587
+ }
588
+ async autoPrune() {
589
+ if (!this.options.pruneAfterHours) return;
590
+ const olderThan = new Date(Date.now() - this.options.pruneAfterHours * 60 * 60 * 1e3);
591
+ await this.storage.prune(olderThan);
592
+ }
593
+ };
594
+
595
+ //#endregion
596
+ Object.defineProperty(exports, 'Telescope', {
597
+ enumerable: true,
598
+ get: function () {
599
+ return Telescope;
600
+ }
601
+ });
602
+ //# sourceMappingURL=Telescope-B3Wd82yk.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telescope-B3Wd82yk.cjs","names":["options: TelescopeOptions","entry: Omit<RequestEntry, 'id' | 'timestamp'>","fullEntry: RequestEntry","entries: LogEntry[]","entries: Array<{\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n context?: Record<string, unknown>;\n requestId?: string;\n }>","logEntries: LogEntry[]","message: string","context?: Record<string, unknown>","requestId?: string","entry: LogEntry","error: Error","entry: ExceptionEntry","options?: QueryOptions","id: string","ws: WebSocket","event: TelescopeEvent","olderThan: Date","path: string","stack: string","frames: StackFrame[]"],"sources":["../src/Telescope.ts"],"sourcesContent":["import { nanoid } from 'nanoid';\nimport type {\n ExceptionEntry,\n LogEntry,\n NormalizedTelescopeOptions,\n QueryOptions,\n RequestEntry,\n StackFrame,\n TelescopeEvent,\n TelescopeOptions,\n TelescopeStorage,\n} from './types';\n\n/**\n * Framework-agnostic Telescope class for debugging and monitoring applications.\n * Use framework-specific adapters (e.g., @geekmidas/telescope/hono) for integration.\n */\nexport class Telescope {\n private storage: TelescopeStorage;\n private options: NormalizedTelescopeOptions;\n private wsClients = new Set<WebSocket>();\n private pruneInterval?: ReturnType<typeof setInterval>;\n\n constructor(options: TelescopeOptions) {\n this.storage = options.storage;\n this.options = this.normalizeOptions(options);\n\n // Set up auto-pruning if configured\n if (this.options.pruneAfterHours) {\n const intervalMs = 60 * 60 * 1000; // 1 hour\n this.pruneInterval = setInterval(() => {\n this.autoPrune().catch(console.error);\n }, intervalMs);\n }\n }\n\n // ============================================\n // Public API - Recording\n // ============================================\n\n /**\n * Record a request entry\n */\n async recordRequest(\n entry: Omit<RequestEntry, 'id' | 'timestamp'>,\n ): Promise<string> {\n if (!this.options.enabled) return '';\n\n const id = nanoid();\n const fullEntry: RequestEntry = {\n ...entry,\n id,\n timestamp: new Date(),\n };\n\n await this.storage.saveRequest(fullEntry);\n this.broadcast({\n type: 'request',\n payload: fullEntry,\n timestamp: Date.now(),\n });\n return id;\n }\n\n /**\n * Log entry input for batch operations\n */\n private async saveLogEntries(entries: LogEntry[]): Promise<void> {\n if (this.storage.saveLogs) {\n await this.storage.saveLogs(entries);\n } else {\n await Promise.all(entries.map((entry) => this.storage.saveLog(entry)));\n }\n\n for (const entry of entries) {\n this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });\n }\n }\n\n /**\n * Record log entries in batch.\n * More efficient than individual calls for database storage.\n *\n * @example\n * await telescope.log([\n * { level: 'info', message: 'Request started' },\n * { level: 'debug', message: 'Processing...', context: { step: 1 } },\n * ]);\n */\n async log(\n entries: Array<{\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n context?: Record<string, unknown>;\n requestId?: string;\n }>,\n ): Promise<void> {\n if (!this.options.enabled || entries.length === 0) return;\n\n const timestamp = new Date();\n const logEntries: LogEntry[] = entries.map((e) => ({\n id: nanoid(),\n level: e.level,\n message: e.message,\n context: e.context,\n requestId: e.requestId,\n timestamp,\n }));\n\n await this.saveLogEntries(logEntries);\n }\n\n /**\n * Log a debug message\n */\n async debug(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n if (!this.options.enabled) return;\n\n const entry: LogEntry = {\n id: nanoid(),\n level: 'debug',\n message,\n context,\n requestId,\n timestamp: new Date(),\n };\n\n await this.storage.saveLog(entry);\n this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });\n }\n\n /**\n * Log an info message\n */\n async info(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n if (!this.options.enabled) return;\n\n const entry: LogEntry = {\n id: nanoid(),\n level: 'info',\n message,\n context,\n requestId,\n timestamp: new Date(),\n };\n\n await this.storage.saveLog(entry);\n this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });\n }\n\n /**\n * Log a warning message\n */\n async warn(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n if (!this.options.enabled) return;\n\n const entry: LogEntry = {\n id: nanoid(),\n level: 'warn',\n message,\n context,\n requestId,\n timestamp: new Date(),\n };\n\n await this.storage.saveLog(entry);\n this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });\n }\n\n /**\n * Log an error message\n */\n async error(\n message: string,\n context?: Record<string, unknown>,\n requestId?: string,\n ): Promise<void> {\n if (!this.options.enabled) return;\n\n const entry: LogEntry = {\n id: nanoid(),\n level: 'error',\n message,\n context,\n requestId,\n timestamp: new Date(),\n };\n\n await this.storage.saveLog(entry);\n this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });\n }\n\n /**\n * Record an exception\n */\n async exception(error: Error, requestId?: string): Promise<void> {\n if (!this.options.enabled) return;\n\n const stack = this.parseStack(error.stack || '');\n\n const entry: ExceptionEntry = {\n id: nanoid(),\n name: error.name,\n message: error.message,\n stack,\n requestId,\n timestamp: new Date(),\n handled: false,\n };\n\n await this.storage.saveException(entry);\n this.broadcast({\n type: 'exception',\n payload: entry,\n timestamp: Date.now(),\n });\n }\n\n // ============================================\n // Public API - Data Access\n // ============================================\n\n /**\n * Get requests from storage\n */\n async getRequests(options?: QueryOptions): Promise<RequestEntry[]> {\n return this.storage.getRequests(options);\n }\n\n /**\n * Get a single request by ID\n */\n async getRequest(id: string): Promise<RequestEntry | null> {\n return this.storage.getRequest(id);\n }\n\n /**\n * Get exceptions from storage\n */\n async getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]> {\n return this.storage.getExceptions(options);\n }\n\n /**\n * Get a single exception by ID\n */\n async getException(id: string): Promise<ExceptionEntry | null> {\n return this.storage.getException(id);\n }\n\n /**\n * Get logs from storage\n */\n async getLogs(options?: QueryOptions): Promise<LogEntry[]> {\n return this.storage.getLogs(options);\n }\n\n /**\n * Get storage statistics\n */\n async getStats() {\n return this.storage.getStats();\n }\n\n // ============================================\n // Public API - WebSocket\n // ============================================\n\n /**\n * Add a WebSocket client for real-time updates\n */\n addWsClient(ws: WebSocket): void {\n this.wsClients.add(ws);\n this.broadcast({\n type: 'connected',\n payload: { clientCount: this.wsClients.size },\n timestamp: Date.now(),\n });\n }\n\n /**\n * Remove a WebSocket client\n */\n removeWsClient(ws: WebSocket): void {\n this.wsClients.delete(ws);\n }\n\n /**\n * Broadcast an event to all connected WebSocket clients\n */\n broadcast(event: TelescopeEvent): void {\n const data = JSON.stringify(event);\n for (const client of this.wsClients) {\n try {\n client.send(data);\n } catch {\n this.wsClients.delete(client);\n }\n }\n }\n\n // ============================================\n // Public API - Lifecycle\n // ============================================\n\n /**\n * Manually prune old entries\n */\n async prune(olderThan: Date): Promise<number> {\n return this.storage.prune(olderThan);\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.pruneInterval) {\n clearInterval(this.pruneInterval);\n }\n this.wsClients.clear();\n }\n\n // ============================================\n // Public API - Configuration\n // ============================================\n\n /**\n * Get the telescope path\n */\n get path(): string {\n return this.options.path;\n }\n\n /**\n * Check if telescope is enabled\n */\n get enabled(): boolean {\n return this.options.enabled;\n }\n\n /**\n * Check if body recording is enabled\n */\n get recordBody(): boolean {\n return this.options.recordBody;\n }\n\n /**\n * Get max body size\n */\n get maxBodySize(): number {\n return this.options.maxBodySize;\n }\n\n /**\n * Check if a path should be ignored\n */\n shouldIgnore(path: string): boolean {\n // Always ignore telescope's own routes\n if (path.startsWith(this.options.path)) {\n return true;\n }\n\n return this.options.ignorePatterns.some((pattern) => {\n if (pattern.includes('*')) {\n const regex = new RegExp(\n '^' + pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.') + '$',\n );\n return regex.test(path);\n }\n return path.startsWith(pattern);\n });\n }\n\n // ============================================\n // Public API - Dashboard\n // ============================================\n\n /**\n * Get the dashboard HTML\n */\n getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Telescope</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #0f0f23;\n color: #e0e0e0;\n min-height: 100vh;\n }\n .container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 24px;\n }\n header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 24px;\n padding-bottom: 16px;\n border-bottom: 1px solid #333;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n h1::before {\n content: '';\n width: 8px;\n height: 8px;\n background: #10b981;\n border-radius: 50%;\n }\n .stats {\n display: flex;\n gap: 24px;\n font-size: 14px;\n color: #888;\n }\n .stat-value {\n color: #fff;\n font-weight: 500;\n }\n nav {\n display: flex;\n gap: 8px;\n margin-bottom: 24px;\n }\n nav a {\n padding: 8px 16px;\n background: #1a1a3e;\n border-radius: 6px;\n color: #e0e0e0;\n text-decoration: none;\n font-size: 14px;\n transition: background 0.2s;\n }\n nav a:hover, nav a.active { background: #2a2a5e; }\n .panel {\n background: #1a1a3e;\n border-radius: 8px;\n overflow: hidden;\n }\n .entry {\n display: grid;\n grid-template-columns: 70px 1fr 100px 80px;\n gap: 16px;\n padding: 12px 16px;\n border-bottom: 1px solid #252550;\n align-items: center;\n cursor: pointer;\n transition: background 0.2s;\n }\n .entry:hover { background: #252550; }\n .method {\n font-weight: 600;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 4px;\n text-align: center;\n }\n .GET { background: #10b981; color: #fff; }\n .POST { background: #3b82f6; color: #fff; }\n .PUT { background: #f59e0b; color: #fff; }\n .PATCH { background: #8b5cf6; color: #fff; }\n .DELETE { background: #ef4444; color: #fff; }\n .path { font-family: monospace; font-size: 13px; }\n .status { font-family: monospace; }\n .status-2xx { color: #10b981; }\n .status-3xx { color: #3b82f6; }\n .status-4xx { color: #f59e0b; }\n .status-5xx { color: #ef4444; }\n .duration { color: #888; font-size: 13px; }\n .empty {\n padding: 48px;\n text-align: center;\n color: #666;\n }\n #entries { max-height: calc(100vh - 200px); overflow-y: auto; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <header>\n <h1>Telescope</h1>\n <div class=\"stats\">\n <span>Requests: <span class=\"stat-value\" id=\"request-count\">-</span></span>\n <span>Exceptions: <span class=\"stat-value\" id=\"exception-count\">-</span></span>\n <span>Logs: <span class=\"stat-value\" id=\"log-count\">-</span></span>\n </div>\n </header>\n\n <nav>\n <a href=\"#\" class=\"active\" data-view=\"requests\">Requests</a>\n <a href=\"#\" data-view=\"exceptions\">Exceptions</a>\n <a href=\"#\" data-view=\"logs\">Logs</a>\n </nav>\n\n <div class=\"panel\">\n <div id=\"entries\"></div>\n </div>\n </div>\n\n <script>\n let currentView = 'requests';\n const basePath = window.location.pathname.replace(/\\\\/$/, '');\n\n async function fetchStats() {\n try {\n const res = await fetch(basePath + '/api/stats');\n const stats = await res.json();\n document.getElementById('request-count').textContent = stats.requests;\n document.getElementById('exception-count').textContent = stats.exceptions;\n document.getElementById('log-count').textContent = stats.logs;\n } catch (e) {\n console.error('Failed to fetch stats:', e);\n }\n }\n\n async function fetchData(type) {\n try {\n const res = await fetch(basePath + '/api/' + type);\n return await res.json();\n } catch (e) {\n console.error('Failed to fetch ' + type + ':', e);\n return [];\n }\n }\n\n function renderRequests(requests) {\n const container = document.getElementById('entries');\n if (requests.length === 0) {\n container.innerHTML = '<div class=\"empty\">No requests recorded yet</div>';\n return;\n }\n container.innerHTML = requests.map(r => \\`\n <div class=\"entry\">\n <span class=\"method \\${r.method}\">\\${r.method}</span>\n <span class=\"path\">\\${r.path}</span>\n <span class=\"status status-\\${Math.floor(r.status/100)}xx\">\\${r.status}</span>\n <span class=\"duration\">\\${r.duration.toFixed(1)}ms</span>\n </div>\n \\`).join('');\n }\n\n function renderExceptions(exceptions) {\n const container = document.getElementById('entries');\n if (exceptions.length === 0) {\n container.innerHTML = '<div class=\"empty\">No exceptions recorded yet</div>';\n return;\n }\n container.innerHTML = exceptions.map(e => \\`\n <div class=\"entry\" style=\"grid-template-columns: 1fr 200px;\">\n <div>\n <div style=\"color: #ef4444; font-weight: 500;\">\\${e.name}</div>\n <div style=\"font-size: 13px; color: #888; margin-top: 4px;\">\\${e.message}</div>\n </div>\n <span class=\"duration\">\\${new Date(e.timestamp).toLocaleTimeString()}</span>\n </div>\n \\`).join('');\n }\n\n function renderLogs(logs) {\n const container = document.getElementById('entries');\n if (logs.length === 0) {\n container.innerHTML = '<div class=\"empty\">No logs recorded yet</div>';\n return;\n }\n const levelColors = { debug: '#888', info: '#3b82f6', warn: '#f59e0b', error: '#ef4444' };\n container.innerHTML = logs.map(l => \\`\n <div class=\"entry\" style=\"grid-template-columns: 60px 1fr 100px;\">\n <span style=\"color: \\${levelColors[l.level]}; font-size: 12px; text-transform: uppercase;\">\\${l.level}</span>\n <span style=\"font-family: monospace; font-size: 13px;\">\\${l.message}</span>\n <span class=\"duration\">\\${new Date(l.timestamp).toLocaleTimeString()}</span>\n </div>\n \\`).join('');\n }\n\n async function loadView(view) {\n currentView = view;\n document.querySelectorAll('nav a').forEach(a => {\n a.classList.toggle('active', a.dataset.view === view);\n });\n\n const data = await fetchData(view);\n if (view === 'requests') renderRequests(data);\n else if (view === 'exceptions') renderExceptions(data);\n else if (view === 'logs') renderLogs(data);\n }\n\n // Navigation\n document.querySelectorAll('nav a').forEach(a => {\n a.addEventListener('click', (e) => {\n e.preventDefault();\n loadView(a.dataset.view);\n });\n });\n\n // WebSocket for real-time updates\n function connectWs() {\n const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n const ws = new WebSocket(protocol + '//' + location.host + basePath + '/ws');\n\n ws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n if (msg.type === currentView.slice(0, -1) ||\n (msg.type === 'request' && currentView === 'requests') ||\n (msg.type === 'exception' && currentView === 'exceptions') ||\n (msg.type === 'log' && currentView === 'logs')) {\n loadView(currentView);\n }\n fetchStats();\n };\n\n ws.onclose = () => {\n setTimeout(connectWs, 1000);\n };\n }\n\n // Initial load\n fetchStats();\n loadView('requests');\n connectWs();\n </script>\n</body>\n</html>`;\n }\n\n // ============================================\n // Private Methods\n // ============================================\n\n private normalizeOptions(\n options: TelescopeOptions,\n ): NormalizedTelescopeOptions {\n return {\n storage: options.storage,\n enabled: options.enabled ?? true,\n path: options.path ?? '/__telescope',\n recordBody: options.recordBody ?? true,\n maxBodySize: options.maxBodySize ?? 64 * 1024, // 64KB\n ignorePatterns: options.ignorePatterns ?? [],\n pruneAfterHours: options.pruneAfterHours,\n };\n }\n\n private parseStack(stack: string): StackFrame[] {\n const lines = stack.split('\\n').slice(1);\n const frames: StackFrame[] = [];\n\n for (const line of lines) {\n // Match: \" at functionName (file:line:column)\"\n // or: \" at file:line:column\"\n const match =\n line.match(/at\\s+(.+?)\\s+\\((.+):(\\d+):(\\d+)\\)/) ||\n line.match(/at\\s+(.+):(\\d+):(\\d+)/);\n\n if (match) {\n if (match.length === 5) {\n // Has function name\n frames.push({\n function: match[1],\n file: match[2],\n line: parseInt(match[3], 10),\n column: parseInt(match[4], 10),\n isApp: !match[2].includes('node_modules'),\n });\n } else if (match.length === 4) {\n // No function name\n frames.push({\n function: '<anonymous>',\n file: match[1],\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10),\n isApp: !match[1].includes('node_modules'),\n });\n }\n }\n }\n\n return frames;\n }\n\n private async autoPrune(): Promise<void> {\n if (!this.options.pruneAfterHours) return;\n\n const olderThan = new Date(\n Date.now() - this.options.pruneAfterHours * 60 * 60 * 1000,\n );\n await this.storage.prune(olderThan);\n }\n}\n"],"mappings":"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"}