mysql-orm-lite 1.0.1 → 2.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.
@@ -1,28 +1,28 @@
1
- 'use strict';
2
- const mysql = require('mysql2/promise');
1
+ import * as mysql from 'mysql2/promise';
2
+ import { DbConfig, Logger } from '../types';
3
3
 
4
4
  // Default logger
5
- const defaultLogger = {
6
- info: (...args) => console.log('[INFO]', ...args),
7
- error: (...args) => console.error('[ERROR]', ...args),
8
- warn: (...args) => console.warn('[WARN]', ...args)
5
+ const defaultLogger: Logger = {
6
+ info: (...args: any[]) => console.log('[INFO]', ...args),
7
+ error: (...args: any[]) => console.error('[ERROR]', ...args),
8
+ warn: (...args: any[]) => console.warn('[WARN]', ...args)
9
9
  };
10
10
 
11
- const pools = new Map();
12
- let logger = defaultLogger;
13
- let defaultConfig = null;
11
+ const pools = new Map<string, mysql.Pool>();
12
+ let logger: Logger = defaultLogger;
13
+ let defaultConfig: DbConfig | null = null;
14
14
 
15
- const getPoolKey = (config) => {
15
+ const getPoolKey = (config: DbConfig): string => {
16
16
  return `${config.host}:${config.port || 3306}:${config.user}:${config.database}`;
17
17
  };
18
18
 
19
19
  const connectionManager = {
20
20
  /**
21
21
  * Initialize with default config and optional logger
22
- * @param {Object} config - Database configuration
23
- * @param {Object} customLogger - Custom logger (optional)
22
+ * @param config - Database configuration
23
+ * @param customLogger - Custom logger (optional)
24
24
  */
25
- init: (config, customLogger) => {
25
+ init: (config: DbConfig, customLogger?: Logger) => {
26
26
  if (!config || !config.database) {
27
27
  throw new Error('Valid database configuration required');
28
28
  }
@@ -35,18 +35,18 @@ const connectionManager = {
35
35
 
36
36
  /**
37
37
  * Set custom logger
38
- * @param {Object} customLogger - Logger with info, error, warn methods
38
+ * @param customLogger - Logger with info, error, warn methods
39
39
  */
40
- setLogger: (customLogger) => {
40
+ setLogger: (customLogger: Logger) => {
41
41
  logger = customLogger;
42
42
  },
43
43
 
44
44
  /**
45
45
  * Get or create a connection pool
46
- * @param {Object} dbConfig - Database configuration (optional)
47
- * @returns {Promise<Pool>} MySQL connection pool
46
+ * @param dbConfig - Database configuration (optional)
47
+ * @returns MySQL connection pool
48
48
  */
49
- getPool: async (dbConfig) => {
49
+ getPool: async (dbConfig?: DbConfig): Promise<mysql.Pool> => {
50
50
  const conf = dbConfig || defaultConfig;
51
51
  if (!conf || !conf.database) {
52
52
  throw new Error('Database configuration missing. Call connectionManager.init() first or provide dbConfig');
@@ -54,7 +54,7 @@ const connectionManager = {
54
54
 
55
55
  const key = getPoolKey(conf);
56
56
  if (pools.has(key)) {
57
- return pools.get(key);
57
+ return pools.get(key)!;
58
58
  }
59
59
 
60
60
  try {
@@ -81,12 +81,12 @@ const connectionManager = {
81
81
 
82
82
  /**
83
83
  * Close a specific pool
84
- * @param {Object} config - Database configuration
84
+ * @param config - Database configuration
85
85
  */
86
- closePool: async (config) => {
86
+ closePool: async (config: DbConfig) => {
87
87
  const key = getPoolKey(config);
88
88
  if (pools.has(key)) {
89
- const pool = pools.get(key);
89
+ const pool = pools.get(key)!;
90
90
  await pool.end();
91
91
  pools.delete(key);
92
92
  logger.info(`Closed connection pool for ${key}`);
@@ -111,7 +111,42 @@ const connectionManager = {
111
111
  /**
112
112
  * Get logger instance
113
113
  */
114
- getLogger: () => logger
114
+ getLogger: (): Logger => logger,
115
+
116
+ /**
117
+ * Get connection pool statistics
118
+ * @param dbConfig - Optional database configuration
119
+ * @returns Connection pool stats
120
+ */
121
+ getPoolStats: async (dbConfig?: DbConfig) => {
122
+ const conf = dbConfig || defaultConfig;
123
+ if (!conf || !conf.database) {
124
+ throw new Error('Database configuration missing');
125
+ }
126
+
127
+ const key = getPoolKey(conf);
128
+ const pool = pools.get(key);
129
+
130
+ if (!pool) {
131
+ return {
132
+ activeConnections: 0,
133
+ idleConnections: 0,
134
+ totalConnections: 0,
135
+ waitingRequests: 0
136
+ };
137
+ }
138
+
139
+ // Access pool internals (mysql2 specific)
140
+ const poolInfo = (pool as any).pool;
141
+
142
+ return {
143
+ activeConnections: poolInfo?._acquiringConnections?.length || 0,
144
+ idleConnections: poolInfo?._freeConnections?.length || 0,
145
+ totalConnections: poolInfo?._allConnections?.length || 0,
146
+ waitingRequests: poolInfo?._connectionQueue?.length || 0
147
+ };
148
+ }
115
149
  };
116
150
 
117
- module.exports = connectionManager;
151
+ export default connectionManager;
152
+ export { connectionManager };
@@ -1,9 +1,10 @@
1
- 'use strict';
2
- const connectionManager = require('./connectionManager');
1
+ import connectionManager from './connectionManager';
2
+ import performanceMonitor from './performanceMonitor';
3
+ import { DbConfig, SelectOptions, UpdateOptions, DeleteOptions, WhereConditions } from '../types';
3
4
 
4
- const utils = {
5
- handleConnectionError(err) {
6
- const errorMessages = {
5
+ export const utils = {
6
+ handleConnectionError(err: any): Error {
7
+ const errorMessages: Record<string, string> = {
7
8
  PROTOCOL_CONNECTION_LOST: 'Database connection was closed.',
8
9
  ER_CON_COUNT_ERROR: 'Database has too many connections.',
9
10
  ECONNREFUSED: 'Database connection was refused.',
@@ -12,7 +13,7 @@ const utils = {
12
13
  return new Error(errorMessages[err.code] || err.message);
13
14
  },
14
15
 
15
- logQueryPerformance(query, startTime, params = []) {
16
+ logQueryPerformance(query: string, startTime: number, params: any[] = []): number {
16
17
  const logger = connectionManager.getLogger();
17
18
  const duration = Date.now() - startTime;
18
19
  if (duration > 1000) {
@@ -25,16 +26,20 @@ const utils = {
25
26
  return duration;
26
27
  },
27
28
 
28
- async executeQuery({ query, params = [], dbConfig, operation = 'query' }) {
29
+ async executeQuery({ query, params = [], dbConfig, operation = 'query' }: { query: string; params?: any[]; dbConfig?: DbConfig; operation?: string }): Promise<any> {
29
30
  const startTime = Date.now();
30
31
  const logger = connectionManager.getLogger();
31
32
 
32
33
  try {
33
34
  const pool = await connectionManager.getPool(dbConfig);
34
35
  const [results] = await pool.query(query, params);
35
- this.logQueryPerformance(query, startTime, params);
36
+ const duration = this.logQueryPerformance(query, startTime, params);
37
+
38
+ // Record in performance monitor if enabled
39
+ performanceMonitor.recordQuery(query, duration, params);
40
+
36
41
  return results;
37
- } catch (error) {
42
+ } catch (error: any) {
38
43
  logger.error(`${operation} Error: ${error.message}`);
39
44
  logger.error(`Query: ${query}`);
40
45
  if (params.length) logger.error('Parameters:', JSON.stringify(params));
@@ -42,8 +47,8 @@ const utils = {
42
47
  }
43
48
  },
44
49
 
45
- validateUpdateParams(table, data, query) {
46
- const errors = [];
50
+ validateUpdateParams(table: string, data: Record<string, any>, query: string): string[] {
51
+ const errors: string[] = [];
47
52
  if (!table || typeof table !== 'string') errors.push('Invalid table name');
48
53
  else if (!/^[a-zA-Z0-9_]+$/.test(table)) errors.push('Table name contains invalid characters');
49
54
 
@@ -56,9 +61,9 @@ const utils = {
56
61
  return errors;
57
62
  },
58
63
 
59
- prepareUpdateParams(data) {
60
- const setFields = [];
61
- const params = [];
64
+ prepareUpdateParams(data: Record<string, any>): { setFields: string[]; params: any[] } {
65
+ const setFields: string[] = [];
66
+ const params: any[] = [];
62
67
 
63
68
  for (const [key, value] of Object.entries(data)) {
64
69
  if (value === undefined) continue;
@@ -79,13 +84,13 @@ const utils = {
79
84
  return { setFields, params };
80
85
  },
81
86
 
82
- _buildWhereClause(conditions) {
83
- const params = [];
84
- const buildCondition = (key, value) => {
87
+ _buildWhereClause(conditions: WhereConditions): { clause: string; params: any[] } {
88
+ const params: any[] = [];
89
+ const buildCondition = (key: string, value: any): string => {
85
90
  const column = key;
86
91
  if (value === null) return `${column} IS NULL`;
87
92
  if (typeof value === 'object' && !Array.isArray(value)) {
88
- return Object.entries(value).map(([op, val]) => {
93
+ return Object.entries(value).map(([op, val]: [string, any]) => {
89
94
  switch (op) {
90
95
  case '$eq': params.push(val); return `${column} = ?`;
91
96
  case '$ne': params.push(val); return `${column} != ?`;
@@ -117,7 +122,7 @@ const utils = {
117
122
  return `${column} = ?`;
118
123
  };
119
124
 
120
- const walk = (cond) => {
125
+ const walk = (cond: any): string => {
121
126
  if (!cond || typeof cond !== 'object') return '';
122
127
  if (Array.isArray(cond)) return cond.map(walk).join(' AND ');
123
128
 
@@ -132,7 +137,7 @@ const utils = {
132
137
  return { clause, params };
133
138
  },
134
139
 
135
- _buildSelectQuery(options) {
140
+ _buildSelectQuery(options: SelectOptions): { query: string; params: any[] } {
136
141
  let { table, fields = '*', joins = [], where, orderBy, limit, offset, groupBy, having, alias, forUpdate = false } = options;
137
142
 
138
143
  if (fields && Array.isArray(fields) && fields.length > 0) {
@@ -142,7 +147,7 @@ const utils = {
142
147
  }
143
148
 
144
149
  let query = `SELECT ${fields} FROM ${table}${alias ? ' ' + alias : ''}`;
145
- const allParams = [];
150
+ const allParams: any[] = [];
146
151
 
147
152
  for (const join of joins) {
148
153
  let { type = 'INNER', table: joinTable, alias, on } = join;
@@ -174,7 +179,7 @@ const utils = {
174
179
  return { query, params: allParams };
175
180
  },
176
181
 
177
- _buildUpdateQuery(options) {
182
+ _buildUpdateQuery(options: UpdateOptions): { query: string; params: any[] } {
178
183
  const { table, data, where } = options;
179
184
  const { setFields, params } = this.prepareUpdateParams(data);
180
185
 
@@ -194,12 +199,12 @@ const utils = {
194
199
  return { query, params: allParams };
195
200
  },
196
201
 
197
- _buildDeleteQuery(options) {
202
+ _buildDeleteQuery(options: DeleteOptions): { query: string; params: any[] } {
198
203
  const { table, where } = options;
199
204
  if (!table) throw new Error('Table name is required for delete');
200
205
 
201
206
  let query = `DELETE FROM ${table}`;
202
- const allParams = [];
207
+ const allParams: any[] = [];
203
208
 
204
209
  if (where) {
205
210
  const { clause, params: whereParams } = this._buildWhereClause(where);
@@ -218,18 +223,18 @@ const utils = {
218
223
  };
219
224
 
220
225
  // Public API
221
- exports.find = async function (query, params = [], dbConfig) {
226
+ export const find = async function (query: string, params: any[] = [], dbConfig?: DbConfig): Promise<any[]> {
222
227
  if (!query) return [];
223
228
  return await utils.executeQuery({ query, params, dbConfig, operation: 'find' });
224
229
  };
225
230
 
226
- exports.findCount = async function (query, params = [], dbConfig) {
231
+ export const findCount = async function (query: string, params: any[] = [], dbConfig?: DbConfig): Promise<number> {
227
232
  if (!query) return 0;
228
233
  const results = await utils.executeQuery({ query, params, dbConfig, operation: 'findCount' });
229
234
  return results && results[0] ? results[0].count : 0;
230
235
  };
231
236
 
232
- exports.insert = async function (table, data, dbConfig, debug = false, isIgnore = false) {
237
+ export const insert = async function (table: string, data: Record<string, any>, dbConfig?: DbConfig, debug = false, isIgnore = false): Promise<any> {
233
238
  if (!table || !data || typeof data !== 'object') throw new Error('Invalid table or data');
234
239
 
235
240
  const logger = connectionManager.getLogger();
@@ -248,7 +253,7 @@ exports.insert = async function (table, data, dbConfig, debug = false, isIgnore
248
253
  return result.insertId;
249
254
  };
250
255
 
251
- exports.update = async function (table, data, query, dbConfig, debug = false) {
256
+ export const update = async function (table: string, data: Record<string, any>, query: string, dbConfig?: DbConfig, debug = false): Promise<number> {
252
257
  const errors = utils.validateUpdateParams(table, data, query);
253
258
  if (errors.length > 0) throw new Error(`Validation failed: ${errors.join(', ')}`);
254
259
 
@@ -265,7 +270,7 @@ exports.update = async function (table, data, query, dbConfig, debug = false) {
265
270
  return result.affectedRows;
266
271
  };
267
272
 
268
- exports.delete = async function (query, table, dbConfig) {
273
+ export const _delete = async function (query: string, table?: string, dbConfig?: DbConfig): Promise<number> {
269
274
  if (!query || !query.toLowerCase().includes('where')) throw new Error('Invalid query or missing WHERE clause');
270
275
 
271
276
  const logger = connectionManager.getLogger();
@@ -275,31 +280,198 @@ exports.delete = async function (query, table, dbConfig) {
275
280
  return result.affectedRows;
276
281
  };
277
282
 
278
- // Query Builder Methods - Multiple naming options for flexibility
279
- exports.buildAndExecuteSelectQuery = async function (options, dbConfig) {
283
+ export { _delete as delete };
284
+
285
+ // Bulk Operations
286
+ export const bulkInsert = async function (
287
+ table: string,
288
+ records: Record<string, any>[],
289
+ options?: { batchSize?: number; ignore?: boolean },
290
+ dbConfig?: DbConfig
291
+ ): Promise<{ totalInserted: number; batches: number; firstInsertId?: number; lastInsertId?: number }> {
292
+ if (!table || !records || !Array.isArray(records) || records.length === 0) {
293
+ throw new Error('Table name and records are required for bulk insert');
294
+ }
295
+
296
+ const batchSize = options?.batchSize || 1000;
297
+ const isIgnore = options?.ignore || false;
298
+ const logger = connectionManager.getLogger();
299
+
300
+ // Validate all records have the same fields
301
+ const fields = Object.keys(records[0]);
302
+ for (const record of records) {
303
+ const recordFields = Object.keys(record);
304
+ if (recordFields.length !== fields.length || !recordFields.every(f => fields.includes(f))) {
305
+ throw new Error('All records must have the same fields for bulk insert');
306
+ }
307
+ }
308
+
309
+ let totalInserted = 0;
310
+ let firstInsertId: number | undefined;
311
+ let lastInsertId: number | undefined;
312
+ const batches = Math.ceil(records.length / batchSize);
313
+
314
+ for (let i = 0; i < records.length; i += batchSize) {
315
+ const batch = records.slice(i, i + batchSize);
316
+ const values: any[] = [];
317
+ const placeholders: string[] = [];
318
+
319
+ for (const record of batch) {
320
+ const recordValues = fields.map(field => record[field]);
321
+ values.push(...recordValues);
322
+ placeholders.push(`(${fields.map(() => '?').join(', ')})`);
323
+ }
324
+
325
+ const sql = isIgnore
326
+ ? `INSERT IGNORE INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')}`
327
+ : `INSERT INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')}`;
328
+
329
+ const result = await utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'bulkInsert' });
330
+
331
+ totalInserted += result.affectedRows || 0;
332
+
333
+ if (i === 0 && result.insertId) {
334
+ firstInsertId = result.insertId;
335
+ }
336
+ if (result.insertId) {
337
+ lastInsertId = result.insertId + (result.affectedRows || 1) - 1;
338
+ }
339
+ }
340
+
341
+ logger.info(`Bulk inserted ${totalInserted} records into ${table} in ${batches} batches`);
342
+
343
+ return {
344
+ totalInserted,
345
+ batches,
346
+ firstInsertId,
347
+ lastInsertId
348
+ };
349
+ };
350
+
351
+ export const upsert = async function (
352
+ table: string,
353
+ data: Record<string, any>,
354
+ options: { conflictKey: string | string[]; updateFields?: string[] },
355
+ dbConfig?: DbConfig
356
+ ): Promise<{ action: 'inserted' | 'updated'; affectedRows: number; insertId?: number }> {
357
+ if (!table || !data || !options.conflictKey) {
358
+ throw new Error('Table, data, and conflictKey are required for upsert');
359
+ }
360
+
361
+ const fields = Object.keys(data);
362
+ const values = Object.values(data);
363
+ const placeholders = fields.map(() => '?').join(', ');
364
+
365
+ // Determine which fields to update on duplicate key
366
+ const conflictKeys = Array.isArray(options.conflictKey) ? options.conflictKey : [options.conflictKey];
367
+ const updateFields = options.updateFields || fields.filter(f => !conflictKeys.includes(f));
368
+
369
+ if (updateFields.length === 0) {
370
+ throw new Error('No fields to update on duplicate key');
371
+ }
372
+
373
+ // Build UPDATE clause
374
+ const updateClause = updateFields.map(field => `${field} = VALUES(${field})`).join(', ');
375
+
376
+ const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateClause}`;
377
+
378
+ const result = await utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'upsert' });
379
+
380
+ // affectedRows = 1 means inserted, 2 means updated, 0 means no change
381
+ const action: 'inserted' | 'updated' = result.affectedRows === 1 ? 'inserted' : 'updated';
382
+
383
+ return {
384
+ action,
385
+ affectedRows: result.affectedRows,
386
+ insertId: result.insertId
387
+ };
388
+ };
389
+
390
+ export const bulkUpsert = async function (
391
+ table: string,
392
+ records: Record<string, any>[],
393
+ options: { conflictKey: string | string[]; updateFields?: string[]; batchSize?: number },
394
+ dbConfig?: DbConfig
395
+ ): Promise<{ totalAffected: number; batches: number }> {
396
+ if (!table || !records || records.length === 0) {
397
+ throw new Error('Table name and records are required for bulk upsert');
398
+ }
399
+
400
+ const batchSize = options?.batchSize || 1000;
401
+ const logger = connectionManager.getLogger();
402
+
403
+ // Validate all records have the same fields
404
+ const fields = Object.keys(records[0]);
405
+ for (const record of records) {
406
+ const recordFields = Object.keys(record);
407
+ if (recordFields.length !== fields.length || !recordFields.every(f => fields.includes(f))) {
408
+ throw new Error('All records must have the same fields for bulk upsert');
409
+ }
410
+ }
411
+
412
+ // Determine which fields to update on duplicate key
413
+ const conflictKeys = Array.isArray(options.conflictKey) ? options.conflictKey : [options.conflictKey];
414
+ const updateFields = options.updateFields || fields.filter(f => !conflictKeys.includes(f));
415
+
416
+ if (updateFields.length === 0) {
417
+ throw new Error('No fields to update on duplicate key');
418
+ }
419
+
420
+ const updateClause = updateFields.map(field => `${field} = VALUES(${field})`).join(', ');
421
+
422
+ let totalAffected = 0;
423
+ const batches = Math.ceil(records.length / batchSize);
424
+
425
+ for (let i = 0; i < records.length; i += batchSize) {
426
+ const batch = records.slice(i, i + batchSize);
427
+ const values: any[] = [];
428
+ const placeholders: string[] = [];
429
+
430
+ for (const record of batch) {
431
+ const recordValues = fields.map(field => record[field]);
432
+ values.push(...recordValues);
433
+ placeholders.push(`(${fields.map(() => '?').join(', ')})`);
434
+ }
435
+
436
+ const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES ${placeholders.join(', ')} ON DUPLICATE KEY UPDATE ${updateClause}`;
437
+
438
+ const result = await utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'bulkUpsert' });
439
+ totalAffected += result.affectedRows || 0;
440
+ }
441
+
442
+ logger.info(`Bulk upsert affected ${totalAffected} records in ${table} in ${batches} batches`);
443
+
444
+ return {
445
+ totalAffected,
446
+ batches
447
+ };
448
+ };
449
+
450
+ // Query Builder Methods
451
+ export const buildAndExecuteSelectQuery = async function (options: SelectOptions, dbConfig?: DbConfig): Promise<any[]> {
280
452
  const { query, params } = utils._buildSelectQuery(options);
281
453
  return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteSelectQuery' });
282
454
  };
283
455
 
284
- exports.buildAndExecuteUpdateQuery = async function (options, dbConfig) {
456
+ export const buildAndExecuteUpdateQuery = async function (options: UpdateOptions, dbConfig?: DbConfig): Promise<number> {
285
457
  const { query, params } = utils._buildUpdateQuery(options);
286
- return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteUpdateQuery' });
458
+ const result = await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteUpdateQuery' });
459
+ return result.affectedRows;
287
460
  };
288
461
 
289
- exports.buildAndExecuteDeleteQuery = async function (options, dbConfig) {
462
+ export const buildAndExecuteDeleteQuery = async function (options: DeleteOptions, dbConfig?: DbConfig): Promise<number> {
290
463
  const { query, params } = utils._buildDeleteQuery(options);
291
- return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteDeleteQuery' });
464
+ const result = await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteDeleteQuery' });
465
+ return result.affectedRows;
292
466
  };
293
467
 
294
- // Alternative shorter method names (aliases)
295
- exports.select = exports.buildAndExecuteSelectQuery;
296
- exports.findWhere = exports.buildAndExecuteSelectQuery;
297
- exports.query = exports.buildAndExecuteSelectQuery;
298
-
299
- exports.updateWhere = exports.buildAndExecuteUpdateQuery;
300
- exports.updateQuery = exports.buildAndExecuteUpdateQuery;
468
+ // Aliases
469
+ export const select = buildAndExecuteSelectQuery;
470
+ export const findWhere = buildAndExecuteSelectQuery;
471
+ export const query = buildAndExecuteSelectQuery;
301
472
 
302
- exports.deleteWhere = exports.buildAndExecuteDeleteQuery;
303
- exports.remove = exports.buildAndExecuteDeleteQuery;
473
+ export const updateWhere = buildAndExecuteUpdateQuery;
474
+ export const updateQuery = buildAndExecuteUpdateQuery;
304
475
 
305
- exports.utils = utils;
476
+ export const deleteWhere = buildAndExecuteDeleteQuery;
477
+ export const remove = buildAndExecuteDeleteQuery;
@@ -0,0 +1,147 @@
1
+ import { QueryMetric, PerformanceStats, ConnectionPoolStats } from '../types';
2
+
3
+ class PerformanceMonitor {
4
+ private enabled: boolean = false;
5
+ private queries: QueryMetric[] = [];
6
+ private startTime: number = 0;
7
+ private queryCountByType = {
8
+ SELECT: 0,
9
+ INSERT: 0,
10
+ UPDATE: 0,
11
+ DELETE: 0,
12
+ OTHER: 0
13
+ };
14
+
15
+ /**
16
+ * Enable performance monitoring
17
+ */
18
+ enable(): void {
19
+ this.enabled = true;
20
+ this.startTime = Date.now();
21
+ console.log('[Performance Monitor] Enabled');
22
+ }
23
+
24
+ /**
25
+ * Disable performance monitoring
26
+ */
27
+ disable(): void {
28
+ this.enabled = false;
29
+ console.log('[Performance Monitor] Disabled');
30
+ }
31
+
32
+ /**
33
+ * Check if monitoring is enabled
34
+ */
35
+ isEnabled(): boolean {
36
+ return this.enabled;
37
+ }
38
+
39
+ /**
40
+ * Record a query execution
41
+ */
42
+ recordQuery(query: string, duration: number, params?: any[]): void {
43
+ if (!this.enabled) return;
44
+
45
+ const type = this.detectQueryType(query);
46
+
47
+ const metric: QueryMetric = {
48
+ query,
49
+ duration,
50
+ timestamp: Date.now(),
51
+ type,
52
+ params
53
+ };
54
+
55
+ this.queries.push(metric);
56
+ this.queryCountByType[type]++;
57
+
58
+ // Keep only last 1000 queries to prevent memory issues
59
+ if (this.queries.length > 1000) {
60
+ this.queries.shift();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Detect query type from SQL string
66
+ */
67
+ private detectQueryType(query: string): QueryMetric['type'] {
68
+ const normalized = query.trim().toUpperCase();
69
+
70
+ if (normalized.startsWith('SELECT')) return 'SELECT';
71
+ if (normalized.startsWith('INSERT')) return 'INSERT';
72
+ if (normalized.startsWith('UPDATE')) return 'UPDATE';
73
+ if (normalized.startsWith('DELETE')) return 'DELETE';
74
+
75
+ return 'OTHER';
76
+ }
77
+
78
+ /**
79
+ * Get current performance statistics
80
+ */
81
+ getStats(): PerformanceStats {
82
+ const totalQueries = this.queries.length;
83
+ const totalDuration = this.queries.reduce((sum, q) => sum + q.duration, 0);
84
+ const averageQueryTime = totalQueries > 0 ? totalDuration / totalQueries : 0;
85
+
86
+ const slowestQueries = [...this.queries]
87
+ .sort((a, b) => b.duration - a.duration)
88
+ .slice(0, 10);
89
+
90
+ return {
91
+ enabled: this.enabled,
92
+ totalQueries,
93
+ averageQueryTime: Math.round(averageQueryTime * 100) / 100,
94
+ slowestQueries,
95
+ queryCountByType: { ...this.queryCountByType },
96
+ startTime: this.startTime,
97
+ uptime: this.startTime > 0 ? Date.now() - this.startTime : 0
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Get slowest queries
103
+ */
104
+ getSlowQueries(limit: number = 10): QueryMetric[] {
105
+ return [...this.queries]
106
+ .sort((a, b) => b.duration - a.duration)
107
+ .slice(0, limit);
108
+ }
109
+
110
+ /**
111
+ * Reset all collected metrics
112
+ */
113
+ reset(): void {
114
+ this.queries = [];
115
+ this.queryCountByType = {
116
+ SELECT: 0,
117
+ INSERT: 0,
118
+ UPDATE: 0,
119
+ DELETE: 0,
120
+ OTHER: 0
121
+ };
122
+ this.startTime = Date.now();
123
+ console.log('[Performance Monitor] Reset');
124
+ }
125
+
126
+ /**
127
+ * Get queries executed in the last N milliseconds
128
+ */
129
+ getRecentQueries(milliseconds: number = 60000): QueryMetric[] {
130
+ const cutoff = Date.now() - milliseconds;
131
+ return this.queries.filter(q => q.timestamp >= cutoff);
132
+ }
133
+
134
+ /**
135
+ * Get queries per second
136
+ */
137
+ getQueriesPerSecond(): number {
138
+ const uptime = this.startTime > 0 ? (Date.now() - this.startTime) / 1000 : 0;
139
+ return uptime > 0 ? this.queries.length / uptime : 0;
140
+ }
141
+ }
142
+
143
+ // Singleton instance
144
+ const performanceMonitor = new PerformanceMonitor();
145
+
146
+ export default performanceMonitor;
147
+ export { performanceMonitor, PerformanceMonitor };