@rws-framework/db 4.1.2 → 4.1.4
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.
|
@@ -19,7 +19,8 @@ function Relation(theModel, relationOptions = _DEFAULTS) {
|
|
|
19
19
|
relationOptions.relationName :
|
|
20
20
|
null
|
|
21
21
|
};
|
|
22
|
-
if
|
|
22
|
+
// Only set default cascade behavior if no explicit cascade was provided
|
|
23
|
+
if (relationOptions.required && !relationOptions.cascade) {
|
|
23
24
|
if (!metaOpts.cascade) {
|
|
24
25
|
metaOpts.cascade = {};
|
|
25
26
|
}
|
|
@@ -113,7 +113,7 @@ class RWSModel {
|
|
|
113
113
|
const relationsAlreadyPopulated = this.checkRelationsPrePopulated(data, relOneData, relManyData);
|
|
114
114
|
if (allowRelations && !relationsAlreadyPopulated) {
|
|
115
115
|
// Use traditional relation hydration if not pre-populated
|
|
116
|
-
await HydrateUtils_1.HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
116
|
+
await HydrateUtils_1.HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data, postLoadExecute);
|
|
117
117
|
}
|
|
118
118
|
else if (allowRelations && relationsAlreadyPopulated) {
|
|
119
119
|
// Relations are already populated from Prisma, just assign them directly
|
|
@@ -355,23 +355,27 @@ class RWSModel {
|
|
|
355
355
|
if (fields && fields.length > 0) {
|
|
356
356
|
return fields.includes(relationName);
|
|
357
357
|
}
|
|
358
|
+
// If no relations config and no fields specified, include all relations
|
|
359
|
+
if (!hasRelationsConfig && (!fields || fields.length === 0)) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
358
362
|
// If no fields specified but relations config exists, include enabled relations
|
|
359
363
|
if (hasRelationsConfig) {
|
|
360
364
|
return allowedRelations[relationName] === true;
|
|
361
365
|
}
|
|
362
|
-
//
|
|
366
|
+
// Default: include all relations when no restrictions
|
|
363
367
|
return true;
|
|
364
368
|
};
|
|
365
|
-
// Add one-to-one and many-to-one relations
|
|
369
|
+
// Add one-to-one and many-to-one relations (without nesting)
|
|
366
370
|
for (const key in relOneData) {
|
|
367
371
|
if (shouldIncludeRelation(key)) {
|
|
368
|
-
includes[key] = true;
|
|
372
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
|
-
// Add one-to-many relations
|
|
375
|
+
// Add one-to-many relations (without nesting)
|
|
372
376
|
for (const key in relManyData) {
|
|
373
377
|
if (shouldIncludeRelation(key)) {
|
|
374
|
-
includes[key] = true;
|
|
378
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
375
379
|
}
|
|
376
380
|
}
|
|
377
381
|
return Object.keys(includes).length > 0 ? includes : null;
|
|
@@ -479,7 +483,7 @@ class RWSModel {
|
|
|
479
483
|
for (const ignoredKey of childIgnoredKeys) {
|
|
480
484
|
delete filteredData[ignoredKey];
|
|
481
485
|
}
|
|
482
|
-
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
486
|
+
await relatedInstance._asyncFill(filteredData, false, false, true); // allowRelations = false
|
|
483
487
|
this[key] = relatedInstance;
|
|
484
488
|
}
|
|
485
489
|
}
|
|
@@ -519,7 +523,7 @@ class RWSModel {
|
|
|
519
523
|
for (const ignoredKey of childIgnoredKeys) {
|
|
520
524
|
delete filteredData[ignoredKey];
|
|
521
525
|
}
|
|
522
|
-
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
526
|
+
await relatedInstance._asyncFill(filteredData, false, false, true); // allowRelations = false
|
|
523
527
|
relatedInstances.push(relatedInstance);
|
|
524
528
|
}
|
|
525
529
|
}
|
|
@@ -543,45 +547,26 @@ class RWSModel {
|
|
|
543
547
|
else {
|
|
544
548
|
where[pk] = this[pk];
|
|
545
549
|
}
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
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, {
|
|
550
|
+
// Use findBy with allowRelations to ensure all relations are loaded
|
|
551
|
+
// For reload(), allow unlimited depth by setting maxDepth to a high value
|
|
552
|
+
const results = await this.constructor.findBy({
|
|
569
553
|
conditions: where,
|
|
570
|
-
|
|
554
|
+
allowRelations: true
|
|
571
555
|
});
|
|
556
|
+
const freshData = results[0];
|
|
572
557
|
if (!freshData) {
|
|
573
558
|
return null;
|
|
574
559
|
}
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (plainData[key] === undefined) {
|
|
580
|
-
plainData[key] = this._relationFields[key];
|
|
560
|
+
// Copy all properties from the fresh instance to this instance
|
|
561
|
+
Object.keys(this).forEach(key => {
|
|
562
|
+
if (!key.startsWith('_') && key !== 'postLoadExecuted') {
|
|
563
|
+
delete this[key];
|
|
581
564
|
}
|
|
582
|
-
}
|
|
583
|
-
//
|
|
584
|
-
|
|
565
|
+
});
|
|
566
|
+
// Copy all data from fresh instance including relations
|
|
567
|
+
Object.keys(freshData).forEach(key => {
|
|
568
|
+
this[key] = freshData[key];
|
|
569
|
+
});
|
|
585
570
|
return this;
|
|
586
571
|
}
|
|
587
572
|
// Helper method to get property with fallback to stored relation fields
|
|
@@ -73,7 +73,7 @@ class HydrateUtils {
|
|
|
73
73
|
[relMeta.foreignKey]: data[pk]
|
|
74
74
|
},
|
|
75
75
|
fields: childFields,
|
|
76
|
-
allowRelations: false,
|
|
76
|
+
allowRelations: false, // Prevent nested relation loading
|
|
77
77
|
cancelPostLoad: !postLoadExecute
|
|
78
78
|
});
|
|
79
79
|
}
|
|
@@ -83,7 +83,7 @@ class HydrateUtils {
|
|
|
83
83
|
[relMeta.foreignKey]: data[pk]
|
|
84
84
|
},
|
|
85
85
|
fields: childFields,
|
|
86
|
-
allowRelations: false,
|
|
86
|
+
allowRelations: false, // Prevent nested relation loading
|
|
87
87
|
cancelPostLoad: !postLoadExecute
|
|
88
88
|
});
|
|
89
89
|
}
|
|
@@ -97,7 +97,12 @@ class HydrateUtils {
|
|
|
97
97
|
const relMeta = relOneData[key];
|
|
98
98
|
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
99
99
|
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
100
|
-
throw
|
|
100
|
+
// Only throw error if this is a fresh load, not a reload of existing model
|
|
101
|
+
if (!model.id) {
|
|
102
|
+
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${model.constructor.name}.`);
|
|
103
|
+
}
|
|
104
|
+
// For existing models (reloads), skip loading this relation if the field is missing
|
|
105
|
+
continue;
|
|
101
106
|
}
|
|
102
107
|
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
103
108
|
const pk = ModelUtils_1.ModelUtils.findPrimaryKeyFields(relMeta.model);
|
|
@@ -115,7 +120,7 @@ class HydrateUtils {
|
|
|
115
120
|
model[relMeta.key] = await relMeta.model.findOneBy({
|
|
116
121
|
conditions: where,
|
|
117
122
|
fields: childFields
|
|
118
|
-
}, { allowRelations: false });
|
|
123
|
+
}, { allowRelations: false }); // Prevent nested relation loading
|
|
119
124
|
}
|
|
120
125
|
// else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
121
126
|
// const newRelModel: RWSModel<any> = await relMeta.model.create(data[relMeta.key]);
|
package/package.json
CHANGED
|
@@ -43,7 +43,8 @@ function Relation(theModel: () => OpModelType<RWSModel<any>>, relationOptions: P
|
|
|
43
43
|
null
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
if
|
|
46
|
+
// Only set default cascade behavior if no explicit cascade was provided
|
|
47
|
+
if(relationOptions.required && !relationOptions.cascade){
|
|
47
48
|
if(!metaOpts.cascade){
|
|
48
49
|
metaOpts.cascade = {};
|
|
49
50
|
}
|
|
@@ -137,7 +137,7 @@ class RWSModel<T> implements IModel {
|
|
|
137
137
|
|
|
138
138
|
if (allowRelations && !relationsAlreadyPopulated) {
|
|
139
139
|
// Use traditional relation hydration if not pre-populated
|
|
140
|
-
await HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
140
|
+
await HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data, postLoadExecute);
|
|
141
141
|
} else if (allowRelations && relationsAlreadyPopulated) {
|
|
142
142
|
// Relations are already populated from Prisma, just assign them directly
|
|
143
143
|
await this.hydratePrePopulatedRelations(data, relOneData, relManyData);
|
|
@@ -471,26 +471,31 @@ class RWSModel<T> implements IModel {
|
|
|
471
471
|
return fields.includes(relationName);
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
// If no relations config and no fields specified, include all relations
|
|
475
|
+
if (!hasRelationsConfig && (!fields || fields.length === 0)) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
474
479
|
// If no fields specified but relations config exists, include enabled relations
|
|
475
480
|
if (hasRelationsConfig) {
|
|
476
481
|
return allowedRelations[relationName] === true;
|
|
477
482
|
}
|
|
478
483
|
|
|
479
|
-
//
|
|
484
|
+
// Default: include all relations when no restrictions
|
|
480
485
|
return true;
|
|
481
486
|
};
|
|
482
487
|
|
|
483
|
-
// Add one-to-one and many-to-one relations
|
|
488
|
+
// Add one-to-one and many-to-one relations (without nesting)
|
|
484
489
|
for (const key in relOneData) {
|
|
485
490
|
if (shouldIncludeRelation(key)) {
|
|
486
|
-
includes[key] = true;
|
|
491
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
487
492
|
}
|
|
488
493
|
}
|
|
489
494
|
|
|
490
|
-
// Add one-to-many relations
|
|
495
|
+
// Add one-to-many relations (without nesting)
|
|
491
496
|
for (const key in relManyData) {
|
|
492
497
|
if (shouldIncludeRelation(key)) {
|
|
493
|
-
includes[key] = true;
|
|
498
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
494
499
|
}
|
|
495
500
|
}
|
|
496
501
|
|
|
@@ -613,7 +618,7 @@ class RWSModel<T> implements IModel {
|
|
|
613
618
|
delete filteredData[ignoredKey];
|
|
614
619
|
}
|
|
615
620
|
|
|
616
|
-
|
|
621
|
+
await relatedInstance._asyncFill(filteredData, false, false, true); // allowRelations = false
|
|
617
622
|
this[key] = relatedInstance;
|
|
618
623
|
}
|
|
619
624
|
} else if (Array.isArray(relationData) && relationData.length > 0) {
|
|
@@ -657,7 +662,7 @@ class RWSModel<T> implements IModel {
|
|
|
657
662
|
delete filteredData[ignoredKey];
|
|
658
663
|
}
|
|
659
664
|
|
|
660
|
-
await relatedInstance._asyncFill(filteredData, false, false, true);
|
|
665
|
+
await relatedInstance._asyncFill(filteredData, false, false, true); // allowRelations = false
|
|
661
666
|
relatedInstances.push(relatedInstance);
|
|
662
667
|
}
|
|
663
668
|
}
|
|
@@ -686,58 +691,30 @@ class RWSModel<T> implements IModel {
|
|
|
686
691
|
where[pk as string] = this[pk as string]
|
|
687
692
|
}
|
|
688
693
|
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
|
|
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>, {
|
|
694
|
+
// Use findBy with allowRelations to ensure all relations are loaded
|
|
695
|
+
// For reload(), allow unlimited depth by setting maxDepth to a high value
|
|
696
|
+
const results = await (this.constructor as OpModelType<any>).findBy({
|
|
721
697
|
conditions: where,
|
|
722
|
-
|
|
698
|
+
allowRelations: true
|
|
723
699
|
});
|
|
724
700
|
|
|
701
|
+
const freshData = results[0];
|
|
702
|
+
|
|
725
703
|
if (!freshData) {
|
|
726
704
|
return null;
|
|
727
705
|
}
|
|
728
706
|
|
|
729
|
-
//
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
for (const key in this._relationFields) {
|
|
734
|
-
if (plainData[key] === undefined) {
|
|
735
|
-
plainData[key] = this._relationFields[key];
|
|
707
|
+
// Copy all properties from the fresh instance to this instance
|
|
708
|
+
Object.keys(this).forEach(key => {
|
|
709
|
+
if (!key.startsWith('_') && key !== 'postLoadExecuted') {
|
|
710
|
+
delete this[key];
|
|
736
711
|
}
|
|
737
|
-
}
|
|
712
|
+
});
|
|
738
713
|
|
|
739
|
-
//
|
|
740
|
-
|
|
714
|
+
// Copy all data from fresh instance including relations
|
|
715
|
+
Object.keys(freshData).forEach(key => {
|
|
716
|
+
this[key] = freshData[key];
|
|
717
|
+
});
|
|
741
718
|
|
|
742
719
|
return this;
|
|
743
720
|
}
|
|
@@ -91,7 +91,7 @@ export class HydrateUtils {
|
|
|
91
91
|
[relMeta.foreignKey]: data[pk]
|
|
92
92
|
},
|
|
93
93
|
fields: childFields,
|
|
94
|
-
allowRelations: false,
|
|
94
|
+
allowRelations: false, // Prevent nested relation loading
|
|
95
95
|
cancelPostLoad: !postLoadExecute
|
|
96
96
|
});
|
|
97
97
|
} else {
|
|
@@ -100,7 +100,7 @@ export class HydrateUtils {
|
|
|
100
100
|
[relMeta.foreignKey]: data[pk]
|
|
101
101
|
},
|
|
102
102
|
fields: childFields,
|
|
103
|
-
allowRelations: false,
|
|
103
|
+
allowRelations: false, // Prevent nested relation loading
|
|
104
104
|
cancelPostLoad: !postLoadExecute
|
|
105
105
|
});
|
|
106
106
|
}
|
|
@@ -117,7 +117,12 @@ export class HydrateUtils {
|
|
|
117
117
|
const relationEnabled = !RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
118
118
|
|
|
119
119
|
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
120
|
-
throw
|
|
120
|
+
// Only throw error if this is a fresh load, not a reload of existing model
|
|
121
|
+
if (!model.id) {
|
|
122
|
+
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${model.constructor.name}.`);
|
|
123
|
+
}
|
|
124
|
+
// For existing models (reloads), skip loading this relation if the field is missing
|
|
125
|
+
continue;
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
@@ -139,7 +144,7 @@ export class HydrateUtils {
|
|
|
139
144
|
model[relMeta.key] = await relMeta.model.findOneBy({
|
|
140
145
|
conditions: where,
|
|
141
146
|
fields: childFields
|
|
142
|
-
}, { allowRelations: false });
|
|
147
|
+
}, { allowRelations: false }); // Prevent nested relation loading
|
|
143
148
|
}
|
|
144
149
|
// else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
145
150
|
// const newRelModel: RWSModel<any> = await relMeta.model.create(data[relMeta.key]);
|