@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.
@@ -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
- await relatedInstance._asyncFill(relationData, false, false, true);
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
- await relatedInstance._asyncFill(relationData, false, false, true);
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
- await relatedInstance._asyncFill(itemData, false, false, true);
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
- // Find the fresh data from database
478
- const freshData = await FindUtils_1.FindUtils.findOneBy(this.constructor, { conditions: where });
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 && (model).constructor._CUT_KEYS.includes(key)) {
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 && model.constructor._CUT_KEYS.includes(key)) {
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 && model.constructor._CUT_KEYS.includes(key)) {
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
- model[relMeta.key] = await relMeta.model.findOneBy({ conditions: where }, { allowRelations: false });
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 = model.constructor._CUT_KEYS;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rws-framework/db",
3
3
  "private": false,
4
- "version": "4.0.1",
4
+ "version": "4.1.1",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -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
- await relatedInstance._asyncFill(relationData, false, false, true);
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
- await relatedInstance._asyncFill(relationData, false, false, true);
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
- await relatedInstance._asyncFill(itemData, false, false, true);
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
- // Find the fresh data from database
608
- const freshData = await FindUtils.findOneBy(this.constructor as OpModelType<any>, { conditions: where });
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 };
@@ -130,7 +130,7 @@ export class FindUtils {
130
130
  } : null;
131
131
 
132
132
  const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams, prismaOptions);
133
-
133
+
134
134
  if (dbData.length) {
135
135
  const instanced: T[] = [];
136
136
 
@@ -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 && ((model).constructor as OpModelType<any>)._CUT_KEYS.includes(key)) {
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 && (model as any).constructor._CUT_KEYS.includes(key)) {
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 && ((model as any).constructor as OpModelType<any>)._CUT_KEYS.includes(key)) {
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
- model[relMeta.key] = await relMeta.model.findOneBy({ conditions: where }, { allowRelations: false });
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 = ((model.constructor as OpModelType<any>)._CUT_KEYS as string[]);
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
  }