@lenne.tech/nest-server 11.27.6 → 11.27.7

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.
Files changed (132) hide show
  1. package/.claude/rules/architecture.md +66 -0
  2. package/.claude/rules/better-auth.md +88 -2
  3. package/.claude/rules/package-management.md +23 -14
  4. package/CLAUDE.md +8 -2
  5. package/FRAMEWORK-API.md +1 -1
  6. package/dist/core/common/decorators/restricted.decorator.d.ts +4 -4
  7. package/dist/core/common/decorators/restricted.decorator.js +21 -22
  8. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  9. package/dist/core/common/helpers/clone.helper.d.ts +6 -0
  10. package/dist/core/common/helpers/clone.helper.js +67 -0
  11. package/dist/core/common/helpers/clone.helper.js.map +1 -0
  12. package/dist/core/common/helpers/db.helper.d.ts +2 -14
  13. package/dist/core/common/helpers/db.helper.js +13 -106
  14. package/dist/core/common/helpers/db.helper.js.map +1 -1
  15. package/dist/core/common/helpers/id.helper.d.ts +15 -0
  16. package/dist/core/common/helpers/id.helper.js +104 -0
  17. package/dist/core/common/helpers/id.helper.js.map +1 -0
  18. package/dist/core/common/helpers/input.helper.d.ts +1 -6
  19. package/dist/core/common/helpers/input.helper.js +11 -70
  20. package/dist/core/common/helpers/input.helper.js.map +1 -1
  21. package/dist/core/common/inputs/combined-filter.input.d.ts +1 -12
  22. package/dist/core/common/inputs/combined-filter.input.js +2 -51
  23. package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
  24. package/dist/core/common/inputs/filter.input.d.ts +10 -1
  25. package/dist/core/common/inputs/filter.input.js +40 -5
  26. package/dist/core/common/inputs/filter.input.js.map +1 -1
  27. package/dist/core/common/services/config.service.js +5 -5
  28. package/dist/core/common/services/config.service.js.map +1 -1
  29. package/dist/core/modules/ai/core-ai.constants.d.ts +22 -0
  30. package/dist/core/modules/ai/core-ai.constants.js +26 -0
  31. package/dist/core/modules/ai/core-ai.constants.js.map +1 -0
  32. package/dist/core/modules/ai/index.d.ts +1 -0
  33. package/dist/core/modules/ai/index.js +1 -0
  34. package/dist/core/modules/ai/index.js.map +1 -1
  35. package/dist/core/modules/ai/interfaces/ai-interaction-record.interface.d.ts +17 -0
  36. package/dist/core/modules/ai/interfaces/ai-interaction-record.interface.js +3 -0
  37. package/dist/core/modules/ai/interfaces/ai-interaction-record.interface.js.map +1 -0
  38. package/dist/core/modules/ai/services/core-ai-budget.service.d.ts +1 -2
  39. package/dist/core/modules/ai/services/core-ai-budget.service.js +7 -5
  40. package/dist/core/modules/ai/services/core-ai-budget.service.js.map +1 -1
  41. package/dist/core/modules/ai/services/core-ai-connection-preference.service.d.ts +1 -2
  42. package/dist/core/modules/ai/services/core-ai-connection-preference.service.js +7 -5
  43. package/dist/core/modules/ai/services/core-ai-connection-preference.service.js.map +1 -1
  44. package/dist/core/modules/ai/services/core-ai-connection.service.d.ts +1 -2
  45. package/dist/core/modules/ai/services/core-ai-connection.service.js +7 -5
  46. package/dist/core/modules/ai/services/core-ai-connection.service.js.map +1 -1
  47. package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +1 -2
  48. package/dist/core/modules/ai/services/core-ai-conversation.service.js +7 -5
  49. package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -1
  50. package/dist/core/modules/ai/services/core-ai-interaction.service.d.ts +2 -3
  51. package/dist/core/modules/ai/services/core-ai-interaction.service.js +7 -5
  52. package/dist/core/modules/ai/services/core-ai-interaction.service.js.map +1 -1
  53. package/dist/core/modules/ai/services/core-ai-mode.service.d.ts +1 -2
  54. package/dist/core/modules/ai/services/core-ai-mode.service.js +7 -5
  55. package/dist/core/modules/ai/services/core-ai-mode.service.js.map +1 -1
  56. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.d.ts +1 -3
  57. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js +7 -5
  58. package/dist/core/modules/ai/services/core-ai-prompt-hint.service.js.map +1 -1
  59. package/dist/core/modules/ai/services/core-ai-prompt.service.d.ts +1 -2
  60. package/dist/core/modules/ai/services/core-ai-prompt.service.js +7 -5
  61. package/dist/core/modules/ai/services/core-ai-prompt.service.js.map +1 -1
  62. package/dist/core/modules/ai/services/core-ai-slot.service.d.ts +1 -2
  63. package/dist/core/modules/ai/services/core-ai-slot.service.js +7 -5
  64. package/dist/core/modules/ai/services/core-ai-slot.service.js.map +1 -1
  65. package/dist/core/modules/ai/services/core-ai-tool-grant.service.d.ts +1 -2
  66. package/dist/core/modules/ai/services/core-ai-tool-grant.service.js +7 -5
  67. package/dist/core/modules/ai/services/core-ai-tool-grant.service.js.map +1 -1
  68. package/dist/core/modules/ai/services/core-ai-tool-policy.service.d.ts +1 -2
  69. package/dist/core/modules/ai/services/core-ai-tool-policy.service.js +7 -5
  70. package/dist/core/modules/ai/services/core-ai-tool-policy.service.js.map +1 -1
  71. package/dist/core/modules/ai/services/core-ai.service.d.ts +2 -17
  72. package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
  73. package/dist/core/modules/better-auth/better-auth-roles.guard.js +2 -2
  74. package/dist/core/modules/better-auth/better-auth-roles.guard.js.map +1 -1
  75. package/dist/core/modules/better-auth/better-auth.config.js +6 -0
  76. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  77. package/dist/core/modules/better-auth/core-better-auth.constants.d.ts +3 -0
  78. package/dist/core/modules/better-auth/core-better-auth.constants.js +7 -0
  79. package/dist/core/modules/better-auth/core-better-auth.constants.js.map +1 -0
  80. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +1 -2
  81. package/dist/core/modules/better-auth/core-better-auth.module.js +23 -20
  82. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  83. package/dist/core/modules/better-auth/core-better-auth.registry.d.ts +4 -0
  84. package/dist/core/modules/better-auth/core-better-auth.registry.js +16 -0
  85. package/dist/core/modules/better-auth/core-better-auth.registry.js.map +1 -0
  86. package/dist/core/modules/better-auth/core-better-auth.service.d.ts +1 -2
  87. package/dist/core/modules/better-auth/core-better-auth.service.js +7 -6
  88. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  89. package/dist/core/modules/better-auth/index.d.ts +1 -0
  90. package/dist/core/modules/better-auth/index.js +1 -0
  91. package/dist/core/modules/better-auth/index.js.map +1 -1
  92. package/dist/core/modules/tus/tus.constants.d.ts +1 -0
  93. package/dist/core/modules/tus/tus.constants.js +5 -0
  94. package/dist/core/modules/tus/tus.constants.js.map +1 -0
  95. package/dist/core/modules/tus/tus.module.d.ts +1 -1
  96. package/dist/core/modules/tus/tus.module.js +8 -6
  97. package/dist/core/modules/tus/tus.module.js.map +1 -1
  98. package/dist/tsconfig.build.tsbuildinfo +1 -1
  99. package/migration-guides/11.27.6-to-11.27.7.md +374 -0
  100. package/package.json +6 -104
  101. package/src/core/common/decorators/restricted.decorator.ts +33 -8
  102. package/src/core/common/helpers/clone.helper.ts +110 -0
  103. package/src/core/common/helpers/db.helper.ts +14 -161
  104. package/src/core/common/helpers/id.helper.ts +198 -0
  105. package/src/core/common/helpers/input.helper.ts +12 -84
  106. package/src/core/common/inputs/combined-filter.input.ts +10 -57
  107. package/src/core/common/inputs/filter.input.ts +92 -1
  108. package/src/core/common/services/config.service.ts +4 -1
  109. package/src/core/modules/ai/core-ai.constants.ts +92 -0
  110. package/src/core/modules/ai/index.ts +1 -0
  111. package/src/core/modules/ai/interfaces/ai-interaction-record.interface.ts +34 -0
  112. package/src/core/modules/ai/services/core-ai-budget.service.ts +8 -4
  113. package/src/core/modules/ai/services/core-ai-connection-preference.service.ts +8 -4
  114. package/src/core/modules/ai/services/core-ai-connection.service.ts +5 -6
  115. package/src/core/modules/ai/services/core-ai-conversation.service.ts +5 -6
  116. package/src/core/modules/ai/services/core-ai-interaction.service.ts +6 -7
  117. package/src/core/modules/ai/services/core-ai-mode.service.ts +8 -2
  118. package/src/core/modules/ai/services/core-ai-prompt-hint.service.ts +7 -4
  119. package/src/core/modules/ai/services/core-ai-prompt.service.ts +8 -2
  120. package/src/core/modules/ai/services/core-ai-slot.service.ts +7 -4
  121. package/src/core/modules/ai/services/core-ai-tool-grant.service.ts +8 -4
  122. package/src/core/modules/ai/services/core-ai-tool-policy.service.ts +8 -4
  123. package/src/core/modules/ai/services/core-ai.service.ts +8 -11
  124. package/src/core/modules/better-auth/better-auth-roles.guard.ts +11 -5
  125. package/src/core/modules/better-auth/better-auth.config.ts +16 -0
  126. package/src/core/modules/better-auth/core-better-auth.constants.ts +73 -0
  127. package/src/core/modules/better-auth/core-better-auth.module.ts +34 -8
  128. package/src/core/modules/better-auth/core-better-auth.registry.ts +53 -0
  129. package/src/core/modules/better-auth/core-better-auth.service.ts +19 -12
  130. package/src/core/modules/better-auth/index.ts +9 -0
  131. package/src/core/modules/tus/tus.constants.ts +25 -0
  132. package/src/core/modules/tus/tus.module.ts +7 -2
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Cloning / freezing helpers.
3
+ *
4
+ * These live in their own file — instead of `input.helper.ts` — so that `config.service` can reach
5
+ * them without importing `input.helper`, which imports `restricted.decorator` behind it.
6
+ *
7
+ * That edge closed a runtime cycle:
8
+ *
9
+ * restricted.decorator → tenant/core-tenant.helpers → config.service → input.helper
10
+ * → restricted.decorator
11
+ *
12
+ * so `input.helper` evaluated while `restricted.decorator` was still initializing. Nothing crashed,
13
+ * but only because every cross-cycle dereference sat inside a function body. A single top-level line
14
+ * in `input.helper` — a module-level alias of `checkRestricted`, an `@Restricted`-decorated class,
15
+ * `design:type` metadata — would have thrown under SWC → CommonJS:
16
+ *
17
+ * ReferenceError: Cannot access 'checkRestricted' before initialization
18
+ *
19
+ * …in the file that drives field-level access control, on a compiler this repo's default build never
20
+ * runs, with a green test suite. Moving these two pure functions out removes the edge, so the cycle
21
+ * no longer exists rather than merely being disarmed.
22
+ *
23
+ * This file imports only Node built-ins, lodash and rfdc — no framework code — so it can never be
24
+ * part of a cycle itself. Keep it that way.
25
+ *
26
+ * `input.helper` re-exports both functions, so every existing import path keeps working.
27
+ * See .claude/rules/architecture.md → "DI Token Placement (SWC-Safe)".
28
+ */
29
+ import * as inspector from 'inspector';
30
+ import _ = require('lodash');
31
+ import rfdc = require('rfdc');
32
+ import * as util from 'util';
33
+
34
+ /**
35
+ * Get clone of object
36
+ *
37
+ * @param object Object to clone
38
+ * @param options Options for cloning
39
+ * @param options.checkResult Whether to check the result of the cloning process
40
+ * @param options.circles Keeping track of circular references will slow down performance with an additional 25% overhead.
41
+ * Even if an object doesn't have any circular references, the tracking overhead is the cost.
42
+ * By default if an object with a circular reference is passed to rfdc, it will throw
43
+ * (similar to how JSON.stringify would throw). Use the circles option to detect and preserve
44
+ * circular references in the object. If performance is important, try removing the circular
45
+ * reference from the object (set to undefined) and then add it back manually after cloning
46
+ * instead of using this option.
47
+ * @param options.proto Copy prototype properties as well as own properties into the new object.
48
+ * It's marginally faster to allow enumerable properties on the prototype to be copied into the
49
+ * cloned object (not onto it's prototype, directly onto the object).
50
+ */
51
+ export function clone(object: any, options?: { checkResult?: boolean; circles?: boolean; proto?: boolean }) {
52
+ const config = {
53
+ checkResult: true,
54
+ circles: true,
55
+ debug: inspector.url() !== undefined,
56
+ proto: false,
57
+ ...options,
58
+ };
59
+
60
+ try {
61
+ const cloned = rfdc(config)(object);
62
+ if (config.checkResult && !util.isDeepStrictEqual(object, cloned)) {
63
+ throw new Error('Cloned object differs from original object');
64
+ }
65
+ return cloned;
66
+ } catch (e) {
67
+ if (!config.circles) {
68
+ if (config.debug) {
69
+ console.debug(e, config, object, 'automatic try to use rfdc with circles');
70
+ }
71
+ try {
72
+ const clonedWithCircles = rfdc({
73
+ ...config,
74
+ circles: true,
75
+ })(object);
76
+ if (config.checkResult && !util.isDeepStrictEqual(object, clonedWithCircles)) {
77
+ throw new Error('Cloned object differs from original object', { cause: e });
78
+ }
79
+ return clonedWithCircles;
80
+ } catch (innerError) {
81
+ if (config.debug) {
82
+ console.debug(innerError, 'rfcd with circles did not work => automatic use of _.clone!');
83
+ }
84
+ return _.cloneDeep(object);
85
+ }
86
+ } else {
87
+ if (config.debug) {
88
+ console.debug(e, config, object, 'automatic try to use _.clone instead rfdc');
89
+ }
90
+ return _.cloneDeep(object);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get deep frozen object
97
+ */
98
+ export function deepFreeze(object: any, visited: WeakSet<object> = new WeakSet()) {
99
+ if (!object || typeof object !== 'object') {
100
+ return object;
101
+ }
102
+ if (visited.has(object)) {
103
+ return object;
104
+ }
105
+ visited.add(object);
106
+ for (const [key, value] of Object.entries(object)) {
107
+ object[key] = deepFreeze(value, visited);
108
+ }
109
+ return Object.freeze(object);
110
+ }
@@ -1,5 +1,4 @@
1
1
  import { FieldNode, GraphQLResolveInfo, SelectionNode } from 'graphql';
2
- import _ = require('lodash');
3
2
  import { Document, Model, PopulateOptions, Query, Types } from 'mongoose';
4
3
 
5
4
  import { ResolveSelector } from '../interfaces/resolve-selector.interface';
@@ -7,8 +6,19 @@ import { CoreModel } from '../models/core-model.model';
7
6
  import { FieldSelection } from '../types/field-selection.type';
8
7
  import { IdsType } from '../types/ids.type';
9
8
  import { StringOrObjectId } from '../types/string-or-object-id.type';
9
+ import { equalIds, getObjectIds, getStringIds } from './id.helper';
10
10
  import { removePropertiesDeep } from './input.helper';
11
11
 
12
+ /**
13
+ * The ID helpers moved to `./id.helper` and are re-exported here so every existing import path keeps
14
+ * working. They were extracted because `restricted.decorator` needs `equalIds` / `getIncludedIds`,
15
+ * and pulling them from HERE put that decorator on a runtime import cycle
16
+ * (restricted.decorator → db.helper → input.helper → restricted.decorator) that is one top-level
17
+ * line away from an SWC temporal-dead-zone crash. `id.helper` imports no framework code, so it can
18
+ * never be part of a cycle. Do NOT move them back.
19
+ */
20
+ export { equalIds, getIncludedIds, getObjectIds, getStringIds } from './id.helper';
21
+
12
22
  // =====================================================================================================================
13
23
  // Export functions
14
24
  // =====================================================================================================================
@@ -71,7 +81,9 @@ export function addIds(
71
81
  // Convert array
72
82
  if (['object', 'string'].includes(convert as string)) {
73
83
  for (let i = 0; i < result.length; i++) {
74
- result[i] = convert === 'string' ? getStringId(result[i]) : getObjectIds(result[i]);
84
+ // getStringIds() delegates to the (module-private) getStringId() for a non-array argument, so
85
+ // this is the same conversion the removed private helper performed — result[i] is a single ID.
86
+ result[i] = convert === 'string' ? getStringIds(result[i]) : getObjectIds(result[i]);
75
87
  }
76
88
  }
77
89
 
@@ -97,20 +109,6 @@ export function checkStringIds(ids: string | string[]): boolean {
97
109
  return false;
98
110
  }
99
111
 
100
- /**
101
- * Checks if all IDs are equal
102
- */
103
- export function equalIds(...ids: IdsType[]): boolean {
104
- if (!ids) {
105
- return false;
106
- }
107
- const compare = getStringIds(ids[0]);
108
- if (!compare) {
109
- return false;
110
- }
111
- return ids.every((id) => getStringIds(id) === compare);
112
- }
113
-
114
112
  /**
115
113
  * Get (and remove) elements with specific IDs from array
116
114
  */
@@ -160,48 +158,6 @@ export function getElementsViaIds<T = any>(
160
158
  // Return elements
161
159
  return elements;
162
160
  }
163
- /**
164
- * Get included ids
165
- * @param includes IdsType, which should be checked if it contains the ID
166
- * @param ids IdsType, which should be included
167
- * @param convert If set the result array will be converted to pure type String array or ObjectId array
168
- * @return IdsType with IDs which are included, undefined if includes or ids are missing or null if none is included
169
- */
170
- export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'string'): string[];
171
- export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'object'): Types.ObjectId[];
172
-
173
- export function getIncludedIds<T = IdsType>(
174
- includes: IdsType,
175
- ids: IdsType | T,
176
- convert?: 'object' | 'string',
177
- ): null | T[] | undefined {
178
- if (!includes || !ids) {
179
- return undefined;
180
- }
181
-
182
- if (!Array.isArray(includes)) {
183
- includes = [includes];
184
- }
185
-
186
- if (!Array.isArray(ids)) {
187
- ids = [ids];
188
- }
189
-
190
- let result = [];
191
- const includesStrings = getStringIds(includes);
192
- for (const id of ids) {
193
- if (includesStrings.includes(getStringIds(id))) {
194
- result.push(id);
195
- }
196
- }
197
-
198
- if (convert) {
199
- result = convert === 'string' ? getStringIds(result) : getObjectIds(result);
200
- }
201
-
202
- return result.length ? result : null;
203
- }
204
-
205
161
  /**
206
162
  * Get indexes of IDs in an array
207
163
  */
@@ -252,18 +208,6 @@ export function getNextFieldNodes(nodes: readonly SelectionNode[]): FieldNode[]
252
208
  return result;
253
209
  }
254
210
 
255
- /**
256
- * Convert string(s) to ObjectId(s)
257
- */
258
- export function getObjectIds(ids: any[]): Types.ObjectId[];
259
- export function getObjectIds(ids: any): Types.ObjectId;
260
- export function getObjectIds<T extends any | any[]>(ids: T): Types.ObjectId | Types.ObjectId[] {
261
- if (Array.isArray(ids)) {
262
- return ids.map((id) => new Types.ObjectId(getStringId(id)));
263
- }
264
- return new Types.ObjectId(getStringId(ids));
265
- }
266
-
267
211
  /**
268
212
  * Get populate options from GraphQL resolve info
269
213
  */
@@ -356,55 +300,6 @@ export function getPopulatOptionsFromSelections(selectionNodes: readonly Selecti
356
300
  return populateOptions;
357
301
  }
358
302
 
359
- /**
360
- * Get IDs from string of ObjectId array in a flat string array
361
- */
362
- export function getStringIds(elements: any[], options?: { deep?: boolean; unique?: boolean }): string[];
363
-
364
- export function getStringIds(elements: any, options?: { deep?: boolean; unique?: boolean }): string;
365
-
366
- export function getStringIds<T extends any | any[]>(
367
- elements: T,
368
- options?: { deep?: boolean; unique?: boolean },
369
- ): string | string[] {
370
- // Process options
371
- const { deep, unique } = {
372
- deep: false,
373
- unique: false,
374
- ...options,
375
- };
376
-
377
- // Check elements
378
- if (!elements) {
379
- return elements as any;
380
- }
381
-
382
- // Init ids
383
- let ids = [];
384
-
385
- // Process non array
386
- if (!Array.isArray(elements)) {
387
- return getStringId(elements);
388
- }
389
-
390
- // Process array
391
- for (const element of elements) {
392
- if (Array.isArray(element)) {
393
- if (deep) {
394
- ids = ids.concat(getStringIds(element, { deep }));
395
- }
396
- } else {
397
- const id = getStringId(element);
398
- if (id) {
399
- ids.push(id);
400
- }
401
- }
402
- }
403
-
404
- // Return (unique) ID array
405
- return unique ? _.uniq(ids) : ids;
406
- }
407
-
408
303
  /**
409
304
  * Convert all ObjectIds to strings
410
305
  */
@@ -664,45 +559,3 @@ export async function setPopulates<T = Document | Query<any, any>>(
664
559
  // =====================================================================================================================
665
560
  // Not exported helper functions
666
561
  // =====================================================================================================================
667
- /**
668
- * Get ID of element as string
669
- */
670
- function getStringId(element: any): string {
671
- // Check element
672
- if (!element) {
673
- return element;
674
- }
675
-
676
- // Buffer handling
677
- if (element instanceof Buffer) {
678
- return element.toString();
679
- }
680
-
681
- // String handling
682
- if (typeof element === 'string') {
683
- return element;
684
- }
685
-
686
- // Object handling
687
- if (typeof element === 'object') {
688
- if (element instanceof Types.ObjectId) {
689
- return element.toHexString();
690
- }
691
-
692
- if (element.id) {
693
- if (element.id instanceof Buffer && element.toHexString) {
694
- return element.toHexString();
695
- }
696
- return getStringId(element.id);
697
- } else if (element._id) {
698
- return getStringId(element._id);
699
- }
700
- }
701
-
702
- // Other types
703
- if (typeof element.toString === 'function') {
704
- return element.toString();
705
- }
706
-
707
- return undefined;
708
- }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * ID helpers.
3
+ *
4
+ * These live in their own file — instead of `db.helper.ts` — so that files which need nothing but
5
+ * ID comparison can reach them without importing `db.helper`, which drags in GraphQL, Mongoose
6
+ * models and `input.helper` behind it.
7
+ *
8
+ * That was not an aesthetic choice. `restricted.decorator.ts` needs exactly two of these functions
9
+ * (`equalIds`, `getIncludedIds`), and importing them from `db.helper` put it on a real runtime
10
+ * cycle:
11
+ *
12
+ * restricted.decorator → db.helper → input.helper → restricted.decorator
13
+ *
14
+ * `input.helper` therefore evaluated while `restricted.decorator` was still initializing. Nothing
15
+ * crashed, but only because every cross-cycle dereference happened to sit inside a function body:
16
+ * a single top-level line in `input.helper` (a module-level alias, an `@Restricted`-decorated class,
17
+ * `design:type` metadata) would have thrown under SWC → CommonJS:
18
+ *
19
+ * ReferenceError: Cannot access 'checkRestricted' before initialization
20
+ *
21
+ * …in the file that drives field-level access control, on a compiler this repo's default build never
22
+ * runs. Moving the ID cluster into this leaf removes the `restricted.decorator → db.helper` edge
23
+ * outright, so the cycle no longer exists rather than merely being disarmed.
24
+ *
25
+ * This file imports only Mongoose's `Types`, lodash, and a type — no framework code — so it can
26
+ * never be part of a cycle itself. Keep it that way.
27
+ *
28
+ * `db.helper` re-exports all four public functions, so every existing import path keeps working.
29
+ * See .claude/rules/architecture.md → "DI Token Placement (SWC-Safe)".
30
+ */
31
+ import _ = require('lodash');
32
+ import { Types } from 'mongoose';
33
+
34
+ import { IdsType } from '../types/ids.type';
35
+
36
+ /**
37
+ * Checks if all IDs are equal
38
+ */
39
+ export function equalIds(...ids: IdsType[]): boolean {
40
+ if (!ids) {
41
+ return false;
42
+ }
43
+ const compare = getStringIds(ids[0]);
44
+ if (!compare) {
45
+ return false;
46
+ }
47
+ return ids.every((id) => getStringIds(id) === compare);
48
+ }
49
+
50
+ /**
51
+ * Get included ids
52
+ * @param includes IdsType, which should be checked if it contains the ID
53
+ * @param ids IdsType, which should be included
54
+ * @param convert If set the result array will be converted to pure type String array or ObjectId array
55
+ * @return IdsType with IDs which are included, undefined if includes or ids are missing or null if none is included
56
+ */
57
+ export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'string'): string[];
58
+ export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'object'): Types.ObjectId[];
59
+
60
+ export function getIncludedIds<T = IdsType>(
61
+ includes: IdsType,
62
+ ids: IdsType | T,
63
+ convert?: 'object' | 'string',
64
+ ): null | T[] | undefined {
65
+ if (!includes || !ids) {
66
+ return undefined;
67
+ }
68
+
69
+ if (!Array.isArray(includes)) {
70
+ includes = [includes];
71
+ }
72
+
73
+ if (!Array.isArray(ids)) {
74
+ ids = [ids];
75
+ }
76
+
77
+ let result = [];
78
+ const includesStrings = getStringIds(includes);
79
+ for (const id of ids) {
80
+ if (includesStrings.includes(getStringIds(id))) {
81
+ result.push(id);
82
+ }
83
+ }
84
+
85
+ if (convert) {
86
+ result = convert === 'string' ? getStringIds(result) : getObjectIds(result);
87
+ }
88
+
89
+ return result.length ? result : null;
90
+ }
91
+
92
+ /**
93
+ * Convert string(s) to ObjectId(s)
94
+ */
95
+ export function getObjectIds(ids: any[]): Types.ObjectId[];
96
+ export function getObjectIds(ids: any): Types.ObjectId;
97
+ export function getObjectIds<T extends any | any[]>(ids: T): Types.ObjectId | Types.ObjectId[] {
98
+ if (Array.isArray(ids)) {
99
+ return ids.map((id) => new Types.ObjectId(getStringId(id)));
100
+ }
101
+ return new Types.ObjectId(getStringId(ids));
102
+ }
103
+
104
+ /**
105
+ * Get IDs from string of ObjectId array in a flat string array
106
+ */
107
+ export function getStringIds(elements: any[], options?: { deep?: boolean; unique?: boolean }): string[];
108
+
109
+ export function getStringIds(elements: any, options?: { deep?: boolean; unique?: boolean }): string;
110
+
111
+ export function getStringIds<T extends any | any[]>(
112
+ elements: T,
113
+ options?: { deep?: boolean; unique?: boolean },
114
+ ): string | string[] {
115
+ // Process options
116
+ const { deep, unique } = {
117
+ deep: false,
118
+ unique: false,
119
+ ...options,
120
+ };
121
+
122
+ // Check elements
123
+ if (!elements) {
124
+ return elements as any;
125
+ }
126
+
127
+ // Init ids
128
+ let ids = [];
129
+
130
+ // Process non array
131
+ if (!Array.isArray(elements)) {
132
+ return getStringId(elements);
133
+ }
134
+
135
+ // Process array
136
+ for (const element of elements) {
137
+ if (Array.isArray(element)) {
138
+ if (deep) {
139
+ ids = ids.concat(getStringIds(element, { deep }));
140
+ }
141
+ } else {
142
+ const id = getStringId(element);
143
+ if (id) {
144
+ ids.push(id);
145
+ }
146
+ }
147
+ }
148
+
149
+ // Return (unique) ID array
150
+ return unique ? _.uniq(ids) : ids;
151
+ }
152
+
153
+ // =====================================================================================================================
154
+ // Not exported helper functions
155
+ // =====================================================================================================================
156
+
157
+ /**
158
+ * Get ID of element as string
159
+ */
160
+ function getStringId(element: any): string {
161
+ // Check element
162
+ if (!element) {
163
+ return element;
164
+ }
165
+
166
+ // Buffer handling
167
+ if (element instanceof Buffer) {
168
+ return element.toString();
169
+ }
170
+
171
+ // String handling
172
+ if (typeof element === 'string') {
173
+ return element;
174
+ }
175
+
176
+ // Object handling
177
+ if (typeof element === 'object') {
178
+ if (element instanceof Types.ObjectId) {
179
+ return element.toHexString();
180
+ }
181
+
182
+ if (element.id) {
183
+ if (element.id instanceof Buffer && element.toHexString) {
184
+ return element.toHexString();
185
+ }
186
+ return getStringId(element.id);
187
+ } else if (element._id) {
188
+ return getStringId(element._id);
189
+ }
190
+ }
191
+
192
+ // Other types
193
+ if (typeof element.toString === 'function') {
194
+ return element.toString();
195
+ }
196
+
197
+ return undefined;
198
+ }
@@ -3,16 +3,23 @@ import { plainToInstance } from 'class-transformer';
3
3
  import { validate } from 'class-validator';
