mysql-orm-lite 1.0.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 +1421 -0
- package/index.js +42 -0
- package/lib/TransactionCRUD.js +208 -0
- package/lib/connectionManager.js +117 -0
- package/lib/crud.js +301 -0
- package/lib/transactionManager.js +21 -0
- package/package.json +41 -0
- package/utils/logger.js +8 -0
package/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const connectionManager = require('./lib/connectionManager');
|
|
4
|
+
const crud = require('./lib/crud');
|
|
5
|
+
const TransactionCRUD = require('./lib/TransactionCRUD');
|
|
6
|
+
const transactionManager = require('./lib/transactionManager');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
// Connection Management
|
|
10
|
+
connectionManager,
|
|
11
|
+
|
|
12
|
+
// CRUD Operations (Non-transactional)
|
|
13
|
+
find: crud.find,
|
|
14
|
+
findCount: crud.findCount,
|
|
15
|
+
insert: crud.insert,
|
|
16
|
+
update: crud.update,
|
|
17
|
+
delete: crud.delete,
|
|
18
|
+
|
|
19
|
+
// Query Builder - Full names
|
|
20
|
+
buildAndExecuteSelectQuery: crud.buildAndExecuteSelectQuery,
|
|
21
|
+
buildAndExecuteUpdateQuery: crud.buildAndExecuteUpdateQuery,
|
|
22
|
+
buildAndExecuteDeleteQuery: crud.buildAndExecuteDeleteQuery,
|
|
23
|
+
|
|
24
|
+
// Query Builder - Alternative shorter names
|
|
25
|
+
select: crud.select,
|
|
26
|
+
findWhere: crud.findWhere,
|
|
27
|
+
query: crud.query,
|
|
28
|
+
updateWhere: crud.updateWhere,
|
|
29
|
+
updateQuery: crud.updateQuery,
|
|
30
|
+
deleteWhere: crud.deleteWhere,
|
|
31
|
+
remove: crud.remove,
|
|
32
|
+
|
|
33
|
+
// Transaction Support
|
|
34
|
+
TransactionCRUD,
|
|
35
|
+
transactionManager,
|
|
36
|
+
|
|
37
|
+
// Utility exports
|
|
38
|
+
utils: crud.utils,
|
|
39
|
+
|
|
40
|
+
// Convenience method to create transaction
|
|
41
|
+
createTransaction: () => new TransactionCRUD()
|
|
42
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const connectionManager = require('./connectionManager');
|
|
3
|
+
const crudUtils = require('./crud').utils;
|
|
4
|
+
|
|
5
|
+
class TransactionCRUD {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.connection = null;
|
|
8
|
+
this.transactionActive = false;
|
|
9
|
+
this.debug = false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize a transaction
|
|
14
|
+
* @param {Object} dbConfig - Database configuration (optional)
|
|
15
|
+
*/
|
|
16
|
+
async init(dbConfig) {
|
|
17
|
+
const logger = connectionManager.getLogger();
|
|
18
|
+
try {
|
|
19
|
+
const pool = await connectionManager.getPool(dbConfig);
|
|
20
|
+
this.connection = await pool.getConnection();
|
|
21
|
+
|
|
22
|
+
await this.connection.beginTransaction();
|
|
23
|
+
this.transactionActive = true;
|
|
24
|
+
logger.info('Transaction initialized successfully');
|
|
25
|
+
|
|
26
|
+
return this.connection;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error('Failed to initialize transaction:', error);
|
|
29
|
+
if (this.connection) {
|
|
30
|
+
this.connection.release();
|
|
31
|
+
this.connection = null;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute a query within the transaction
|
|
39
|
+
*/
|
|
40
|
+
async executeQuery(query, params = [], operation = 'TRANS_QUERY') {
|
|
41
|
+
if (!this.connection || !this.transactionActive) {
|
|
42
|
+
throw new Error('No active transaction. Call init() first.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const logger = connectionManager.getLogger();
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
try {
|
|
48
|
+
const [results] = await this.connection.query(query, params);
|
|
49
|
+
|
|
50
|
+
const duration = Date.now() - startTime;
|
|
51
|
+
if (duration > 1000) {
|
|
52
|
+
logger.warn(`[TRANS] Slow Query (${duration}ms): ${query}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return results;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error(`${operation} Failed: ${error.message}`);
|
|
58
|
+
logger.error(`Query: ${query}`);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Commit the transaction
|
|
65
|
+
*/
|
|
66
|
+
async commit() {
|
|
67
|
+
if (!this.connection || !this.transactionActive) {
|
|
68
|
+
throw new Error('No active transaction to commit');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const logger = connectionManager.getLogger();
|
|
72
|
+
try {
|
|
73
|
+
await this.connection.commit();
|
|
74
|
+
logger.info('Transaction committed');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('Commit failed, rolling back...', error);
|
|
77
|
+
await this.rollback();
|
|
78
|
+
throw error;
|
|
79
|
+
} finally {
|
|
80
|
+
this._cleanup();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Rollback the transaction
|
|
86
|
+
*/
|
|
87
|
+
async rollback() {
|
|
88
|
+
if (!this.connection) return;
|
|
89
|
+
|
|
90
|
+
const logger = connectionManager.getLogger();
|
|
91
|
+
try {
|
|
92
|
+
await this.connection.rollback();
|
|
93
|
+
logger.info('Transaction rolled back');
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error('Rollback failed:', error);
|
|
96
|
+
} finally {
|
|
97
|
+
this._cleanup();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_cleanup() {
|
|
102
|
+
if (this.connection) {
|
|
103
|
+
this.connection.release();
|
|
104
|
+
this.connection = null;
|
|
105
|
+
}
|
|
106
|
+
this.transactionActive = false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// CRUD Methods
|
|
110
|
+
|
|
111
|
+
async find(query, params = []) {
|
|
112
|
+
return await this.executeQuery(query, params, 'TRANS_FIND');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async insert(table, data) {
|
|
116
|
+
if (!table || !data) throw new Error('Invalid table or data');
|
|
117
|
+
const fields = Object.keys(data);
|
|
118
|
+
const values = Object.values(data);
|
|
119
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
120
|
+
const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`;
|
|
121
|
+
|
|
122
|
+
const result = await this.executeQuery(sql, values, 'TRANS_INSERT');
|
|
123
|
+
return result.insertId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async update(table, data, query) {
|
|
127
|
+
if (!query || !query.toLowerCase().includes('where')) throw new Error('Update requires WHERE');
|
|
128
|
+
|
|
129
|
+
const { setFields, params } = crudUtils.prepareUpdateParams(data);
|
|
130
|
+
const sql = `UPDATE ${table} SET ${setFields.join(', ')} ${query}`;
|
|
131
|
+
|
|
132
|
+
const result = await this.executeQuery(sql, params, 'TRANS_UPDATE');
|
|
133
|
+
return result.affectedRows;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async delete(query, table) {
|
|
137
|
+
if (!query || !query.toLowerCase().includes('where')) throw new Error('Delete requires WHERE');
|
|
138
|
+
const sql = table ? `DELETE FROM ${table} ${query}` : query;
|
|
139
|
+
const result = await this.executeQuery(sql, [], 'TRANS_DELETE');
|
|
140
|
+
return result.affectedRows;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async buildAndExecuteSelectQuery(options = {}) {
|
|
144
|
+
const logger = connectionManager.getLogger();
|
|
145
|
+
try {
|
|
146
|
+
const { query, params } = crudUtils._buildSelectQuery(options);
|
|
147
|
+
return await this.executeQuery(query, params, 'TRANS_BUILD_SELECT');
|
|
148
|
+
} catch (err) {
|
|
149
|
+
logger.error('buildAndExecuteSelectQuery failed:', err);
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async buildAndExecuteUpdateQuery(options = {}) {
|
|
155
|
+
const logger = connectionManager.getLogger();
|
|
156
|
+
try {
|
|
157
|
+
const { query, params } = crudUtils._buildUpdateQuery(options);
|
|
158
|
+
const result = await this.executeQuery(query, params, 'TRANS_BUILD_UPDATE');
|
|
159
|
+
return result.affectedRows;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error('buildAndExecuteUpdateQuery failed:', err);
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async buildAndExecuteDeleteQuery(options = {}) {
|
|
167
|
+
const logger = connectionManager.getLogger();
|
|
168
|
+
try {
|
|
169
|
+
const { query, params } = crudUtils._buildDeleteQuery(options);
|
|
170
|
+
const result = await this.executeQuery(query, params, 'TRANS_BUILD_DELETE');
|
|
171
|
+
return result.affectedRows;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
logger.error('buildAndExecuteDeleteQuery failed:', err);
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Alternative shorter method names (aliases)
|
|
179
|
+
async select(options) {
|
|
180
|
+
return this.buildAndExecuteSelectQuery(options);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async findWhere(options) {
|
|
184
|
+
return this.buildAndExecuteSelectQuery(options);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async query(options) {
|
|
188
|
+
return this.buildAndExecuteSelectQuery(options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async updateWhere(options) {
|
|
192
|
+
return this.buildAndExecuteUpdateQuery(options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async updateQuery(options) {
|
|
196
|
+
return this.buildAndExecuteUpdateQuery(options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async deleteWhere(options) {
|
|
200
|
+
return this.buildAndExecuteDeleteQuery(options);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async remove(options) {
|
|
204
|
+
return this.buildAndExecuteDeleteQuery(options);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = TransactionCRUD;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const mysql = require('mysql2/promise');
|
|
3
|
+
|
|
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)
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const pools = new Map();
|
|
12
|
+
let logger = defaultLogger;
|
|
13
|
+
let defaultConfig = null;
|
|
14
|
+
|
|
15
|
+
const getPoolKey = (config) => {
|
|
16
|
+
return `${config.host}:${config.port || 3306}:${config.user}:${config.database}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const connectionManager = {
|
|
20
|
+
/**
|
|
21
|
+
* Initialize with default config and optional logger
|
|
22
|
+
* @param {Object} config - Database configuration
|
|
23
|
+
* @param {Object} customLogger - Custom logger (optional)
|
|
24
|
+
*/
|
|
25
|
+
init: (config, customLogger) => {
|
|
26
|
+
if (!config || !config.database) {
|
|
27
|
+
throw new Error('Valid database configuration required');
|
|
28
|
+
}
|
|
29
|
+
defaultConfig = config;
|
|
30
|
+
if (customLogger) {
|
|
31
|
+
logger = customLogger;
|
|
32
|
+
}
|
|
33
|
+
logger.info('Connection manager initialized');
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set custom logger
|
|
38
|
+
* @param {Object} customLogger - Logger with info, error, warn methods
|
|
39
|
+
*/
|
|
40
|
+
setLogger: (customLogger) => {
|
|
41
|
+
logger = customLogger;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get or create a connection pool
|
|
46
|
+
* @param {Object} dbConfig - Database configuration (optional)
|
|
47
|
+
* @returns {Promise<Pool>} MySQL connection pool
|
|
48
|
+
*/
|
|
49
|
+
getPool: async (dbConfig) => {
|
|
50
|
+
const conf = dbConfig || defaultConfig;
|
|
51
|
+
if (!conf || !conf.database) {
|
|
52
|
+
throw new Error('Database configuration missing. Call connectionManager.init() first or provide dbConfig');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const key = getPoolKey(conf);
|
|
56
|
+
if (pools.has(key)) {
|
|
57
|
+
return pools.get(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
logger.info(`Creating new connection pool for ${key}`);
|
|
62
|
+
const pool = mysql.createPool({
|
|
63
|
+
host: conf.host,
|
|
64
|
+
user: conf.user,
|
|
65
|
+
password: conf.password,
|
|
66
|
+
database: conf.database,
|
|
67
|
+
port: conf.port || 3306,
|
|
68
|
+
waitForConnections: true,
|
|
69
|
+
connectionLimit: conf.connectionLimit || 10,
|
|
70
|
+
queueLimit: 0,
|
|
71
|
+
enableKeepAlive: true,
|
|
72
|
+
keepAliveInitialDelay: 0
|
|
73
|
+
});
|
|
74
|
+
pools.set(key, pool);
|
|
75
|
+
return pool;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.error('Failed to create connection pool:', error);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Close a specific pool
|
|
84
|
+
* @param {Object} config - Database configuration
|
|
85
|
+
*/
|
|
86
|
+
closePool: async (config) => {
|
|
87
|
+
const key = getPoolKey(config);
|
|
88
|
+
if (pools.has(key)) {
|
|
89
|
+
const pool = pools.get(key);
|
|
90
|
+
await pool.end();
|
|
91
|
+
pools.delete(key);
|
|
92
|
+
logger.info(`Closed connection pool for ${key}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Close all pools (call on application shutdown)
|
|
98
|
+
*/
|
|
99
|
+
closeAllPools: async () => {
|
|
100
|
+
for (const [key, pool] of pools.entries()) {
|
|
101
|
+
try {
|
|
102
|
+
await pool.end();
|
|
103
|
+
logger.info(`Closed connection pool for ${key}`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(`Error closing pool ${key}:`, error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
pools.clear();
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get logger instance
|
|
113
|
+
*/
|
|
114
|
+
getLogger: () => logger
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports = connectionManager;
|
package/lib/crud.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const connectionManager = require('./connectionManager');
|
|
3
|
+
|
|
4
|
+
const utils = {
|
|
5
|
+
handleConnectionError(err) {
|
|
6
|
+
const errorMessages = {
|
|
7
|
+
PROTOCOL_CONNECTION_LOST: 'Database connection was closed.',
|
|
8
|
+
ER_CON_COUNT_ERROR: 'Database has too many connections.',
|
|
9
|
+
ECONNREFUSED: 'Database connection was refused.',
|
|
10
|
+
POOL_CLOSED: 'Connection pool was closed'
|
|
11
|
+
};
|
|
12
|
+
return new Error(errorMessages[err.code] || err.message);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
logQueryPerformance(query, startTime, params = []) {
|
|
16
|
+
const logger = connectionManager.getLogger();
|
|
17
|
+
const duration = Date.now() - startTime;
|
|
18
|
+
if (duration > 1000) {
|
|
19
|
+
logger.warn(' Slow Query ================================================================== ');
|
|
20
|
+
logger.warn(` Execution Time: ${duration}ms`);
|
|
21
|
+
logger.warn(` Query: ${query}`);
|
|
22
|
+
if (params.length) logger.warn(` Parameters: ${JSON.stringify(params)}`);
|
|
23
|
+
logger.warn(' =========================================================================== ');
|
|
24
|
+
}
|
|
25
|
+
return duration;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async executeQuery({ query, params = [], dbConfig, operation = 'query' }) {
|
|
29
|
+
const startTime = Date.now();
|
|
30
|
+
const logger = connectionManager.getLogger();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const pool = await connectionManager.getPool(dbConfig);
|
|
34
|
+
const [results] = await pool.query(query, params);
|
|
35
|
+
this.logQueryPerformance(query, startTime, params);
|
|
36
|
+
return results;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error(`${operation} Error: ${error.message}`);
|
|
39
|
+
logger.error(`Query: ${query}`);
|
|
40
|
+
if (params.length) logger.error('Parameters:', JSON.stringify(params));
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
validateUpdateParams(table, data, query) {
|
|
46
|
+
const errors = [];
|
|
47
|
+
if (!table || typeof table !== 'string') errors.push('Invalid table name');
|
|
48
|
+
else if (!/^[a-zA-Z0-9_]+$/.test(table)) errors.push('Table name contains invalid characters');
|
|
49
|
+
|
|
50
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) errors.push('Data must be a non-null object');
|
|
51
|
+
else if (Object.keys(data).length === 0) errors.push('Data object cannot be empty');
|
|
52
|
+
|
|
53
|
+
if (!query || typeof query !== 'string') errors.push('Invalid WHERE clause');
|
|
54
|
+
else if (!query.toLowerCase().includes('where')) errors.push('WHERE clause is required for security');
|
|
55
|
+
|
|
56
|
+
return errors;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
prepareUpdateParams(data) {
|
|
60
|
+
const setFields = [];
|
|
61
|
+
const params = [];
|
|
62
|
+
|
|
63
|
+
for (const [key, value] of Object.entries(data)) {
|
|
64
|
+
if (value === undefined) continue;
|
|
65
|
+
|
|
66
|
+
if (value && typeof value === 'object' && value.__raw) {
|
|
67
|
+
setFields.push(`${key} = ${value.value}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (value === null) {
|
|
72
|
+
setFields.push(`${key} = NULL`);
|
|
73
|
+
} else {
|
|
74
|
+
setFields.push(`${key} = ?`);
|
|
75
|
+
params.push(value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { setFields, params };
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
_buildWhereClause(conditions) {
|
|
83
|
+
const params = [];
|
|
84
|
+
const buildCondition = (key, value) => {
|
|
85
|
+
const column = key;
|
|
86
|
+
if (value === null) return `${column} IS NULL`;
|
|
87
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
88
|
+
return Object.entries(value).map(([op, val]) => {
|
|
89
|
+
switch (op) {
|
|
90
|
+
case '$eq': params.push(val); return `${column} = ?`;
|
|
91
|
+
case '$ne': params.push(val); return `${column} != ?`;
|
|
92
|
+
case '$gt': params.push(val); return `${column} > ?`;
|
|
93
|
+
case '$gte': params.push(val); return `${column} >= ?`;
|
|
94
|
+
case '$lt': params.push(val); return `${column} < ?`;
|
|
95
|
+
case '$lte': params.push(val); return `${column} <= ?`;
|
|
96
|
+
case '$in':
|
|
97
|
+
if (!val.length) return 'FALSE';
|
|
98
|
+
params.push(...val);
|
|
99
|
+
return `${column} IN (${val.map(() => '?').join(', ')})`;
|
|
100
|
+
case '$notIn':
|
|
101
|
+
case '$nIn':
|
|
102
|
+
case '$nin':
|
|
103
|
+
if (!val.length) return 'TRUE';
|
|
104
|
+
params.push(...val);
|
|
105
|
+
return `${column} NOT IN (${val.map(() => '?').join(', ')})`;
|
|
106
|
+
case '$like':
|
|
107
|
+
params.push(val); return `${column} LIKE ?`;
|
|
108
|
+
case '$between':
|
|
109
|
+
if (val.length !== 2) throw new Error('$between requires [min, max]');
|
|
110
|
+
params.push(val[0], val[1]);
|
|
111
|
+
return `${column} BETWEEN ? AND ?`;
|
|
112
|
+
default: throw new Error(`Unsupported operator: ${op}`);
|
|
113
|
+
}
|
|
114
|
+
}).join(' AND ');
|
|
115
|
+
}
|
|
116
|
+
params.push(value);
|
|
117
|
+
return `${column} = ?`;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const walk = (cond) => {
|
|
121
|
+
if (!cond || typeof cond !== 'object') return '';
|
|
122
|
+
if (Array.isArray(cond)) return cond.map(walk).join(' AND ');
|
|
123
|
+
|
|
124
|
+
if ('$and' in cond) return `(${cond.$and.map(walk).join(' AND ')})`;
|
|
125
|
+
if ('$or' in cond) return `(${cond.$or.map(walk).join(' OR ')})`;
|
|
126
|
+
if ('$not' in cond) return `(NOT ${walk(cond.$not)})`;
|
|
127
|
+
|
|
128
|
+
return Object.entries(cond).map(([k, v]) => buildCondition(k, v)).join(' AND ');
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const clause = walk(conditions);
|
|
132
|
+
return { clause, params };
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
_buildSelectQuery(options) {
|
|
136
|
+
let { table, fields = '*', joins = [], where, orderBy, limit, offset, groupBy, having, alias } = options;
|
|
137
|
+
|
|
138
|
+
if (fields && Array.isArray(fields) && fields.length > 0) {
|
|
139
|
+
fields = fields.filter(field => field && field.trim() !== '').join(', ');
|
|
140
|
+
} else if (fields && Array.isArray(fields)) {
|
|
141
|
+
fields = '*';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let query = `SELECT ${fields} FROM ${table}${alias ? ' ' + alias : ''}`;
|
|
145
|
+
const allParams = [];
|
|
146
|
+
|
|
147
|
+
for (const join of joins) {
|
|
148
|
+
let { type = 'INNER', table: joinTable, alias, on } = join;
|
|
149
|
+
const onClause = Array.isArray(on) ? on.join(' AND ') : on;
|
|
150
|
+
query += ` ${type.toUpperCase()} JOIN ${joinTable}${alias ? ' ' + alias : ''} ON ${onClause}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (where) {
|
|
154
|
+
const { clause, params } = this._buildWhereClause(where);
|
|
155
|
+
if (clause) {
|
|
156
|
+
query += ` WHERE ${clause}`;
|
|
157
|
+
allParams.push(...params);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (groupBy) query += ` GROUP BY ${groupBy}`;
|
|
162
|
+
if (having) query += ` HAVING ${having}`;
|
|
163
|
+
if (orderBy) query += ` ORDER BY ${orderBy}`;
|
|
164
|
+
|
|
165
|
+
if (typeof limit === 'number' && limit && limit > 0) {
|
|
166
|
+
query += ` LIMIT ${limit}`;
|
|
167
|
+
if (typeof offset === 'number') query += ` OFFSET ${offset}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { query, params: allParams };
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
_buildUpdateQuery(options) {
|
|
174
|
+
const { table, data, where } = options;
|
|
175
|
+
const { setFields, params } = this.prepareUpdateParams(data);
|
|
176
|
+
|
|
177
|
+
if (setFields.length === 0) throw new Error('No valid fields to update');
|
|
178
|
+
|
|
179
|
+
let query = `UPDATE ${table} SET ${setFields.join(', ')}`;
|
|
180
|
+
const allParams = [...params];
|
|
181
|
+
|
|
182
|
+
if (where) {
|
|
183
|
+
const { clause, params: whereParams } = this._buildWhereClause(where);
|
|
184
|
+
if (clause) {
|
|
185
|
+
query += ` WHERE ${clause}`;
|
|
186
|
+
allParams.push(...whereParams);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { query, params: allParams };
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
_buildDeleteQuery(options) {
|
|
194
|
+
const { table, where } = options;
|
|
195
|
+
if (!table) throw new Error('Table name is required for delete');
|
|
196
|
+
|
|
197
|
+
let query = `DELETE FROM ${table}`;
|
|
198
|
+
const allParams = [];
|
|
199
|
+
|
|
200
|
+
if (where) {
|
|
201
|
+
const { clause, params: whereParams } = this._buildWhereClause(where);
|
|
202
|
+
if (clause) {
|
|
203
|
+
query += ` WHERE ${clause}`;
|
|
204
|
+
allParams.push(...whereParams);
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error('DELETE requires a valid WHERE clause for safety');
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
throw new Error('DELETE requires a WHERE clause for safety');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { query, params: allParams };
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Public API
|
|
217
|
+
exports.find = async function (query, params = [], dbConfig) {
|
|
218
|
+
if (!query) return [];
|
|
219
|
+
return await utils.executeQuery({ query, params, dbConfig, operation: 'find' });
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
exports.findCount = async function (query, params = [], dbConfig) {
|
|
223
|
+
if (!query) return 0;
|
|
224
|
+
const results = await utils.executeQuery({ query, params, dbConfig, operation: 'findCount' });
|
|
225
|
+
return results && results[0] ? results[0].count : 0;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
exports.insert = async function (table, data, dbConfig, debug = false, isIgnore = false) {
|
|
229
|
+
if (!table || !data || typeof data !== 'object') throw new Error('Invalid table or data');
|
|
230
|
+
|
|
231
|
+
const logger = connectionManager.getLogger();
|
|
232
|
+
const fields = Object.keys(data);
|
|
233
|
+
const values = Object.values(data);
|
|
234
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
235
|
+
const sql = isIgnore
|
|
236
|
+
? `INSERT IGNORE INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`
|
|
237
|
+
: `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`;
|
|
238
|
+
|
|
239
|
+
const result = await utils.executeQuery({ query: sql, params: values, dbConfig, operation: 'insert' });
|
|
240
|
+
|
|
241
|
+
if (debug) {
|
|
242
|
+
logger.info({ operation: 'INSERT', table, insertId: result.insertId });
|
|
243
|
+
}
|
|
244
|
+
return result.insertId;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
exports.update = async function (table, data, query, dbConfig, debug = false) {
|
|
248
|
+
const errors = utils.validateUpdateParams(table, data, query);
|
|
249
|
+
if (errors.length > 0) throw new Error(`Validation failed: ${errors.join(', ')}`);
|
|
250
|
+
|
|
251
|
+
const logger = connectionManager.getLogger();
|
|
252
|
+
const { setFields, params } = utils.prepareUpdateParams(data);
|
|
253
|
+
if (setFields.length === 0) throw new Error('No valid fields to update');
|
|
254
|
+
|
|
255
|
+
const sql = `UPDATE ${table} SET ${setFields.join(', ')} ${query}`;
|
|
256
|
+
const result = await utils.executeQuery({ query: sql, params, dbConfig, operation: 'update' });
|
|
257
|
+
|
|
258
|
+
if (debug) {
|
|
259
|
+
logger.info({ operation: 'UPDATE', table, affectedRows: result.affectedRows });
|
|
260
|
+
}
|
|
261
|
+
return result.affectedRows;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
exports.delete = async function (query, table, dbConfig) {
|
|
265
|
+
if (!query || !query.toLowerCase().includes('where')) throw new Error('Invalid query or missing WHERE clause');
|
|
266
|
+
|
|
267
|
+
const logger = connectionManager.getLogger();
|
|
268
|
+
const sql = table ? `DELETE FROM ${table} ${query}` : query;
|
|
269
|
+
const result = await utils.executeQuery({ query: sql, params: [], dbConfig, operation: 'delete' });
|
|
270
|
+
logger.info(`Deleted ${result.affectedRows} records from ${table || 'custom query'}`);
|
|
271
|
+
return result.affectedRows;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Query Builder Methods - Multiple naming options for flexibility
|
|
275
|
+
exports.buildAndExecuteSelectQuery = async function (options, dbConfig) {
|
|
276
|
+
const { query, params } = utils._buildSelectQuery(options);
|
|
277
|
+
return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteSelectQuery' });
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
exports.buildAndExecuteUpdateQuery = async function (options, dbConfig) {
|
|
281
|
+
const { query, params } = utils._buildUpdateQuery(options);
|
|
282
|
+
return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteUpdateQuery' });
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
exports.buildAndExecuteDeleteQuery = async function (options, dbConfig) {
|
|
286
|
+
const { query, params } = utils._buildDeleteQuery(options);
|
|
287
|
+
return await utils.executeQuery({ query, params, dbConfig, operation: 'buildAndExecuteDeleteQuery' });
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Alternative shorter method names (aliases)
|
|
291
|
+
exports.select = exports.buildAndExecuteSelectQuery;
|
|
292
|
+
exports.findWhere = exports.buildAndExecuteSelectQuery;
|
|
293
|
+
exports.query = exports.buildAndExecuteSelectQuery;
|
|
294
|
+
|
|
295
|
+
exports.updateWhere = exports.buildAndExecuteUpdateQuery;
|
|
296
|
+
exports.updateQuery = exports.buildAndExecuteUpdateQuery;
|
|
297
|
+
|
|
298
|
+
exports.deleteWhere = exports.buildAndExecuteDeleteQuery;
|
|
299
|
+
exports.remove = exports.buildAndExecuteDeleteQuery;
|
|
300
|
+
|
|
301
|
+
exports.utils = utils;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let transactionInstance = null;
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
setInstance: (instance) => {
|
|
7
|
+
transactionInstance = instance;
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
getInstance: () => {
|
|
11
|
+
return transactionInstance || null;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
clearInstance: () => {
|
|
15
|
+
transactionInstance = null;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
hasActiveTransaction: () => {
|
|
19
|
+
return transactionInstance !== null && transactionInstance.transactionActive;
|
|
20
|
+
}
|
|
21
|
+
};
|