masterrecord 0.3.15 → 0.3.17
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/.claude/settings.local.json +4 -1
- package/Migrations/schema.js +10 -9
- package/SQLLiteEngine.js +83 -48
- package/context.js +48 -38
- package/deleteManager.js +11 -11
- package/insertManager.js +7 -7
- package/mySQLAsyncConnect.js +44 -0
- package/package.json +2 -2
- package/realMySQLEngine.js +836 -0
- package/test/parameterizedPlaceholderTest.js +1 -1
- package/mySQLEngine.js +0 -1105
- package/mySQLSyncConnect.js +0 -82
package/Migrations/schema.js
CHANGED
|
@@ -295,32 +295,33 @@ class schema{
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
// EnsureCreated equivalent for MySQL: create DB if missing
|
|
298
|
-
createDatabase(){
|
|
298
|
+
async createDatabase(){
|
|
299
299
|
try{
|
|
300
300
|
if(!(this.context && this.context.isMySQL)){ return; }
|
|
301
|
-
const
|
|
301
|
+
const MySQLAsyncClient = require('masterrecord/mySQLAsyncConnect');
|
|
302
302
|
const client = this.context.db; // main client (may not be connected yet)
|
|
303
303
|
if(!client || !client.config || !client.config.database){ return; }
|
|
304
304
|
const dbName = client.config.database;
|
|
305
305
|
// Build server-level connection (no database)
|
|
306
306
|
const baseConfig = { ...client.config };
|
|
307
307
|
delete baseConfig.database;
|
|
308
|
-
const admin = new
|
|
309
|
-
admin.connect();
|
|
310
|
-
|
|
308
|
+
const admin = new MySQLAsyncClient(baseConfig);
|
|
309
|
+
await admin.connect();
|
|
310
|
+
const pool = admin.getPool();
|
|
311
|
+
if(!pool){ return; }
|
|
311
312
|
|
|
312
313
|
// Use parameterized query for checking database existence
|
|
313
|
-
const
|
|
314
|
-
const exists = Array.isArray(
|
|
314
|
+
const [rows] = await pool.execute(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`, [dbName]);
|
|
315
|
+
const exists = Array.isArray(rows) && rows.length > 0;
|
|
315
316
|
if(!exists){
|
|
316
317
|
// Validate database name (alphanumeric, underscore, hyphen only)
|
|
317
318
|
if(!/^[a-zA-Z0-9_-]+$/.test(dbName)){
|
|
318
319
|
throw new Error(`Invalid database name: ${dbName}. Only alphanumeric characters, underscores, and hyphens are allowed.`);
|
|
319
320
|
}
|
|
320
321
|
// CREATE DATABASE doesn't support placeholders, but we've validated the name
|
|
321
|
-
|
|
322
|
+
await pool.execute(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
|
322
323
|
}
|
|
323
|
-
admin.close();
|
|
324
|
+
await admin.close();
|
|
324
325
|
this._dbEnsured = true;
|
|
325
326
|
}catch(err){
|
|
326
327
|
// Non-fatal: migrations may still proceed if DB already exists or permissions blocked
|
package/SQLLiteEngine.js
CHANGED
|
@@ -6,7 +6,7 @@ class SQLLiteEngine {
|
|
|
6
6
|
|
|
7
7
|
unsupportedWords = ["order"]
|
|
8
8
|
|
|
9
|
-
update(query){
|
|
9
|
+
async update(query){
|
|
10
10
|
// Security: ONLY use parameterized queries - no fallback to string concatenation
|
|
11
11
|
// query.arg must contain {sql, params} from _buildSQLEqualToParameterized
|
|
12
12
|
if(!query.arg || typeof query.arg !== 'object' || !query.arg.sql || !query.arg.params){
|
|
@@ -18,17 +18,17 @@ class SQLLiteEngine {
|
|
|
18
18
|
WHERE [${query.tableName}].[${query.primaryKey}] = ?`;
|
|
19
19
|
// Add primaryKeyValue to params array
|
|
20
20
|
var params = [...query.arg.params, query.primaryKeyValue];
|
|
21
|
-
return this._runWithParams(sqlQuery, params);
|
|
21
|
+
return Promise.resolve(this._runWithParams(sqlQuery, params));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
delete(queryObject){
|
|
24
|
+
async delete(queryObject){
|
|
25
25
|
var sqlObject = this._buildDeleteObject(queryObject);
|
|
26
26
|
// Use parameterized query to prevent SQL injection
|
|
27
27
|
var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ?`;
|
|
28
|
-
return this._executeWithParams(sqlQuery, [sqlObject.value]);
|
|
28
|
+
return Promise.resolve(this._executeWithParams(sqlQuery, [sqlObject.value]));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
insert(queryObject){
|
|
31
|
+
async insert(queryObject){
|
|
32
32
|
// Use NEW SECURE parameterized version
|
|
33
33
|
var sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
|
|
34
34
|
if(sqlObject === -1){
|
|
@@ -40,28 +40,35 @@ class SQLLiteEngine {
|
|
|
40
40
|
var open = {
|
|
41
41
|
"id": queryObj.lastInsertRowid
|
|
42
42
|
};
|
|
43
|
-
return open;
|
|
43
|
+
return Promise.resolve(open);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Batch insert multiple entities in a single transaction
|
|
48
48
|
* Performance: 100x faster than N separate inserts
|
|
49
49
|
*/
|
|
50
|
-
bulkInsert(entities) {
|
|
51
|
-
if (!entities || entities.length === 0) return [];
|
|
50
|
+
async bulkInsert(entities) {
|
|
51
|
+
if (!entities || entities.length === 0) return Promise.resolve([]);
|
|
52
52
|
|
|
53
53
|
const results = [];
|
|
54
|
-
// SQLite: Use transaction for batch inserts
|
|
55
|
-
this.
|
|
54
|
+
// SQLite: Use transaction for batch inserts (only if not already in one)
|
|
55
|
+
const needsTransaction = !this.db.inTransaction;
|
|
56
|
+
if (needsTransaction) {
|
|
57
|
+
await this.startTransaction();
|
|
58
|
+
}
|
|
56
59
|
try {
|
|
57
60
|
for (const entity of entities) {
|
|
58
|
-
const result = this.insert(entity);
|
|
61
|
+
const result = await this.insert(entity);
|
|
59
62
|
results.push(result);
|
|
60
63
|
}
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
if (needsTransaction) {
|
|
65
|
+
await this.endTransaction();
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve(results);
|
|
63
68
|
} catch (error) {
|
|
64
|
-
|
|
69
|
+
if (needsTransaction) {
|
|
70
|
+
await this.errorTransaction();
|
|
71
|
+
}
|
|
65
72
|
throw error;
|
|
66
73
|
}
|
|
67
74
|
}
|
|
@@ -69,17 +76,25 @@ class SQLLiteEngine {
|
|
|
69
76
|
/**
|
|
70
77
|
* Batch update multiple entities
|
|
71
78
|
*/
|
|
72
|
-
bulkUpdate(updateQueries) {
|
|
73
|
-
if (!updateQueries || updateQueries.length === 0) return;
|
|
79
|
+
async bulkUpdate(updateQueries) {
|
|
80
|
+
if (!updateQueries || updateQueries.length === 0) return Promise.resolve();
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
// Only start transaction if not already in one
|
|
83
|
+
const needsTransaction = !this.db.inTransaction;
|
|
84
|
+
if (needsTransaction) {
|
|
85
|
+
await this.startTransaction();
|
|
86
|
+
}
|
|
76
87
|
try {
|
|
77
88
|
for (const query of updateQueries) {
|
|
78
|
-
this.update(query);
|
|
89
|
+
await this.update(query);
|
|
90
|
+
}
|
|
91
|
+
if (needsTransaction) {
|
|
92
|
+
await this.endTransaction();
|
|
79
93
|
}
|
|
80
|
-
this.endTransaction();
|
|
81
94
|
} catch (error) {
|
|
82
|
-
|
|
95
|
+
if (needsTransaction) {
|
|
96
|
+
await this.errorTransaction();
|
|
97
|
+
}
|
|
83
98
|
throw error;
|
|
84
99
|
}
|
|
85
100
|
}
|
|
@@ -87,15 +102,15 @@ class SQLLiteEngine {
|
|
|
87
102
|
/**
|
|
88
103
|
* Batch delete multiple entities using WHERE IN
|
|
89
104
|
*/
|
|
90
|
-
bulkDelete(tableName, ids) {
|
|
91
|
-
if (!ids || ids.length === 0) return;
|
|
105
|
+
async bulkDelete(tableName, ids) {
|
|
106
|
+
if (!ids || ids.length === 0) return Promise.resolve();
|
|
92
107
|
|
|
93
108
|
const placeholders = ids.map(() => '?').join(', ');
|
|
94
109
|
const query = `DELETE FROM [${tableName}] WHERE id IN (${placeholders})`;
|
|
95
|
-
return this._runWithParams(query, ids);
|
|
110
|
+
return Promise.resolve(this._runWithParams(query, ids));
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
get(query, entity, context){
|
|
113
|
+
async get(query, entity, context){
|
|
99
114
|
var queryString = {};
|
|
100
115
|
try {
|
|
101
116
|
if(query.raw){
|
|
@@ -117,26 +132,26 @@ class SQLLiteEngine {
|
|
|
117
132
|
console.debug("[Params]", params);
|
|
118
133
|
}
|
|
119
134
|
var queryReturn = this.db.prepare(queryString.query).get(...params);
|
|
120
|
-
return queryReturn;
|
|
135
|
+
return Promise.resolve(queryReturn);
|
|
121
136
|
}
|
|
122
|
-
return null;
|
|
137
|
+
return Promise.resolve(null);
|
|
123
138
|
} catch (err) {
|
|
124
139
|
console.error(err);
|
|
125
|
-
return null;
|
|
140
|
+
return Promise.resolve(null);
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
// Introspection helpers
|
|
130
|
-
tableExists(tableName){
|
|
145
|
+
async tableExists(tableName){
|
|
131
146
|
try{
|
|
132
147
|
// Use parameterized query to prevent SQL injection
|
|
133
148
|
const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
|
|
134
149
|
const row = this.db.prepare(sql).get(tableName);
|
|
135
|
-
return !!row;
|
|
136
|
-
}catch(_){ return false; }
|
|
150
|
+
return Promise.resolve(!!row);
|
|
151
|
+
}catch(_){ return Promise.resolve(false); }
|
|
137
152
|
}
|
|
138
153
|
|
|
139
|
-
getTableInfo(tableName){
|
|
154
|
+
async getTableInfo(tableName){
|
|
140
155
|
try{
|
|
141
156
|
// Security: Validate table name to prevent SQL injection
|
|
142
157
|
// PRAGMA statements don't support parameterized queries
|
|
@@ -149,11 +164,11 @@ class SQLLiteEngine {
|
|
|
149
164
|
}
|
|
150
165
|
const sql = `PRAGMA table_info(${tableName})`;
|
|
151
166
|
const rows = this.db.prepare(sql).all();
|
|
152
|
-
return rows || [];
|
|
153
|
-
}catch(_){ return []; }
|
|
167
|
+
return Promise.resolve(rows || []);
|
|
168
|
+
}catch(_){ return Promise.resolve([]); }
|
|
154
169
|
}
|
|
155
170
|
|
|
156
|
-
getCount(queryObject, entity, context){
|
|
171
|
+
async getCount(queryObject, entity, context){
|
|
157
172
|
var query = queryObject.script;
|
|
158
173
|
var queryString = {};
|
|
159
174
|
try {
|
|
@@ -176,23 +191,23 @@ class SQLLiteEngine {
|
|
|
176
191
|
console.debug("[Params]", params);
|
|
177
192
|
}
|
|
178
193
|
var queryReturn = this.db.prepare(queryCount).get(...params);
|
|
179
|
-
return queryReturn;
|
|
194
|
+
return Promise.resolve(queryReturn);
|
|
180
195
|
}
|
|
181
|
-
return null;
|
|
196
|
+
return Promise.resolve(null);
|
|
182
197
|
} catch (err) {
|
|
183
198
|
console.error(err);
|
|
184
|
-
return null;
|
|
199
|
+
return Promise.resolve(null);
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
|
|
188
|
-
all(query, entity, context){
|
|
203
|
+
async all(query, entity, context){
|
|
189
204
|
var selectQuery = {};
|
|
190
205
|
try {
|
|
191
206
|
if(query.raw){
|
|
192
207
|
selectQuery.query = query.raw;
|
|
193
208
|
}
|
|
194
209
|
else{
|
|
195
|
-
|
|
210
|
+
|
|
196
211
|
selectQuery = this.buildQuery(query, entity, context);
|
|
197
212
|
}
|
|
198
213
|
if(selectQuery.query){
|
|
@@ -203,12 +218,12 @@ class SQLLiteEngine {
|
|
|
203
218
|
console.debug("[Params]", params);
|
|
204
219
|
}
|
|
205
220
|
var queryReturn = this.db.prepare(selectQuery.query).all(...params);
|
|
206
|
-
return queryReturn;
|
|
221
|
+
return Promise.resolve(queryReturn);
|
|
207
222
|
}
|
|
208
|
-
return null;
|
|
223
|
+
return Promise.resolve(null);
|
|
209
224
|
} catch (err) {
|
|
210
225
|
console.error(err);
|
|
211
|
-
return null;
|
|
226
|
+
return Promise.resolve(null);
|
|
212
227
|
}
|
|
213
228
|
}
|
|
214
229
|
|
|
@@ -623,16 +638,25 @@ class SQLLiteEngine {
|
|
|
623
638
|
return false;
|
|
624
639
|
}
|
|
625
640
|
|
|
626
|
-
startTransaction(){
|
|
627
|
-
|
|
641
|
+
async startTransaction(){
|
|
642
|
+
// Prevent nested transactions (SQLite limitation)
|
|
643
|
+
return Promise.resolve(
|
|
644
|
+
this.db.inTransaction ? null : this.db.prepare('BEGIN').run()
|
|
645
|
+
);
|
|
628
646
|
}
|
|
629
647
|
|
|
630
|
-
endTransaction(){
|
|
631
|
-
|
|
648
|
+
async endTransaction(){
|
|
649
|
+
// Only commit if transaction is active
|
|
650
|
+
return Promise.resolve(
|
|
651
|
+
this.db.inTransaction ? this.db.prepare('COMMIT').run() : null
|
|
652
|
+
);
|
|
632
653
|
}
|
|
633
654
|
|
|
634
|
-
errorTransaction(){
|
|
635
|
-
|
|
655
|
+
async errorTransaction(){
|
|
656
|
+
// Only rollback if transaction is active
|
|
657
|
+
return Promise.resolve(
|
|
658
|
+
this.db.inTransaction ? this.db.prepare('ROLLBACK').run() : null
|
|
659
|
+
);
|
|
636
660
|
}
|
|
637
661
|
|
|
638
662
|
_buildSQLEqualTo(model){
|
|
@@ -1222,6 +1246,17 @@ class SQLLiteEngine {
|
|
|
1222
1246
|
this.db = db;
|
|
1223
1247
|
this.dbType = type; // this will let us know which type of sqlengine to use.
|
|
1224
1248
|
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Close database connection
|
|
1252
|
+
* Required for proper cleanup of better-sqlite3 native bindings
|
|
1253
|
+
*/
|
|
1254
|
+
close() {
|
|
1255
|
+
if (this.db) {
|
|
1256
|
+
this.db.close();
|
|
1257
|
+
console.log('SQLite database closed');
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1225
1260
|
}
|
|
1226
1261
|
|
|
1227
1262
|
module.exports = SQLLiteEngine;
|
package/context.js
CHANGED
|
@@ -20,7 +20,7 @@ const modelBuilder = require('./Entity/entityModelBuilder');
|
|
|
20
20
|
const query = require('masterrecord/QueryLanguage/queryMethods');
|
|
21
21
|
const tools = require('./Tools');
|
|
22
22
|
const SQLLiteEngine = require('masterrecord/SQLLiteEngine');
|
|
23
|
-
const
|
|
23
|
+
const MySQLEngine = require('masterrecord/realMySQLEngine');
|
|
24
24
|
const PostgresEngine = require('masterrecord/postgresEngine');
|
|
25
25
|
const insertManager = require('./insertManager');
|
|
26
26
|
const deleteManager = require('./deleteManager');
|
|
@@ -28,7 +28,7 @@ const globSearch = require('glob');
|
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
30
|
const appRoot = require('app-root-path');
|
|
31
|
-
const
|
|
31
|
+
const MySQLAsyncClient = require('masterrecord/mySQLAsyncConnect');
|
|
32
32
|
const PostgresClient = require('masterrecord/postgresSyncConnect');
|
|
33
33
|
const QueryCache = require('./Cache/QueryCache');
|
|
34
34
|
|
|
@@ -284,7 +284,7 @@ class context {
|
|
|
284
284
|
* @returns {object} MySQL connection instance
|
|
285
285
|
* @throws {DatabaseConnectionError} If connection fails
|
|
286
286
|
*/
|
|
287
|
-
__mysqlInit(env, sqlName) {
|
|
287
|
+
async __mysqlInit(env, sqlName) {
|
|
288
288
|
try {
|
|
289
289
|
// Validate required MySQL configuration
|
|
290
290
|
if (!env.database || typeof env.database !== 'string') {
|
|
@@ -303,11 +303,18 @@ class context {
|
|
|
303
303
|
);
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
console.log('[MySQL] Initializing async connection pool...');
|
|
307
|
+
const client = new MySQLAsyncClient(env);
|
|
308
|
+
await client.connect();
|
|
309
|
+
|
|
310
|
+
const pool = client.getPool();
|
|
311
|
+
this._SQLEngine = new MySQLEngine();
|
|
312
|
+
this._SQLEngine.setDB(pool);
|
|
308
313
|
this._SQLEngine.__name = sqlName;
|
|
314
|
+
this.isMySQL = true;
|
|
309
315
|
|
|
310
|
-
|
|
316
|
+
console.log('[MySQL] Connection pool ready');
|
|
317
|
+
return client;
|
|
311
318
|
} catch (error) {
|
|
312
319
|
// Preserve original error if it's already a ContextError
|
|
313
320
|
if (error instanceof ContextError) {
|
|
@@ -624,15 +631,18 @@ class context {
|
|
|
624
631
|
return this;
|
|
625
632
|
}
|
|
626
633
|
|
|
627
|
-
// MySQL initialization
|
|
634
|
+
// MySQL initialization (async)
|
|
628
635
|
if (type === DB_TYPES.MYSQL) {
|
|
629
636
|
this.isMySQL = true;
|
|
630
637
|
this.isSQLite = false;
|
|
631
638
|
this.isPostgres = false;
|
|
632
639
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
640
|
+
// MySQL is async - caller must await env()
|
|
641
|
+
return (async () => {
|
|
642
|
+
this.db = await this.__mysqlInit(options, 'mysql2');
|
|
643
|
+
// Note: engine is already set in __mysqlInit
|
|
644
|
+
return this;
|
|
645
|
+
})();
|
|
636
646
|
}
|
|
637
647
|
|
|
638
648
|
// PostgreSQL initialization (async)
|
|
@@ -833,7 +843,7 @@ class context {
|
|
|
833
843
|
* @example
|
|
834
844
|
* context.useMySql('./config/environments');
|
|
835
845
|
*/
|
|
836
|
-
useMySql(rootFolderLocation) {
|
|
846
|
+
async useMySql(rootFolderLocation) {
|
|
837
847
|
try {
|
|
838
848
|
this.isMySQL = true;
|
|
839
849
|
this.isSQLite = false;
|
|
@@ -857,8 +867,8 @@ class context {
|
|
|
857
867
|
}
|
|
858
868
|
|
|
859
869
|
this.validateSQLiteOptions(options);
|
|
860
|
-
this.db = this.__mysqlInit(options, 'mysql2');
|
|
861
|
-
|
|
870
|
+
this.db = await this.__mysqlInit(options, 'mysql2');
|
|
871
|
+
// Note: engine is already set in __mysqlInit
|
|
862
872
|
return this;
|
|
863
873
|
} catch (error) {
|
|
864
874
|
// Preserve original error if it's already a ContextError
|
|
@@ -966,7 +976,7 @@ class context {
|
|
|
966
976
|
* @private
|
|
967
977
|
* @param {Array<object>} tracked - Array of tracked entities
|
|
968
978
|
*/
|
|
969
|
-
_processTrackedEntities(tracked) {
|
|
979
|
+
async _processTrackedEntities(tracked) {
|
|
970
980
|
// Group entities by state for batch operations (single pass)
|
|
971
981
|
const toInsert = [];
|
|
972
982
|
const toUpdate = [];
|
|
@@ -998,17 +1008,17 @@ class context {
|
|
|
998
1008
|
|
|
999
1009
|
// Batch insert operations
|
|
1000
1010
|
if (toInsert.length > 0) {
|
|
1001
|
-
this._processBatchInserts(toInsert);
|
|
1011
|
+
await this._processBatchInserts(toInsert);
|
|
1002
1012
|
}
|
|
1003
1013
|
|
|
1004
1014
|
// Batch update operations
|
|
1005
1015
|
if (toUpdate.length > 0) {
|
|
1006
|
-
this._processBatchUpdates(toUpdate);
|
|
1016
|
+
await this._processBatchUpdates(toUpdate);
|
|
1007
1017
|
}
|
|
1008
1018
|
|
|
1009
1019
|
// Batch delete operations
|
|
1010
1020
|
if (toDelete.length > 0) {
|
|
1011
|
-
this._processBatchDeletes(toDelete);
|
|
1021
|
+
await this._processBatchDeletes(toDelete);
|
|
1012
1022
|
}
|
|
1013
1023
|
}
|
|
1014
1024
|
|
|
@@ -1018,21 +1028,21 @@ class context {
|
|
|
1018
1028
|
* @private
|
|
1019
1029
|
* @param {Array<object>} entities - Entities to insert
|
|
1020
1030
|
*/
|
|
1021
|
-
_processBatchInserts(entities) {
|
|
1031
|
+
async _processBatchInserts(entities) {
|
|
1022
1032
|
if (entities.length === 1) {
|
|
1023
1033
|
// Single insert - use existing insertManager
|
|
1024
1034
|
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
1025
|
-
insert.init(entities[0]);
|
|
1035
|
+
await insert.init(entities[0]);
|
|
1026
1036
|
} else {
|
|
1027
1037
|
// Batch insert - 100x faster for multiple records
|
|
1028
1038
|
try {
|
|
1029
|
-
this._SQLEngine.bulkInsert(entities);
|
|
1039
|
+
await this._SQLEngine.bulkInsert(entities);
|
|
1030
1040
|
} catch (error) {
|
|
1031
1041
|
console.error('[Context] Bulk insert failed, falling back to individual inserts:', error.message);
|
|
1032
1042
|
// Fallback to individual inserts
|
|
1033
1043
|
for (const entity of entities) {
|
|
1034
1044
|
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
1035
|
-
insert.init(entity);
|
|
1045
|
+
await insert.init(entity);
|
|
1036
1046
|
}
|
|
1037
1047
|
}
|
|
1038
1048
|
}
|
|
@@ -1044,7 +1054,7 @@ class context {
|
|
|
1044
1054
|
* @private
|
|
1045
1055
|
* @param {Array<object>} entities - Entities to update
|
|
1046
1056
|
*/
|
|
1047
|
-
_processBatchUpdates(entities) {
|
|
1057
|
+
async _processBatchUpdates(entities) {
|
|
1048
1058
|
if (entities.length === 1) {
|
|
1049
1059
|
// Single update - use existing logic
|
|
1050
1060
|
const currentModel = entities[0];
|
|
@@ -1059,7 +1069,7 @@ class context {
|
|
|
1059
1069
|
primaryKey: primaryKey,
|
|
1060
1070
|
primaryKeyValue: cleanCurrentModel[primaryKey]
|
|
1061
1071
|
};
|
|
1062
|
-
this._SQLEngine.update(sqlUpdate);
|
|
1072
|
+
await this._SQLEngine.update(sqlUpdate);
|
|
1063
1073
|
} else {
|
|
1064
1074
|
console.warn('[Context] Entity marked for update but no changes detected');
|
|
1065
1075
|
}
|
|
@@ -1084,12 +1094,12 @@ class context {
|
|
|
1084
1094
|
|
|
1085
1095
|
if (updateQueries.length > 0) {
|
|
1086
1096
|
try {
|
|
1087
|
-
this._SQLEngine.bulkUpdate(updateQueries);
|
|
1097
|
+
await this._SQLEngine.bulkUpdate(updateQueries);
|
|
1088
1098
|
} catch (error) {
|
|
1089
1099
|
console.error('[Context] Bulk update failed, falling back to individual updates:', error.message);
|
|
1090
1100
|
// Fallback to individual updates
|
|
1091
1101
|
for (const query of updateQueries) {
|
|
1092
|
-
this._SQLEngine.update(query);
|
|
1102
|
+
await this._SQLEngine.update(query);
|
|
1093
1103
|
}
|
|
1094
1104
|
}
|
|
1095
1105
|
}
|
|
@@ -1102,11 +1112,11 @@ class context {
|
|
|
1102
1112
|
* @private
|
|
1103
1113
|
* @param {Array<object>} entities - Entities to delete
|
|
1104
1114
|
*/
|
|
1105
|
-
_processBatchDeletes(entities) {
|
|
1115
|
+
async _processBatchDeletes(entities) {
|
|
1106
1116
|
if (entities.length === 1) {
|
|
1107
1117
|
// Single delete - use existing deleteManager
|
|
1108
1118
|
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
1109
|
-
deleteObject.init(entities[0]);
|
|
1119
|
+
await deleteObject.init(entities[0]);
|
|
1110
1120
|
} else {
|
|
1111
1121
|
// Batch delete - group by table for efficiency
|
|
1112
1122
|
const deletesByTable = new Map(); // Use Map instead of object for better performance
|
|
@@ -1125,14 +1135,14 @@ class context {
|
|
|
1125
1135
|
try {
|
|
1126
1136
|
// Performance: Use for...of with Map entries
|
|
1127
1137
|
for (const [tableName, ids] of deletesByTable.entries()) {
|
|
1128
|
-
this._SQLEngine.bulkDelete(tableName, ids);
|
|
1138
|
+
await this._SQLEngine.bulkDelete(tableName, ids);
|
|
1129
1139
|
}
|
|
1130
1140
|
} catch (error) {
|
|
1131
1141
|
console.error('[Context] Bulk delete failed, falling back to individual deletes:', error.message);
|
|
1132
1142
|
// Fallback to individual deletes
|
|
1133
1143
|
for (const entity of entities) {
|
|
1134
1144
|
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
1135
|
-
deleteObject.init(entity);
|
|
1145
|
+
await deleteObject.init(entity);
|
|
1136
1146
|
}
|
|
1137
1147
|
}
|
|
1138
1148
|
}
|
|
@@ -1152,7 +1162,7 @@ class context {
|
|
|
1152
1162
|
* user.name = 'Alice';
|
|
1153
1163
|
* db.saveChanges();
|
|
1154
1164
|
*/
|
|
1155
|
-
saveChanges() {
|
|
1165
|
+
async saveChanges() {
|
|
1156
1166
|
try {
|
|
1157
1167
|
const tracked = this.__trackedEntities;
|
|
1158
1168
|
|
|
@@ -1171,22 +1181,22 @@ class context {
|
|
|
1171
1181
|
|
|
1172
1182
|
// Handle transactions based on database type
|
|
1173
1183
|
if (this.isSQLite) {
|
|
1174
|
-
this._SQLEngine.startTransaction();
|
|
1184
|
+
await this._SQLEngine.startTransaction();
|
|
1175
1185
|
try {
|
|
1176
|
-
this._processTrackedEntities(tracked);
|
|
1186
|
+
await this._processTrackedEntities(tracked);
|
|
1177
1187
|
this.__clearErrorHandler();
|
|
1178
|
-
this._SQLEngine.endTransaction();
|
|
1188
|
+
await this._SQLEngine.endTransaction();
|
|
1179
1189
|
} catch (error) {
|
|
1180
|
-
this._SQLEngine.errorTransaction();
|
|
1190
|
+
await this._SQLEngine.errorTransaction();
|
|
1181
1191
|
throw error;
|
|
1182
1192
|
}
|
|
1183
1193
|
} else if (this.isMySQL) {
|
|
1184
|
-
// MySQL:
|
|
1185
|
-
this._processTrackedEntities(tracked);
|
|
1194
|
+
// MySQL: Async operations
|
|
1195
|
+
await this._processTrackedEntities(tracked);
|
|
1186
1196
|
this.__clearErrorHandler();
|
|
1187
1197
|
} else if (this.isPostgres) {
|
|
1188
|
-
// PostgreSQL: Async operations
|
|
1189
|
-
this._processTrackedEntities(tracked);
|
|
1198
|
+
// PostgreSQL: Async operations
|
|
1199
|
+
await this._processTrackedEntities(tracked);
|
|
1190
1200
|
this.__clearErrorHandler();
|
|
1191
1201
|
}
|
|
1192
1202
|
|
package/deleteManager.js
CHANGED
|
@@ -26,14 +26,14 @@ class DeleteManager {
|
|
|
26
26
|
* @param {Object|Array} currentModel - Entity or entities to delete
|
|
27
27
|
* @throws {Error} If deletion fails
|
|
28
28
|
*/
|
|
29
|
-
init(currentModel) {
|
|
29
|
+
async init(currentModel) {
|
|
30
30
|
// Input validation
|
|
31
31
|
if (!currentModel) {
|
|
32
32
|
throw new Error('DeleteManager.init() requires a valid model');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
try {
|
|
36
|
-
this.cascadeDelete(currentModel);
|
|
36
|
+
await this.cascadeDelete(currentModel);
|
|
37
37
|
} catch (error) {
|
|
38
38
|
// Add context to error
|
|
39
39
|
const entityName = currentModel.__entity?.__name || 'unknown';
|
|
@@ -46,15 +46,15 @@ class DeleteManager {
|
|
|
46
46
|
* @param {Object|Array} currentModel - Entity or entities to delete
|
|
47
47
|
* @throws {Error} If cascade deletion fails
|
|
48
48
|
*/
|
|
49
|
-
cascadeDelete(currentModel) {
|
|
49
|
+
async cascadeDelete(currentModel) {
|
|
50
50
|
if (!currentModel) {
|
|
51
51
|
return; // Nothing to delete
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (!Array.isArray(currentModel)) {
|
|
55
|
-
this._deleteSingleEntity(currentModel);
|
|
55
|
+
await this._deleteSingleEntity(currentModel);
|
|
56
56
|
} else {
|
|
57
|
-
this._deleteMultipleEntities(currentModel);
|
|
57
|
+
await this._deleteMultipleEntities(currentModel);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ class DeleteManager {
|
|
|
63
63
|
* @private
|
|
64
64
|
* @param {Object} entity - Entity to delete
|
|
65
65
|
*/
|
|
66
|
-
_deleteSingleEntity(entity) {
|
|
66
|
+
async _deleteSingleEntity(entity) {
|
|
67
67
|
// Validate entity structure
|
|
68
68
|
if (!entity.__entity) {
|
|
69
69
|
throw new Error('Entity missing __entity metadata');
|
|
@@ -90,13 +90,13 @@ class DeleteManager {
|
|
|
90
90
|
}
|
|
91
91
|
} else {
|
|
92
92
|
// Recursively delete related entities
|
|
93
|
-
this.cascadeDelete(relatedModel);
|
|
93
|
+
await this.cascadeDelete(relatedModel);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Delete the entity itself after cascading
|
|
99
|
-
this._SQLEngine.delete(entity);
|
|
99
|
+
await this._SQLEngine.delete(entity);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
@@ -104,7 +104,7 @@ class DeleteManager {
|
|
|
104
104
|
* @private
|
|
105
105
|
* @param {Array} entities - Array of entities to delete
|
|
106
106
|
*/
|
|
107
|
-
_deleteMultipleEntities(entities) {
|
|
107
|
+
async _deleteMultipleEntities(entities) {
|
|
108
108
|
if (entities.length === 0) {
|
|
109
109
|
return; // Nothing to delete
|
|
110
110
|
}
|
|
@@ -132,13 +132,13 @@ class DeleteManager {
|
|
|
132
132
|
const relatedModel = entity[property];
|
|
133
133
|
|
|
134
134
|
if (relatedModel !== null && relatedModel !== undefined) {
|
|
135
|
-
this.cascadeDelete(relatedModel);
|
|
135
|
+
await this.cascadeDelete(relatedModel);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// Delete the entity
|
|
141
|
-
this._SQLEngine.delete(entity);
|
|
141
|
+
await this._SQLEngine.delete(entity);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|