masterrecord 0.3.34 → 0.3.35

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.
@@ -15,10 +15,21 @@ class Migrations{
15
15
 
16
16
  #organizeSchemaByTables(oldSchema, newSchema){
17
17
  var tables = []
18
+ var seenTableNames = new Set(); // Track processed table names to prevent duplicates
19
+
18
20
  if(oldSchema.length === 0){
19
21
  newSchema.forEach(function (item, index) {
22
+ var tableName = item["__name"];
23
+
24
+ // Skip if we've already processed this table name
25
+ if(seenTableNames.has(tableName)){
26
+ console.warn(`Warning: Duplicate table definition detected for "${tableName}" - using first occurrence only`);
27
+ return;
28
+ }
29
+ seenTableNames.add(tableName);
30
+
20
31
  var table = {
21
- name: item["__name"],
32
+ name: tableName,
22
33
  new :item,
23
34
  old : {},
24
35
  newColumns : [],
@@ -36,8 +47,17 @@ class Migrations{
36
47
  }
37
48
  else{
38
49
  newSchema.forEach(function (item, index) {
50
+ var tableName = item["__name"];
51
+
52
+ // Skip if we've already processed this table name
53
+ if(seenTableNames.has(tableName)){
54
+ console.warn(`Warning: Duplicate table definition detected for "${tableName}" - using first occurrence only`);
55
+ return;
56
+ }
57
+ seenTableNames.add(tableName);
58
+
39
59
  var table = {
40
- name: item["__name"],
60
+ name: tableName,
41
61
  old: null,
42
62
  new :item,
43
63
  newColumns : [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.34",
3
+ "version": "0.3.35",
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/readme.md CHANGED
@@ -3369,18 +3369,43 @@ user.name = null; // Error if name is { nullable: false }
3369
3369
 
3370
3370
  ## Changelog
3371
3371
 
3372
- ### Version 0.3.34 (2026-02-05)
3372
+ ### Version 0.3.35 (2026-02-05) - CRITICAL FIX
3373
+
3374
+ #### Critical Bug Fix
3375
+ - **FIXED**: Duplicate table operations in migrations when snapshots contain duplicate table definitions
3376
+ - **Root Cause**: If a context registered the same entity twice (via multiple `dbset()` calls), the snapshot would contain duplicate table definitions with the same `__name`
3377
+ - **Impact**: Migrations would generate duplicate `createTable()` and `seed()` calls, causing duplicate records in the database
3378
+ - **Examples**:
3379
+ - ragContext: Settings table appeared twice → 2x seed insertions
3380
+ - qaContext: TaxonomyTemplate appeared twice → 18 template records instead of 9
3381
+ - **Fix**: Added deduplication in `#organizeSchemaByTables()` using a `Set` to track processed table names
3382
+ - **User Impact**: If you ran migrations from v0.3.34 or earlier and have duplicate records:
3383
+ 1. Manually remove duplicates from your database
3384
+ 2. Regenerate migrations with v0.3.35: `masterrecord add-migration YourContext "regenerate"`
3385
+ 3. Future migrations will not create duplicates
3386
+
3387
+ #### Technical Details
3388
+ - Modified `Migrations/migrations.js` - `#organizeSchemaByTables()` method
3389
+ - Added `seenTableNames` Set to track processed tables and prevent duplicates
3390
+ - Emits warning: `"Warning: Duplicate table definition detected for 'TableName' - using first occurrence only"`
3391
+ - Works for both initial migrations (`oldSchema.length === 0`) and subsequent migrations
3392
+
3393
+ #### Testing
3394
+ - Added 2 new tests for duplicate table scenarios (13 total tests)
3395
+ - All tests passing with duplicate detection and deduplication verified
3396
+
3397
+ ### Version 0.3.34 (2026-02-05) - PARTIALLY EFFECTIVE
3398
+
3399
+ ⚠️ **Note**: This version fixed the seed API bug but did NOT fix duplicate operations. Use v0.3.35 instead.
3373
3400
 
3374
3401
  #### Bug Fixes
3375
3402
  - **Fixed**: Seed API migration generation - resolved `table.EntityName.create is not a function` error
3376
3403
  - Migrations now use `this.seed('TableName', data)` instead of `table.TableName.create(data)`
3377
3404
  - Ensures compatibility with the migration schema base class
3378
- - **Fixed**: Eliminated duplicate table creation statements in generated migrations
3379
- - **Fixed**: Eliminated duplicate seed insertion statements in generated migrations
3380
- - **Fixed**: Duplicate index creation when using `.index()` in column definitions
3381
- - Index operations now properly deduplicated during migration generation
3382
3405
  - **Fixed**: Missing `await` keywords on `createIndex()`, `dropIndex()`, `createCompositeIndex()`, and `dropCompositeIndex()` calls in migrations
3383
3406
  - All async index operations now properly awaited for consistency
3407
+ - ~~**Fixed**: Eliminated duplicate table creation statements~~ ❌ **NOT ACTUALLY FIXED** - see v0.3.35
3408
+ - ~~**Fixed**: Eliminated duplicate seed insertion statements~~ ❌ **NOT ACTUALLY FIXED** - see v0.3.35
3384
3409
 
3385
3410
  #### Improvements
3386
3411
  - **Enhanced**: Query builders now use whitelist validation for column definitions
@@ -3408,7 +3433,7 @@ user.name = null; // Error if name is { nullable: false }
3408
3433
 
3409
3434
  | Component | Version | Notes |
3410
3435
  |---------------|---------------|------------------------------------------|
3411
- | MasterRecord | 0.3.34 | Current version with bug fixes and improvements |
3436
+ | MasterRecord | 0.3.35 | Current version - fixes critical duplicate table bug |
3412
3437
  | Node.js | 14+ | Async/await support required |
3413
3438
  | PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
3414
3439
  | MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
@@ -291,6 +291,86 @@ test('Complex scenario: Multiple tables with seed data, indexes, and composite i
291
291
  assert(migrationCode.includes('await this.createCompositeIndex'), 'Composite indexes should have await');
292
292
  });
293
293
 
294
+ // =============================================================================
295
+ // Test Suite 4: Duplicate Table Deduplication (v0.3.35)
296
+ // =============================================================================
297
+ console.log("\n📋 Test Suite 4: Duplicate Table Deduplication\n");
298
+
299
+ test('Deduplicates when snapshot has duplicate table definitions', () => {
300
+ const migrations = new Migrations();
301
+
302
+ // Simulate snapshot with duplicate Settings table (real-world bug from ragContext)
303
+ const oldSchema = [];
304
+ const newSchema = [
305
+ {
306
+ __name: 'Settings',
307
+ id: { name: 'id', type: 'integer', primaryKey: true },
308
+ disable_rag: { name: 'disable_rag', type: 'integer' }
309
+ },
310
+ {
311
+ __name: 'Document',
312
+ id: { name: 'id', type: 'integer', primaryKey: true }
313
+ },
314
+ {
315
+ __name: 'Settings', // DUPLICATE - this caused the bug
316
+ id: { name: 'id', type: 'integer', primaryKey: true },
317
+ disable_rag: { name: 'disable_rag', type: 'integer' }
318
+ }
319
+ ];
320
+
321
+ const seedData = {
322
+ Settings: [{ disable_rag: 0 }]
323
+ };
324
+
325
+ const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
326
+
327
+ const createTableCount = (migrationCode.match(/createTable\(table\.Settings\)/g) || []).length;
328
+ const seedCount = (migrationCode.match(/this\.seed\('Settings'/g) || []).length;
329
+
330
+ assert(createTableCount === 1, `Expected 1 createTable for Settings, got ${createTableCount}`);
331
+ assert(seedCount === 1, `Expected 1 seed call for Settings, got ${seedCount}`);
332
+ });
333
+
334
+ test('Deduplicates multiple tables with multiple seeds (qaContext scenario)', () => {
335
+ const migrations = new Migrations();
336
+
337
+ const oldSchema = [];
338
+ const newSchema = [
339
+ {
340
+ __name: 'TaxonomyTemplate',
341
+ id: { name: 'id', type: 'integer', primaryKey: true },
342
+ template_id: { name: 'template_id', type: 'string' }
343
+ },
344
+ {
345
+ __name: 'Document',
346
+ id: { name: 'id', type: 'integer', primaryKey: true }
347
+ },
348
+ {
349
+ __name: 'TaxonomyTemplate', // DUPLICATE
350
+ id: { name: 'id', type: 'integer', primaryKey: true },
351
+ template_id: { name: 'template_id', type: 'string' }
352
+ }
353
+ ];
354
+
355
+ const seedData = {
356
+ TaxonomyTemplate: [
357
+ { template_id: 'template1' },
358
+ { template_id: 'template2' },
359
+ { template_id: 'template3' },
360
+ { template_id: 'template4' },
361
+ { template_id: 'template5' }
362
+ ]
363
+ };
364
+
365
+ const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
366
+
367
+ const createTableCount = (migrationCode.match(/createTable\(table\.TaxonomyTemplate\)/g) || []).length;
368
+ const seedCount = (migrationCode.match(/this\.seed\('TaxonomyTemplate'/g) || []).length;
369
+
370
+ assert(createTableCount === 1, `Expected 1 createTable for TaxonomyTemplate, got ${createTableCount}`);
371
+ assert(seedCount === 5, `Expected 5 seed calls for TaxonomyTemplate, got ${seedCount}`);
372
+ });
373
+
294
374
  // =============================================================================
295
375
  // Test Results Summary
296
376
  // =============================================================================
@@ -298,7 +378,7 @@ console.log("\n" + "=".repeat(68));
298
378
  console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed\n`);
299
379
 
300
380
  if (failed === 0) {
301
- console.log("✅ All tests passed! v0.3.34 fixes are working correctly.\n");
381
+ console.log("✅ All tests passed! v0.3.34/0.3.35 fixes are working correctly.\n");
302
382
  process.exit(0);
303
383
  } else {
304
384
  console.log(`⚠️ ${failed} test(s) failed. Please review the output above.\n`);