4
4
  import { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions';
5
5
  import { Kind } from 'graphql/index';
6
- import * as inspector from 'inspector';
7
- import _ = require('lodash');
8
- import rfdc = require('rfdc');
9
- import * as util from 'util';
10
6
 
11
7
  import { checkRestricted } from '../decorators/restricted.decorator';
12
8
  import { ProcessType } from '../enums/process-type.enum';
13
9
  import { RoleEnum } from '../enums/role.enum';
10
+ import { clone } from './clone.helper';
14
11
  import { merge } from './config.helper';
15
- import { equalIds } from './db.helper';
12
+ import { equalIds } from './id.helper';
13
+
14
+ /**
15
+ * `clone` and `deepFreeze` moved to `./clone.helper` and are re-exported here so every existing
16
+ * import path keeps working. They were extracted because `config.service` needs `clone`/`deepFreeze`,
17
+ * and pulling them from HERE put it on a runtime import cycle
18
+ * (restricted.decorator → core-tenant.helpers → config.service → input.helper → restricted.decorator)
19
+ * that is one top-level line away from an SWC temporal-dead-zone crash. `clone.helper` imports no
20
+ * framework code, so it can never be part of a cycle. Do NOT move them back.
21
+ */
22
+ export { clone, deepFreeze } from './clone.helper';
16
23
 
17
24
  /**
18
25
  * Helper class for inputs
@@ -341,68 +348,6 @@ export function checkAndGetDate(input: any): Date {
341
348
  return date;
342
349
  }
343
350
 
344
- /**
345
- * Clone object
346
- * @param object Any object
347
- * @param options Finetuning of rfdc cloning
348
- * @param options.checkResult Whether to compare object and cloned object via JSON.stringify and try alternative cloning
349
- * methods if they are not equal
350
- * @param options.circles Keeping track of circular references will slow down performance with an additional 25% overhead.
351
- * Even if an object doesn't have any circular references, the tracking overhead is the cost.
352
- * By default if an object with a circular reference is passed to rfdc, it will throw
353
- * (similar to how JSON.stringify would throw). Use the circles option to detect and preserve
354
- * circular references in the object. If performance is important, try removing the circular
355
- * reference from the object (set to undefined) and then add it back manually after cloning
356
- * instead of using this option.
357
- * @param options.debug Whether to shoe console.debug messages
358
- * @param options.proto Copy prototype properties as well as own properties into the new object.
359
- * It's marginally faster to allow enumerable properties on the prototype to be copied into the
360
- * cloned object (not onto it's prototype, directly onto the object).
361
- */
362
- export function clone(object: any, options?: { checkResult?: boolean; circles?: boolean; proto?: boolean }) {
363
- const config = {
364
- checkResult: true,
365
- circles: true,
366
- debug: inspector.url() !== undefined,
367
- proto: false,
368
- ...options,
369
- };
370
-
371
- try {
372
- const cloned = rfdc(config)(object);
373
- if (config.checkResult && !util.isDeepStrictEqual(object, cloned)) {
374
- throw new Error('Cloned object differs from original object');
375
- }
376
- return cloned;
377
- } catch (e) {
378
- if (!config.circles) {
379
- if (config.debug) {
380
- console.debug(e, config, object, 'automatic try to use rfdc with circles');
381
- }
382
- try {
383
- const clonedWithCircles = rfdc({
384
- ...config,
385
- circles: true,
386
- })(object);
387
- if (config.checkResult && !util.isDeepStrictEqual(object, clonedWithCircles)) {
388
- throw new Error('Cloned object differs from original object', { cause: e });
389
- }
390
- return clonedWithCircles;
391
- } catch (innerError) {
392
- if (config.debug) {
393
- console.debug(innerError, 'rfcd with circles did not work => automatic use of _.clone!');
394
- }
395
- return _.cloneDeep(object);
396
- }
397
- } else {
398
- if (config.debug) {
399
- console.debug(e, config, object, 'automatic try to use _.clone instead rfdc');
400
- }
401
- return _.cloneDeep(object);
402
- }
403
- }
404
- }
405
-
406
351
  /**
407
352
  * Combines objects to a new single plain object and ignores undefined
408
353
  */
@@ -410,23 +355,6 @@ export function combinePlain(...args: Record<any, any>[]): any {
410
355
  return assignPlain({}, ...args);
411
356
  }
412
357
 
413
- /**
414
- * Get deep frozen object
415
- */
416
- export function deepFreeze(object: any, visited: WeakSet<object> = new WeakSet()) {
417
- if (!object || typeof object !== 'object') {
418
- return object;
419
- }
420
- if (visited.has(object)) {
421
- return object;
422
- }
423
- visited.add(object);
424
- for (const [key, value] of Object.entries(object)) {
425
- object[key] = deepFreeze(value, visited);
426
- }
427
- return Object.freeze(object);
428
- }
429
-
430
358
  /**
431
359
  * Standard error function
432
360
  */