@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.
@@ -0,0 +1,297 @@
1
+ import { Kysely } from 'kysely';
2
+ import { KyseraLogger } from '@kysera/core';
3
+
4
+ /**
5
+ * Debug plugin for Kysely.
6
+ *
7
+ * @module @kysera/debug
8
+ */
9
+
10
+ /**
11
+ * Query metrics data.
12
+ */
13
+ interface QueryMetrics {
14
+ /** SQL query string */
15
+ sql: string;
16
+ /** Query parameters */
17
+ params?: unknown[];
18
+ /** Query execution duration in milliseconds */
19
+ duration: number;
20
+ /** Timestamp when query was executed */
21
+ timestamp: number;
22
+ }
23
+ /**
24
+ * Options for debug plugin.
25
+ */
26
+ interface DebugOptions {
27
+ /**
28
+ * Log query SQL.
29
+ * @default true
30
+ */
31
+ logQuery?: boolean;
32
+ /**
33
+ * Log query parameters.
34
+ * @default false
35
+ */
36
+ logParams?: boolean;
37
+ /**
38
+ * Duration threshold (ms) to consider a query slow.
39
+ * @default 100
40
+ */
41
+ slowQueryThreshold?: number;
42
+ /**
43
+ * Callback for slow queries.
44
+ */
45
+ onSlowQuery?: (sql: string, duration: number) => void;
46
+ /**
47
+ * Logger for debug messages.
48
+ * @default consoleLogger
49
+ */
50
+ logger?: KyseraLogger;
51
+ /**
52
+ * Maximum number of metrics to keep in memory.
53
+ * When limit is reached, oldest metrics are removed (circular buffer).
54
+ * @default 1000
55
+ */
56
+ maxMetrics?: number;
57
+ }
58
+ /**
59
+ * Database with debug capabilities.
60
+ */
61
+ interface DebugDatabase<DB> extends Kysely<DB> {
62
+ /** Get all collected query metrics */
63
+ getMetrics(): QueryMetrics[];
64
+ /** Clear all collected metrics */
65
+ clearMetrics(): void;
66
+ }
67
+ /**
68
+ * Wrap a Kysely database with debug capabilities.
69
+ *
70
+ * Adds query logging, metrics collection, and slow query detection.
71
+ *
72
+ * @param db - Kysely database instance
73
+ * @param options - Debug options
74
+ * @returns Database with debug capabilities
75
+ *
76
+ * @example Basic usage
77
+ * ```typescript
78
+ * import { withDebug } from '@kysera/debug';
79
+ *
80
+ * const debugDb = withDebug(db);
81
+ *
82
+ * // Queries are now logged and timed
83
+ * await debugDb.selectFrom('users').selectAll().execute();
84
+ *
85
+ * // Get collected metrics
86
+ * const metrics = debugDb.getMetrics();
87
+ * console.log(`Total queries: ${metrics.length}`);
88
+ * ```
89
+ *
90
+ * @example With custom options
91
+ * ```typescript
92
+ * import { withDebug } from '@kysera/debug';
93
+ *
94
+ * const debugDb = withDebug(db, {
95
+ * logQuery: true,
96
+ * logParams: true,
97
+ * slowQueryThreshold: 50,
98
+ * maxMetrics: 500,
99
+ * onSlowQuery: (sql, duration) => {
100
+ * alertService.notify(`Slow query: ${duration}ms`);
101
+ * },
102
+ * });
103
+ * ```
104
+ */
105
+ declare function withDebug<DB>(db: Kysely<DB>, options?: DebugOptions): DebugDatabase<DB>;
106
+
107
+ /**
108
+ * Query profiler for performance analysis.
109
+ *
110
+ * @module @kysera/debug
111
+ */
112
+
113
+ /**
114
+ * Query profiler summary.
115
+ */
116
+ interface ProfilerSummary {
117
+ /** Total number of recorded queries */
118
+ totalQueries: number;
119
+ /** Sum of all query durations */
120
+ totalDuration: number;
121
+ /** Average query duration */
122
+ averageDuration: number;
123
+ /** Slowest recorded query */
124
+ slowestQuery: QueryMetrics | null;
125
+ /** Fastest recorded query */
126
+ fastestQuery: QueryMetrics | null;
127
+ /** All recorded queries */
128
+ queries: QueryMetrics[];
129
+ }
130
+ /**
131
+ * Options for QueryProfiler.
132
+ */
133
+ interface ProfilerOptions {
134
+ /**
135
+ * Maximum number of queries to keep in memory.
136
+ * @default 1000
137
+ */
138
+ maxQueries?: number;
139
+ }
140
+ /**
141
+ * Query profiler for collecting and analyzing query performance.
142
+ *
143
+ * Provides detailed statistics about query execution times
144
+ * including average, min, max, and query counts.
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * import { QueryProfiler } from '@kysera/debug';
149
+ *
150
+ * const profiler = new QueryProfiler({ maxQueries: 500 });
151
+ *
152
+ * // Record queries manually
153
+ * profiler.record({
154
+ * sql: 'SELECT * FROM users',
155
+ * duration: 10,
156
+ * timestamp: Date.now(),
157
+ * });
158
+ *
159
+ * // Get summary
160
+ * const summary = profiler.getSummary();
161
+ * console.log(`Total queries: ${summary.totalQueries}`);
162
+ * console.log(`Average duration: ${summary.averageDuration.toFixed(2)}ms`);
163
+ *
164
+ * // Clear recorded queries
165
+ * profiler.clear();
166
+ * ```
167
+ */
168
+ declare class QueryProfiler {
169
+ private queries;
170
+ private readonly maxQueries;
171
+ /**
172
+ * Create a new query profiler.
173
+ *
174
+ * @param options - Profiler options
175
+ */
176
+ constructor(options?: ProfilerOptions);
177
+ /**
178
+ * Record a query metric.
179
+ *
180
+ * @param metric - Query metrics to record
181
+ */
182
+ record(metric: QueryMetrics): void;
183
+ /**
184
+ * Get profiling summary.
185
+ *
186
+ * @returns Summary of all recorded queries
187
+ */
188
+ getSummary(): ProfilerSummary;
189
+ /**
190
+ * Get the slowest N queries.
191
+ *
192
+ * @param count - Number of queries to return
193
+ * @returns Array of slowest queries
194
+ */
195
+ getSlowestQueries(count: number): QueryMetrics[];
196
+ /**
197
+ * Get queries slower than a threshold.
198
+ *
199
+ * @param thresholdMs - Duration threshold in milliseconds
200
+ * @returns Array of slow queries
201
+ */
202
+ getSlowQueries(thresholdMs: number): QueryMetrics[];
203
+ /**
204
+ * Clear all recorded queries.
205
+ */
206
+ clear(): void;
207
+ /**
208
+ * Get the number of recorded queries.
209
+ */
210
+ get count(): number;
211
+ }
212
+
213
+ /**
214
+ * SQL formatting utilities.
215
+ *
216
+ * @module @kysera/debug
217
+ */
218
+ /**
219
+ * Format SQL for better readability.
220
+ *
221
+ * Adds newlines before major SQL keywords to make
222
+ * complex queries easier to read.
223
+ *
224
+ * @param sql - SQL string to format
225
+ * @returns Formatted SQL string
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * import { formatSQL } from '@kysera/debug';
230
+ *
231
+ * const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name';
232
+ * console.log(formatSQL(sql));
233
+ * // SELECT id, name
234
+ * // FROM users
235
+ * // WHERE active = true
236
+ * // ORDER BY name
237
+ * ```
238
+ */
239
+ declare function formatSQL(sql: string): string;
240
+ /**
241
+ * Format SQL with indentation for nested queries.
242
+ *
243
+ * More advanced formatting with proper indentation
244
+ * for subqueries and nested structures.
245
+ *
246
+ * @param sql - SQL string to format
247
+ * @param indentSize - Number of spaces for indentation
248
+ * @returns Formatted SQL string
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * import { formatSQLPretty } from '@kysera/debug';
253
+ *
254
+ * const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)';
255
+ * console.log(formatSQLPretty(sql));
256
+ * ```
257
+ */
258
+ declare function formatSQLPretty(sql: string, indentSize?: number): string;
259
+ /**
260
+ * Minify SQL by removing unnecessary whitespace.
261
+ *
262
+ * @param sql - SQL string to minify
263
+ * @returns Minified SQL string
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * import { minifySQL } from '@kysera/debug';
268
+ *
269
+ * const sql = `
270
+ * SELECT id, name
271
+ * FROM users
272
+ * WHERE active = true
273
+ * `;
274
+ * console.log(minifySQL(sql));
275
+ * // SELECT id, name FROM users WHERE active = true
276
+ * ```
277
+ */
278
+ declare function minifySQL(sql: string): string;
279
+ /**
280
+ * Highlight SQL keywords in a query.
281
+ *
282
+ * Returns SQL with ANSI color codes for terminal output.
283
+ *
284
+ * @param sql - SQL string to highlight
285
+ * @returns SQL with ANSI color codes
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * import { highlightSQL } from '@kysera/debug';
290
+ *
291
+ * console.log(highlightSQL('SELECT * FROM users'));
292
+ * // Keywords will be highlighted in blue
293
+ * ```
294
+ */
295
+ declare function highlightSQL(sql: string): string;
296
+
297
+ export { type DebugDatabase, type DebugOptions, type ProfilerOptions, type ProfilerSummary, type QueryMetrics, QueryProfiler, formatSQL, formatSQLPretty, highlightSQL, minifySQL, withDebug };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {DefaultQueryCompiler}from'kysely';import {consoleLogger}from'@kysera/core';var n=class{metrics=[];queryData=new WeakMap;maxMetrics;logger;options;onSlowQuery;constructor(e={}){this.logger=e.logger??consoleLogger,this.maxMetrics=e.maxMetrics??1e3,this.onSlowQuery=e.onSlowQuery,this.options={logQuery:e.logQuery??true,logParams:e.logParams??false,slowQueryThreshold:e.slowQueryThreshold??100};}transformQuery(e){let r=performance.now(),s=new DefaultQueryCompiler().compileQuery(e.node,e.queryId);return this.queryData.set(e.queryId,{startTime:r,sql:s.sql,params:s.parameters}),e.node}transformResult(e){let r=this.queryData.get(e.queryId);if(r){let s=performance.now()-r.startTime;this.queryData.delete(e.queryId);let o={sql:r.sql,params:[...r.params],duration:s,timestamp:Date.now()};if(this.metrics.push(o),this.metrics.length>this.maxMetrics&&this.metrics.shift(),this.options.logQuery){let m=this.options.logParams?`[SQL] ${r.sql}
2
+ [Params] ${JSON.stringify(r.params)}`:`[SQL] ${r.sql}`;this.logger.debug(m),this.logger.debug(`[Duration] ${s.toFixed(2)}ms`);}s>this.options.slowQueryThreshold&&(this.onSlowQuery?this.onSlowQuery(r.sql,s):this.logger.warn(`[SLOW QUERY] ${s.toFixed(2)}ms: ${r.sql}`));}return Promise.resolve(e.result)}getMetrics(){return [...this.metrics]}clearMetrics(){this.metrics=[];}};function p(i,e={}){let r=new n(e),t=i.withPlugin(r);return t.getMetrics=()=>r.getMetrics(),t.clearMetrics=()=>{r.clearMetrics();},t}var u=class{queries=[];maxQueries;constructor(e={}){this.maxQueries=e.maxQueries??1e3;}record(e){this.queries.push(e),this.queries.length>this.maxQueries&&this.queries.shift();}getSummary(){if(this.queries.length===0)return {totalQueries:0,totalDuration:0,averageDuration:0,slowestQuery:null,fastestQuery:null,queries:[]};let e=this.queries.reduce((t,s)=>t+s.duration,0),r=[...this.queries].sort((t,s)=>s.duration-t.duration);return {totalQueries:this.queries.length,totalDuration:e,averageDuration:e/this.queries.length,slowestQuery:r[0]??null,fastestQuery:r[r.length-1]??null,queries:[...this.queries]}}getSlowestQueries(e){return [...this.queries].sort((r,t)=>t.duration-r.duration).slice(0,e)}getSlowQueries(e){return this.queries.filter(r=>r.duration>e)}clear(){this.queries=[];}get count(){return this.queries.length}};function a(i){return i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var l=["SELECT","FROM","WHERE","JOIN","LEFT JOIN","RIGHT JOIN","INNER JOIN","OUTER JOIN","CROSS JOIN","ORDER BY","GROUP BY","HAVING","LIMIT","OFFSET","INSERT INTO","VALUES","UPDATE","SET","DELETE FROM","UNION","EXCEPT","INTERSECT","ON","AND","OR","RETURNING"];function g(i){let e=i;for(let r of l){let t=new RegExp(`(\\s+)(${a(r)})\\s+`,"gi");e=e.replace(t,`
3
+ $2 `);}return e.trim()}function h(i,e=2){let r=g(i),t=" ".repeat(e),s=0;return r=r.replace(/\(/g,()=>(s++,`(
4
+ `+t.repeat(s))),r=r.replace(/\)/g,()=>(s=Math.max(0,s-1),`
5
+ `+t.repeat(s)+")")),r=r.replace(/\n\s*\n/g,`
6
+ `),r.trim()}function Q(i){return i.replace(/\s+/g," ").replace(/\s*,\s*/g,", ").replace(/\s*\(\s*/g," (").replace(/\s*\)\s*/g,") ").trim()}function f(i){let e="\x1B[34m",r="\x1B[0m",t=i;for(let s of l){let o=new RegExp(`\\b(${a(s)})\\b`,"gi");t=t.replace(o,`${e}$1${r}`);}return t}export{u as QueryProfiler,g as formatSQL,h as formatSQLPretty,f as highlightSQL,Q as minifySQL,p as withDebug};//# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts","../src/profiler.ts","../src/format.ts"],"names":["DebugPlugin","options","consoleLogger","args","startTime","compiled","DefaultQueryCompiler","data","duration","metric","message","withDebug","db","plugin","debugDb","QueryProfiler","totalDuration","sum","q","sorted","a","b","count","thresholdMs","escapeRegex","str","SQL_KEYWORDS","formatSQL","sql","formatted","keyword","regex","formatSQLPretty","indentSize","indent","level","minifySQL","highlightSQL","BLUE","RESET","highlighted"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug plugin for Kysely.\n *\n * @module @kysera/debug\n */\n\nimport type {\n Kysely,\n PluginTransformQueryArgs,\n PluginTransformResultArgs,\n QueryResult,\n UnknownRow,\n KyselyPlugin,\n RootOperationNode,\n} from 'kysely';\nimport { DefaultQueryCompiler } from 'kysely';\nimport { consoleLogger, type KyseraLogger } from '@kysera/core';\n\n/**\n * Query metrics data.\n */\nexport interface QueryMetrics {\n /** SQL query string */\n sql: string;\n /** Query parameters */\n params?: unknown[];\n /** Query execution duration in milliseconds */\n duration: number;\n /** Timestamp when query was executed */\n timestamp: number;\n}\n\n/**\n * Options for debug plugin.\n */\nexport interface DebugOptions {\n /**\n * Log query SQL.\n * @default true\n */\n logQuery?: boolean;\n\n /**\n * Log query parameters.\n * @default false\n */\n logParams?: boolean;\n\n /**\n * Duration threshold (ms) to consider a query slow.\n * @default 100\n */\n slowQueryThreshold?: number;\n\n /**\n * Callback for slow queries.\n */\n onSlowQuery?: (sql: string, duration: number) => void;\n\n /**\n * Logger for debug messages.\n * @default consoleLogger\n */\n logger?: KyseraLogger;\n\n /**\n * Maximum number of metrics to keep in memory.\n * When limit is reached, oldest metrics are removed (circular buffer).\n * @default 1000\n */\n maxMetrics?: number;\n}\n\n/**\n * Internal query data for tracking execution.\n * @internal\n */\ninterface QueryData {\n startTime: number;\n sql: string;\n params: readonly unknown[];\n}\n\n/**\n * Debug plugin implementation.\n * @internal\n */\nclass DebugPlugin implements KyselyPlugin {\n private metrics: QueryMetrics[] = [];\n private queryData = new WeakMap<object, QueryData>();\n private readonly maxMetrics: number;\n private readonly logger: KyseraLogger;\n private readonly options: Required<Pick<DebugOptions, 'logQuery' | 'logParams' | 'slowQueryThreshold'>>;\n private readonly onSlowQuery: ((sql: string, duration: number) => void) | undefined;\n\n constructor(options: DebugOptions = {}) {\n this.logger = options.logger ?? consoleLogger;\n this.maxMetrics = options.maxMetrics ?? 1000;\n this.onSlowQuery = options.onSlowQuery;\n this.options = {\n logQuery: options.logQuery ?? true,\n logParams: options.logParams ?? false,\n slowQueryThreshold: options.slowQueryThreshold ?? 100,\n };\n }\n\n transformQuery(args: PluginTransformQueryArgs): RootOperationNode {\n const startTime = performance.now();\n\n // Compile the query to get SQL and parameters\n const compiler = new DefaultQueryCompiler();\n const compiled = compiler.compileQuery(args.node, args.queryId);\n\n // Store query data for later use in transformResult\n this.queryData.set(args.queryId, {\n startTime,\n sql: compiled.sql,\n params: compiled.parameters,\n });\n\n return args.node;\n }\n\n transformResult(args: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>> {\n const data = this.queryData.get(args.queryId);\n\n if (data) {\n const endTime = performance.now();\n const duration = endTime - data.startTime;\n this.queryData.delete(args.queryId);\n\n const metric: QueryMetrics = {\n sql: data.sql,\n params: [...data.params],\n duration,\n timestamp: Date.now(),\n };\n\n // Circular buffer: keep only last N metrics\n this.metrics.push(metric);\n if (this.metrics.length > this.maxMetrics) {\n this.metrics.shift();\n }\n\n if (this.options.logQuery) {\n const message = this.options.logParams\n ? `[SQL] ${data.sql}\\n[Params] ${JSON.stringify(data.params)}`\n : `[SQL] ${data.sql}`;\n this.logger.debug(message);\n this.logger.debug(`[Duration] ${duration.toFixed(2)}ms`);\n }\n\n // Check for slow query\n if (duration > this.options.slowQueryThreshold) {\n if (this.onSlowQuery) {\n this.onSlowQuery(data.sql, duration);\n } else {\n this.logger.warn(`[SLOW QUERY] ${duration.toFixed(2)}ms: ${data.sql}`);\n }\n }\n }\n\n return Promise.resolve(args.result);\n }\n\n getMetrics(): QueryMetrics[] {\n return [...this.metrics];\n }\n\n clearMetrics(): void {\n this.metrics = [];\n }\n}\n\n/**\n * Database with debug capabilities.\n */\nexport interface DebugDatabase<DB> extends Kysely<DB> {\n /** Get all collected query metrics */\n getMetrics(): QueryMetrics[];\n /** Clear all collected metrics */\n clearMetrics(): void;\n}\n\n/**\n * Wrap a Kysely database with debug capabilities.\n *\n * Adds query logging, metrics collection, and slow query detection.\n *\n * @param db - Kysely database instance\n * @param options - Debug options\n * @returns Database with debug capabilities\n *\n * @example Basic usage\n * ```typescript\n * import { withDebug } from '@kysera/debug';\n *\n * const debugDb = withDebug(db);\n *\n * // Queries are now logged and timed\n * await debugDb.selectFrom('users').selectAll().execute();\n *\n * // Get collected metrics\n * const metrics = debugDb.getMetrics();\n * console.log(`Total queries: ${metrics.length}`);\n * ```\n *\n * @example With custom options\n * ```typescript\n * import { withDebug } from '@kysera/debug';\n *\n * const debugDb = withDebug(db, {\n * logQuery: true,\n * logParams: true,\n * slowQueryThreshold: 50,\n * maxMetrics: 500,\n * onSlowQuery: (sql, duration) => {\n * alertService.notify(`Slow query: ${duration}ms`);\n * },\n * });\n * ```\n */\nexport function withDebug<DB>(\n db: Kysely<DB>,\n options: DebugOptions = {}\n): DebugDatabase<DB> {\n const plugin = new DebugPlugin(options);\n const debugDb = db.withPlugin(plugin) as DebugDatabase<DB>;\n\n // Attach metrics methods\n debugDb.getMetrics = (): QueryMetrics[] => plugin.getMetrics();\n debugDb.clearMetrics = (): void => {\n plugin.clearMetrics();\n };\n\n return debugDb;\n}\n","/**\n * Query profiler for performance analysis.\n *\n * @module @kysera/debug\n */\n\nimport type { QueryMetrics } from './plugin.js';\n\n/**\n * Query profiler summary.\n */\nexport interface ProfilerSummary {\n /** Total number of recorded queries */\n totalQueries: number;\n /** Sum of all query durations */\n totalDuration: number;\n /** Average query duration */\n averageDuration: number;\n /** Slowest recorded query */\n slowestQuery: QueryMetrics | null;\n /** Fastest recorded query */\n fastestQuery: QueryMetrics | null;\n /** All recorded queries */\n queries: QueryMetrics[];\n}\n\n/**\n * Options for QueryProfiler.\n */\nexport interface ProfilerOptions {\n /**\n * Maximum number of queries to keep in memory.\n * @default 1000\n */\n maxQueries?: number;\n}\n\n/**\n * Query profiler for collecting and analyzing query performance.\n *\n * Provides detailed statistics about query execution times\n * including average, min, max, and query counts.\n *\n * @example\n * ```typescript\n * import { QueryProfiler } from '@kysera/debug';\n *\n * const profiler = new QueryProfiler({ maxQueries: 500 });\n *\n * // Record queries manually\n * profiler.record({\n * sql: 'SELECT * FROM users',\n * duration: 10,\n * timestamp: Date.now(),\n * });\n *\n * // Get summary\n * const summary = profiler.getSummary();\n * console.log(`Total queries: ${summary.totalQueries}`);\n * console.log(`Average duration: ${summary.averageDuration.toFixed(2)}ms`);\n *\n * // Clear recorded queries\n * profiler.clear();\n * ```\n */\nexport class QueryProfiler {\n private queries: QueryMetrics[] = [];\n private readonly maxQueries: number;\n\n /**\n * Create a new query profiler.\n *\n * @param options - Profiler options\n */\n constructor(options: ProfilerOptions = {}) {\n this.maxQueries = options.maxQueries ?? 1000;\n }\n\n /**\n * Record a query metric.\n *\n * @param metric - Query metrics to record\n */\n record(metric: QueryMetrics): void {\n this.queries.push(metric);\n // Circular buffer: keep only last N queries\n if (this.queries.length > this.maxQueries) {\n this.queries.shift();\n }\n }\n\n /**\n * Get profiling summary.\n *\n * @returns Summary of all recorded queries\n */\n getSummary(): ProfilerSummary {\n if (this.queries.length === 0) {\n return {\n totalQueries: 0,\n totalDuration: 0,\n averageDuration: 0,\n slowestQuery: null,\n fastestQuery: null,\n queries: [],\n };\n }\n\n const totalDuration = this.queries.reduce((sum, q) => sum + q.duration, 0);\n const sorted = [...this.queries].sort((a, b) => b.duration - a.duration);\n\n return {\n totalQueries: this.queries.length,\n totalDuration,\n averageDuration: totalDuration / this.queries.length,\n slowestQuery: sorted[0] ?? null,\n fastestQuery: sorted[sorted.length - 1] ?? null,\n queries: [...this.queries],\n };\n }\n\n /**\n * Get the slowest N queries.\n *\n * @param count - Number of queries to return\n * @returns Array of slowest queries\n */\n getSlowestQueries(count: number): QueryMetrics[] {\n return [...this.queries]\n .sort((a, b) => b.duration - a.duration)\n .slice(0, count);\n }\n\n /**\n * Get queries slower than a threshold.\n *\n * @param thresholdMs - Duration threshold in milliseconds\n * @returns Array of slow queries\n */\n getSlowQueries(thresholdMs: number): QueryMetrics[] {\n return this.queries.filter((q) => q.duration > thresholdMs);\n }\n\n /**\n * Clear all recorded queries.\n */\n clear(): void {\n this.queries = [];\n }\n\n /**\n * Get the number of recorded queries.\n */\n get count(): number {\n return this.queries.length;\n }\n}\n","/**\n * SQL formatting utilities.\n *\n * @module @kysera/debug\n */\n\n/**\n * Escape special regex characters in a string.\n * @internal\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * SQL keywords that should start on a new line.\n */\nconst SQL_KEYWORDS = [\n 'SELECT',\n 'FROM',\n 'WHERE',\n 'JOIN',\n 'LEFT JOIN',\n 'RIGHT JOIN',\n 'INNER JOIN',\n 'OUTER JOIN',\n 'CROSS JOIN',\n 'ORDER BY',\n 'GROUP BY',\n 'HAVING',\n 'LIMIT',\n 'OFFSET',\n 'INSERT INTO',\n 'VALUES',\n 'UPDATE',\n 'SET',\n 'DELETE FROM',\n 'UNION',\n 'EXCEPT',\n 'INTERSECT',\n 'ON',\n 'AND',\n 'OR',\n 'RETURNING',\n];\n\n/**\n * Format SQL for better readability.\n *\n * Adds newlines before major SQL keywords to make\n * complex queries easier to read.\n *\n * @param sql - SQL string to format\n * @returns Formatted SQL string\n *\n * @example\n * ```typescript\n * import { formatSQL } from '@kysera/debug';\n *\n * const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name';\n * console.log(formatSQL(sql));\n * // SELECT id, name\n * // FROM users\n * // WHERE active = true\n * // ORDER BY name\n * ```\n */\nexport function formatSQL(sql: string): string {\n let formatted = sql;\n\n // Add newlines before SQL keywords\n for (const keyword of SQL_KEYWORDS) {\n const regex = new RegExp(`(\\\\s+)(${escapeRegex(keyword)})\\\\s+`, 'gi');\n formatted = formatted.replace(regex, `\\n$2 `);\n }\n\n return formatted.trim();\n}\n\n/**\n * Format SQL with indentation for nested queries.\n *\n * More advanced formatting with proper indentation\n * for subqueries and nested structures.\n *\n * @param sql - SQL string to format\n * @param indentSize - Number of spaces for indentation\n * @returns Formatted SQL string\n *\n * @example\n * ```typescript\n * import { formatSQLPretty } from '@kysera/debug';\n *\n * const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)';\n * console.log(formatSQLPretty(sql));\n * ```\n */\nexport function formatSQLPretty(sql: string, indentSize = 2): string {\n let formatted = formatSQL(sql);\n const indent = ' '.repeat(indentSize);\n let level = 0;\n\n // Handle parentheses for subqueries\n formatted = formatted.replace(/\\(/g, () => {\n level++;\n return '(\\n' + indent.repeat(level);\n });\n\n formatted = formatted.replace(/\\)/g, () => {\n level = Math.max(0, level - 1);\n return '\\n' + indent.repeat(level) + ')';\n });\n\n // Clean up excessive newlines\n formatted = formatted.replace(/\\n\\s*\\n/g, '\\n');\n\n return formatted.trim();\n}\n\n/**\n * Minify SQL by removing unnecessary whitespace.\n *\n * @param sql - SQL string to minify\n * @returns Minified SQL string\n *\n * @example\n * ```typescript\n * import { minifySQL } from '@kysera/debug';\n *\n * const sql = `\n * SELECT id, name\n * FROM users\n * WHERE active = true\n * `;\n * console.log(minifySQL(sql));\n * // SELECT id, name FROM users WHERE active = true\n * ```\n */\nexport function minifySQL(sql: string): string {\n return sql\n .replace(/\\s+/g, ' ') // Collapse whitespace\n .replace(/\\s*,\\s*/g, ', ') // Normalize commas\n .replace(/\\s*\\(\\s*/g, ' (') // Normalize opening parens\n .replace(/\\s*\\)\\s*/g, ') ') // Normalize closing parens\n .trim();\n}\n\n/**\n * Highlight SQL keywords in a query.\n *\n * Returns SQL with ANSI color codes for terminal output.\n *\n * @param sql - SQL string to highlight\n * @returns SQL with ANSI color codes\n *\n * @example\n * ```typescript\n * import { highlightSQL } from '@kysera/debug';\n *\n * console.log(highlightSQL('SELECT * FROM users'));\n * // Keywords will be highlighted in blue\n * ```\n */\nexport function highlightSQL(sql: string): string {\n const BLUE = '\\x1b[34m';\n const RESET = '\\x1b[0m';\n\n let highlighted = sql;\n\n for (const keyword of SQL_KEYWORDS) {\n const regex = new RegExp(`\\\\b(${escapeRegex(keyword)})\\\\b`, 'gi');\n highlighted = highlighted.replace(regex, `${BLUE}$1${RESET}`);\n }\n\n return highlighted;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@kysera/debug",
3
+ "version": "0.6.0",
4
+ "description": "Debug utilities for Kysera ORM - query logging, profiling, SQL formatting",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "peerDependencies": {
19
+ "kysely": ">=0.28.8"
20
+ },
21
+ "dependencies": {
22
+ "@kysera/core": "0.6.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.10.1",
26
+ "@vitest/coverage-v8": "^4.0.15",
27
+ "kysely": "^0.28.8",
28
+ "tsup": "^8.5.1",
29
+ "typescript": "^5.9.3",
30
+ "vitest": "^4.0.15"
31
+ },
32
+ "keywords": [
33
+ "kysely",
34
+ "orm",
35
+ "database",
36
+ "debug",
37
+ "profiling",
38
+ "logging",
39
+ "sql"
40
+ ],
41
+ "author": "Kysera Team",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/kysera-dev/kysera.git",
46
+ "directory": "packages/debug"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/kysera-dev/kysera/issues"
50
+ },
51
+ "homepage": "https://github.com/kysera-dev/kysera#readme",
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "sideEffects": false,
56
+ "engines": {
57
+ "node": ">=20.0.0",
58
+ "bun": ">=1.0.0"
59
+ },
60
+ "scripts": {
61
+ "build": "tsup",
62
+ "dev": "tsup --watch",
63
+ "test": "vitest run",
64
+ "test:watch": "vitest watch",
65
+ "test:coverage": "vitest run --coverage",
66
+ "typecheck": "tsc --noEmit",
67
+ "lint": "eslint ."
68
+ }
69
+ }
package/src/format.ts ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * SQL formatting utilities.
3
+ *
4
+ * @module @kysera/debug
5
+ */
6
+
7
+ /**
8
+ * Escape special regex characters in a string.
9
+ * @internal
10
+ */
11
+ function escapeRegex(str: string): string {
12
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13
+ }
14
+
15
+ /**
16
+ * SQL keywords that should start on a new line.
17
+ */
18
+ const SQL_KEYWORDS = [
19
+ 'SELECT',
20
+ 'FROM',
21
+ 'WHERE',
22
+ 'JOIN',
23
+ 'LEFT JOIN',
24
+ 'RIGHT JOIN',
25
+ 'INNER JOIN',
26
+ 'OUTER JOIN',
27
+ 'CROSS JOIN',
28
+ 'ORDER BY',
29
+ 'GROUP BY',
30
+ 'HAVING',
31
+ 'LIMIT',
32
+ 'OFFSET',
33
+ 'INSERT INTO',
34
+ 'VALUES',
35
+ 'UPDATE',
36
+ 'SET',
37
+ 'DELETE FROM',
38
+ 'UNION',
39
+ 'EXCEPT',
40
+ 'INTERSECT',
41
+ 'ON',
42
+ 'AND',
43
+ 'OR',
44
+ 'RETURNING',
45
+ ];
46
+
47
+ /**
48
+ * Format SQL for better readability.
49
+ *
50
+ * Adds newlines before major SQL keywords to make
51
+ * complex queries easier to read.
52
+ *
53
+ * @param sql - SQL string to format
54
+ * @returns Formatted SQL string
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import { formatSQL } from '@kysera/debug';
59
+ *
60
+ * const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name';
61
+ * console.log(formatSQL(sql));
62
+ * // SELECT id, name
63
+ * // FROM users
64
+ * // WHERE active = true
65
+ * // ORDER BY name
66
+ * ```
67
+ */
68
+ export function formatSQL(sql: string): string {
69
+ let formatted = sql;
70
+
71
+ // Add newlines before SQL keywords
72
+ for (const keyword of SQL_KEYWORDS) {
73
+ const regex = new RegExp(`(\\s+)(${escapeRegex(keyword)})\\s+`, 'gi');
74
+ formatted = formatted.replace(regex, `\n$2 `);
75
+ }
76
+
77
+ return formatted.trim();
78
+ }
79
+
80
+ /**
81
+ * Format SQL with indentation for nested queries.
82
+ *
83
+ * More advanced formatting with proper indentation
84
+ * for subqueries and nested structures.
85
+ *
86
+ * @param sql - SQL string to format
87
+ * @param indentSize - Number of spaces for indentation
88
+ * @returns Formatted SQL string
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * import { formatSQLPretty } from '@kysera/debug';
93
+ *
94
+ * const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)';
95
+ * console.log(formatSQLPretty(sql));
96
+ * ```
97
+ */
98
+ export function formatSQLPretty(sql: string, indentSize = 2): string {
99
+ let formatted = formatSQL(sql);
100
+ const indent = ' '.repeat(indentSize);
101
+ let level = 0;
102
+
103
+ // Handle parentheses for subqueries
104
+ formatted = formatted.replace(/\(/g, () => {
105
+ level++;
106
+ return '(\n' + indent.repeat(level);
107
+ });
108
+
109
+ formatted = formatted.replace(/\)/g, () => {
110
+ level = Math.max(0, level - 1);
111
+ return '\n' + indent.repeat(level) + ')';
112
+ });
113
+
114
+ // Clean up excessive newlines
115
+ formatted = formatted.replace(/\n\s*\n/g, '\n');
116
+
117
+ return formatted.trim();
118
+ }
119
+
120
+ /**
121
+ * Minify SQL by removing unnecessary whitespace.
122
+ *
123
+ * @param sql - SQL string to minify
124
+ * @returns Minified SQL string
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * import { minifySQL } from '@kysera/debug';
129
+ *
130
+ * const sql = `
131
+ * SELECT id, name
132
+ * FROM users
133
+ * WHERE active = true
134
+ * `;
135
+ * console.log(minifySQL(sql));
136
+ * // SELECT id, name FROM users WHERE active = true
137
+ * ```
138
+ */
139
+ export function minifySQL(sql: string): string {
140
+ return sql
141
+ .replace(/\s+/g, ' ') // Collapse whitespace
142
+ .replace(/\s*,\s*/g, ', ') // Normalize commas
143
+ .replace(/\s*\(\s*/g, ' (') // Normalize opening parens
144
+ .replace(/\s*\)\s*/g, ') ') // Normalize closing parens
145
+ .trim();
146
+ }
147
+
148
+ /**
149
+ * Highlight SQL keywords in a query.
150
+ *
151
+ * Returns SQL with ANSI color codes for terminal output.
152
+ *
153
+ * @param sql - SQL string to highlight
154
+ * @returns SQL with ANSI color codes
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * import { highlightSQL } from '@kysera/debug';
159
+ *
160
+ * console.log(highlightSQL('SELECT * FROM users'));
161
+ * // Keywords will be highlighted in blue
162
+ * ```
163
+ */
164
+ export function highlightSQL(sql: string): string {
165
+ const BLUE = '\x1b[34m';
166
+ const RESET = '\x1b[0m';
167
+
168
+ let highlighted = sql;
169
+
170
+ for (const keyword of SQL_KEYWORDS) {
171
+ const regex = new RegExp(`\\b(${escapeRegex(keyword)})\\b`, 'gi');
172
+ highlighted = highlighted.replace(regex, `${BLUE}$1${RESET}`);
173
+ }
174
+
175
+ return highlighted;
176
+ }
package/src/index.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @kysera/debug - Debug utilities for Kysera ORM
3
+ *
4
+ * Provides query logging, metrics collection, profiling,
5
+ * and SQL formatting utilities.
6
+ *
7
+ * @module @kysera/debug
8
+ *
9
+ * @example Basic usage
10
+ * ```typescript
11
+ * import { withDebug, QueryProfiler, formatSQL } from '@kysera/debug';
12
+ *
13
+ * // Add debug capabilities to database
14
+ * const debugDb = withDebug(db, {
15
+ * logQuery: true,
16
+ * slowQueryThreshold: 100,
17
+ * });
18
+ *
19
+ * // Execute queries
20
+ * await debugDb.selectFrom('users').selectAll().execute();
21
+ *
22
+ * // Analyze metrics
23
+ * const metrics = debugDb.getMetrics();
24
+ * console.log(`Executed ${metrics.length} queries`);
25
+ *
26
+ * // Format SQL for display
27
+ * console.log(formatSQL(metrics[0].sql));
28
+ * ```
29
+ */
30
+
31
+ // Plugin
32
+ export {
33
+ type QueryMetrics,
34
+ type DebugOptions,
35
+ type DebugDatabase,
36
+ withDebug,
37
+ } from './plugin.js';
38
+
39
+ // Profiler
40
+ export {
41
+ type ProfilerSummary,
42
+ type ProfilerOptions,
43
+ QueryProfiler,
44
+ } from './profiler.js';
45
+
46
+ // Formatting
47
+ export {
48
+ formatSQL,
49
+ formatSQLPretty,
50
+ minifySQL,
51
+ highlightSQL,
52
+ } from './format.js';