devlog-ui 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.
@@ -0,0 +1,796 @@
1
+ /**
2
+ * DevLogger - Browser-based Dev Logger with UI
3
+ *
4
+ * A lightweight, framework-agnostic dev logger with a beautiful debug UI.
5
+ * Zero dependencies, zero production overhead (via tree-shaking).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { logger, DevLoggerUI } from 'devlogger';
10
+ *
11
+ * DevLoggerUI.init();
12
+ * logger.info('App started');
13
+ * logger.debug('Config loaded', { theme: 'dark' });
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
19
+ /**
20
+ * Clear all persisted data
21
+ */
22
+ declare function clear(): void;
23
+
24
+ /**
25
+ * Compute diff between two objects
26
+ */
27
+ export declare function computeDiff(oldObj: unknown, newObj: unknown, path?: string, includeUnchanged?: boolean): DiffEntry[];
28
+
29
+ /**
30
+ * Context-bound logger - logs with specific context attached
31
+ */
32
+ export declare class ContextLogger {
33
+ private logger;
34
+ private context;
35
+ constructor(logger: LoggerCore, context: LogContext);
36
+ debug(message: string, ...data: unknown[]): void;
37
+ info(message: string, ...data: unknown[]): void;
38
+ warn(message: string, ...data: unknown[]): void;
39
+ error(message: string, ...data: unknown[]): void;
40
+ /** Create a span with this context */
41
+ span(name: string, extraContext?: LogContext): LogSpan;
42
+ /** Create a new context logger with merged context */
43
+ withContext(extraContext: LogContext): ContextLogger;
44
+ }
45
+
46
+ /**
47
+ * Create a full diff result with summary
48
+ */
49
+ export declare function createDiffResult(oldObj: unknown, newObj: unknown): DiffResult;
50
+
51
+ /**
52
+ * Create a timeline instance
53
+ */
54
+ export declare function createTimeline(config: TimelineConfig): Timeline;
55
+
56
+ /**
57
+ * DevLogger UI Public API
58
+ */
59
+ export declare const DevLoggerUI: {
60
+ /**
61
+ * Initialize the overlay UI
62
+ */
63
+ init(): void;
64
+ /**
65
+ * Open the overlay panel
66
+ */
67
+ open(): void;
68
+ /**
69
+ * Close the overlay panel
70
+ */
71
+ close(): void;
72
+ /**
73
+ * Toggle the overlay panel
74
+ */
75
+ toggle(): void;
76
+ /**
77
+ * Open logs in a separate window
78
+ */
79
+ popout(): void;
80
+ /**
81
+ * Close the pop-out window
82
+ */
83
+ closePopout(): void;
84
+ /**
85
+ * Check if pop-out window is open
86
+ */
87
+ isPopoutOpen(): boolean;
88
+ /**
89
+ * Set filter state
90
+ */
91
+ setFilter(filter: Partial<FilterState>): void;
92
+ /**
93
+ * Get current filter state
94
+ */
95
+ getFilter(): FilterState;
96
+ /**
97
+ * Clear all filters
98
+ */
99
+ clearFilter(): void;
100
+ /**
101
+ * Destroy the UI and clean up
102
+ */
103
+ destroy(): void;
104
+ /**
105
+ * Check if the UI is currently visible
106
+ */
107
+ isVisible(): boolean;
108
+ /**
109
+ * Check if the UI has been initialized
110
+ */
111
+ isInitialized(): boolean;
112
+ };
113
+
114
+ /**
115
+ * Diff change type
116
+ */
117
+ export declare type DiffChangeType = 'added' | 'removed' | 'changed' | 'unchanged';
118
+
119
+ /**
120
+ * A single diff entry for object comparison
121
+ */
122
+ export declare interface DiffEntry {
123
+ /** Path to the property (e.g., "user.profile.name") */
124
+ path: string;
125
+ /** Type of change */
126
+ type: DiffChangeType;
127
+ /** Old value (for removed/changed) */
128
+ oldValue?: unknown;
129
+ /** New value (for added/changed) */
130
+ newValue?: unknown;
131
+ }
132
+
133
+ /**
134
+ * Diff result for object comparison
135
+ */
136
+ export declare interface DiffResult {
137
+ /** All changes found */
138
+ changes: DiffEntry[];
139
+ /** Summary counts */
140
+ summary: {
141
+ added: number;
142
+ removed: number;
143
+ changed: number;
144
+ unchanged: number;
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Disable persistence
150
+ */
151
+ declare function disable(): void;
152
+
153
+ /**
154
+ * Enable persistence
155
+ */
156
+ declare function enable(options?: PersistenceConfig): void;
157
+
158
+ /**
159
+ * Error Capture Public API
160
+ */
161
+ export declare const ErrorCapture: {
162
+ /**
163
+ * Install global error handlers
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * // Install with defaults
168
+ * ErrorCapture.install();
169
+ *
170
+ * // Install with custom config
171
+ * ErrorCapture.install({
172
+ * captureErrors: true,
173
+ * captureRejections: true,
174
+ * errorPrefix: '[ERROR]',
175
+ * });
176
+ * ```
177
+ */
178
+ install: typeof install;
179
+ /**
180
+ * Uninstall global error handlers and restore originals
181
+ */
182
+ uninstall: typeof uninstall;
183
+ /**
184
+ * Check if error capture is currently active
185
+ */
186
+ isActive: typeof isActive;
187
+ /**
188
+ * Get current configuration
189
+ */
190
+ getConfig: typeof getConfig;
191
+ };
192
+
193
+ /**
194
+ * Global Error Capture Module
195
+ *
196
+ * Automatically captures uncaught errors and unhandled promise rejections
197
+ * and logs them through the DevLogger system.
198
+ *
199
+ * Features:
200
+ * - window.onerror for synchronous errors
201
+ * - window.onunhandledrejection for promise rejections
202
+ * - Preserves existing handlers (chaining)
203
+ * - Can be enabled/disabled at runtime
204
+ * - Zero-throw policy maintained
205
+ */
206
+ /** Configuration for error capture */
207
+ export declare interface ErrorCaptureConfig {
208
+ /** Capture synchronous errors via window.onerror */
209
+ captureErrors?: boolean;
210
+ /** Capture unhandled promise rejections */
211
+ captureRejections?: boolean;
212
+ /** Prefix for error messages */
213
+ errorPrefix?: string;
214
+ /** Prefix for rejection messages */
215
+ rejectionPrefix?: string;
216
+ }
217
+
218
+ /**
219
+ * Export format options
220
+ */
221
+ export declare interface ExportOptions {
222
+ /** Export format: 'json' or 'text' */
223
+ format?: 'json' | 'text';
224
+ /** Include only logs from the last N milliseconds */
225
+ lastMs?: number;
226
+ /** Include only logs matching these levels */
227
+ levels?: LogLevel[];
228
+ /** Include only logs matching this search */
229
+ search?: string;
230
+ /** Pretty print JSON (default: true) */
231
+ pretty?: boolean;
232
+ }
233
+
234
+ /**
235
+ * Filter state interface
236
+ */
237
+ export declare interface FilterState {
238
+ /** Active log levels (empty = all) */
239
+ levels: Set<LogLevel>;
240
+ /** Text search query */
241
+ search: string;
242
+ /** File filter (partial match) */
243
+ file: string;
244
+ }
245
+
246
+ /**
247
+ * Format a value for display (handles special types)
248
+ */
249
+ export declare function formatValue(value: unknown): string;
250
+
251
+ /**
252
+ * Get current configuration
253
+ */
254
+ declare function getConfig(): Readonly<Required<ErrorCaptureConfig>>;
255
+
256
+ /**
257
+ * Get current configuration
258
+ */
259
+ declare function getConfig_2(): Readonly<Required<PersistenceConfig>>;
260
+
261
+ /**
262
+ * Get persisted logs (for rehydration)
263
+ */
264
+ declare function getPersistedLogs(): LogEvent[];
265
+
266
+ /**
267
+ * Check if last session crashed
268
+ */
269
+ declare function hadCrash(): boolean;
270
+
271
+ /**
272
+ * Check if there are any changes
273
+ */
274
+ export declare function hasChanges(diff: DiffResult): boolean;
275
+
276
+ /**
277
+ * Install global error handlers
278
+ */
279
+ declare function install(options?: ErrorCaptureConfig): void;
280
+
281
+ /**
282
+ * Check if error capture is installed
283
+ */
284
+ declare function isActive(): boolean;
285
+
286
+ /**
287
+ * Check if persistence is enabled
288
+ */
289
+ declare function isActive_2(): boolean;
290
+
291
+ /**
292
+ * Context/tags for log correlation
293
+ */
294
+ export declare type LogContext = Record<string, string | number | boolean>;
295
+
296
+ /**
297
+ * A single log event with all metadata
298
+ */
299
+ export declare interface LogEvent {
300
+ /** Unique identifier for this log entry */
301
+ id: string;
302
+ /** Unix timestamp in milliseconds */
303
+ timestamp: number;
304
+ /** Log severity level */
305
+ level: LogLevel;
306
+ /** Primary log message */
307
+ message: string;
308
+ /** Additional data passed to the log call */
309
+ data: unknown[];
310
+ /** Source code location */
311
+ source: Source;
312
+ /** Browser session identifier */
313
+ sessionId: string;
314
+ /** Optional context/tags for filtering */
315
+ context?: LogContext;
316
+ /** Span ID if this log belongs to a span */
317
+ spanId?: string;
318
+ }
319
+
320
+ export declare const logger: LoggerCore;
321
+
322
+ /**
323
+ * Logger configuration options
324
+ */
325
+ export declare interface LoggerConfig {
326
+ /** Maximum number of logs to keep in memory (default: 1000) */
327
+ maxLogs?: number;
328
+ /** Persist logs to sessionStorage (default: false) */
329
+ persist?: boolean;
330
+ /** Minimum log level to capture (default: 'debug') */
331
+ minLevel?: LogLevel;
332
+ /** Enable/disable logging entirely (default: true) */
333
+ enabled?: boolean;
334
+ }
335
+
336
+ /**
337
+ * Core Logger Class
338
+ *
339
+ * Lifecycle of a log:
340
+ * 1. Log is created (info/warn/error/debug called)
341
+ * 2. Log is enriched (timestamp, source, sessionId added)
342
+ * 3. Log is stored (in-memory, with rotation)
343
+ * 4. Log is distributed (subscribers notified)
344
+ * 5. Log is displayed (by UI subscribers)
345
+ */
346
+ declare class LoggerCore {
347
+ private logs;
348
+ private spans;
349
+ private subscribers;
350
+ private spanSubscribers;
351
+ private config;
352
+ private sessionId;
353
+ private globalContext;
354
+ constructor();
355
+ /**
356
+ * Core logging method - all public methods delegate to this
357
+ */
358
+ private log;
359
+ /**
360
+ * Log with context (used by ContextLogger)
361
+ */
362
+ private logWithContext;
363
+ /**
364
+ * Log with span (used by LogSpan)
365
+ */
366
+ private logWithSpan;
367
+ /**
368
+ * Notify span subscribers
369
+ */
370
+ private notifySpan;
371
+ /**
372
+ * Safely clone data to prevent mutations and handle special cases
373
+ */
374
+ private safeCloneData;
375
+ /**
376
+ * Deep clone with circular reference handling
377
+ */
378
+ private safeClone;
379
+ /**
380
+ * Store log with FIFO rotation
381
+ */
382
+ private store;
383
+ /**
384
+ * Notify all subscribers of new log
385
+ */
386
+ private notify;
387
+ /**
388
+ * Log an info message
389
+ */
390
+ info(message: string, ...data: unknown[]): void;
391
+ /**
392
+ * Log a warning message
393
+ */
394
+ warn(message: string, ...data: unknown[]): void;
395
+ /**
396
+ * Log an error message
397
+ */
398
+ error(message: string, ...data: unknown[]): void;
399
+ /**
400
+ * Log a debug message
401
+ */
402
+ debug(message: string, ...data: unknown[]): void;
403
+ /**
404
+ * Update logger configuration
405
+ */
406
+ configure(config: Partial<LoggerConfig>): void;
407
+ /**
408
+ * Clear all stored logs and spans
409
+ */
410
+ clear(): void;
411
+ /**
412
+ * Import logs (used for rehydration from persistence)
413
+ * Imported logs are added at the beginning, preserving order
414
+ */
415
+ importLogs(logs: LogEvent[]): void;
416
+ /**
417
+ * Get all stored logs (readonly)
418
+ */
419
+ getLogs(): readonly LogEvent[];
420
+ /**
421
+ * Subscribe to new log events
422
+ */
423
+ subscribe(fn: LogSubscriber): Unsubscribe;
424
+ /**
425
+ * Get current session ID
426
+ */
427
+ getSessionId(): string;
428
+ /**
429
+ * Get current configuration
430
+ */
431
+ getConfig(): Readonly<Required<LoggerConfig>>;
432
+ /**
433
+ * Check if logging is currently enabled
434
+ */
435
+ isEnabled(): boolean;
436
+ /**
437
+ * Create a new span for grouping related logs
438
+ */
439
+ span(name: string, context?: LogContext): LogSpan;
440
+ /**
441
+ * Get all spans
442
+ */
443
+ getSpans(): readonly SpanEvent[];
444
+ /**
445
+ * Get a specific span by ID
446
+ */
447
+ getSpan(spanId: string): SpanEvent | undefined;
448
+ /**
449
+ * Get logs belonging to a specific span
450
+ */
451
+ getSpanLogs(spanId: string): readonly LogEvent[];
452
+ /**
453
+ * Subscribe to span events
454
+ */
455
+ subscribeSpans(fn: SpanSubscriber): Unsubscribe;
456
+ /**
457
+ * Set global context that will be attached to all logs
458
+ */
459
+ setGlobalContext(context: LogContext): void;
460
+ /**
461
+ * Update global context (merge with existing)
462
+ */
463
+ updateGlobalContext(context: LogContext): void;
464
+ /**
465
+ * Get current global context
466
+ */
467
+ getGlobalContext(): Readonly<LogContext>;
468
+ /**
469
+ * Clear global context
470
+ */
471
+ clearGlobalContext(): void;
472
+ /**
473
+ * Create a context-bound logger
474
+ */
475
+ withContext(context: LogContext): ContextLogger;
476
+ /**
477
+ * Export logs in specified format
478
+ */
479
+ exportLogs(options?: ExportOptions): string;
480
+ /**
481
+ * Format logs as human-readable text
482
+ */
483
+ private formatLogsAsText;
484
+ /**
485
+ * Copy logs to clipboard
486
+ */
487
+ copyLogs(options?: ExportOptions): Promise<boolean>;
488
+ /**
489
+ * Log a visual diff between two objects
490
+ */
491
+ diff(message: string, oldObj: unknown, newObj: unknown, level?: LogLevel): DiffResult;
492
+ /**
493
+ * Compute diff without logging (utility method)
494
+ */
495
+ computeDiff(oldObj: unknown, newObj: unknown): DiffResult;
496
+ }
497
+
498
+ /**
499
+ * Core type definitions for DevLogger
500
+ *
501
+ * These types define the contract for all logging operations.
502
+ * They are stable and should not change without major version bump.
503
+ */
504
+ /**
505
+ * Available log levels in order of severity
506
+ */
507
+ export declare type LogLevel = 'debug' | 'info' | 'warn' | 'error';
508
+
509
+ /**
510
+ * Log Persistence Public API
511
+ */
512
+ export declare const LogPersistence: {
513
+ /**
514
+ * Enable log persistence
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * LogPersistence.enable();
519
+ *
520
+ * // With options
521
+ * LogPersistence.enable({
522
+ * storage: 'session',
523
+ * maxPersisted: 500,
524
+ * debounceMs: 100
525
+ * });
526
+ * ```
527
+ */
528
+ enable: typeof enable;
529
+ /**
530
+ * Disable log persistence
531
+ */
532
+ disable: typeof disable;
533
+ /**
534
+ * Check if persistence is enabled
535
+ */
536
+ isActive: typeof isActive_2;
537
+ /**
538
+ * Check if the last session had a crash (unclean shutdown)
539
+ */
540
+ hadCrash: typeof hadCrash;
541
+ /**
542
+ * Get persisted logs from previous session (without importing)
543
+ */
544
+ getPersistedLogs: typeof getPersistedLogs;
545
+ /**
546
+ * Rehydrate logs from storage into the logger
547
+ * Call this at app startup to restore logs from previous session
548
+ *
549
+ * @returns Number of logs rehydrated
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * // At app start
554
+ * LogPersistence.enable();
555
+ * const count = LogPersistence.rehydrate();
556
+ * if (LogPersistence.hadCrash()) {
557
+ * console.log(`Recovered ${count} logs from crash`);
558
+ * }
559
+ * ```
560
+ */
561
+ rehydrate: typeof rehydrate;
562
+ /**
563
+ * Clear all persisted logs
564
+ */
565
+ clear: typeof clear;
566
+ /**
567
+ * Get current configuration
568
+ */
569
+ getConfig: typeof getConfig_2;
570
+ };
571
+
572
+ /**
573
+ * Log Span class - represents a grouped set of related logs
574
+ */
575
+ export declare class LogSpan {
576
+ private logger;
577
+ private _event;
578
+ private _ended;
579
+ constructor(logger: LoggerCore, name: string, context?: LogContext, parentId?: string);
580
+ /** Get span ID */
581
+ get id(): string;
582
+ /** Get span event data */
583
+ get event(): Readonly<SpanEvent>;
584
+ /** Check if span has ended */
585
+ get ended(): boolean;
586
+ /** Log debug within this span */
587
+ debug(message: string, ...data: unknown[]): void;
588
+ /** Log info within this span */
589
+ info(message: string, ...data: unknown[]): void;
590
+ /** Log warn within this span */
591
+ warn(message: string, ...data: unknown[]): void;
592
+ /** Log error within this span */
593
+ error(message: string, ...data: unknown[]): void;
594
+ /** Create a child span */
595
+ span(name: string, context?: LogContext): LogSpan;
596
+ /** End the span successfully */
597
+ end(): void;
598
+ /** End the span with error status */
599
+ fail(error?: Error | string): void;
600
+ /** Internal finish method */
601
+ private finish;
602
+ }
603
+
604
+ /**
605
+ * Subscriber callback for new log events
606
+ */
607
+ export declare type LogSubscriber = (event: LogEvent) => void;
608
+
609
+ /**
610
+ * Network Capture API
611
+ */
612
+ export declare const NetworkCapture: {
613
+ /**
614
+ * Install network capture hooks
615
+ */
616
+ install(userConfig?: NetworkCaptureConfig): void;
617
+ /**
618
+ * Uninstall network capture hooks
619
+ */
620
+ uninstall(): void;
621
+ /**
622
+ * Check if capture is active
623
+ */
624
+ isActive(): boolean;
625
+ /**
626
+ * Get current configuration
627
+ */
628
+ getConfig(): Readonly<Required<NetworkCaptureConfig>>;
629
+ /**
630
+ * Update ignore patterns
631
+ */
632
+ addIgnorePattern(pattern: string | RegExp): void;
633
+ };
634
+
635
+ /**
636
+ * Network capture configuration
637
+ */
638
+ export declare interface NetworkCaptureConfig {
639
+ /** Capture fetch requests (default: true) */
640
+ captureFetch?: boolean;
641
+ /** Capture XHR requests (default: true) */
642
+ captureXHR?: boolean;
643
+ /** Include request headers (default: false, may contain sensitive data) */
644
+ includeHeaders?: boolean;
645
+ /** Include request body (default: false, may be large) */
646
+ includeBody?: boolean;
647
+ /** Include response body (default: false, may be large) */
648
+ includeResponse?: boolean;
649
+ /** Max response body length to capture (default: 1000) */
650
+ maxResponseLength?: number;
651
+ /** URL patterns to ignore (e.g., analytics, hot reload) */
652
+ ignorePatterns?: (string | RegExp)[];
653
+ /** Custom context to add to all network logs */
654
+ context?: LogContext;
655
+ }
656
+
657
+ /** Configuration for persistence */
658
+ export declare interface PersistenceConfig {
659
+ /** Storage type: 'session' (sessionStorage) or 'local' (localStorage) */
660
+ storage?: 'session' | 'local';
661
+ /** Maximum logs to persist (default: 500) */
662
+ maxPersisted?: number;
663
+ /** Debounce delay in ms for batching writes (default: 100) */
664
+ debounceMs?: number;
665
+ }
666
+
667
+ /**
668
+ * Rehydrate logs from storage into the logger
669
+ * Returns number of logs rehydrated
670
+ */
671
+ declare function rehydrate(): number;
672
+
673
+ /**
674
+ * Source location of a log entry
675
+ */
676
+ export declare interface Source {
677
+ /** File path or name */
678
+ file: string;
679
+ /** Line number (1-based) */
680
+ line: number;
681
+ /** Column number (1-based, optional) */
682
+ column?: number;
683
+ /** Function or method name (optional) */
684
+ function?: string;
685
+ }
686
+
687
+ /**
688
+ * A span (grouped logs) event
689
+ */
690
+ export declare interface SpanEvent {
691
+ /** Unique span identifier */
692
+ id: string;
693
+ /** Span name/label */
694
+ name: string;
695
+ /** Start timestamp */
696
+ startTime: number;
697
+ /** End timestamp (set when span ends) */
698
+ endTime?: number;
699
+ /** Duration in milliseconds */
700
+ duration?: number;
701
+ /** Current status */
702
+ status: SpanStatus;
703
+ /** Parent span ID for nesting */
704
+ parentId?: string;
705
+ /** Context inherited by all logs in this span */
706
+ context?: LogContext;
707
+ /** Source where span was created */
708
+ source: Source;
709
+ /** Session ID */
710
+ sessionId: string;
711
+ }
712
+
713
+ /**
714
+ * Span status for grouped logs
715
+ */
716
+ export declare type SpanStatus = 'running' | 'success' | 'error';
717
+
718
+ /**
719
+ * Subscriber callback for span events
720
+ */
721
+ export declare type SpanSubscriber = (event: SpanEvent) => void;
722
+
723
+ /**
724
+ * Timeline class
725
+ */
726
+ export declare class Timeline {
727
+ private container;
728
+ private config;
729
+ private canvas;
730
+ private ctx;
731
+ private intervalId;
732
+ private tooltip;
733
+ private spanBounds;
734
+ private logBounds;
735
+ constructor(userConfig: TimelineConfig);
736
+ private init;
737
+ private resizeCanvas;
738
+ private startRefresh;
739
+ private stopRefresh;
740
+ setTimeWindow(ms: number): void;
741
+ private render;
742
+ private drawTimeGrid;
743
+ private drawSpans;
744
+ private drawLogs;
745
+ private handleMouseMove;
746
+ private handleMouseLeave;
747
+ private showTooltip;
748
+ private hideTooltip;
749
+ /**
750
+ * Destroy the timeline
751
+ */
752
+ destroy(): void;
753
+ }
754
+
755
+ /**
756
+ * Timeline / Flame-View Component
757
+ *
758
+ * Lightweight timeline visualization for logs and spans.
759
+ * Features:
760
+ * - Logs displayed on time axis
761
+ * - Spans visualized as bars
762
+ * - Zoom to last X seconds
763
+ * - Hover for details
764
+ */
765
+ /**
766
+ * Timeline configuration
767
+ */
768
+ export declare interface TimelineConfig {
769
+ /** Container element or selector */
770
+ container: HTMLElement | string;
771
+ /** Time window in milliseconds (default: 60000 = 1 minute) */
772
+ timeWindow?: number;
773
+ /** Auto-refresh interval in ms (default: 1000) */
774
+ refreshInterval?: number;
775
+ /** Show span bars (default: true) */
776
+ showSpans?: boolean;
777
+ /** Show log markers (default: true) */
778
+ showLogs?: boolean;
779
+ /** Height of timeline in pixels (default: 200) */
780
+ height?: number;
781
+ }
782
+
783
+ /**
784
+ * Uninstall global error handlers and restore originals
785
+ */
786
+ declare function uninstall(): void;
787
+
788
+ /**
789
+ * Function to unsubscribe from log events
790
+ */
791
+ export declare type Unsubscribe = () => void;
792
+
793
+ /** Current package version */
794
+ export declare const VERSION = "0.1.0";
795
+
796
+ export { }