@lenne.tech/nest-server 11.20.0 → 11.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.20.0",
3
+ "version": "11.20.1",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -550,6 +550,7 @@ export function removeUnresolvedReferences<T = any>(
550
550
  populated: T,
551
551
  populatedOptions: (PopulateOptions | string)[] | PopulateOptions | PopulateOptions[] | string,
552
552
  ignoreFirst = true,
553
+ visited: WeakSet<object> = new WeakSet(),
553
554
  ): T {
554
555
  // Check parameter
555
556
  if (!populated || !populatedOptions) {
@@ -558,21 +559,24 @@ export function removeUnresolvedReferences<T = any>(
558
559
 
559
560
  // Process array
560
561
  if (Array.isArray(populated)) {
561
- populated.forEach((p) => removeUnresolvedReferences(p, populatedOptions, false));
562
+ if (visited.has(populated)) return populated;
563
+ visited.add(populated);
564
+ populated.forEach((p) => removeUnresolvedReferences(p, populatedOptions, false, visited));
562
565
  return populated;
563
566
  }
564
567
 
565
568
  // Process object
566
569
  if (typeof populated === 'object') {
567
- // populatedOptions is an array
570
+ // populatedOptions is an array — iterate options for the same object
571
+ // Each option targets a different property path, so do not mark populated as visited here
568
572
  if (Array.isArray(populatedOptions)) {
569
573
  populatedOptions.forEach((po) =>
570
- removeUnresolvedReferences(populated, ignoreFirst && typeof po === 'object' ? po.populate : po, false),
574
+ removeUnresolvedReferences(populated, ignoreFirst && typeof po === 'object' ? po.populate : po, false, visited),
571
575
  );
572
576
  return populated;
573
577
  }
574
578
 
575
- // populatedOptions is a string
579
+ // populatedOptions is a string — leaf operation, no deep recursion risk
576
580
  if (typeof populatedOptions === 'string') {
577
581
  if (!['_id', 'id'].includes(populatedOptions) && populated[populatedOptions] instanceof Types.ObjectId) {
578
582
  populated[populatedOptions] = null;
@@ -580,13 +584,16 @@ export function removeUnresolvedReferences<T = any>(
580
584
  return populated;
581
585
  }
582
586
 
583
- // populatedOptions is an PopulateOptions object
587
+ // populatedOptions is a PopulateOptions object
584
588
  if (populatedOptions.path) {
585
589
  const key = populatedOptions.path;
586
590
  if (!['_id', 'id'].includes(key) && populated[key] instanceof Types.ObjectId) {
587
591
  populated[key] = null;
588
592
  } else if (populatedOptions.populate) {
589
- removeUnresolvedReferences(populated[key], populatedOptions.populate, false);
593
+ // Prevent circular reference loops when descending into nested populates
594
+ if (visited.has(populated as object)) return populated;
595
+ visited.add(populated as object);
596
+ removeUnresolvedReferences(populated[key], populatedOptions.populate, false, visited);
590
597
  }
591
598
  }
592
599
  }
@@ -413,12 +413,16 @@ export function combinePlain(...args: Record<any, any>[]): any {
413
413
  /**
414
414
  * Get deep frozen object
415
415
  */
416
- export function deepFreeze(object: any) {
416
+ export function deepFreeze(object: any, visited: WeakSet<object> = new WeakSet()) {
417
417
  if (!object || typeof object !== 'object') {
418
418
  return object;
419
419
  }
420
+ if (visited.has(object)) {
421
+ return object;
422
+ }
423
+ visited.add(object);
420
424
  for (const [key, value] of Object.entries(object)) {
421
- object[key] = deepFreeze(value);
425
+ object[key] = deepFreeze(value, visited);
422
426
  }
423
427
  return Object.freeze(object);
424
428
  }
@@ -116,17 +116,25 @@ export class CheckSecurityInterceptor implements NestInterceptor {
116
116
  const proto = Object.getPrototypeOf(val);
117
117
  return proto === null || proto === Object.prototype || typeof val.constructor === 'function';
118
118
  };
119
+ const visited = new WeakSet();
119
120
  const removeSecrets = (data: any) => {
120
121
  if (!this.config.removeSecretFields || !data || typeof data !== 'object') {
121
122
  return data;
122
123
  }
123
124
  if (Array.isArray(data)) {
125
+ if (visited.has(data)) return data;
126
+ visited.add(data);
124
127
  data.forEach(removeSecrets);
125
128
  return data;
126
129
  }
127
130
  if (!isPlainLike(data)) {
128
131
  return data;
129
132
  }
133
+ // Prevent infinite recursion on circular references
134
+ if (visited.has(data)) {
135
+ return data;
136
+ }
137
+ visited.add(data);
130
138
  for (const field of this.config.secretFields) {
131
139
  if (field in data && data[field] !== undefined) {
132
140
  data[field] = undefined;