masterrecord 0.3.26 → 0.3.29

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/context.js CHANGED
@@ -983,7 +983,15 @@ class context {
983
983
  this.__entities.push(validModel); // Store model object
984
984
  const buildMod = tools.createNewInstance(validModel, query, this);
985
985
  this.__builderEntities.push(buildMod); // Store query builder entity
986
- this[validModel.__name] = buildMod; // Attach to context for easy access
986
+
987
+ // Use getter to return fresh query instance each time (prevents parameter accumulation)
988
+ Object.defineProperty(this, validModel.__name, {
989
+ get: function() {
990
+ return tools.createNewInstance(validModel, query, this);
991
+ },
992
+ configurable: true,
993
+ enumerable: true
994
+ });
987
995
  }
988
996
 
989
997
  /**
@@ -1057,14 +1065,34 @@ class context {
1057
1065
  * @param {Array<object>} entities - Entities to insert
1058
1066
  */
1059
1067
  async _processBatchInserts(entities) {
1068
+ // Execute beforeSave hooks
1069
+ for (const entity of entities) {
1070
+ if (typeof entity.beforeSave === 'function') {
1071
+ await entity.beforeSave.call(entity);
1072
+ }
1073
+ }
1074
+
1060
1075
  if (entities.length === 1) {
1061
- // Single insert - use existing insertManager
1076
+ // Single insert - use existing insertManager (already sets ID)
1062
1077
  const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
1063
1078
  await insert.init(entities[0]);
1064
1079
  } else {
1065
1080
  // Batch insert - 100x faster for multiple records
1066
1081
  try {
1067
- await this._SQLEngine.bulkInsert(entities);
1082
+ const results = await this._SQLEngine.bulkInsert(entities);
1083
+
1084
+ // Set auto-increment IDs back on entities
1085
+ for (let i = 0; i < entities.length; i++) {
1086
+ const entity = entities[i];
1087
+ const result = results[i];
1088
+
1089
+ if (result && result.id) {
1090
+ const primaryKey = tools.getPrimaryKeyObject(entity.__entity);
1091
+ if (entity.__entity[primaryKey]?.auto === true) {
1092
+ entity[primaryKey] = result.id;
1093
+ }
1094
+ }
1095
+ }
1068
1096
  } catch (error) {
1069
1097
  console.error('[Context] Bulk insert failed, falling back to individual inserts:', error.message);
1070
1098
  // Fallback to individual inserts
@@ -1074,6 +1102,13 @@ class context {
1074
1102
  }
1075
1103
  }
1076
1104
  }
1105
+
1106
+ // Execute afterSave hooks
1107
+ for (const entity of entities) {
1108
+ if (typeof entity.afterSave === 'function') {
1109
+ await entity.afterSave.call(entity);
1110
+ }
1111
+ }
1077
1112
  }
1078
1113
 
1079
1114
  /**
@@ -1083,6 +1118,13 @@ class context {
1083
1118
  * @param {Array<object>} entities - Entities to update
1084
1119
  */
1085
1120
  async _processBatchUpdates(entities) {
1121
+ // Execute beforeSave hooks
1122
+ for (const entity of entities) {
1123
+ if (typeof entity.beforeSave === 'function') {
1124
+ await entity.beforeSave.call(entity);
1125
+ }
1126
+ }
1127
+
1086
1128
  if (entities.length === 1) {
1087
1129
  // Single update - use existing logic
1088
1130
  const currentModel = entities[0];
@@ -1132,6 +1174,13 @@ class context {
1132
1174
  }
1133
1175
  }
1134
1176
  }
1177
+
1178
+ // Execute afterSave hooks
1179
+ for (const entity of entities) {
1180
+ if (typeof entity.afterSave === 'function') {
1181
+ await entity.afterSave.call(entity);
1182
+ }
1183
+ }
1135
1184
  }
1136
1185
 
1137
1186
  /**
@@ -1141,6 +1190,13 @@ class context {
1141
1190
  * @param {Array<object>} entities - Entities to delete
1142
1191
  */
