@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,411 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import type { ExceptionEntry, LogEntry, RequestEntry } from '../../types';
3
+ import { InMemoryStorage } from '../memory';
4
+
5
+ describe('InMemoryStorage', () => {
6
+ let storage: InMemoryStorage;
7
+
8
+ beforeEach(() => {
9
+ storage = new InMemoryStorage();
10
+ });
11
+
12
+ describe('requests', () => {
13
+ const createRequest = (
14
+ overrides: Partial<RequestEntry> = {},
15
+ ): RequestEntry => ({
16
+ id: `req-${Math.random().toString(36).slice(2)}`,
17
+ method: 'GET',
18
+ path: '/api/users',
19
+ url: 'http://localhost:3000/api/users',
20
+ headers: { 'content-type': 'application/json' },
21
+ query: {},
22
+ status: 200,
23
+ responseHeaders: {},
24
+ duration: 50,
25
+ timestamp: new Date(),
26
+ ...overrides,
27
+ });
28
+
29
+ it('should save and retrieve a request', async () => {
30
+ const request = createRequest({ id: 'req-123' });
31
+ await storage.saveRequest(request);
32
+
33
+ const retrieved = await storage.getRequest('req-123');
34
+ expect(retrieved).toEqual(request);
35
+ });
36
+
37
+ it('should return null for non-existent request', async () => {
38
+ const result = await storage.getRequest('non-existent');
39
+ expect(result).toBeNull();
40
+ });
41
+
42
+ it('should list requests in reverse chronological order', async () => {
43
+ const req1 = createRequest({
44
+ id: 'req-1',
45
+ timestamp: new Date('2024-01-01T10:00:00Z'),
46
+ });
47
+ const req2 = createRequest({
48
+ id: 'req-2',
49
+ timestamp: new Date('2024-01-01T11:00:00Z'),
50
+ });
51
+ const req3 = createRequest({
52
+ id: 'req-3',
53
+ timestamp: new Date('2024-01-01T12:00:00Z'),
54
+ });
55
+
56
+ await storage.saveRequest(req1);
57
+ await storage.saveRequest(req2);
58
+ await storage.saveRequest(req3);
59
+
60
+ const requests = await storage.getRequests();
61
+ expect(requests.map((r) => r.id)).toEqual(['req-3', 'req-2', 'req-1']);
62
+ });
63
+
64
+ it('should respect limit and offset options', async () => {
65
+ for (let i = 0; i < 10; i++) {
66
+ await storage.saveRequest(
67
+ createRequest({
68
+ id: `req-${i}`,
69
+ timestamp: new Date(Date.now() + i * 1000),
70
+ }),
71
+ );
72
+ }
73
+
74
+ const page1 = await storage.getRequests({ limit: 3, offset: 0 });
75
+ expect(page1).toHaveLength(3);
76
+ expect(page1[0].id).toBe('req-9');
77
+
78
+ const page2 = await storage.getRequests({ limit: 3, offset: 3 });
79
+ expect(page2).toHaveLength(3);
80
+ expect(page2[0].id).toBe('req-6');
81
+ });
82
+
83
+ it('should filter by search term in path', async () => {
84
+ await storage.saveRequest(
85
+ createRequest({
86
+ id: 'req-1',
87
+ path: '/api/users',
88
+ url: 'http://localhost/api/users',
89
+ }),
90
+ );
91
+ await storage.saveRequest(
92
+ createRequest({
93
+ id: 'req-2',
94
+ path: '/api/posts',
95
+ url: 'http://localhost/api/posts',
96
+ }),
97
+ );
98
+ await storage.saveRequest(
99
+ createRequest({
100
+ id: 'req-3',
101
+ path: '/api/orders',
102
+ url: 'http://localhost/api/orders',
103
+ }),
104
+ );
105
+
106
+ const results = await storage.getRequests({ search: 'posts' });
107
+ expect(results).toHaveLength(1);
108
+ expect(results[0].id).toBe('req-2');
109
+ });
110
+
111
+ it('should filter by date range', async () => {
112
+ await storage.saveRequest(
113
+ createRequest({ id: 'req-1', timestamp: new Date('2024-01-01') }),
114
+ );
115
+ await storage.saveRequest(
116
+ createRequest({ id: 'req-2', timestamp: new Date('2024-01-15') }),
117
+ );
118
+ await storage.saveRequest(
119
+ createRequest({ id: 'req-3', timestamp: new Date('2024-02-01') }),
120
+ );
121
+
122
+ const results = await storage.getRequests({
123
+ after: new Date('2024-01-10'),
124
+ before: new Date('2024-01-20'),
125
+ });
126
+
127
+ expect(results).toHaveLength(1);
128
+ expect(results[0].id).toBe('req-2');
129
+ });
130
+
131
+ it('should filter by tags', async () => {
132
+ await storage.saveRequest(
133
+ createRequest({ id: 'req-1', tags: ['auth', 'api'] }),
134
+ );
135
+ await storage.saveRequest(createRequest({ id: 'req-2', tags: ['api'] }));
136
+ await storage.saveRequest(
137
+ createRequest({ id: 'req-3', tags: ['admin'] }),
138
+ );
139
+
140
+ const results = await storage.getRequests({ tags: ['auth'] });
141
+ expect(results).toHaveLength(1);
142
+ expect(results[0].id).toBe('req-1');
143
+ });
144
+
145
+ it('should enforce maxEntries limit', async () => {
146
+ const smallStorage = new InMemoryStorage({ maxEntries: 5 });
147
+
148
+ for (let i = 0; i < 10; i++) {
149
+ await smallStorage.saveRequest(
150
+ createRequest({
151
+ id: `req-${i}`,
152
+ timestamp: new Date(Date.now() + i * 1000),
153
+ }),
154
+ );
155
+ }
156
+
157
+ const requests = await smallStorage.getRequests();
158
+ expect(requests).toHaveLength(5);
159
+ // Should keep the newest entries
160
+ expect(requests[0].id).toBe('req-9');
161
+ expect(requests[4].id).toBe('req-5');
162
+ });
163
+ });
164
+
165
+ describe('exceptions', () => {
166
+ const createException = (
167
+ overrides: Partial<ExceptionEntry> = {},
168
+ ): ExceptionEntry => ({
169
+ id: `exc-${Math.random().toString(36).slice(2)}`,
170
+ name: 'Error',
171
+ message: 'Something went wrong',
172
+ stack: [
173
+ {
174
+ file: '/app/src/index.ts',
175
+ line: 10,
176
+ column: 5,
177
+ function: 'main',
178
+ isApp: true,
179
+ },
180
+ ],
181
+ timestamp: new Date(),
182
+ handled: false,
183
+ ...overrides,
184
+ });
185
+
186
+ it('should save and retrieve an exception', async () => {
187
+ const exception = createException({ id: 'exc-123' });
188
+ await storage.saveException(exception);
189
+
190
+ const retrieved = await storage.getException('exc-123');
191
+ expect(retrieved).toEqual(exception);
192
+ });
193
+
194
+ it('should return null for non-existent exception', async () => {
195
+ const result = await storage.getException('non-existent');
196
+ expect(result).toBeNull();
197
+ });
198
+
199
+ it('should list exceptions in reverse chronological order', async () => {
200
+ await storage.saveException(
201
+ createException({
202
+ id: 'exc-1',
203
+ timestamp: new Date('2024-01-01T10:00:00Z'),
204
+ }),
205
+ );
206
+ await storage.saveException(
207
+ createException({
208
+ id: 'exc-2',
209
+ timestamp: new Date('2024-01-01T11:00:00Z'),
210
+ }),
211
+ );
212
+
213
+ const exceptions = await storage.getExceptions();
214
+ expect(exceptions.map((e) => e.id)).toEqual(['exc-2', 'exc-1']);
215
+ });
216
+
217
+ it('should filter exceptions by search term in message', async () => {
218
+ await storage.saveException(
219
+ createException({ id: 'exc-1', message: 'Database connection failed' }),
220
+ );
221
+ await storage.saveException(
222
+ createException({ id: 'exc-2', message: 'Invalid user input' }),
223
+ );
224
+
225
+ const results = await storage.getExceptions({ search: 'database' });
226
+ expect(results).toHaveLength(1);
227
+ expect(results[0].id).toBe('exc-1');
228
+ });
229
+ });
230
+
231
+ describe('logs', () => {
232
+ const createLog = (overrides: Partial<LogEntry> = {}): LogEntry => ({
233
+ id: `log-${Math.random().toString(36).slice(2)}`,
234
+ level: 'info',
235
+ message: 'Test log message',
236
+ timestamp: new Date(),
237
+ ...overrides,
238
+ });
239
+
240
+ it('should save and retrieve logs', async () => {
241
+ await storage.saveLog(createLog({ id: 'log-1', message: 'First log' }));
242
+ await storage.saveLog(createLog({ id: 'log-2', message: 'Second log' }));
243
+
244
+ const logs = await storage.getLogs();
245
+ expect(logs).toHaveLength(2);
246
+ });
247
+
248
+ it('should filter logs by level via search', async () => {
249
+ await storage.saveLog(createLog({ id: 'log-1', level: 'debug' }));
250
+ await storage.saveLog(createLog({ id: 'log-2', level: 'error' }));
251
+ await storage.saveLog(createLog({ id: 'log-3', level: 'error' }));
252
+
253
+ const results = await storage.getLogs({ search: 'error' });
254
+ expect(results).toHaveLength(2);
255
+ });
256
+
257
+ it('should include context in logs', async () => {
258
+ await storage.saveLog(
259
+ createLog({
260
+ id: 'log-1',
261
+ context: { userId: '123', action: 'login' },
262
+ }),
263
+ );
264
+
265
+ const logs = await storage.getLogs();
266
+ expect(logs[0].context).toEqual({ userId: '123', action: 'login' });
267
+ });
268
+ });
269
+
270
+ describe('prune', () => {
271
+ it('should remove entries older than specified date', async () => {
272
+ const oldDate = new Date('2024-01-01');
273
+ const newDate = new Date('2024-06-01');
274
+
275
+ await storage.saveRequest({
276
+ id: 'req-old',
277
+ method: 'GET',
278
+ path: '/old',
279
+ url: 'http://localhost/old',
280
+ headers: {},
281
+ query: {},
282
+ status: 200,
283
+ responseHeaders: {},
284
+ duration: 10,
285
+ timestamp: oldDate,
286
+ });
287
+
288
+ await storage.saveRequest({
289
+ id: 'req-new',
290
+ method: 'GET',
291
+ path: '/new',
292
+ url: 'http://localhost/new',
293
+ headers: {},
294
+ query: {},
295
+ status: 200,
296
+ responseHeaders: {},
297
+ duration: 10,
298
+ timestamp: newDate,
299
+ });
300
+
301
+ await storage.saveException({
302
+ id: 'exc-old',
303
+ name: 'Error',
304
+ message: 'Old error',
305
+ stack: [],
306
+ timestamp: oldDate,
307
+ handled: false,
308
+ });
309
+
310
+ await storage.saveLog({
311
+ id: 'log-old',
312
+ level: 'info',
313
+ message: 'Old log',
314
+ timestamp: oldDate,
315
+ });
316
+
317
+ const pruneDate = new Date('2024-03-01');
318
+ const deleted = await storage.prune(pruneDate);
319
+
320
+ expect(deleted).toBe(3);
321
+
322
+ const requests = await storage.getRequests();
323
+ expect(requests).toHaveLength(1);
324
+ expect(requests[0].id).toBe('req-new');
325
+
326
+ const exceptions = await storage.getExceptions();
327
+ expect(exceptions).toHaveLength(0);
328
+
329
+ const logs = await storage.getLogs();
330
+ expect(logs).toHaveLength(0);
331
+ });
332
+ });
333
+
334
+ describe('getStats', () => {
335
+ it('should return correct statistics', async () => {
336
+ await storage.saveRequest({
337
+ id: 'req-1',
338
+ method: 'GET',
339
+ path: '/api',
340
+ url: 'http://localhost/api',
341
+ headers: {},
342
+ query: {},
343
+ status: 200,
344
+ responseHeaders: {},
345
+ duration: 10,
346
+ timestamp: new Date(),
347
+ });
348
+
349
+ await storage.saveException({
350
+ id: 'exc-1',
351
+ name: 'Error',
352
+ message: 'Test',
353
+ stack: [],
354
+ timestamp: new Date(),
355
+ handled: false,
356
+ });
357
+
358
+ await storage.saveLog({
359
+ id: 'log-1',
360
+ level: 'info',
361
+ message: 'Test',
362
+ timestamp: new Date(),
363
+ });
364
+ await storage.saveLog({
365
+ id: 'log-2',
366
+ level: 'error',
367
+ message: 'Test',
368
+ timestamp: new Date(),
369
+ });
370
+
371
+ const stats = await storage.getStats();
372
+
373
+ expect(stats.requests).toBe(1);
374
+ expect(stats.exceptions).toBe(1);
375
+ expect(stats.logs).toBe(2);
376
+ });
377
+ });
378
+
379
+ describe('clear', () => {
380
+ it('should remove all entries', async () => {
381
+ await storage.saveRequest({
382
+ id: 'req-1',
383
+ method: 'GET',
384
+ path: '/api',
385
+ url: 'http://localhost/api',
386
+ headers: {},
387
+ query: {},
388
+ status: 200,
389
+ responseHeaders: {},
390
+ duration: 10,
391
+ timestamp: new Date(),
392
+ });
393
+
394
+ await storage.saveException({
395
+ id: 'exc-1',
396
+ name: 'Error',
397
+ message: 'Test',
398
+ stack: [],
399
+ timestamp: new Date(),
400
+ handled: false,
401
+ });
402
+
403
+ storage.clear();
404
+
405
+ const stats = await storage.getStats();
406
+ expect(stats.requests).toBe(0);
407
+ expect(stats.exceptions).toBe(0);
408
+ expect(stats.logs).toBe(0);
409
+ });
410
+ });
411
+ });