@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 +21 -0
- package/README.md +663 -0
- package/dist/index.d.ts +297 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/src/format.ts +176 -0
- package/src/index.ts +52 -0
- package/src/plugin.ts +237 -0
- package/src/profiler.ts +157 -0
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)
|