1143
1192
  async _processBatchDeletes(entities) {
1193
+ // Execute beforeDelete hooks
1194
+ for (const entity of entities) {
1195
+ if (typeof entity.beforeDelete === 'function') {
1196
+ await entity.beforeDelete.call(entity);
1197
+ }
1198
+ }
1199
+
1144
1200
  if (entities.length === 1) {
1145
1201
  // Single delete - use existing deleteManager
1146
1202
  const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
@@ -1174,6 +1230,13 @@ class context {
1174
1230
  }
1175
1231
  }
1176
1232
  }
1233
+
1234
+ // Execute afterDelete hooks
1235
+ for (const entity of entities) {
1236
+ if (typeof entity.afterDelete === 'function') {
1237
+ await entity.afterDelete.call(entity);
1238
+ }
1239
+ }
1177
1240
  }
1178
1241
 
1179
1242
  /**
@@ -1202,8 +1265,8 @@ class context {
1202
1265
  // Performance: Collect affected tables for cache invalidation (single pass)
1203
1266
  const affectedTables = new Set();
1204
1267
  for (const entity of tracked) {
1205
- if (entity.__entity && entity.__entity.__name) {
1206
- affectedTables.add(entity.__entity.__name);
1268
+ if (entity.__name) {
1269
+ affectedTables.add(entity.__name);
1207
1270
  }
1208
1271
  }
1209
1272
 
@@ -1286,6 +1349,136 @@ class context {
1286
1349
  this._queryCache.clear();
1287
1350
  }
1288
1351
 
1352
+ /**
1353
+ * Bulk create multiple entities at once
1354
+ *
1355
+ * Creates multiple entity instances and saves them in a single batch operation.
1356
+ * Much faster than creating entities individually.
1357
+ *
1358
+ * @param {string} entityName - Name of the entity class (e.g., 'User')
1359
+ * @param {Array<Object>} data - Array of objects with entity properties
1360
+ * @returns {Promise<Array<Object>>} Array of created entities with IDs set
1361
+ *
1362
+ * @example
1363
+ * const users = await db.bulkCreate('User', [
1364
+ * { name: 'Alice', email: 'alice@example.com' },
1365
+ * { name: 'Bob', email: 'bob@example.com' },
1366
+ * { name: 'Charlie', email: 'charlie@example.com' }
1367
+ * ]);
1368
+ * console.log(users[0].id); // IDs are automatically set
1369
+ */
1370
+ async bulkCreate(entityName, data) {
1371
+ if (!Array.isArray(data) || data.length === 0) {
1372
+ throw new Error('bulkCreate requires a non-empty array of data');
1373
+ }
1374
+
1375
+ const EntityClass = this[entityName];
1376
+ if (!EntityClass) {
1377
+ throw new Error(`Entity ${entityName} not found in context`);
1378
+ }
1379
+
1380
+ const entities = [];
1381
+ for (const item of data) {
1382
+ const entity = EntityClass.new();
1383
+ // Copy properties from data object to entity
1384
+ for (const key in item) {
1385
+ if (item.hasOwnProperty(key)) {
1386
+ entity[key] = item[key];
1387
+ }
1388
+ }
1389
+ entities.push(entity);
1390
+ }
1391
+
1392
+ await this.saveChanges();
1393
+ return entities;
1394
+ }
1395
+
1396
+ /**
1397
+ * Bulk update multiple entities at once
1398
+ *
1399
+ * Updates multiple existing entities in a single batch operation.
1400
+ * Entities must already be tracked in the context.
1401
+ *
1402
+ * @param {string} entityName - Name of the entity class (e.g., 'User')
1403
+ * @param {Array<Object>} updates - Array of objects with id and properties to update
1404
+ * @returns {Promise<boolean>} True if updates were successful
1405
+ *
1406
+ * @example
1407
+ * await db.bulkUpdate('User', [
1408
+ * { id: 1, status: 'active' },
1409
+ * { id: 2, status: 'active' },
1410
+ * { id: 3, status: 'inactive' }
1411
+ * ]);
1412
+ */
1413
+ async bulkUpdate(entityName, updates) {
1414
+ if (!Array.isArray(updates) || updates.length === 0) {
1415
+ throw new Error('bulkUpdate requires a non-empty array of updates');
1416
+ }
1417
+
1418
+ const EntityClass = this[entityName];
1419
+ if (!EntityClass) {
1420
+ throw new Error(`Entity ${entityName} not found in context`);
1421
+ }
1422
+
1423
+ // Fetch all entities by ID
1424
+ const ids = updates.map(u => u.id).filter(id => id !== undefined);
1425
+ if (ids.length !== updates.length) {
1426
+ throw new Error('All update objects must have an id property');
1427
+ }
1428
+
1429
+ // Load entities and apply updates
1430
+ for (const update of updates) {
1431
+ const entity = await EntityClass.findById(update.id);
1432
+ if (!entity) {
1433
+ throw new Error(`${entityName} with id ${update.id} not found`);
1434
+ }
1435
+
1436
+ // Apply updates to entity
1437
+ for (const key in update) {
1438
+ if (update.hasOwnProperty(key) && key !== 'id') {
1439
+ entity[key] = update[key];
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ await this.saveChanges();
1445
+ return true;
1446
+ }
1447
+
1448
+ /**
1449
+ * Bulk delete multiple entities at once
1450
+ *
1451
+ * Deletes multiple entities by their IDs in a single batch operation.
1452
+ * Much faster than deleting entities individually.
1453
+ *
1454
+ * @param {string} entityName - Name of the entity class (e.g., 'User')
1455
+ * @param {Array<number|string>} ids - Array of entity IDs to delete
1456
+ * @returns {Promise<boolean>} True if deletions were successful
1457
+ *
1458
+ * @example
1459
+ * await db.bulkDelete('User', [1, 2, 3, 4, 5]);
1460
+ */
1461
+ async bulkDelete(entityName, ids) {
1462
+ if (!Array.isArray(ids) || ids.length === 0) {
1463
+ throw new Error('bulkDelete requires a non-empty array of IDs');
1464
+ }
1465
+
1466
+ const EntityClass = this[entityName];
1467
+ if (!EntityClass) {
1468
+ throw new Error(`Entity ${entityName} not found in context`);
1469
+ }
1470
+
1471
+ // Load entities and mark for deletion
1472
+ for (const id of ids) {
1473
+ const entity = await EntityClass.findById(id);
1474
+ if (entity) {
1475
+ await entity.delete();
1476
+ }
1477
+ }
1478
+
1479
+ return true;
1480
+ }
1481
+
1289
1482
  /**
1290
1483
  * Enable or disable query caching
1291
1484
  *
package/mySQLEngine.js CHANGED
@@ -93,7 +93,13 @@ class MySQLEngine {
93
93
 
94
94
  const query = `INSERT INTO \`${first.tableName}\` (${first.columns}) VALUES ${valueGroups.join(', ')}`;
95
95
  const result = await this._runWithParams(query, allParams);
96
- results.push(result);
96
+
97
+ // MySQL returns insertId (first ID) and affectedRows (count)
98
+ // Generate individual result objects for each entity
99
+ const firstId = result.insertId;
100
+ for (let i = 0; i < tableEntities.length; i++) {
101
+ results.push({ id: firstId + i });
102
+ }
97
103
  }
98
104
 
99
105
  return results;
@@ -447,6 +453,10 @@ class MySQLEngine {
447
453
 
448
454
  for (const ent in entity) {
449
455
  if (!ent.startsWith("_")) {
456
+ // Skip lifecycle hooks - they are not database columns
457
+ if (entity[ent].lifecycle === true) {
458
+ continue;
459
+ }
450
460
  if (!entity[ent].foreignKey) {
451
461
  if (entity[ent].relationshipTable) {
452
462
  if ($that.chechUnsupportedWords(entity[ent].relationshipTable)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.26",
3
+ "version": "0.3.29",
4
4
  "description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
5
5
  "main": "MasterRecord.js",
6
6
  "bin": {
package/postgresEngine.js CHANGED
@@ -120,7 +120,12 @@ class postgresEngine {
120
120
 
121
121
  const query = `INSERT INTO "${first.tableName}" (${first.columns}) VALUES ${valueGroups.join(', ')} RETURNING ${primaryKey}`;
122
122
  const result = await this._runWithParams(query, allParams);
123
- results.push(result.rows);
123
+
124
+ // PostgreSQL returns rows with the primary key values
125
+ // Convert to consistent format: { id: value }
126
+ for (const row of result.rows) {
127
+ results.push({ id: row[primaryKey] });
128
+ }
124
129
  }
125
130
 
126
131
  return results;