@rws-framework/db 4.1.2 → 4.1.3

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.
@@ -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
- // If no relations config and no fields specified, include all relations
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
- // 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, {
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
- fields: fields
554
+ allowRelations: true
571
555
  });
556
+ const freshData = results[0];
572
557
  if (!freshData) {
573
558
  return null;
574
559
  }
575
- // Convert the fresh instance back to plain data for hydration
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];
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
- // Hydrate current instance with fresh data including relations
584
- await this._asyncFill(plainData, true, true, true);
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 new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rws-framework/db",
3
3
  "private": false,
4
- "version": "4.1.2",
4
+ "version": "4.1.3",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -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
- // If no relations config and no fields specified, include all relations
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
- await relatedInstance._asyncFill(filteredData, false, false, true);
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
- // 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>, {
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
- fields: fields
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
- // Convert the fresh instance back to plain data for hydration
730
- const plainData = await freshData.toMongo();
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];
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
- // Hydrate current instance with fresh data including relations
740
- await this._asyncFill(plainData, true, true, true);
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 new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`)
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]);