@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.
- package/README.md +521 -0
- package/dist/Telescope-B3Wd82yk.cjs +602 -0
- package/dist/Telescope-B3Wd82yk.cjs.map +1 -0
- package/dist/Telescope-C5dyDYYB.d.cts +133 -0
- package/dist/Telescope-D-uoZB6b.mjs +596 -0
- package/dist/Telescope-D-uoZB6b.mjs.map +1 -0
- package/dist/Telescope-DyIWgh9-.d.mts +133 -0
- package/dist/Telescope.cjs +3 -0
- package/dist/Telescope.d.cts +3 -0
- package/dist/Telescope.d.mts +3 -0
- package/dist/Telescope.mjs +3 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/logger/console.cjs +161 -0
- package/dist/logger/console.cjs.map +1 -0
- package/dist/logger/console.d.cts +109 -0
- package/dist/logger/console.d.mts +109 -0
- package/dist/logger/console.mjs +159 -0
- package/dist/logger/console.mjs.map +1 -0
- package/dist/logger/pino.cjs +118 -0
- package/dist/logger/pino.cjs.map +1 -0
- package/dist/logger/pino.d.cts +89 -0
- package/dist/logger/pino.d.mts +89 -0
- package/dist/logger/pino.mjs +116 -0
- package/dist/logger/pino.mjs.map +1 -0
- package/dist/memory-9-B9WACq.cjs +110 -0
- package/dist/memory-9-B9WACq.cjs.map +1 -0
- package/dist/memory-Cm0eevCS.d.mts +38 -0
- package/dist/memory-DiP1a-pp.d.cts +38 -0
- package/dist/memory-SdN5vtG9.mjs +104 -0
- package/dist/memory-SdN5vtG9.mjs.map +1 -0
- package/dist/server/hono.cjs +180 -0
- package/dist/server/hono.cjs.map +1 -0
- package/dist/server/hono.d.cts +26 -0
- package/dist/server/hono.d.mts +26 -0
- package/dist/server/hono.mjs +176 -0
- package/dist/server/hono.mjs.map +1 -0
- package/dist/storage/kysely.cjs +336 -0
- package/dist/storage/kysely.cjs.map +1 -0
- package/dist/storage/kysely.d.cts +161 -0
- package/dist/storage/kysely.d.mts +161 -0
- package/dist/storage/kysely.mjs +334 -0
- package/dist/storage/kysely.mjs.map +1 -0
- package/dist/storage/memory.cjs +3 -0
- package/dist/storage/memory.d.cts +3 -0
- package/dist/storage/memory.d.mts +3 -0
- package/dist/storage/memory.mjs +3 -0
- package/dist/types-BGDhFv4R.d.cts +170 -0
- package/dist/types-CZbzz8kx.d.mts +170 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +0 -0
- package/dist/ui-assets-D6-8TAr_.mjs +30 -0
- package/dist/ui-assets-D6-8TAr_.mjs.map +1 -0
- package/dist/ui-assets-ulevVble.cjs +48 -0
- package/dist/ui-assets-ulevVble.cjs.map +1 -0
- package/dist/ui-assets.cjs +5 -0
- package/dist/ui-assets.d.cts +12 -0
- package/dist/ui-assets.d.mts +12 -0
- package/dist/ui-assets.mjs +3 -0
- package/package.json +83 -0
- package/scripts/embed-ui.ts +90 -0
- package/src/Telescope.ts +714 -0
- package/src/__tests__/Telescope.spec.ts +356 -0
- package/src/index.ts +23 -0
- package/src/logger/__tests__/console.spec.ts +266 -0
- package/src/logger/__tests__/pino.spec.ts +217 -0
- package/src/logger/console.ts +230 -0
- package/src/logger/pino.ts +191 -0
- package/src/server/__tests__/hono.spec.ts +340 -0
- package/src/server/hono.ts +247 -0
- package/src/storage/__tests__/kysely.spec.ts +715 -0
- package/src/storage/__tests__/memory.spec.ts +411 -0
- package/src/storage/kysely.ts +572 -0
- package/src/storage/memory.ts +168 -0
- package/src/types.ts +188 -0
- package/src/ui-assets.ts +40 -0
- package/ui/index.html +12 -0
- package/ui/node_modules/.bin/browserslist +21 -0
- package/ui/node_modules/.bin/jiti +21 -0
- package/ui/node_modules/.bin/terser +21 -0
- package/ui/node_modules/.bin/tsc +21 -0
- package/ui/node_modules/.bin/tsserver +21 -0
- package/ui/node_modules/.bin/tsx +21 -0
- package/ui/node_modules/.bin/vite +21 -0
- package/ui/package.json +24 -0
- package/ui/src/App.tsx +342 -0
- package/ui/src/api.ts +75 -0
- package/ui/src/components/ExceptionDetail.tsx +100 -0
- package/ui/src/components/LogDetail.tsx +91 -0
- package/ui/src/components/RequestDetail.tsx +143 -0
- package/ui/src/main.tsx +10 -0
- package/ui/src/styles.css +10 -0
- package/ui/src/types.ts +63 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/src/vite-plugin-gkm-config.ts +54 -0
- package/ui/tsconfig.json +20 -0
- package/ui/tsconfig.tsbuildinfo +14 -0
- package/ui/vite.config.ts +13 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExceptionEntry,
|
|
3
|
+
LogEntry,
|
|
4
|
+
QueryOptions,
|
|
5
|
+
RequestEntry,
|
|
6
|
+
TelescopeStats,
|
|
7
|
+
TelescopeStorage,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
export interface InMemoryStorageOptions {
|
|
11
|
+
/** Maximum number of entries to keep per type (default: 1000) */
|
|
12
|
+
maxEntries?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* In-memory storage for Telescope data.
|
|
17
|
+
* Ideal for development and testing.
|
|
18
|
+
* Data is lost when the process restarts.
|
|
19
|
+
*/
|
|
20
|
+
export class InMemoryStorage implements TelescopeStorage {
|
|
21
|
+
private requests: RequestEntry[] = [];
|
|
22
|
+
private exceptions: ExceptionEntry[] = [];
|
|
23
|
+
private logs: LogEntry[] = [];
|
|
24
|
+
private maxEntries: number;
|
|
25
|
+
|
|
26
|
+
constructor(options?: InMemoryStorageOptions) {
|
|
27
|
+
this.maxEntries = options?.maxEntries ?? 1000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Requests
|
|
31
|
+
|
|
32
|
+
async saveRequest(entry: RequestEntry): Promise<void> {
|
|
33
|
+
this.requests.unshift(entry);
|
|
34
|
+
this.enforceLimit('requests');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async saveRequests(entries: RequestEntry[]): Promise<void> {
|
|
38
|
+
this.requests.unshift(...entries);
|
|
39
|
+
this.enforceLimit('requests');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getRequests(options?: QueryOptions): Promise<RequestEntry[]> {
|
|
43
|
+
return this.filterEntries(this.requests, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getRequest(id: string): Promise<RequestEntry | null> {
|
|
47
|
+
return this.requests.find((r) => r.id === id) ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Exceptions
|
|
51
|
+
|
|
52
|
+
async saveException(entry: ExceptionEntry): Promise<void> {
|
|
53
|
+
this.exceptions.unshift(entry);
|
|
54
|
+
this.enforceLimit('exceptions');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async saveExceptions(entries: ExceptionEntry[]): Promise<void> {
|
|
58
|
+
this.exceptions.unshift(...entries);
|
|
59
|
+
this.enforceLimit('exceptions');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]> {
|
|
63
|
+
return this.filterEntries(this.exceptions, options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getException(id: string): Promise<ExceptionEntry | null> {
|
|
67
|
+
return this.exceptions.find((e) => e.id === id) ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Logs
|
|
71
|
+
|
|
72
|
+
async saveLog(entry: LogEntry): Promise<void> {
|
|
73
|
+
this.logs.unshift(entry);
|
|
74
|
+
this.enforceLimit('logs');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async saveLogs(entries: LogEntry[]): Promise<void> {
|
|
78
|
+
this.logs.unshift(...entries);
|
|
79
|
+
this.enforceLimit('logs');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getLogs(options?: QueryOptions): Promise<LogEntry[]> {
|
|
83
|
+
return this.filterEntries(this.logs, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Cleanup
|
|
87
|
+
|
|
88
|
+
async prune(olderThan: Date): Promise<number> {
|
|
89
|
+
const beforeCount =
|
|
90
|
+
this.requests.length + this.exceptions.length + this.logs.length;
|
|
91
|
+
|
|
92
|
+
this.requests = this.requests.filter((r) => r.timestamp >= olderThan);
|
|
93
|
+
this.exceptions = this.exceptions.filter((e) => e.timestamp >= olderThan);
|
|
94
|
+
this.logs = this.logs.filter((l) => l.timestamp >= olderThan);
|
|
95
|
+
|
|
96
|
+
const afterCount =
|
|
97
|
+
this.requests.length + this.exceptions.length + this.logs.length;
|
|
98
|
+
return beforeCount - afterCount;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Stats
|
|
102
|
+
|
|
103
|
+
async getStats(): Promise<TelescopeStats> {
|
|
104
|
+
const allTimestamps = [
|
|
105
|
+
...this.requests.map((r) => r.timestamp),
|
|
106
|
+
...this.exceptions.map((e) => e.timestamp),
|
|
107
|
+
...this.logs.map((l) => l.timestamp),
|
|
108
|
+
].sort((a, b) => a.getTime() - b.getTime());
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
requests: this.requests.length,
|
|
112
|
+
exceptions: this.exceptions.length,
|
|
113
|
+
logs: this.logs.length,
|
|
114
|
+
oldestEntry: allTimestamps[0],
|
|
115
|
+
newestEntry: allTimestamps[allTimestamps.length - 1],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Clear all data (useful for testing)
|
|
120
|
+
|
|
121
|
+
clear(): void {
|
|
122
|
+
this.requests = [];
|
|
123
|
+
this.exceptions = [];
|
|
124
|
+
this.logs = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Private helpers
|
|
128
|
+
|
|
129
|
+
private enforceLimit(type: 'requests' | 'exceptions' | 'logs'): void {
|
|
130
|
+
if (this[type].length > this.maxEntries) {
|
|
131
|
+
this[type] = this[type].slice(0, this.maxEntries);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private filterEntries<T extends { timestamp: Date; tags?: string[] }>(
|
|
136
|
+
entries: T[],
|
|
137
|
+
options?: QueryOptions,
|
|
138
|
+
): T[] {
|
|
139
|
+
let result = entries;
|
|
140
|
+
|
|
141
|
+
if (options?.after) {
|
|
142
|
+
result = result.filter((e) => e.timestamp >= options.after!);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options?.before) {
|
|
146
|
+
result = result.filter((e) => e.timestamp <= options.before!);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (options?.tags && options.tags.length > 0) {
|
|
150
|
+
result = result.filter(
|
|
151
|
+
(e) => e.tags && options.tags!.some((t) => e.tags!.includes(t)),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (options?.search) {
|
|
156
|
+
const searchLower = options.search.toLowerCase();
|
|
157
|
+
result = result.filter((e) => {
|
|
158
|
+
const str = JSON.stringify(e).toLowerCase();
|
|
159
|
+
return str.includes(searchLower);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const offset = options?.offset ?? 0;
|
|
164
|
+
const limit = options?.limit ?? 50;
|
|
165
|
+
|
|
166
|
+
return result.slice(offset, offset + limit);
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack frame from a parsed error stack trace
|
|
3
|
+
*/
|
|
4
|
+
export interface StackFrame {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
function: string;
|
|
9
|
+
/** Whether this frame is from application code (vs node_modules) */
|
|
10
|
+
isApp: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Source code context around an error
|
|
15
|
+
*/
|
|
16
|
+
export interface SourceContext {
|
|
17
|
+
file: string;
|
|
18
|
+
line: number;
|
|
19
|
+
column: number;
|
|
20
|
+
lines: Array<{ num: number; code: string; highlight: boolean }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Recorded HTTP request entry
|
|
25
|
+
*/
|
|
26
|
+
export interface RequestEntry {
|
|
27
|
+
id: string;
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
url: string;
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
body?: unknown;
|
|
33
|
+
query?: Record<string, string>;
|
|
34
|
+
status: number;
|
|
35
|
+
responseHeaders: Record<string, string>;
|
|
36
|
+
responseBody?: unknown;
|
|
37
|
+
duration: number;
|
|
38
|
+
timestamp: Date;
|
|
39
|
+
ip?: string;
|
|
40
|
+
userId?: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recorded exception entry
|
|
46
|
+
*/
|
|
47
|
+
export interface ExceptionEntry {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
message: string;
|
|
51
|
+
stack: StackFrame[];
|
|
52
|
+
source?: SourceContext;
|
|
53
|
+
requestId?: string;
|
|
54
|
+
timestamp: Date;
|
|
55
|
+
handled: boolean;
|
|
56
|
+
tags?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Recorded log entry
|
|
61
|
+
*/
|
|
62
|
+
export interface LogEntry {
|
|
63
|
+
id: string;
|
|
64
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
65
|
+
message: string;
|
|
66
|
+
context?: Record<string, unknown>;
|
|
67
|
+
requestId?: string;
|
|
68
|
+
timestamp: Date;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Query options for retrieving entries
|
|
73
|
+
*/
|
|
74
|
+
export interface QueryOptions {
|
|
75
|
+
limit?: number;
|
|
76
|
+
offset?: number;
|
|
77
|
+
before?: Date;
|
|
78
|
+
after?: Date;
|
|
79
|
+
search?: string;
|
|
80
|
+
tags?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Storage interface for Telescope data
|
|
85
|
+
* Implementations can use memory, database, or any other backend
|
|
86
|
+
*/
|
|
87
|
+
export interface TelescopeStorage {
|
|
88
|
+
// Requests
|
|
89
|
+
saveRequest(entry: RequestEntry): Promise<void>;
|
|
90
|
+
saveRequests?(entries: RequestEntry[]): Promise<void>;
|
|
91
|
+
getRequests(options?: QueryOptions): Promise<RequestEntry[]>;
|
|
92
|
+
getRequest(id: string): Promise<RequestEntry | null>;
|
|
93
|
+
|
|
94
|
+
// Exceptions
|
|
95
|
+
saveException(entry: ExceptionEntry): Promise<void>;
|
|
96
|
+
saveExceptions?(entries: ExceptionEntry[]): Promise<void>;
|
|
97
|
+
getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]>;
|
|
98
|
+
getException(id: string): Promise<ExceptionEntry | null>;
|
|
99
|
+
|
|
100
|
+
// Logs
|
|
101
|
+
saveLog(entry: LogEntry): Promise<void>;
|
|
102
|
+
saveLogs?(entries: LogEntry[]): Promise<void>;
|
|
103
|
+
getLogs(options?: QueryOptions): Promise<LogEntry[]>;
|
|
104
|
+
|
|
105
|
+
// Cleanup
|
|
106
|
+
prune(olderThan: Date): Promise<number>;
|
|
107
|
+
|
|
108
|
+
// Stats
|
|
109
|
+
getStats(): Promise<TelescopeStats>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Statistics about stored entries
|
|
114
|
+
*/
|
|
115
|
+
export interface TelescopeStats {
|
|
116
|
+
requests: number;
|
|
117
|
+
exceptions: number;
|
|
118
|
+
logs: number;
|
|
119
|
+
oldestEntry?: Date;
|
|
120
|
+
newestEntry?: Date;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Configuration options for Telescope
|
|
125
|
+
*/
|
|
126
|
+
export interface TelescopeOptions {
|
|
127
|
+
/** Storage backend for persisting data */
|
|
128
|
+
storage: TelescopeStorage;
|
|
129
|
+
/** Whether telescope is enabled (default: true) */
|
|
130
|
+
enabled?: boolean;
|
|
131
|
+
/** Dashboard path (default: '/__telescope') */
|
|
132
|
+
path?: string;
|
|
133
|
+
/** Whether to record request/response bodies (default: true) */
|
|
134
|
+
recordBody?: boolean;
|
|
135
|
+
/** Maximum body size to record in bytes (default: 64KB) */
|
|
136
|
+
maxBodySize?: number;
|
|
137
|
+
/** URL patterns to ignore (default: []) */
|
|
138
|
+
ignorePatterns?: string[];
|
|
139
|
+
/** Auto-prune entries older than this many hours (default: undefined - no auto-prune) */
|
|
140
|
+
pruneAfterHours?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalized telescope options with defaults applied
|
|
145
|
+
*/
|
|
146
|
+
export interface NormalizedTelescopeOptions {
|
|
147
|
+
storage: TelescopeStorage;
|
|
148
|
+
enabled: boolean;
|
|
149
|
+
path: string;
|
|
150
|
+
recordBody: boolean;
|
|
151
|
+
maxBodySize: number;
|
|
152
|
+
ignorePatterns: string[];
|
|
153
|
+
pruneAfterHours?: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* WebSocket event types
|
|
158
|
+
*/
|
|
159
|
+
export type TelescopeEventType =
|
|
160
|
+
| 'request'
|
|
161
|
+
| 'exception'
|
|
162
|
+
| 'log'
|
|
163
|
+
| 'stats'
|
|
164
|
+
| 'connected';
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* WebSocket event payload
|
|
168
|
+
*/
|
|
169
|
+
export interface TelescopeEvent<T = unknown> {
|
|
170
|
+
type: TelescopeEventType;
|
|
171
|
+
payload: T;
|
|
172
|
+
timestamp: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Context stored during request processing
|
|
177
|
+
*/
|
|
178
|
+
export interface RequestContext {
|
|
179
|
+
id: string;
|
|
180
|
+
startTime: number;
|
|
181
|
+
method: string;
|
|
182
|
+
path: string;
|
|
183
|
+
url: string;
|
|
184
|
+
headers: Record<string, string>;
|
|
185
|
+
query: Record<string, string>;
|
|
186
|
+
body?: unknown;
|
|
187
|
+
ip?: string;
|
|
188
|
+
}
|