@rws-framework/db 4.0.1 → 4.1.1
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/dist/models/core/RWSModel.d.ts +2 -0
- package/dist/models/core/RWSModel.js +111 -5
- package/dist/models/utils/HydrateUtils.d.ts +4 -0
- package/dist/models/utils/HydrateUtils.js +44 -5
- package/dist/models/utils/ModelUtils.js +1 -1
- package/package.json +1 -1
- package/src/models/core/RWSModel.ts +135 -5
- package/src/models/utils/FindUtils.ts +1 -1
- package/src/models/utils/HydrateUtils.ts +59 -7
- package/src/models/utils/ModelUtils.ts +1 -1
|
@@ -15,6 +15,7 @@ declare class RWSModel<T> implements IModel {
|
|
|
15
15
|
static _BANNED_KEYS: string[];
|
|
16
16
|
static allModels: OpModelType<any>[];
|
|
17
17
|
static _CUT_KEYS: string[];
|
|
18
|
+
private _relationFields;
|
|
18
19
|
private postLoadExecuted;
|
|
19
20
|
constructor(data?: any);
|
|
20
21
|
isPostLoadExecuted(): boolean;
|
|
@@ -86,5 +87,6 @@ declare class RWSModel<T> implements IModel {
|
|
|
86
87
|
private hydratePrePopulatedRelations;
|
|
87
88
|
static getDb(): DBService;
|
|
88
89
|
reload(): Promise<RWSModel<T> | null>;
|
|
90
|
+
getPropertyValue(key: string): any;
|
|
89
91
|
}
|
|
90
92
|
export { RWSModel };
|
|
@@ -27,6 +27,8 @@ class RWSModel {
|
|
|
27
27
|
static _BANNED_KEYS = ['_collection'];
|
|
28
28
|
static allModels = [];
|
|
29
29
|
static _CUT_KEYS = [];
|
|
30
|
+
// Store relation foreign key fields for reload() functionality
|
|
31
|
+
_relationFields = {};
|
|
30
32
|
postLoadExecuted = false;
|
|
31
33
|
constructor(data = null) {
|
|
32
34
|
if (!this.getCollection()) {
|
|
@@ -420,8 +422,15 @@ class RWSModel {
|
|
|
420
422
|
// Check if it's already a full object with data or just an ID reference
|
|
421
423
|
if (relationData.id || Object.keys(relationData).length > 1) {
|
|
422
424
|
// Create new instance and hydrate ONLY basic fields, NO RELATIONS
|
|
425
|
+
// Respect ignored_keys from child model
|
|
426
|
+
const childIgnoredKeys = ModelClass._CUT_KEYS || [];
|
|
423
427
|
const relatedInstance = new ModelClass();
|
|
424
|
-
|
|
428
|
+
// Filter relationData to exclude ignored keys
|
|
429
|
+
const filteredData = { ...relationData };
|
|
430
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
431
|
+
delete filteredData[ignoredKey];
|
|
432
|
+
}
|
|
433
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
425
434
|
this[key] = relatedInstance;
|
|
426
435
|
}
|
|
427
436
|
}
|
|
@@ -439,8 +448,38 @@ class RWSModel {
|
|
|
439
448
|
// Handle singular inverse relation as a single object
|
|
440
449
|
if (typeof relationData === 'object' && relationData !== null &&
|
|
441
450
|
(relationData.id || Object.keys(relationData).length > 1)) {
|
|
451
|
+
const childIgnoredKeys = ModelClass._CUT_KEYS || [];
|
|
442
452
|
const relatedInstance = new ModelClass();
|
|
443
|
-
|
|
453
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
454
|
+
const tempInstance = new ModelClass();
|
|
455
|
+
const childClassFields = FieldsHelper_1.FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
456
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
457
|
+
RelationUtils_1.RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
458
|
+
RelationUtils_1.RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
459
|
+
]);
|
|
460
|
+
// Store foreign key fields from relation metadata
|
|
461
|
+
for (const relKey in childRelOneData) {
|
|
462
|
+
const relMeta = childRelOneData[relKey];
|
|
463
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
464
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
465
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
466
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (const relKey in childRelManyData) {
|
|
470
|
+
const relMeta = childRelManyData[relKey];
|
|
471
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
472
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
473
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
474
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Filter relationData to exclude ignored keys
|
|
478
|
+
const filteredData = { ...relationData };
|
|
479
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
480
|
+
delete filteredData[ignoredKey];
|
|
481
|
+
}
|
|
482
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
444
483
|
this[key] = relatedInstance;
|
|
445
484
|
}
|
|
446
485
|
}
|
|
@@ -449,8 +488,38 @@ class RWSModel {
|
|
|
449
488
|
const relatedInstances = [];
|
|
450
489
|
for (const itemData of relationData) {
|
|
451
490
|
if (typeof itemData === 'object' && itemData !== null) {
|
|
491
|
+
const childIgnoredKeys = ModelClass._CUT_KEYS || [];
|
|
452
492
|
const relatedInstance = new ModelClass();
|
|
453
|
-
|
|
493
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
494
|
+
const tempInstance = new ModelClass();
|
|
495
|
+
const childClassFields = FieldsHelper_1.FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
496
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
497
|
+
RelationUtils_1.RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
498
|
+
RelationUtils_1.RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
499
|
+
]);
|
|
500
|
+
// Store foreign key fields from relation metadata
|
|
501
|
+
for (const relKey in childRelOneData) {
|
|
502
|
+
const relMeta = childRelOneData[relKey];
|
|
503
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
504
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
505
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
506
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const relKey in childRelManyData) {
|
|
510
|
+
const relMeta = childRelManyData[relKey];
|
|
511
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
512
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
513
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
514
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Filter itemData to exclude ignored keys
|
|
518
|
+
const filteredData = { ...itemData };
|
|
519
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
520
|
+
delete filteredData[ignoredKey];
|
|
521
|
+
}
|
|
522
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
454
523
|
relatedInstances.push(relatedInstance);
|
|
455
524
|
}
|
|
456
525
|
}
|
|
@@ -474,17 +543,54 @@ class RWSModel {
|
|
|
474
543
|
else {
|
|
475
544
|
where[pk] = this[pk];
|
|
476
545
|
}
|
|
477
|
-
//
|
|
478
|
-
const
|
|
546
|
+
// Get ignored keys from model's @RWSCollection decorator
|
|
547
|
+
const ignoredKeys = (this.constructor._CUT_KEYS || []);
|
|
548
|
+
let fields = undefined;
|
|
549
|
+
// Build fields list excluding ignored ones if there are ignored keys
|
|
550
|
+
if (ignoredKeys.length > 0) {
|
|
551
|
+
// Get proper database fields from model annotations
|
|
552
|
+
const annotations = await ModelUtils_1.ModelUtils.getModelAnnotations(this.constructor);
|
|
553
|
+
// Get scalar fields (TrackType decorated fields)
|
|
554
|
+
const scalarFields = ModelUtils_1.ModelUtils.getModelScalarFields(this);
|
|
555
|
+
// Get relation fields from annotations
|
|
556
|
+
const relationFields = Object.keys(annotations).filter(key => annotations[key].annotationType === 'Relation' ||
|
|
557
|
+
annotations[key].annotationType === 'InverseRelation');
|
|
558
|
+
// Combine all database fields
|
|
559
|
+
const allDbFields = [...scalarFields, ...relationFields];
|
|
560
|
+
// Filter out ignored keys
|
|
561
|
+
fields = allDbFields.filter(field => !ignoredKeys.includes(field));
|
|
562
|
+
// Always include id if not ignored
|
|
563
|
+
if (!fields.includes('id') && !ignoredKeys.includes('id')) {
|
|
564
|
+
fields.push('id');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Find the fresh data from database with field filtering
|
|
568
|
+
const freshData = await FindUtils_1.FindUtils.findOneBy(this.constructor, {
|
|
569
|
+
conditions: where,
|
|
570
|
+
fields: fields
|
|
571
|
+
});
|
|
479
572
|
if (!freshData) {
|
|
480
573
|
return null;
|
|
481
574
|
}
|
|
482
575
|
// Convert the fresh instance back to plain data for hydration
|
|
483
576
|
const plainData = await freshData.toMongo();
|
|
577
|
+
// Preserve foreign key fields from _relationFields to ensure relations can be hydrated
|
|
578
|
+
for (const key in this._relationFields) {
|
|
579
|
+
if (plainData[key] === undefined) {
|
|
580
|
+
plainData[key] = this._relationFields[key];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
484
583
|
// Hydrate current instance with fresh data including relations
|
|
485
584
|
await this._asyncFill(plainData, true, true, true);
|
|
486
585
|
return this;
|
|
487
586
|
}
|
|
587
|
+
// Helper method to get property with fallback to stored relation fields
|
|
588
|
+
getPropertyValue(key) {
|
|
589
|
+
if (this.hasOwnProperty(key) || this[key] !== undefined) {
|
|
590
|
+
return this[key];
|
|
591
|
+
}
|
|
592
|
+
return this._relationFields[key];
|
|
593
|
+
}
|
|
488
594
|
}
|
|
489
595
|
exports.RWSModel = RWSModel;
|
|
490
596
|
__decorate([
|
|
@@ -10,4 +10,8 @@ export declare class HydrateUtils {
|
|
|
10
10
|
static hydrateRelations(model: RWSModel<any>, relManyData: RelManyMetaType<IRWSModel>, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
}, postLoadExecute?: boolean): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Get all database fields for a model excluding ignored ones
|
|
15
|
+
*/
|
|
16
|
+
private static getFieldsExcludingIgnored;
|
|
13
17
|
}
|
|
@@ -19,9 +19,11 @@ class HydrateUtils {
|
|
|
19
19
|
foreignKeyFields.add(relationMeta.hydrationField);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
// Get ignored keys from model's @RWSCollection decorator
|
|
23
|
+
const ignoredKeys = (model).constructor._CUT_KEYS || [];
|
|
22
24
|
for (const key in data) {
|
|
23
25
|
if (data.hasOwnProperty(key)) {
|
|
24
|
-
if (!fullDataMode &&
|
|
26
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
25
27
|
continue;
|
|
26
28
|
}
|
|
27
29
|
// Skip relation property names
|
|
@@ -52,20 +54,25 @@ class HydrateUtils {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
static async hydrateRelations(model, relManyData, relOneData, seriesHydrationfields, fullDataMode, data, postLoadExecute = false) {
|
|
57
|
+
const ignoredKeys = (model).constructor._CUT_KEYS || [];
|
|
55
58
|
// Handle many-to-many relations
|
|
56
59
|
for (const key in relManyData) {
|
|
57
|
-
if (!fullDataMode &&
|
|
60
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
58
61
|
continue;
|
|
59
62
|
}
|
|
60
63
|
const relMeta = relManyData[key];
|
|
61
64
|
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
62
65
|
if (relationEnabled) {
|
|
63
66
|
const pk = ModelUtils_1.ModelUtils.findPrimaryKeyFields(model.constructor);
|
|
67
|
+
// Get child model ignored keys to pass to find operations
|
|
68
|
+
const childIgnoredKeys = relMeta.inversionModel._CUT_KEYS || [];
|
|
69
|
+
const childFields = childIgnoredKeys.length > 0 ? await this.getFieldsExcludingIgnored(relMeta.inversionModel, childIgnoredKeys) : undefined;
|
|
64
70
|
if (relMeta.singular) {
|
|
65
71
|
model[relMeta.key] = await relMeta.inversionModel.findOneBy({
|
|
66
72
|
conditions: {
|
|
67
73
|
[relMeta.foreignKey]: data[pk]
|
|
68
74
|
},
|
|
75
|
+
fields: childFields,
|
|
69
76
|
allowRelations: false,
|
|
70
77
|
cancelPostLoad: !postLoadExecute
|
|
71
78
|
});
|
|
@@ -75,6 +82,7 @@ class HydrateUtils {
|
|
|
75
82
|
conditions: {
|
|
76
83
|
[relMeta.foreignKey]: data[pk]
|
|
77
84
|
},
|
|
85
|
+
fields: childFields,
|
|
78
86
|
allowRelations: false,
|
|
79
87
|
cancelPostLoad: !postLoadExecute
|
|
80
88
|
});
|
|
@@ -83,7 +91,7 @@ class HydrateUtils {
|
|
|
83
91
|
}
|
|
84
92
|
// Handle one-to-one relations
|
|
85
93
|
for (const key in relOneData) {
|
|
86
|
-
if (!fullDataMode &&
|
|
94
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
87
95
|
continue;
|
|
88
96
|
}
|
|
89
97
|
const relMeta = relOneData[key];
|
|
@@ -101,13 +109,19 @@ class HydrateUtils {
|
|
|
101
109
|
else {
|
|
102
110
|
where[pk] = data[relMeta.hydrationField];
|
|
103
111
|
}
|
|
104
|
-
|
|
112
|
+
// Get child model ignored keys to pass to find operation
|
|
113
|
+
const childIgnoredKeys = relMeta.model._CUT_KEYS || [];
|
|
114
|
+
const childFields = childIgnoredKeys.length > 0 ? await this.getFieldsExcludingIgnored(relMeta.model, childIgnoredKeys) : undefined;
|
|
115
|
+
model[relMeta.key] = await relMeta.model.findOneBy({
|
|
116
|
+
conditions: where,
|
|
117
|
+
fields: childFields
|
|
118
|
+
}, { allowRelations: false });
|
|
105
119
|
}
|
|
106
120
|
else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
107
121
|
const newRelModel = await relMeta.model.create(data[relMeta.key]);
|
|
108
122
|
model[relMeta.key] = await newRelModel.save();
|
|
109
123
|
}
|
|
110
|
-
const cutKeys =
|
|
124
|
+
const cutKeys = ignoredKeys;
|
|
111
125
|
const trackedField = Object.keys((await ModelUtils_1.ModelUtils.getModelAnnotations(model.constructor))).includes(relMeta.hydrationField);
|
|
112
126
|
if (!cutKeys.includes(relMeta.hydrationField) && !trackedField) {
|
|
113
127
|
cutKeys.push(relMeta.hydrationField);
|
|
@@ -115,5 +129,30 @@ class HydrateUtils {
|
|
|
115
129
|
// seriesHydrationfields.push(relMeta.hydrationField);
|
|
116
130
|
}
|
|
117
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Get all database fields for a model excluding ignored ones
|
|
134
|
+
*/
|
|
135
|
+
static async getFieldsExcludingIgnored(modelClass, ignoredKeys) {
|
|
136
|
+
if (!ignoredKeys || ignoredKeys.length === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
// Get proper database fields from model annotations
|
|
140
|
+
const tempInstance = new modelClass();
|
|
141
|
+
const annotations = await ModelUtils_1.ModelUtils.getModelAnnotations(modelClass);
|
|
142
|
+
// Get scalar fields (TrackType decorated fields)
|
|
143
|
+
const scalarFields = ModelUtils_1.ModelUtils.getModelScalarFields(tempInstance);
|
|
144
|
+
// Get relation fields from annotations
|
|
145
|
+
const relationFields = Object.keys(annotations).filter(key => annotations[key].annotationType === 'Relation' ||
|
|
146
|
+
annotations[key].annotationType === 'InverseRelation');
|
|
147
|
+
// Combine all database fields
|
|
148
|
+
const allDbFields = [...scalarFields, ...relationFields];
|
|
149
|
+
// Filter out ignored keys
|
|
150
|
+
const filteredFields = allDbFields.filter(field => !ignoredKeys.includes(field));
|
|
151
|
+
// Always include id if not ignored
|
|
152
|
+
if (!filteredFields.includes('id') && !ignoredKeys.includes('id')) {
|
|
153
|
+
filteredFields.push('id');
|
|
154
|
+
}
|
|
155
|
+
return filteredFields.length > 0 ? filteredFields : undefined;
|
|
156
|
+
}
|
|
118
157
|
}
|
|
119
158
|
exports.HydrateUtils = HydrateUtils;
|
|
@@ -55,7 +55,7 @@ class ModelUtils {
|
|
|
55
55
|
return baseClass.prototype.isPrototypeOf(constructor.prototype);
|
|
56
56
|
}
|
|
57
57
|
static getModelScalarFields(model) {
|
|
58
|
-
return FieldsHelper_1.FieldsHelper.getAllClassFields(model)
|
|
58
|
+
return FieldsHelper_1.FieldsHelper.getAllClassFields(model.constructor)
|
|
59
59
|
.filter((item) => item.indexOf('TrackType') === 0)
|
|
60
60
|
.map((item) => item.split(':').at(-1));
|
|
61
61
|
}
|
package/package.json
CHANGED
|
@@ -27,6 +27,9 @@ class RWSModel<T> implements IModel {
|
|
|
27
27
|
static _BANNED_KEYS = ['_collection'];
|
|
28
28
|
static allModels: OpModelType<any>[] = [];
|
|
29
29
|
static _CUT_KEYS: string[] = [];
|
|
30
|
+
|
|
31
|
+
// Store relation foreign key fields for reload() functionality
|
|
32
|
+
private _relationFields: Record<string, any> = {};
|
|
30
33
|
|
|
31
34
|
private postLoadExecuted: boolean = false;
|
|
32
35
|
|
|
@@ -544,8 +547,17 @@ class RWSModel<T> implements IModel {
|
|
|
544
547
|
// Check if it's already a full object with data or just an ID reference
|
|
545
548
|
if (relationData.id || Object.keys(relationData).length > 1) {
|
|
546
549
|
// Create new instance and hydrate ONLY basic fields, NO RELATIONS
|
|
550
|
+
// Respect ignored_keys from child model
|
|
551
|
+
const childIgnoredKeys = (ModelClass as OpModelType<any>)._CUT_KEYS || [];
|
|
547
552
|
const relatedInstance = new ModelClass();
|
|
548
|
-
|
|
553
|
+
|
|
554
|
+
// Filter relationData to exclude ignored keys
|
|
555
|
+
const filteredData = { ...relationData };
|
|
556
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
557
|
+
delete filteredData[ignoredKey];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
549
561
|
this[key] = relatedInstance;
|
|
550
562
|
}
|
|
551
563
|
}
|
|
@@ -565,8 +577,43 @@ class RWSModel<T> implements IModel {
|
|
|
565
577
|
// Handle singular inverse relation as a single object
|
|
566
578
|
if (typeof relationData === 'object' && relationData !== null &&
|
|
567
579
|
(relationData.id || Object.keys(relationData).length > 1)) {
|
|
580
|
+
const childIgnoredKeys = (ModelClass as OpModelType<any>)._CUT_KEYS || [];
|
|
568
581
|
const relatedInstance = new ModelClass();
|
|
569
|
-
|
|
582
|
+
|
|
583
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
584
|
+
const tempInstance = new ModelClass();
|
|
585
|
+
const childClassFields = FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
586
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
587
|
+
RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
588
|
+
RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
// Store foreign key fields from relation metadata
|
|
592
|
+
for (const relKey in childRelOneData) {
|
|
593
|
+
const relMeta = childRelOneData[relKey];
|
|
594
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
595
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
596
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
597
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
for (const relKey in childRelManyData) {
|
|
602
|
+
const relMeta = childRelManyData[relKey];
|
|
603
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
604
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
605
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
606
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Filter relationData to exclude ignored keys
|
|
611
|
+
const filteredData = { ...relationData };
|
|
612
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
613
|
+
delete filteredData[ignoredKey];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
570
617
|
this[key] = relatedInstance;
|
|
571
618
|
}
|
|
572
619
|
} else if (Array.isArray(relationData) && relationData.length > 0) {
|
|
@@ -574,8 +621,43 @@ class RWSModel<T> implements IModel {
|
|
|
574
621
|
const relatedInstances = [];
|
|
575
622
|
for (const itemData of relationData) {
|
|
576
623
|
if (typeof itemData === 'object' && itemData !== null) {
|
|
624
|
+
const childIgnoredKeys = (ModelClass as OpModelType<any>)._CUT_KEYS || [];
|
|
577
625
|
const relatedInstance = new ModelClass();
|
|
578
|
-
|
|
626
|
+
|
|
627
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
628
|
+
const tempInstance = new ModelClass();
|
|
629
|
+
const childClassFields = FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
630
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
631
|
+
RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
632
|
+
RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
633
|
+
]);
|
|
634
|
+
|
|
635
|
+
// Store foreign key fields from relation metadata
|
|
636
|
+
for (const relKey in childRelOneData) {
|
|
637
|
+
const relMeta = childRelOneData[relKey];
|
|
638
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
639
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
640
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
641
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const relKey in childRelManyData) {
|
|
646
|
+
const relMeta = childRelManyData[relKey];
|
|
647
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
648
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
649
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
650
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Filter itemData to exclude ignored keys
|
|
655
|
+
const filteredData = { ...itemData };
|
|
656
|
+
for (const ignoredKey of childIgnoredKeys) {
|
|
657
|
+
delete filteredData[ignoredKey];
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
579
661
|
relatedInstances.push(relatedInstance);
|
|
580
662
|
}
|
|
581
663
|
}
|
|
@@ -604,8 +686,41 @@ class RWSModel<T> implements IModel {
|
|
|
604
686
|
where[pk as string] = this[pk as string]
|
|
605
687
|
}
|
|
606
688
|
|
|
607
|
-
//
|
|
608
|
-
const
|
|
689
|
+
// Get ignored keys from model's @RWSCollection decorator
|
|
690
|
+
const ignoredKeys = ((this.constructor as OpModelType<any>)._CUT_KEYS || []);
|
|
691
|
+
let fields: string[] | undefined = undefined;
|
|
692
|
+
|
|
693
|
+
// Build fields list excluding ignored ones if there are ignored keys
|
|
694
|
+
if (ignoredKeys.length > 0) {
|
|
695
|
+
// Get proper database fields from model annotations
|
|
696
|
+
const annotations = await ModelUtils.getModelAnnotations(this.constructor as OpModelType<any>);
|
|
697
|
+
|
|
698
|
+
// Get scalar fields (TrackType decorated fields)
|
|
699
|
+
const scalarFields = ModelUtils.getModelScalarFields(this);
|
|
700
|
+
|
|
701
|
+
// Get relation fields from annotations
|
|
702
|
+
const relationFields = Object.keys(annotations).filter(key =>
|
|
703
|
+
annotations[key].annotationType === 'Relation' ||
|
|
704
|
+
annotations[key].annotationType === 'InverseRelation'
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
// Combine all database fields
|
|
708
|
+
const allDbFields = [...scalarFields, ...relationFields];
|
|
709
|
+
|
|
710
|
+
// Filter out ignored keys
|
|
711
|
+
fields = allDbFields.filter(field => !ignoredKeys.includes(field));
|
|
712
|
+
|
|
713
|
+
// Always include id if not ignored
|
|
714
|
+
if (!fields.includes('id') && !ignoredKeys.includes('id')) {
|
|
715
|
+
fields.push('id');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Find the fresh data from database with field filtering
|
|
720
|
+
const freshData = await FindUtils.findOneBy(this.constructor as OpModelType<any>, {
|
|
721
|
+
conditions: where,
|
|
722
|
+
fields: fields
|
|
723
|
+
});
|
|
609
724
|
|
|
610
725
|
if (!freshData) {
|
|
611
726
|
return null;
|
|
@@ -614,11 +729,26 @@ class RWSModel<T> implements IModel {
|
|
|
614
729
|
// Convert the fresh instance back to plain data for hydration
|
|
615
730
|
const plainData = await freshData.toMongo();
|
|
616
731
|
|
|
732
|
+
// Preserve foreign key fields from _relationFields to ensure relations can be hydrated
|
|
733
|
+
for (const key in this._relationFields) {
|
|
734
|
+
if (plainData[key] === undefined) {
|
|
735
|
+
plainData[key] = this._relationFields[key];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
617
739
|
// Hydrate current instance with fresh data including relations
|
|
618
740
|
await this._asyncFill(plainData, true, true, true);
|
|
619
741
|
|
|
620
742
|
return this;
|
|
621
743
|
}
|
|
744
|
+
|
|
745
|
+
// Helper method to get property with fallback to stored relation fields
|
|
746
|
+
getPropertyValue(key: string): any {
|
|
747
|
+
if (this.hasOwnProperty(key) || this[key] !== undefined) {
|
|
748
|
+
return this[key];
|
|
749
|
+
}
|
|
750
|
+
return this._relationFields[key];
|
|
751
|
+
}
|
|
622
752
|
}
|
|
623
753
|
|
|
624
754
|
export { RWSModel };
|
|
@@ -5,6 +5,7 @@ import { TimeSeriesUtils } from "./TimeSeriesUtils";
|
|
|
5
5
|
import { RelationUtils } from "./RelationUtils";
|
|
6
6
|
import { OpModelType } from "..";
|
|
7
7
|
import { ModelUtils } from "./ModelUtils";
|
|
8
|
+
import { FieldsHelper } from "../../helper/FieldsHelper";
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
|
|
10
11
|
export class HydrateUtils {
|
|
@@ -20,9 +21,12 @@ export class HydrateUtils {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
// Get ignored keys from model's @RWSCollection decorator
|
|
25
|
+
const ignoredKeys = ((model).constructor as OpModelType<any>)._CUT_KEYS || [];
|
|
26
|
+
|
|
23
27
|
for (const key in data) {
|
|
24
28
|
if (data.hasOwnProperty(key)) {
|
|
25
|
-
if (!fullDataMode &&
|
|
29
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
26
30
|
continue;
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -62,9 +66,11 @@ export class HydrateUtils {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
static async hydrateRelations(model: RWSModel<any>, relManyData: RelManyMetaType<IRWSModel>, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: { [key: string]: any }, postLoadExecute = false) {
|
|
69
|
+
const ignoredKeys = ((model).constructor as OpModelType<any>)._CUT_KEYS || [];
|
|
70
|
+
|
|
65
71
|
// Handle many-to-many relations
|
|
66
72
|
for (const key in relManyData) {
|
|
67
|
-
if (!fullDataMode &&
|
|
73
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
68
74
|
continue;
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -72,16 +78,19 @@ export class HydrateUtils {
|
|
|
72
78
|
|
|
73
79
|
const relationEnabled = !RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
81
|
if (relationEnabled) {
|
|
78
82
|
const pk = ModelUtils.findPrimaryKeyFields(model.constructor as OpModelType<any>) as string;
|
|
79
83
|
|
|
84
|
+
// Get child model ignored keys to pass to find operations
|
|
85
|
+
const childIgnoredKeys = (relMeta.inversionModel as OpModelType<any>)._CUT_KEYS || [];
|
|
86
|
+
const childFields = childIgnoredKeys.length > 0 ? await this.getFieldsExcludingIgnored(relMeta.inversionModel, childIgnoredKeys) : undefined;
|
|
87
|
+
|
|
80
88
|
if (relMeta.singular) {
|
|
81
89
|
model[relMeta.key] = await relMeta.inversionModel.findOneBy({
|
|
82
90
|
conditions: {
|
|
83
91
|
[relMeta.foreignKey]: data[pk]
|
|
84
92
|
},
|
|
93
|
+
fields: childFields,
|
|
85
94
|
allowRelations: false,
|
|
86
95
|
cancelPostLoad: !postLoadExecute
|
|
87
96
|
});
|
|
@@ -90,6 +99,7 @@ export class HydrateUtils {
|
|
|
90
99
|
conditions: {
|
|
91
100
|
[relMeta.foreignKey]: data[pk]
|
|
92
101
|
},
|
|
102
|
+
fields: childFields,
|
|
93
103
|
allowRelations: false,
|
|
94
104
|
cancelPostLoad: !postLoadExecute
|
|
95
105
|
});
|
|
@@ -99,7 +109,7 @@ export class HydrateUtils {
|
|
|
99
109
|
|
|
100
110
|
// Handle one-to-one relations
|
|
101
111
|
for (const key in relOneData) {
|
|
102
|
-
if (!fullDataMode &&
|
|
112
|
+
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
103
113
|
continue;
|
|
104
114
|
}
|
|
105
115
|
|
|
@@ -122,14 +132,21 @@ export class HydrateUtils {
|
|
|
122
132
|
where[pk as string] = data[relMeta.hydrationField]
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
// Get child model ignored keys to pass to find operation
|
|
136
|
+
const childIgnoredKeys = (relMeta.model as OpModelType<any>)._CUT_KEYS || [];
|
|
137
|
+
const childFields = childIgnoredKeys.length > 0 ? await this.getFieldsExcludingIgnored(relMeta.model, childIgnoredKeys) : undefined;
|
|
138
|
+
|
|
139
|
+
model[relMeta.key] = await relMeta.model.findOneBy({
|
|
140
|
+
conditions: where,
|
|
141
|
+
fields: childFields
|
|
142
|
+
}, { allowRelations: false });
|
|
126
143
|
}
|
|
127
144
|
else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
128
145
|
const newRelModel: RWSModel<any> = await relMeta.model.create(data[relMeta.key]);
|
|
129
146
|
model[relMeta.key] = await newRelModel.save();
|
|
130
147
|
}
|
|
131
148
|
|
|
132
|
-
const cutKeys =
|
|
149
|
+
const cutKeys = ignoredKeys;
|
|
133
150
|
|
|
134
151
|
const trackedField = Object.keys((await ModelUtils.getModelAnnotations(model.constructor as OpModelType<any>))).includes(relMeta.hydrationField);
|
|
135
152
|
|
|
@@ -140,4 +157,39 @@ export class HydrateUtils {
|
|
|
140
157
|
// seriesHydrationfields.push(relMeta.hydrationField);
|
|
141
158
|
}
|
|
142
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get all database fields for a model excluding ignored ones
|
|
163
|
+
*/
|
|
164
|
+
private static async getFieldsExcludingIgnored(modelClass: OpModelType<any>, ignoredKeys: string[]): Promise<string[] | undefined> {
|
|
165
|
+
if (!ignoredKeys || ignoredKeys.length === 0) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get proper database fields from model annotations
|
|
170
|
+
const tempInstance = new modelClass();
|
|
171
|
+
const annotations = await ModelUtils.getModelAnnotations(modelClass);
|
|
172
|
+
|
|
173
|
+
// Get scalar fields (TrackType decorated fields)
|
|
174
|
+
const scalarFields = ModelUtils.getModelScalarFields(tempInstance);
|
|
175
|
+
|
|
176
|
+
// Get relation fields from annotations
|
|
177
|
+
const relationFields = Object.keys(annotations).filter(key =>
|
|
178
|
+
annotations[key].annotationType === 'Relation' ||
|
|
179
|
+
annotations[key].annotationType === 'InverseRelation'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Combine all database fields
|
|
183
|
+
const allDbFields = [...scalarFields, ...relationFields];
|
|
184
|
+
|
|
185
|
+
// Filter out ignored keys
|
|
186
|
+
const filteredFields = allDbFields.filter(field => !ignoredKeys.includes(field));
|
|
187
|
+
|
|
188
|
+
// Always include id if not ignored
|
|
189
|
+
if (!filteredFields.includes('id') && !ignoredKeys.includes('id')) {
|
|
190
|
+
filteredFields.push('id');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return filteredFields.length > 0 ? filteredFields : undefined;
|
|
194
|
+
}
|
|
143
195
|
}
|
|
@@ -70,7 +70,7 @@ export class ModelUtils {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
static getModelScalarFields(model: RWSModel<any>): string[] {
|
|
73
|
-
return FieldsHelper.getAllClassFields(model)
|
|
73
|
+
return FieldsHelper.getAllClassFields(model.constructor)
|
|
74
74
|
.filter((item: string) => item.indexOf('TrackType') === 0)
|
|
75
75
|
.map((item: string) => item.split(':').at(-1));
|
|
76
76
|
}
|