masterrecord 0.3.33 ā 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/.claude/settings.local.json +2 -1
- package/.github/FUTURE_IMPROVEMENTS.md +138 -0
- package/Entity/entityModel.js +17 -0
- package/Migrations/migrationMySQLQuery.js +9 -2
- package/Migrations/migrationPostgresQuery.js +10 -2
- package/Migrations/migrationSQLiteQuery.js +7 -0
- package/Migrations/migrationTemplate.js +13 -13
- package/Migrations/migrations.js +22 -2
- package/package.json +1 -1
- package/readme.md +63 -1
- package/test/v0.3.34-bug-fixes-test.js +386 -0
- package/test/debug-id-test.js +0 -63
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Future Improvements for MasterRecord
|
|
2
|
+
|
|
3
|
+
## Metadata Property Handling
|
|
4
|
+
|
|
5
|
+
### Context
|
|
6
|
+
The `.index()` bug fix (commit 6e774a7) introduced a blacklist approach to skip metadata properties during schema processing. While this solves the immediate problem, there are architectural improvements to consider for future versions.
|
|
7
|
+
|
|
8
|
+
### Current Solution (v0.3.33)
|
|
9
|
+
```javascript
|
|
10
|
+
for (var key in table) {
|
|
11
|
+
// Skip metadata properties (indexes, __compositeIndexes, __name, etc.)
|
|
12
|
+
if(key === 'indexes' || key.startsWith('__')){
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
// ... process columns
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Status:** ā
Shipped - Works well, well-tested, solves the problem
|
|
20
|
+
|
|
21
|
+
### Future Considerations
|
|
22
|
+
|
|
23
|
+
#### 1. Document the Metadata Convention
|
|
24
|
+
**Priority:** Medium
|
|
25
|
+
**Effort:** Low
|
|
26
|
+
|
|
27
|
+
Add clear documentation explaining that properties starting with `__` are reserved for internal metadata and will be skipped during schema processing. This should be documented in:
|
|
28
|
+
- README.md (Entity Model section)
|
|
29
|
+
- Code comments in `entityModel.js`
|
|
30
|
+
- Migration guide if breaking changes are made
|
|
31
|
+
|
|
32
|
+
**Example documentation:**
|
|
33
|
+
```markdown
|
|
34
|
+
## Reserved Property Names
|
|
35
|
+
- Properties starting with `__` (double underscore) are reserved for internal metadata
|
|
36
|
+
- `indexes` property is reserved for index definitions
|
|
37
|
+
- These properties are automatically skipped during table creation
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### 2. Whitelist Approach (Long-term)
|
|
41
|
+
**Priority:** Low
|
|
42
|
+
**Effort:** Medium
|
|
43
|
+
**Breaking Change:** Potentially
|
|
44
|
+
|
|
45
|
+
Instead of blacklisting specific properties, consider only processing properties that are actually column definitions.
|
|
46
|
+
|
|
47
|
+
**Pros:**
|
|
48
|
+
- More robust if new metadata properties are added
|
|
49
|
+
- Explicit about what gets processed
|
|
50
|
+
- Self-documenting code
|
|
51
|
+
|
|
52
|
+
**Cons:**
|
|
53
|
+
- Requires careful design to not break existing functionality
|
|
54
|
+
- More complex validation logic
|
|
55
|
+
- Could impact performance
|
|
56
|
+
|
|
57
|
+
**Example approach:**
|
|
58
|
+
```javascript
|
|
59
|
+
for (var key in table) {
|
|
60
|
+
if(typeof table[key] !== "object") continue;
|
|
61
|
+
|
|
62
|
+
var col = table[key];
|
|
63
|
+
|
|
64
|
+
// Only process objects that have column-like properties
|
|
65
|
+
if(!col.name || !col.type) continue;
|
|
66
|
+
|
|
67
|
+
// Skip relationship types
|
|
68
|
+
if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 3. Use JavaScript Symbols for Metadata (v1.0+)
|
|
77
|
+
**Priority:** Low
|
|
78
|
+
**Effort:** High
|
|
79
|
+
**Breaking Change:** Yes
|
|
80
|
+
|
|
81
|
+
For future major versions, consider using JavaScript Symbols for all internal metadata. Symbols don't appear in `for...in` loops or `Object.keys()`, eliminating this entire class of bugs.
|
|
82
|
+
|
|
83
|
+
**Benefits:**
|
|
84
|
+
- Metadata becomes truly invisible to iteration
|
|
85
|
+
- No special-case filtering needed
|
|
86
|
+
- Cleaner separation of concerns
|
|
87
|
+
- More idiomatic JavaScript
|
|
88
|
+
|
|
89
|
+
**Migration path:**
|
|
90
|
+
```javascript
|
|
91
|
+
// Current (v0.x)
|
|
92
|
+
table.__name = 'Users';
|
|
93
|
+
table.indexes = ['idx_name'];
|
|
94
|
+
|
|
95
|
+
// Future (v1.0+)
|
|
96
|
+
const META_NAME = Symbol('name');
|
|
97
|
+
const META_INDEXES = Symbol('indexes');
|
|
98
|
+
|
|
99
|
+
table[META_NAME] = 'Users';
|
|
100
|
+
table[META_INDEXES] = ['idx_name'];
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Challenges:**
|
|
104
|
+
- Requires refactoring all metadata access
|
|
105
|
+
- Breaks existing code that accesses `__name`, `__compositeIndexes`, etc.
|
|
106
|
+
- Would need comprehensive migration guide
|
|
107
|
+
- Testing burden is significant
|
|
108
|
+
|
|
109
|
+
**When to consider:**
|
|
110
|
+
- During a major version bump (v1.0)
|
|
111
|
+
- When other breaking changes are planned
|
|
112
|
+
- After gathering user feedback on current metadata usage patterns
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Implementation Notes
|
|
117
|
+
|
|
118
|
+
### Current Fix is Good to Ship ā
|
|
119
|
+
The blacklist approach (`indexes` and `__*` properties) is:
|
|
120
|
+
- Practical and solves the immediate problem
|
|
121
|
+
- Well-tested (5 comprehensive tests)
|
|
122
|
+
- Easy to understand and maintain
|
|
123
|
+
- Not over-engineered
|
|
124
|
+
|
|
125
|
+
### Recommendation
|
|
126
|
+
1. **Ship the current fix** (already committed)
|
|
127
|
+
2. **Add documentation** about the `__` convention in next minor release
|
|
128
|
+
3. **Track whitelist approach** as a potential v0.4+ enhancement
|
|
129
|
+
4. **Consider Symbols** only for v1.0 major version
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Related Issues
|
|
134
|
+
- Index bug fix: commit 6e774a7
|
|
135
|
+
- Test suite: `test/index-bug-fix-test.js`
|
|
136
|
+
|
|
137
|
+
## Feedback Credit
|
|
138
|
+
These suggestions came from code review feedback on the index bug fix implementation.
|
package/Entity/entityModel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
|
|
3
|
+
Supported column types:
|
|
3
4
|
:binary
|
|
4
5
|
:boolean
|
|
5
6
|
:date
|
|
@@ -15,6 +16,11 @@
|
|
|
15
16
|
:time
|
|
16
17
|
:timestamp
|
|
17
18
|
|
|
19
|
+
Reserved Property Names:
|
|
20
|
+
- Properties starting with '__' (double underscore) are reserved for internal metadata
|
|
21
|
+
- 'indexes' property is reserved for index definitions
|
|
22
|
+
- These properties are automatically skipped during table creation in migration queries
|
|
23
|
+
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
26
|
// version 0.0.5
|
|
@@ -115,6 +121,17 @@ class EntityModel {
|
|
|
115
121
|
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Adds an index to this column definition.
|
|
126
|
+
*
|
|
127
|
+
* Note: The 'indexes' property is metadata and will be automatically skipped
|
|
128
|
+
* during table creation. Properties starting with '__' are also reserved for
|
|
129
|
+
* internal metadata and filtered during schema processing.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} indexName - Optional custom name for the index. If not provided,
|
|
132
|
+
* a default name will be generated.
|
|
133
|
+
* @returns {EntityModel} Returns this for method chaining
|
|
134
|
+
*/
|
|
118
135
|
index(indexName){
|
|
119
136
|
if(!this.obj.indexes){
|
|
120
137
|
this.obj.indexes = [];
|
|
@@ -199,9 +199,16 @@ class migrationMySQLQuery {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if(typeof table[key] === "object"){
|
|
202
|
+
var col = table[key];
|
|
203
|
+
|
|
204
|
+
if(col.type !== "hasOne" && col.type !== "hasMany" && col.type !== "hasManyThrough"){
|
|
205
|
+
// Whitelist: Only process objects that look like column definitions
|
|
206
|
+
// Valid columns must have 'name' and 'type' properties
|
|
207
|
+
if(!col.name || !col.type){
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
202
210
|
|
|
203
|
-
|
|
204
|
-
queryVar += `${this.#columnMapping(table[key])}, `;
|
|
211
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
}
|
|
@@ -202,8 +202,16 @@ class migrationPostgresQuery {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
if(typeof table[key] === "object"){
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
var col = table[key];
|
|
206
|
+
|
|
207
|
+
if(col.type !== "hasOne" && col.type !== "hasMany" && col.type !== "hasManyThrough"){
|
|
208
|
+
// Whitelist: Only process objects that look like column definitions
|
|
209
|
+
// Valid columns must have 'name' and 'type' properties
|
|
210
|
+
if(!col.name || !col.type){
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
queryVar += `${this.#columnMapping(col)}, `;
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
}
|
|
@@ -158,6 +158,13 @@ class migrationSQLiteQuery {
|
|
|
158
158
|
if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
|
|
159
159
|
continue;
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
// Whitelist: Only process objects that look like column definitions
|
|
163
|
+
// Valid columns must have 'name' and 'type' properties
|
|
164
|
+
if(!col.name || !col.type){
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
161
168
|
queryVar += `${this.#columnMapping(col)}, `;
|
|
162
169
|
}
|
|
163
170
|
}
|
|
@@ -94,10 +94,10 @@ module.exports = ${this.name};
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
if(type === "up"){
|
|
97
|
-
this.#up += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
97
|
+
this.#up += os.EOL + ` await this.createIndex(${indexInfoStr});`
|
|
98
98
|
}
|
|
99
99
|
else{
|
|
100
|
-
this.#down += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
100
|
+
this.#down += os.EOL + ` await this.dropIndex(${indexInfoStr});`
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -113,10 +113,10 @@ module.exports = ${this.name};
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
if(type === "up"){
|
|
116
|
-
this.#up += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
116
|
+
this.#up += os.EOL + ` await this.dropIndex(${indexInfoStr});`
|
|
117
117
|
}
|
|
118
118
|
else{
|
|
119
|
-
this.#down += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
119
|
+
this.#down += os.EOL + ` await this.createIndex(${indexInfoStr});`
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -129,10 +129,10 @@ module.exports = ${this.name};
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
if(type === "up"){
|
|
132
|
-
this.#up += os.EOL + ` this.createCompositeIndex(${indexInfoStr});`
|
|
132
|
+
this.#up += os.EOL + ` await this.createCompositeIndex(${indexInfoStr});`
|
|
133
133
|
}
|
|
134
134
|
else{
|
|
135
|
-
this.#down += os.EOL + ` this.dropCompositeIndex(${indexInfoStr});`
|
|
135
|
+
this.#down += os.EOL + ` await this.dropCompositeIndex(${indexInfoStr});`
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -145,10 +145,10 @@ module.exports = ${this.name};
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
if(type === "up"){
|
|
148
|
-
this.#up += os.EOL + ` this.dropCompositeIndex(${indexInfoStr});`
|
|
148
|
+
this.#up += os.EOL + ` await this.dropCompositeIndex(${indexInfoStr});`
|
|
149
149
|
}
|
|
150
150
|
else{
|
|
151
|
-
this.#down += os.EOL + ` this.createCompositeIndex(${indexInfoStr});`
|
|
151
|
+
this.#down += os.EOL + ` await this.createCompositeIndex(${indexInfoStr});`
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -187,7 +187,7 @@ module.exports = ${this.name};
|
|
|
187
187
|
|
|
188
188
|
this.#up += os.EOL + ` ];`;
|
|
189
189
|
this.#up += os.EOL + ` for (const record of factoryRecords) {`;
|
|
190
|
-
this.#up += os.EOL + `
|
|
190
|
+
this.#up += os.EOL + ` this.seed('${tableName}', record);`;
|
|
191
191
|
this.#up += os.EOL + ` }`;
|
|
192
192
|
} else {
|
|
193
193
|
// Standard individual inserts for non-factory or small batches
|
|
@@ -214,10 +214,10 @@ module.exports = ${this.name};
|
|
|
214
214
|
const formattedRecord = JSON.stringify(cleanRecord, null, 12)
|
|
215
215
|
.split('\n')
|
|
216
216
|
.join(os.EOL + ' ');
|
|
217
|
-
this.#up += os.EOL + `
|
|
217
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${formattedRecord});`;
|
|
218
218
|
} else {
|
|
219
219
|
// Single-line format
|
|
220
|
-
this.#up += os.EOL + `
|
|
220
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${recordStr});`;
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
});
|
|
@@ -236,7 +236,7 @@ module.exports = ${this.name};
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
this.#up += os.EOL + ` {`;
|
|
239
|
-
this.#up += os.EOL + ` const existing = await
|
|
239
|
+
this.#up += os.EOL + ` const existing = await this.context.${tableName}.where(r => r.${conflictKey} == ${JSON.stringify(conflictValue)}).single();`;
|
|
240
240
|
this.#up += os.EOL + ` if (existing) {`;
|
|
241
241
|
|
|
242
242
|
// Update logic
|
|
@@ -257,7 +257,7 @@ module.exports = ${this.name};
|
|
|
257
257
|
|
|
258
258
|
this.#up += os.EOL + ` await existing.save();`;
|
|
259
259
|
this.#up += os.EOL + ` } else {`;
|
|
260
|
-
this.#up += os.EOL + `
|
|
260
|
+
this.#up += os.EOL + ` this.seed('${tableName}', ${JSON.stringify(cleanRecord)});`;
|
|
261
261
|
this.#up += os.EOL + ` }`;
|
|
262
262
|
this.#up += os.EOL + ` }`;
|
|
263
263
|
}
|
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
|
@@ -3367,11 +3367,73 @@ if (ids.length > 0) {
|
|
|
3367
3367
|
user.name = null; // Error if name is { nullable: false }
|
|
3368
3368
|
```
|
|
3369
3369
|
|
|
3370
|
+
## Changelog
|
|
3371
|
+
|
|
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.
|
|
3400
|
+
|
|
3401
|
+
#### Bug Fixes
|
|
3402
|
+
- **Fixed**: Seed API migration generation - resolved `table.EntityName.create is not a function` error
|
|
3403
|
+
- Migrations now use `this.seed('TableName', data)` instead of `table.TableName.create(data)`
|
|
3404
|
+
- Ensures compatibility with the migration schema base class
|
|
3405
|
+
- **Fixed**: Missing `await` keywords on `createIndex()`, `dropIndex()`, `createCompositeIndex()`, and `dropCompositeIndex()` calls in migrations
|
|
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
|
|
3409
|
+
|
|
3410
|
+
#### Improvements
|
|
3411
|
+
- **Enhanced**: Query builders now use whitelist validation for column definitions
|
|
3412
|
+
- Validates that objects have both `name` and `type` properties before processing as columns
|
|
3413
|
+
- More robust metadata property filtering in schema processing
|
|
3414
|
+
- Combines blacklist (skip `indexes`, `__*` prefixed) with whitelist (require `name` and `type`) for comprehensive validation
|
|
3415
|
+
- **Documentation**: Enhanced migration generation documentation
|
|
3416
|
+
- **Testing**: Added comprehensive test suite for v0.3.34 bug fixes
|
|
3417
|
+
- Tests whitelist validation across all three database backends
|
|
3418
|
+
- Tests async/await consistency in index operations
|
|
3419
|
+
- Tests seed API correctness and deduplication
|
|
3420
|
+
|
|
3421
|
+
#### Technical Details
|
|
3422
|
+
- Query builders (SQLite, MySQL, PostgreSQL) validate column definitions have required `name` and `type` properties
|
|
3423
|
+
- Migration template generates proper ORM API calls for seed data using `this.seed()` method
|
|
3424
|
+
- Index deduplication logic prevents duplicate CREATE INDEX statements
|
|
3425
|
+
- All migration operations consistently use `await` for async consistency
|
|
3426
|
+
|
|
3427
|
+
#### Migration Notes
|
|
3428
|
+
- Existing migrations generated with older versions will continue to work
|
|
3429
|
+
- New migrations will use the corrected seed API syntax
|
|
3430
|
+
- If you have migrations with `table.EntityName.create()` that haven't been run, regenerate them with this version
|
|
3431
|
+
|
|
3370
3432
|
## Version Compatibility
|
|
3371
3433
|
|
|
3372
3434
|
| Component | Version | Notes |
|
|
3373
3435
|
|---------------|---------------|------------------------------------------|
|
|
3374
|
-
| MasterRecord | 0.3.
|
|
3436
|
+
| MasterRecord | 0.3.35 | Current version - fixes critical duplicate table bug |
|
|
3375
3437
|
| Node.js | 14+ | Async/await support required |
|
|
3376
3438
|
| PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
|
|
3377
3439
|
| MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Tests for v0.3.34 Bug Fixes
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. Whitelist validation in query builders
|
|
6
|
+
* 2. No duplicate index creation
|
|
7
|
+
* 3. Seed API migration generation (no duplicates, correct API usage)
|
|
8
|
+
* 4. All async operations have await keywords
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
12
|
+
console.log("ā MasterRecord v0.3.34 Bug Fixes Test ā");
|
|
13
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n");
|
|
14
|
+
|
|
15
|
+
const Migrations = require('../Migrations/migrations');
|
|
16
|
+
const MigrationSQLiteQuery = require('../Migrations/migrationSQLiteQuery');
|
|
17
|
+
const MigrationMySQLQuery = require('../Migrations/migrationMySQLQuery');
|
|
18
|
+
const MigrationPostgresQuery = require('../Migrations/migrationPostgresQuery');
|
|
19
|
+
|
|
20
|
+
let passed = 0;
|
|
21
|
+
let failed = 0;
|
|
22
|
+
|
|
23
|
+
function assert(condition, message) {
|
|
24
|
+
if (!condition) {
|
|
25
|
+
console.log(`ā FAIL: ${message}`);
|
|
26
|
+
failed++;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function test(description, fn) {
|
|
33
|
+
try {
|
|
34
|
+
fn();
|
|
35
|
+
console.log(`ā ${description}`);
|
|
36
|
+
passed++;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.log(`ā FAIL: ${description}`);
|
|
39
|
+
console.log(` Error: ${e.message}`);
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Test Suite 1: Whitelist Validation
|
|
46
|
+
// =============================================================================
|
|
47
|
+
console.log("\nš Test Suite 1: Whitelist Validation in Query Builders\n");
|
|
48
|
+
|
|
49
|
+
test('SQLite query builder skips objects without name property', () => {
|
|
50
|
+
const queryBuilder = new MigrationSQLiteQuery();
|
|
51
|
+
const table = {
|
|
52
|
+
__name: 'TestTable',
|
|
53
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
54
|
+
weirdMetadata: { foo: 'bar' }, // No name or type - should be skipped
|
|
55
|
+
name: { name: 'name', type: 'string' }
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const query = queryBuilder.createTable(table);
|
|
59
|
+
|
|
60
|
+
assert(!query.includes('undefined'), 'SQLite: Should not contain undefined');
|
|
61
|
+
assert(query.includes('id'), 'SQLite: Should contain id column');
|
|
62
|
+
assert(query.includes('name'), 'SQLite: Should contain name column');
|
|
63
|
+
assert(!query.includes('foo'), 'SQLite: Should not process metadata without name/type');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('MySQL query builder skips objects without name property', () => {
|
|
67
|
+
const queryBuilder = new MigrationMySQLQuery();
|
|
68
|
+
const table = {
|
|
69
|
+
__name: 'TestTable',
|
|
70
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
71
|
+
weirdMetadata: { foo: 'bar' },
|
|
72
|
+
name: { name: 'name', type: 'string' }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const query = queryBuilder.createTable(table);
|
|
76
|
+
|
|
77
|
+
assert(!query.includes('undefined'), 'MySQL: Should not contain undefined');
|
|
78
|
+
assert(query.includes('id'), 'MySQL: Should contain id column');
|
|
79
|
+
assert(query.includes('name'), 'MySQL: Should contain name column');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('PostgreSQL query builder skips objects without name property', () => {
|
|
83
|
+
const queryBuilder = new MigrationPostgresQuery();
|
|
84
|
+
const table = {
|
|
85
|
+
__name: 'TestTable',
|
|
86
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
87
|
+
weirdMetadata: { foo: 'bar' },
|
|
88
|
+
name: { name: 'name', type: 'string' }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const query = queryBuilder.createTable(table);
|
|
92
|
+
|
|
93
|
+
assert(!query.includes('undefined'), 'PostgreSQL: Should not contain undefined');
|
|
94
|
+
assert(query.includes('id'), 'PostgreSQL: Should contain id column');
|
|
95
|
+
assert(query.includes('name'), 'PostgreSQL: Should contain name column');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('Query builders correctly skip indexes property', () => {
|
|
99
|
+
const queryBuilder = new MigrationSQLiteQuery();
|
|
100
|
+
const table = {
|
|
101
|
+
__name: 'TestTable',
|
|
102
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
103
|
+
email: { name: 'email', type: 'string', indexes: ['idx_email'] },
|
|
104
|
+
indexes: ['some', 'array'] // This is metadata, should be skipped
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const query = queryBuilder.createTable(table);
|
|
108
|
+
|
|
109
|
+
assert(!query.includes('undefined'), 'Should not contain undefined');
|
|
110
|
+
assert(query.includes('email'), 'Should contain email column');
|
|
111
|
+
assert(!query.match(/some.*NOT NULL/), 'Should not process indexes array as column');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// Test Suite 2: Index Creation with Await
|
|
116
|
+
// =============================================================================
|
|
117
|
+
console.log("\nš Test Suite 2: Index Creation with Await Keywords\n");
|
|
118
|
+
|
|
119
|
+
test('createIndex generates code with await keyword', () => {
|
|
120
|
+
const migrations = new Migrations();
|
|
121
|
+
const oldSchema = [];
|
|
122
|
+
const newSchema = [{
|
|
123
|
+
__name: 'User',
|
|
124
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
125
|
+
email: { name: 'email', type: 'text', indexes: ['idx_user_email'] }
|
|
126
|
+
}];
|
|
127
|
+
|
|
128
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
|
|
129
|
+
|
|
130
|
+
const createIndexMatches = migrationCode.match(/await this\.createIndex/g);
|
|
131
|
+
assert(createIndexMatches && createIndexMatches.length > 0, 'createIndex should have await keyword');
|
|
132
|
+
assert(!migrationCode.match(/\n\s+this\.createIndex\(/), 'Should not have createIndex without await');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('createCompositeIndex generates code with await keyword', () => {
|
|
136
|
+
const migrations = new Migrations();
|
|
137
|
+
const oldSchema = [];
|
|
138
|
+
const newSchema = [{
|
|
139
|
+
__name: 'User',
|
|
140
|
+
id: { name: 'id', type: 'integer' },
|
|
141
|
+
__compositeIndexes: [{
|
|
142
|
+
name: 'idx_composite',
|
|
143
|
+
columns: ['col1', 'col2'],
|
|
144
|
+
unique: false
|
|
145
|
+
}]
|
|
146
|
+
}];
|
|
147
|
+
|
|
148
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
|
|
149
|
+
|
|
150
|
+
const compositeMatches = migrationCode.match(/await this\.createCompositeIndex/g);
|
|
151
|
+
assert(compositeMatches && compositeMatches.length > 0, 'createCompositeIndex should have await keyword');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Test Suite 3: Seed API Migration Generation
|
|
156
|
+
// =============================================================================
|
|
157
|
+
console.log("\nš Test Suite 3: Seed API Migration Generation\n");
|
|
158
|
+
|
|
159
|
+
test('Seed data uses correct API (this.seed instead of table.EntityName.create)', () => {
|
|
160
|
+
const migrations = new Migrations();
|
|
161
|
+
const oldSchema = [];
|
|
162
|
+
const newSchema = [{
|
|
163
|
+
__name: 'Settings',
|
|
164
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
165
|
+
disable_rag: { name: 'disable_rag', type: 'integer' }
|
|
166
|
+
}];
|
|
167
|
+
const seedData = {
|
|
168
|
+
Settings: [{ disable_rag: 0 }]
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
172
|
+
|
|
173
|
+
assert(migrationCode.includes("this.seed('Settings'"), 'Should use this.seed() method');
|
|
174
|
+
assert(!migrationCode.includes('table.Settings.create'), 'Should NOT use table.Settings.create()');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('No duplicate createTable calls for tables with seed data', () => {
|
|
178
|
+
const migrations = new Migrations();
|
|
179
|
+
const oldSchema = [];
|
|
180
|
+
const newSchema = [
|
|
181
|
+
{
|
|
182
|
+
__name: 'Document',
|
|
183
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
__name: 'Settings',
|
|
187
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
const seedData = {
|
|
191
|
+
Settings: [{ id: 1 }]
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
195
|
+
|
|
196
|
+
const settingsCreateMatches = migrationCode.match(/createTable\(table\.Settings\)/g);
|
|
197
|
+
const settingsCreateCount = settingsCreateMatches ? settingsCreateMatches.length : 0;
|
|
198
|
+
|
|
199
|
+
assert(settingsCreateCount === 1, `Expected 1 createTable for Settings, got ${settingsCreateCount}`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('No duplicate seed insertion calls', () => {
|
|
203
|
+
const migrations = new Migrations();
|
|
204
|
+
const oldSchema = [];
|
|
205
|
+
const newSchema = [{
|
|
206
|
+
__name: 'Settings',
|
|
207
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
208
|
+
}];
|
|
209
|
+
const seedData = {
|
|
210
|
+
Settings: [{ id: 1, value: 'test' }]
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
214
|
+
|
|
215
|
+
const seedMatches = migrationCode.match(/this\.seed\('Settings'/g);
|
|
216
|
+
const seedCount = seedMatches ? seedMatches.length : 0;
|
|
217
|
+
|
|
218
|
+
assert(seedCount === 1, `Expected 1 seed call, got ${seedCount}`);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('No duplicate dropTable calls in down migration', () => {
|
|
222
|
+
const migrations = new Migrations();
|
|
223
|
+
const oldSchema = [];
|
|
224
|
+
const newSchema = [{
|
|
225
|
+
__name: 'Settings',
|
|
226
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
227
|
+
}];
|
|
228
|
+
const seedData = {
|
|
229
|
+
Settings: [{ id: 1 }]
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const migrationCode = migrations.template('TestMigration', oldSchema, newSchema, seedData);
|
|
233
|
+
|
|
234
|
+
const dropMatches = migrationCode.match(/dropTable\(table\.Settings\)/g);
|
|
235
|
+
const dropCount = dropMatches ? dropMatches.length : 0;
|
|
236
|
+
|
|
237
|
+
assert(dropCount === 1, `Expected 1 dropTable call, got ${dropCount}`);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('Complex scenario: Multiple tables with seed data, indexes, and composite indexes', () => {
|
|
241
|
+
const migrations = new Migrations();
|
|
242
|
+
const oldSchema = [];
|
|
243
|
+
const newSchema = [
|
|
244
|
+
{
|
|
245
|
+
__name: 'Document',
|
|
246
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
247
|
+
embedding_model_id: { name: 'embedding_model_id', type: 'integer', indexes: ['idx_doc_embed'] }
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
__name: 'DocumentChunk',
|
|
251
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
252
|
+
__compositeIndexes: [{
|
|
253
|
+
name: 'idx_chunk_unique',
|
|
254
|
+
columns: ['doc_id', 'chunk_index'],
|
|
255
|
+
unique: true
|
|
256
|
+
}]
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
__name: 'Settings',
|
|
260
|
+
id: { name: 'id', type: 'integer', primaryKey: true },
|
|
261
|
+
disable_rag: { name: 'disable_rag', type: 'integer' }
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
__name: 'Job',
|
|
265
|
+
id: { name: 'id', type: 'integer', primaryKey: true }
|
|
266
|
+
}
|
|
267
|
+
];
|
|
268
|
+
const seedData = {
|
|
269
|
+
Settings: [{ disable_rag: 0 }]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const migrationCode = migrations.template('InitialCreate', oldSchema, newSchema, seedData);
|
|
273
|
+
|
|
274
|
+
// Check each table appears exactly once
|
|
275
|
+
const docCreate = (migrationCode.match(/createTable\(table\.Document\)/g) || []).length;
|
|
276
|
+
const chunkCreate = (migrationCode.match(/createTable\(table\.DocumentChunk\)/g) || []).length;
|
|
277
|
+
const settingsCreate = (migrationCode.match(/createTable\(table\.Settings\)/g) || []).length;
|
|
278
|
+
const jobCreate = (migrationCode.match(/createTable\(table\.Job\)/g) || []).length;
|
|
279
|
+
|
|
280
|
+
assert(docCreate === 1, `Document: Expected 1 createTable, got ${docCreate}`);
|
|
281
|
+
assert(chunkCreate === 1, `DocumentChunk: Expected 1 createTable, got ${chunkCreate}`);
|
|
282
|
+
assert(settingsCreate === 1, `Settings: Expected 1 createTable, got ${settingsCreate}`);
|
|
283
|
+
assert(jobCreate === 1, `Job: Expected 1 createTable, got ${jobCreate}`);
|
|
284
|
+
|
|
285
|
+
// Check seed uses correct API
|
|
286
|
+
assert(migrationCode.includes("this.seed('Settings'"), 'Should use this.seed() for Settings');
|
|
287
|
+
assert(!migrationCode.includes('table.Settings.create'), 'Should NOT use table.Settings.create');
|
|
288
|
+
|
|
289
|
+
// Check indexes have await
|
|
290
|
+
assert(migrationCode.includes('await this.createIndex'), 'Indexes should have await');
|
|
291
|
+
assert(migrationCode.includes('await this.createCompositeIndex'), 'Composite indexes should have await');
|
|
292
|
+
});
|
|
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
|
+
|
|
374
|
+
// =============================================================================
|
|
375
|
+
// Test Results Summary
|
|
376
|
+
// =============================================================================
|
|
377
|
+
console.log("\n" + "=".repeat(68));
|
|
378
|
+
console.log(`\nš Test Results: ${passed} passed, ${failed} failed\n`);
|
|
379
|
+
|
|
380
|
+
if (failed === 0) {
|
|
381
|
+
console.log("ā
All tests passed! v0.3.34/0.3.35 fixes are working correctly.\n");
|
|
382
|
+
process.exit(0);
|
|
383
|
+
} else {
|
|
384
|
+
console.log(`ā ļø ${failed} test(s) failed. Please review the output above.\n`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
package/test/debug-id-test.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug test to trace ID setting
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const masterrecord = require('../MasterRecord.js');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
|
|
9
|
-
class User {
|
|
10
|
-
id(db) {
|
|
11
|
-
db.integer().primary().auto();
|
|
12
|
-
}
|
|
13
|
-
name(db) {
|
|
14
|
-
db.string();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class TestContext extends masterrecord.context {
|
|
19
|
-
constructor() {
|
|
20
|
-
super();
|
|
21
|
-
this.database = path.join(__dirname, '..', 'database', 'debugIdTest.db');
|
|
22
|
-
}
|
|
23
|
-
onConfig(db) {
|
|
24
|
-
this.dbset(User);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Clean
|
|
29
|
-
if (fs.existsSync(path.join(__dirname, '..', 'database', 'debugIdTest.db'))) {
|
|
30
|
-
fs.unlinkSync(path.join(__dirname, '..', 'database', 'debugIdTest.db'));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function test() {
|
|
34
|
-
const db = new TestContext();
|
|
35
|
-
db.onConfig();
|
|
36
|
-
|
|
37
|
-
const user = db.User.new();
|
|
38
|
-
user.name = 'Test';
|
|
39
|
-
|
|
40
|
-
console.log('\n=== Manual ID set test ===');
|
|
41
|
-
console.log('Before manual set - user.id:', user.id);
|
|
42
|
-
|
|
43
|
-
user.id = 123;
|
|
44
|
-
console.log('After user.id = 123 - user.id:', user.id);
|
|
45
|
-
console.log('After manual set - user.__proto__._id:', user.__proto__._id);
|
|
46
|
-
|
|
47
|
-
user.id = 456;
|
|
48
|
-
console.log('After user.id = 456 - user.id:', user.id);
|
|
49
|
-
|
|
50
|
-
console.log('\n=== Now test with save ===');
|
|
51
|
-
const user2 = db.User.new();
|
|
52
|
-
user2.name = 'Test2';
|
|
53
|
-
|
|
54
|
-
console.log('Before save - tracked entities:', db.__trackedEntities.length);
|
|
55
|
-
console.log('Before save - user2.__state:', user2.__state);
|
|
56
|
-
|
|
57
|
-
await user2.save();
|
|
58
|
-
|
|
59
|
-
console.log('After save - user2.id:', user2.id);
|
|
60
|
-
console.log('After save - user2.__proto__._id:', user2.__proto__._id);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
test().catch(console.error);
|