masterrecord 0.3.8 → 0.3.10
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/.eslintrc.js +290 -0
- package/.prettierrc.js +109 -0
- package/CHANGES.md +170 -0
- package/Entity/entityTrackerModel.js +17 -3
- package/Migrations/cli.js +4 -2
- package/Migrations/migrations.js +13 -10
- package/Migrations/pathUtils.js +76 -0
- package/Migrations/pathUtils.test.js +53 -0
- package/QueryLanguage/queryMethods.js +15 -0
- package/context.js +1186 -398
- package/deleteManager.js +137 -40
- package/docs/ACTIVE_RECORD_PATTERN.md +477 -0
- package/docs/DETACHED_ENTITIES_GUIDE.md +445 -0
- package/insertManager.js +358 -200
- package/package.json +1 -1
- package/readme.md +217 -7
- package/test/attachDetached.test.js +303 -0
- /package/{QUERY_CACHING_GUIDE.md → docs/QUERY_CACHING_GUIDE.md} +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for MasterRecord migrations
|
|
3
|
+
* Handles path resolution to prevent duplicate db/migrations in paths
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve migrations directory, avoiding duplicate db/migrations paths
|
|
10
|
+
*
|
|
11
|
+
* If the context file is already inside a db/migrations folder structure,
|
|
12
|
+
* returns that directory. Otherwise, appends db/migrations to the context directory.
|
|
13
|
+
*
|
|
14
|
+
* This prevents the bug where:
|
|
15
|
+
* /components/qa/app/models/db/migrations/qaContext.js
|
|
16
|
+
* becomes:
|
|
17
|
+
* /components/qa/app/models/db/migrations/db/migrations/qacontext_contextSnapShot.json
|
|
18
|
+
*
|
|
19
|
+
* @param {string} contextFilePath - Absolute path to the context file
|
|
20
|
+
* @returns {string} Absolute path to the migrations directory
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Context already in migrations folder
|
|
24
|
+
* resolveMigrationsDirectory('/app/models/db/migrations/Context.js')
|
|
25
|
+
* // Returns: /app/models/db/migrations
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Context NOT in migrations folder
|
|
29
|
+
* resolveMigrationsDirectory('/app/models/Context.js')
|
|
30
|
+
* // Returns: /app/models/db/migrations
|
|
31
|
+
*/
|
|
32
|
+
function resolveMigrationsDirectory(contextFilePath) {
|
|
33
|
+
const contextDir = path.dirname(contextFilePath);
|
|
34
|
+
const contextDirNormalized = contextDir.split(path.sep).join('/');
|
|
35
|
+
|
|
36
|
+
// Check if context is already inside a db/migrations folder
|
|
37
|
+
const alreadyInMigrations = contextDirNormalized.includes('/db/migrations') ||
|
|
38
|
+
contextDirNormalized.includes('\\db\\migrations');
|
|
39
|
+
|
|
40
|
+
if (alreadyInMigrations) {
|
|
41
|
+
// Context is already in db/migrations - find and use that directory
|
|
42
|
+
let currentDir = contextDir;
|
|
43
|
+
while (currentDir && currentDir !== path.dirname(currentDir)) {
|
|
44
|
+
const dirName = path.basename(currentDir);
|
|
45
|
+
const parentName = path.basename(path.dirname(currentDir));
|
|
46
|
+
|
|
47
|
+
if (dirName === 'migrations' && parentName === 'db') {
|
|
48
|
+
return currentDir;
|
|
49
|
+
}
|
|
50
|
+
currentDir = path.dirname(currentDir);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fallback if we couldn't find it (shouldn't happen)
|
|
54
|
+
console.warn(`[pathUtils] Warning: Context is in a path containing 'db/migrations' but couldn't locate exact directory. Using context directory: ${contextDir}`);
|
|
55
|
+
return contextDir;
|
|
56
|
+
} else {
|
|
57
|
+
// Context is NOT in db/migrations - return standard path
|
|
58
|
+
return path.join(contextDir, 'db', 'migrations');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a path is already inside a db/migrations directory structure
|
|
64
|
+
*
|
|
65
|
+
* @param {string} filePath - Path to check
|
|
66
|
+
* @returns {boolean} True if path contains db/migrations
|
|
67
|
+
*/
|
|
68
|
+
function isInMigrationsDirectory(filePath) {
|
|
69
|
+
const normalized = filePath.split(path.sep).join('/');
|
|
70
|
+
return normalized.includes('/db/migrations') || normalized.includes('\\db\\migrations');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
resolveMigrationsDirectory,
|
|
75
|
+
isInMigrationsDirectory
|
|
76
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pathUtils.js - Path resolution for migrations
|
|
3
|
+
*
|
|
4
|
+
* Run with: node pathUtils.test.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { resolveMigrationsDirectory, isInMigrationsDirectory } = require('./pathUtils');
|
|
9
|
+
|
|
10
|
+
function assert(condition, message) {
|
|
11
|
+
if (!condition) {
|
|
12
|
+
throw new Error(`❌ Test failed: ${message}`);
|
|
13
|
+
}
|
|
14
|
+
console.log(`✓ ${message}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log('\n🧪 Running pathUtils tests...\n');
|
|
18
|
+
|
|
19
|
+
// Test 1: Context NOT in migrations folder
|
|
20
|
+
const test1 = resolveMigrationsDirectory('/app/models/Context.js');
|
|
21
|
+
const expected1 = path.join('/app/models', 'db', 'migrations');
|
|
22
|
+
assert(test1 === expected1,
|
|
23
|
+
`Context NOT in migrations: Expected '${expected1}', got '${test1}'`);
|
|
24
|
+
|
|
25
|
+
// Test 2: Context already IN migrations folder
|
|
26
|
+
const test2 = resolveMigrationsDirectory('/app/models/db/migrations/Context.js');
|
|
27
|
+
const expected2 = '/app/models/db/migrations';
|
|
28
|
+
assert(test2 === expected2,
|
|
29
|
+
`Context IN migrations: Expected '${expected2}', got '${test2}'`);
|
|
30
|
+
|
|
31
|
+
// Test 3: Context in nested migrations folder
|
|
32
|
+
const test3 = resolveMigrationsDirectory('/components/qa/app/models/db/migrations/qaContext.js');
|
|
33
|
+
const expected3 = '/components/qa/app/models/db/migrations';
|
|
34
|
+
assert(test3 === expected3,
|
|
35
|
+
`Context in nested migrations: Expected '${expected3}', got '${test3}'`);
|
|
36
|
+
|
|
37
|
+
// Test 4: isInMigrationsDirectory - true case
|
|
38
|
+
const test4 = isInMigrationsDirectory('/app/models/db/migrations/Context.js');
|
|
39
|
+
assert(test4 === true,
|
|
40
|
+
'isInMigrationsDirectory should return true for path with db/migrations');
|
|
41
|
+
|
|
42
|
+
// Test 5: isInMigrationsDirectory - false case
|
|
43
|
+
const test5 = isInMigrationsDirectory('/app/models/Context.js');
|
|
44
|
+
assert(test5 === false,
|
|
45
|
+
'isInMigrationsDirectory should return false for path without db/migrations');
|
|
46
|
+
|
|
47
|
+
// Test 6: Windows path with backslashes
|
|
48
|
+
const test6Windows = '/app\\models\\db\\migrations\\Context.js'.replace(/\\/g, path.sep);
|
|
49
|
+
const result6 = isInMigrationsDirectory(test6Windows);
|
|
50
|
+
assert(result6 === true,
|
|
51
|
+
'isInMigrationsDirectory should handle Windows paths with backslashes');
|
|
52
|
+
|
|
53
|
+
console.log('\n✅ All tests passed!\n');
|
|
@@ -465,6 +465,21 @@ class queryMethods{
|
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
// Add Active Record-style .save() method
|
|
469
|
+
newEntity.save = async function() {
|
|
470
|
+
if (!this.__context) {
|
|
471
|
+
throw new Error('Cannot save: entity is not attached to a context');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Ensure entity is tracked
|
|
475
|
+
if (!this.__context.__trackedEntitiesMap.has(this.__ID)) {
|
|
476
|
+
this.__context.__track(this);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Save all tracked changes in the context
|
|
480
|
+
return await this.__context.saveChanges();
|
|
481
|
+
};
|
|
482
|
+
|
|
468
483
|
// Track the entity
|
|
469
484
|
this.__context.__track(newEntity);
|
|
470
485
|
return newEntity;
|