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.
- package/Migrations/migrations.js +22 -2
- package/package.json +1 -1
- package/readme.md +31 -6
- package/test/v0.3.34-bug-fixes-test.js +81 -1
package/Migrations/migrations.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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`);
|