masterrecord 0.3.16 → 0.3.18

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/Migrations/cli.js CHANGED
@@ -153,7 +153,7 @@ program.option('-V', 'output the version');
153
153
 
154
154
  if(typeof mig.createdatabase === 'function'){
155
155
  try{
156
- mig.createdatabase();
156
+ await mig.createdatabase();
157
157
  console.log('✓ Database ensured');
158
158
  await __cleanupAndExit(contextInstance, 0);
159
159
  }catch(err){
@@ -163,7 +163,7 @@ program.option('-V', 'output the version');
163
163
  }
164
164
  } else if(typeof mig.createDatabase === 'function'){
165
165
  try{
166
- mig.createDatabase();
166
+ await mig.createDatabase();
167
167
  console.log('✓ Database ensured');
168
168
  await __cleanupAndExit(contextInstance, 0);
169
169
  }catch(err){
@@ -447,7 +447,7 @@ program.option('-V', 'output the version');
447
447
  try{
448
448
  var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
449
449
  var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
450
- newMigrationProjectInstance.up(tableObj);
450
+ await newMigrationProjectInstance.up(tableObj);
451
451
  }catch(err){
452
452
  console.error(`\n❌ Error - Migration failed during execution`);
453
453
  console.error(`\nMigration file: ${mFile}`);
@@ -571,7 +571,7 @@ program.option('-V', 'output the version');
571
571
  var MigCtor = require(latestFile);
572
572
  var migInstance = new MigCtor(ContextCtor);
573
573
  if(typeof migInstance.down === 'function'){
574
- migInstance.down(tableObj);
574
+ await migInstance.down(tableObj);
575
575
  }else{
576
576
  console.log(`Warning - Migration '${path.basename(latestFile)}' has no down method; skipping.`);
577
577
  }
@@ -668,7 +668,7 @@ program.option('-V', 'output the version');
668
668
  var migrationProjectFile = require(migFile);
669
669
  var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
670
670
  var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
671
- newMigrationProjectInstance.up(tableObj);
671
+ await newMigrationProjectInstance.up(tableObj);
672
672
  }
673
673
  const snap = {
674
674
  file : contextAbs,
@@ -820,7 +820,7 @@ program.option('-V', 'output the version');
820
820
  var MigCtor = require(migFile);
821
821
  var migInstance = new MigCtor(ContextCtor);
822
822
  if(typeof migInstance.down === 'function'){
823
- migInstance.down(tableObj);
823
+ await migInstance.down(tableObj);
824
824
  } else {
825
825
  console.log(`Warning - Migration '${path.basename(migFile)}' has no down method; skipping.`);
826
826
  }
@@ -1004,7 +1004,7 @@ program.option('-V', 'output the version');
1004
1004
  var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
1005
1005
  var cleanEntities = migration.cleanEntities(contextInstance.__entities);
1006
1006
  var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
1007
- newMigrationProjectInstance.up(tableObj);
1007
+ await newMigrationProjectInstance.up(tableObj);
1008
1008
  var snap = {
1009
1009
  file : entry.contextAbs,
1010
1010
  executedLocation : executedLocation,
@@ -82,13 +82,13 @@ class schema{
82
82
  }
83
83
  }
84
84
 
85
- createTable(table){
85
+ async createTable(table){
86
86
 
87
87
  if(table){
88
88
  // If table exists, run sync instead of blind create
89
89
  const tableName = table.__name;
90
- if(this.context._SQLEngine.tableExists && this.context._SQLEngine.tableExists(tableName)){
91
- this.syncTable(table);
90
+ if(this.context._SQLEngine.tableExists && await this.context._SQLEngine.tableExists(tableName)){
91
+ await this.syncTable(table);
92
92
  } else {
93
93
  if(this.context.isSQLite){
94
94
  var sqliteQuery = require("./migrationSQLiteQuery");
@@ -117,10 +117,10 @@ class schema{
117
117
  }
118
118
 
119
119
  // Compute diffs and apply minimal changes
120
- syncTable(table){
120
+ async syncTable(table){
121
121
  const engine = this.context._SQLEngine;
122
122
  const tableName = table.__name;
123
- const existing = engine.getTableInfo ? engine.getTableInfo(tableName) : [];
123
+ const existing = engine.getTableInfo ? await engine.getTableInfo(tableName) : [];
124
124
  // Build a set of existing columns (sqlite: name, mysql: name)
125
125
  const existingNames = new Set((existing || []).map(c => (c.name || c.COLUMN_NAME))); // both engines map to name
126
126
  // Add missing columns only (safe path)
@@ -254,7 +254,7 @@ class schema{
254
254
  const create = queryBuilder.createTable(table);
255
255
  this.context._execute(create);
256
256
  // compute common columns
257
- const oldInfo = engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || engine.getTableInfo("_temp_alter_column_update");
257
+ const oldInfo = await engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || await engine.getTableInfo("_temp_alter_column_update");
258
258
  const oldNames = new Set((oldInfo || existing).map(r => r.name));
259
259
  const newNames = desiredCols.map(d => d.name);
260
260
  const common = newNames.filter(n => oldNames.has(n));
@@ -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 MySQLClient = require('masterrecord/mySQLSyncConnect');
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 MySQLClient(baseConfig);
309
- admin.connect();
310
- if(!admin.connection){ return; }
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 check = admin.query(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`, [dbName]);
314
- const exists = Array.isArray(check) ? check.length > 0 : !!check?.length;
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
- admin.query(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
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.startTransaction();
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
- this.endTransaction();
62
- return results;
64
+ if (needsTransaction) {
65
+ await this.endTransaction();
66
+ }
67
+ return Promise.resolve(results);
63
68
  } catch (error) {
64
- this.errorTransaction();
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
- this.startTransaction();
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
- this.errorTransaction();
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
- this.db.prepare('BEGIN').run();
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
- this.db.prepare('COMMIT').run();
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
- this.db.prepare('ROLLBACK').run();
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){
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 MYSQLEngine = require('masterrecord/mySQLEngine');
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 MySQLClient = require('masterrecord/mySQLSyncConnect');
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
- const connection = new MySQLClient(env);
307
- this._SQLEngine = new MYSQLEngine();
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
- return connection;
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
- this.db = this.__mysqlInit(options, 'mysql2');
634
- this._SQLEngine.setDB(this.db, 'mysql');
635
- return this;
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
- this._SQLEngine.setDB(this.db, 'mysql');
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: Synchronous operations (transaction handling managed elsewhere)
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 (transaction handling managed elsewhere)
1189
- this._processTrackedEntities(tracked);
1198
+ // PostgreSQL: Async operations
1199
+ await this._processTrackedEntities(tracked);
1190
1200
  this.__clearErrorHandler();
1191
1201
  }
1192
1202