@kysera/debug 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LuxQuant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,663 @@
1
+ # @kysera/debug
2
+
3
+ Debug utilities for Kysera ORM - query logging, profiling, SQL formatting, and performance analysis.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # npm
9
+ npm install @kysera/debug
10
+
11
+ # pnpm
12
+ pnpm add @kysera/debug
13
+
14
+ # yarn
15
+ yarn add @kysera/debug
16
+
17
+ # bun
18
+ bun add @kysera/debug
19
+ ```
20
+
21
+ ## Features
22
+
23
+ - **Query Logging** - Automatic logging of SQL queries and execution times
24
+ - **Performance Metrics** - Collect and analyze query performance data
25
+ - **Slow Query Detection** - Identify and alert on slow database queries
26
+ - **SQL Formatting** - Format and highlight SQL for better readability
27
+ - **Query Profiling** - Detailed performance analysis with statistics
28
+ - **Circular Buffer** - Memory-efficient metrics storage with automatic cleanup
29
+ - **Zero Dependencies** - Only depends on `@kysera/core` and peer-depends on `kysely`
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { Kysely } from 'kysely';
35
+ import { withDebug } from '@kysera/debug';
36
+
37
+ // Wrap your database with debug capabilities
38
+ const db = new Kysely<Database>({ /* config */ });
39
+ const debugDb = withDebug(db);
40
+
41
+ // Execute queries - they're automatically logged and timed
42
+ await debugDb.selectFrom('users').selectAll().execute();
43
+
44
+ // Get collected metrics
45
+ const metrics = debugDb.getMetrics();
46
+ console.log(`Executed ${metrics.length} queries`);
47
+ ```
48
+
49
+ ## API Documentation
50
+
51
+ ### withDebug()
52
+
53
+ Wraps a Kysely database instance with debug capabilities, adding query logging, metrics collection, and slow query detection.
54
+
55
+ ```typescript
56
+ function withDebug<DB>(
57
+ db: Kysely<DB>,
58
+ options?: DebugOptions
59
+ ): DebugDatabase<DB>
60
+ ```
61
+
62
+ #### Parameters
63
+
64
+ - `db` - Kysely database instance to wrap
65
+ - `options` - Optional debug configuration
66
+
67
+ #### Returns
68
+
69
+ `DebugDatabase<DB>` - Enhanced database instance with debug methods
70
+
71
+ #### Example
72
+
73
+ ```typescript
74
+ import { withDebug } from '@kysera/debug';
75
+
76
+ const debugDb = withDebug(db, {
77
+ logQuery: true,
78
+ logParams: true,
79
+ slowQueryThreshold: 100,
80
+ maxMetrics: 1000,
81
+ onSlowQuery: (sql, duration) => {
82
+ console.warn(`Slow query detected: ${duration}ms`);
83
+ },
84
+ });
85
+ ```
86
+
87
+ ### DebugOptions
88
+
89
+ Configuration options for the debug plugin.
90
+
91
+ ```typescript
92
+ interface DebugOptions {
93
+ /**
94
+ * Log query SQL.
95
+ * @default true
96
+ */
97
+ logQuery?: boolean;
98
+
99
+ /**
100
+ * Log query parameters.
101
+ * @default false
102
+ */
103
+ logParams?: boolean;
104
+
105
+ /**
106
+ * Duration threshold (ms) to consider a query slow.
107
+ * @default 100
108
+ */
109
+ slowQueryThreshold?: number;
110
+
111
+ /**
112
+ * Callback for slow queries.
113
+ */
114
+ onSlowQuery?: (sql: string, duration: number) => void;
115
+
116
+ /**
117
+ * Logger for debug messages.
118
+ * @default consoleLogger
119
+ */
120
+ logger?: KyseraLogger;
121
+
122
+ /**
123
+ * Maximum number of metrics to keep in memory.
124
+ * When limit is reached, oldest metrics are removed (circular buffer).
125
+ * @default 1000
126
+ */
127
+ maxMetrics?: number;
128
+ }
129
+ ```
130
+
131
+ ### DebugDatabase
132
+
133
+ Extended database interface with debug capabilities.
134
+
135
+ ```typescript
136
+ interface DebugDatabase<DB> extends Kysely<DB> {
137
+ /** Get all collected query metrics */
138
+ getMetrics(): QueryMetrics[];
139
+
140
+ /** Clear all collected metrics */
141
+ clearMetrics(): void;
142
+ }
143
+ ```
144
+
145
+ #### Methods
146
+
147
+ ##### getMetrics()
148
+
149
+ Returns all collected query metrics.
150
+
151
+ ```typescript
152
+ const metrics = debugDb.getMetrics();
153
+ console.log(`Total queries: ${metrics.length}`);
154
+ console.log(`Average duration: ${metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length}ms`);
155
+ ```
156
+
157
+ ##### clearMetrics()
158
+
159
+ Clears all collected metrics from memory.
160
+
161
+ ```typescript
162
+ debugDb.clearMetrics();
163
+ ```
164
+
165
+ ### QueryMetrics
166
+
167
+ Query performance metrics data.
168
+
169
+ ```typescript
170
+ interface QueryMetrics {
171
+ /** SQL query string */
172
+ sql: string;
173
+
174
+ /** Query parameters */
175
+ params?: unknown[];
176
+
177
+ /** Query execution duration in milliseconds */
178
+ duration: number;
179
+
180
+ /** Timestamp when query was executed */
181
+ timestamp: number;
182
+ }
183
+ ```
184
+
185
+ ### QueryProfiler
186
+
187
+ Advanced query profiler for collecting and analyzing query performance with detailed statistics.
188
+
189
+ ```typescript
190
+ class QueryProfiler {
191
+ constructor(options?: ProfilerOptions);
192
+
193
+ /** Record a query metric */
194
+ record(metric: QueryMetrics): void;
195
+
196
+ /** Get profiling summary */
197
+ getSummary(): ProfilerSummary;
198
+
199
+ /** Get the slowest N queries */
200
+ getSlowestQueries(count: number): QueryMetrics[];
201
+
202
+ /** Get queries slower than a threshold */
203
+ getSlowQueries(thresholdMs: number): QueryMetrics[];
204
+
205
+ /** Clear all recorded queries */
206
+ clear(): void;
207
+
208
+ /** Get the number of recorded queries */
209
+ get count(): number;
210
+ }
211
+ ```
212
+
213
+ #### Example
214
+
215
+ ```typescript
216
+ import { QueryProfiler } from '@kysera/debug';
217
+
218
+ const profiler = new QueryProfiler({ maxQueries: 500 });
219
+
220
+ // Record queries manually
221
+ profiler.record({
222
+ sql: 'SELECT * FROM users WHERE id = $1',
223
+ params: [123],
224
+ duration: 10.5,
225
+ timestamp: Date.now(),
226
+ });
227
+
228
+ // Get summary
229
+ const summary = profiler.getSummary();
230
+ console.log(`Total queries: ${summary.totalQueries}`);
231
+ console.log(`Average duration: ${summary.averageDuration.toFixed(2)}ms`);
232
+ console.log(`Slowest query: ${summary.slowestQuery?.duration.toFixed(2)}ms`);
233
+
234
+ // Get slow queries
235
+ const slowQueries = profiler.getSlowQueries(50);
236
+ console.log(`Queries slower than 50ms: ${slowQueries.length}`);
237
+
238
+ // Get top 10 slowest
239
+ const top10 = profiler.getSlowestQueries(10);
240
+ ```
241
+
242
+ ### ProfilerOptions
243
+
244
+ Configuration options for QueryProfiler.
245
+
246
+ ```typescript
247
+ interface ProfilerOptions {
248
+ /**
249
+ * Maximum number of queries to keep in memory.
250
+ * @default 1000
251
+ */
252
+ maxQueries?: number;
253
+ }
254
+ ```
255
+
256
+ ### ProfilerSummary
257
+
258
+ Summary statistics from the query profiler.
259
+
260
+ ```typescript
261
+ interface ProfilerSummary {
262
+ /** Total number of recorded queries */
263
+ totalQueries: number;
264
+
265
+ /** Sum of all query durations */
266
+ totalDuration: number;
267
+
268
+ /** Average query duration */
269
+ averageDuration: number;
270
+
271
+ /** Slowest recorded query */
272
+ slowestQuery: QueryMetrics | null;
273
+
274
+ /** Fastest recorded query */
275
+ fastestQuery: QueryMetrics | null;
276
+
277
+ /** All recorded queries */
278
+ queries: QueryMetrics[];
279
+ }
280
+ ```
281
+
282
+ ### SQL Formatting Functions
283
+
284
+ #### formatSQL()
285
+
286
+ Format SQL for better readability by adding newlines before major keywords.
287
+
288
+ ```typescript
289
+ function formatSQL(sql: string): string
290
+ ```
291
+
292
+ **Example:**
293
+
294
+ ```typescript
295
+ import { formatSQL } from '@kysera/debug';
296
+
297
+ const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name';
298
+ console.log(formatSQL(sql));
299
+ // Output:
300
+ // SELECT id, name
301
+ // FROM users
302
+ // WHERE active = true
303
+ // ORDER BY name
304
+ ```
305
+
306
+ #### formatSQLPretty()
307
+
308
+ Format SQL with indentation for nested queries and subqueries.
309
+
310
+ ```typescript
311
+ function formatSQLPretty(sql: string, indentSize?: number): string
312
+ ```
313
+
314
+ **Parameters:**
315
+ - `sql` - SQL string to format
316
+ - `indentSize` - Number of spaces for indentation (default: 2)
317
+
318
+ **Example:**
319
+
320
+ ```typescript
321
+ import { formatSQLPretty } from '@kysera/debug';
322
+
323
+ const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100)';
324
+ console.log(formatSQLPretty(sql));
325
+ // Output with proper indentation for subqueries
326
+ ```
327
+
328
+ #### minifySQL()
329
+
330
+ Minify SQL by removing unnecessary whitespace.
331
+
332
+ ```typescript
333
+ function minifySQL(sql: string): string
334
+ ```
335
+
336
+ **Example:**
337
+
338
+ ```typescript
339
+ import { minifySQL } from '@kysera/debug';
340
+
341
+ const sql = `
342
+ SELECT id, name
343
+ FROM users
344
+ WHERE active = true
345
+ `;
346
+ console.log(minifySQL(sql));
347
+ // Output: SELECT id, name FROM users WHERE active = true
348
+ ```
349
+
350
+ #### highlightSQL()
351
+
352
+ Highlight SQL keywords with ANSI color codes for terminal output.
353
+
354
+ ```typescript
355
+ function highlightSQL(sql: string): string
356
+ ```
357
+
358
+ **Example:**
359
+
360
+ ```typescript
361
+ import { highlightSQL } from '@kysera/debug';
362
+
363
+ console.log(highlightSQL('SELECT * FROM users WHERE active = true'));
364
+ // Keywords will be highlighted in blue in terminal
365
+ ```
366
+
367
+ ## Usage Examples
368
+
369
+ ### Basic Query Logging
370
+
371
+ ```typescript
372
+ import { Kysely } from 'kysely';
373
+ import { withDebug } from '@kysera/debug';
374
+
375
+ const db = new Kysely<Database>({ /* config */ });
376
+ const debugDb = withDebug(db, {
377
+ logQuery: true,
378
+ logParams: false,
379
+ });
380
+
381
+ // Queries are automatically logged
382
+ await debugDb
383
+ .selectFrom('users')
384
+ .where('active', '=', true)
385
+ .selectAll()
386
+ .execute();
387
+ // Console output:
388
+ // [SQL] SELECT * FROM "users" WHERE "active" = $1
389
+ // [Duration] 12.34ms
390
+ ```
391
+
392
+ ### Detecting Slow Queries
393
+
394
+ ```typescript
395
+ import { withDebug } from '@kysera/debug';
396
+
397
+ const debugDb = withDebug(db, {
398
+ slowQueryThreshold: 50, // 50ms threshold
399
+ onSlowQuery: (sql, duration) => {
400
+ // Send to monitoring service
401
+ monitoring.recordSlowQuery({ sql, duration });
402
+
403
+ // Log to error tracking
404
+ logger.warn(`Slow query detected: ${duration.toFixed(2)}ms`, { sql });
405
+ },
406
+ });
407
+
408
+ // If query takes > 50ms, callback is triggered
409
+ await debugDb.selectFrom('users').selectAll().execute();
410
+ ```
411
+
412
+ ### Collecting and Analyzing Metrics
413
+
414
+ ```typescript
415
+ import { withDebug, formatSQL } from '@kysera/debug';
416
+
417
+ const debugDb = withDebug(db, {
418
+ maxMetrics: 500, // Keep last 500 queries
419
+ });
420
+
421
+ // Execute some queries
422
+ await debugDb.selectFrom('users').selectAll().execute();
423
+ await debugDb.selectFrom('posts').selectAll().execute();
424
+
425
+ // Analyze metrics
426
+ const metrics = debugDb.getMetrics();
427
+ const totalDuration = metrics.reduce((sum, m) => sum + m.duration, 0);
428
+ const avgDuration = totalDuration / metrics.length;
429
+
430
+ console.log(`Total queries: ${metrics.length}`);
431
+ console.log(`Average duration: ${avgDuration.toFixed(2)}ms`);
432
+
433
+ // Find slowest query
434
+ const slowest = metrics.reduce((max, m) =>
435
+ m.duration > max.duration ? m : max
436
+ );
437
+ console.log('Slowest query:');
438
+ console.log(formatSQL(slowest.sql));
439
+ console.log(`Duration: ${slowest.duration.toFixed(2)}ms`);
440
+ ```
441
+
442
+ ### Advanced Profiling
443
+
444
+ ```typescript
445
+ import { QueryProfiler } from '@kysera/debug';
446
+
447
+ const profiler = new QueryProfiler({ maxQueries: 1000 });
448
+
449
+ // Record queries from debug database
450
+ const debugDb = withDebug(db);
451
+ // ... execute queries ...
452
+
453
+ const metrics = debugDb.getMetrics();
454
+ metrics.forEach(m => profiler.record(m));
455
+
456
+ // Get comprehensive summary
457
+ const summary = profiler.getSummary();
458
+ console.log('Query Performance Summary:');
459
+ console.log(` Total Queries: ${summary.totalQueries}`);
460
+ console.log(` Total Time: ${summary.totalDuration.toFixed(2)}ms`);
461
+ console.log(` Average: ${summary.averageDuration.toFixed(2)}ms`);
462
+ console.log(` Slowest: ${summary.slowestQuery?.duration.toFixed(2)}ms`);
463
+ console.log(` Fastest: ${summary.fastestQuery?.duration.toFixed(2)}ms`);
464
+
465
+ // Analyze slow queries
466
+ const slowQueries = profiler.getSlowQueries(100);
467
+ if (slowQueries.length > 0) {
468
+ console.log(`\nFound ${slowQueries.length} queries slower than 100ms:`);
469
+ slowQueries.forEach(q => {
470
+ console.log(` ${q.duration.toFixed(2)}ms: ${q.sql.substring(0, 80)}...`);
471
+ });
472
+ }
473
+
474
+ // Get top 5 slowest
475
+ const top5 = profiler.getSlowestQueries(5);
476
+ console.log('\nTop 5 Slowest Queries:');
477
+ top5.forEach((q, i) => {
478
+ console.log(`${i + 1}. ${q.duration.toFixed(2)}ms`);
479
+ console.log(formatSQL(q.sql));
480
+ console.log('');
481
+ });
482
+ ```
483
+
484
+ ### Custom Logger Integration
485
+
486
+ ```typescript
487
+ import { withDebug } from '@kysera/debug';
488
+ import type { KyseraLogger } from '@kysera/core';
489
+
490
+ // Custom logger implementation
491
+ const customLogger: KyseraLogger = {
492
+ debug: (message: string) => {
493
+ // Send to logging service
494
+ loggingService.debug('db-query', message);
495
+ },
496
+ info: (message: string) => {
497
+ loggingService.info('db-query', message);
498
+ },
499
+ warn: (message: string) => {
500
+ loggingService.warn('db-query', message);
501
+ },
502
+ error: (message: string) => {
503
+ loggingService.error('db-query', message);
504
+ },
505
+ };
506
+
507
+ const debugDb = withDebug(db, {
508
+ logger: customLogger,
509
+ logQuery: true,
510
+ logParams: true,
511
+ });
512
+ ```
513
+
514
+ ### Production Monitoring
515
+
516
+ ```typescript
517
+ import { withDebug } from '@kysera/debug';
518
+
519
+ const debugDb = withDebug(db, {
520
+ logQuery: false, // Don't log in production
521
+ slowQueryThreshold: 200, // Alert on queries > 200ms
522
+ maxMetrics: 100, // Keep only recent queries
523
+ onSlowQuery: (sql, duration) => {
524
+ // Send to APM
525
+ apm.recordTransaction({
526
+ type: 'db.query',
527
+ duration,
528
+ metadata: { sql },
529
+ });
530
+
531
+ // Alert if extremely slow
532
+ if (duration > 1000) {
533
+ alerting.critical('Database query exceeded 1 second', {
534
+ sql,
535
+ duration,
536
+ });
537
+ }
538
+ },
539
+ });
540
+
541
+ // Periodic metrics reporting
542
+ setInterval(() => {
543
+ const metrics = debugDb.getMetrics();
544
+ if (metrics.length > 0) {
545
+ const avgDuration = metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length;
546
+
547
+ monitoring.gauge('db.query.avg_duration', avgDuration);
548
+ monitoring.gauge('db.query.count', metrics.length);
549
+
550
+ debugDb.clearMetrics(); // Reset for next interval
551
+ }
552
+ }, 60000); // Every minute
553
+ ```
554
+
555
+ ### Formatting SQL for Display
556
+
557
+ ```typescript
558
+ import { formatSQL, formatSQLPretty, highlightSQL } from '@kysera/debug';
559
+
560
+ const sql = 'SELECT u.id, u.name, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON u.id = p.user_id WHERE u.active = true GROUP BY u.id, u.name ORDER BY post_count DESC';
561
+
562
+ // Basic formatting
563
+ console.log('Basic Format:');
564
+ console.log(formatSQL(sql));
565
+
566
+ // Pretty formatting with indentation
567
+ console.log('\nPretty Format:');
568
+ console.log(formatSQLPretty(sql));
569
+
570
+ // Highlighted for terminal
571
+ console.log('\nHighlighted:');
572
+ console.log(highlightSQL(formatSQL(sql)));
573
+ ```
574
+
575
+ ## TypeScript Support
576
+
577
+ Full TypeScript support with strict type checking:
578
+
579
+ ```typescript
580
+ import type {
581
+ QueryMetrics,
582
+ DebugOptions,
583
+ DebugDatabase,
584
+ ProfilerSummary,
585
+ ProfilerOptions,
586
+ } from '@kysera/debug';
587
+
588
+ // Type-safe debug options
589
+ const options: DebugOptions = {
590
+ logQuery: true,
591
+ slowQueryThreshold: 100,
592
+ onSlowQuery: (sql: string, duration: number) => {
593
+ console.warn(`Slow: ${sql} (${duration}ms)`);
594
+ },
595
+ };
596
+
597
+ // Type-safe database
598
+ interface Database {
599
+ users: { id: number; name: string };
600
+ }
601
+
602
+ const debugDb: DebugDatabase<Database> = withDebug(db, options);
603
+ ```
604
+
605
+ ## Performance Considerations
606
+
607
+ ### Memory Management
608
+
609
+ The debug plugin uses a circular buffer to manage memory efficiently:
610
+
611
+ - Default limit: 1000 metrics
612
+ - Oldest metrics automatically removed when limit reached
613
+ - Configure via `maxMetrics` option
614
+
615
+ ```typescript
616
+ const debugDb = withDebug(db, {
617
+ maxMetrics: 500, // Keep only last 500 queries
618
+ });
619
+ ```
620
+
621
+ ### Production Usage
622
+
623
+ For production environments:
624
+
625
+ 1. **Disable verbose logging:**
626
+ ```typescript
627
+ const debugDb = withDebug(db, {
628
+ logQuery: false,
629
+ logParams: false,
630
+ });
631
+ ```
632
+
633
+ 2. **Use slow query detection only:**
634
+ ```typescript
635
+ const debugDb = withDebug(db, {
636
+ logQuery: false,
637
+ slowQueryThreshold: 500,
638
+ onSlowQuery: (sql, duration) => {
639
+ monitoring.recordSlowQuery({ sql, duration });
640
+ },
641
+ });
642
+ ```
643
+
644
+ 3. **Limit metrics collection:**
645
+ ```typescript
646
+ const debugDb = withDebug(db, {
647
+ maxMetrics: 100, // Smaller buffer for production
648
+ });
649
+ ```
650
+
651
+ ## Runtime Compatibility
652
+
653
+ - Node.js >= 20.0.0
654
+ - Bun >= 1.0.0
655
+ - Deno (with Kysely Deno support)
656
+
657
+ ## License
658
+
659
+ MIT
660
+
661
+ ## Repository
662
+
663
+ [GitHub](https://github.com/kysera-dev/kysera/tree/main/packages/debug)