@bairock/lenz 0.0.15 → 0.0.17

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 (134) hide show
  1. package/README.md +195 -19
  2. package/dist/cli/commands/generate/crud.d.ts +3 -0
  3. package/dist/cli/commands/generate/crud.d.ts.map +1 -0
  4. package/dist/cli/commands/generate/crud.js +123 -0
  5. package/dist/cli/commands/generate/crud.js.map +1 -0
  6. package/dist/cli/commands/generate/index.d.ts +3 -0
  7. package/dist/cli/commands/generate/index.d.ts.map +1 -0
  8. package/dist/cli/commands/generate/index.js +8 -0
  9. package/dist/cli/commands/generate/index.js.map +1 -0
  10. package/dist/cli/commands/generate/orm.d.ts +3 -0
  11. package/dist/cli/commands/generate/orm.d.ts.map +1 -0
  12. package/dist/cli/commands/generate/orm.js +107 -0
  13. package/dist/cli/commands/generate/orm.js.map +1 -0
  14. package/dist/cli/commands/generate.d.ts.map +1 -1
  15. package/dist/cli/commands/generate.js +34 -8
  16. package/dist/cli/commands/generate.js.map +1 -1
  17. package/dist/cli/commands/init.d.ts.map +1 -1
  18. package/dist/cli/commands/init.js +0 -2
  19. package/dist/cli/commands/init.js.map +1 -1
  20. package/dist/cli/index.js +1 -3
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/config/index.d.ts +4 -6
  23. package/dist/config/index.d.ts.map +1 -1
  24. package/dist/config/index.js +24 -3
  25. package/dist/config/index.js.map +1 -1
  26. package/dist/engine/CodeGenerator.d.ts +8 -28
  27. package/dist/engine/CodeGenerator.d.ts.map +1 -1
  28. package/dist/engine/CodeGenerator.js +28 -1969
  29. package/dist/engine/CodeGenerator.js.map +1 -1
  30. package/dist/engine/GraphQLParseHelpers.d.ts +25 -0
  31. package/dist/engine/GraphQLParseHelpers.d.ts.map +1 -0
  32. package/dist/engine/GraphQLParseHelpers.js +128 -0
  33. package/dist/engine/GraphQLParseHelpers.js.map +1 -0
  34. package/dist/engine/GraphQLParser.d.ts +23 -10
  35. package/dist/engine/GraphQLParser.d.ts.map +1 -1
  36. package/dist/engine/GraphQLParser.js +154 -240
  37. package/dist/engine/GraphQLParser.js.map +1 -1
  38. package/dist/engine/GraphQLRelationAnalyzer.d.ts +10 -0
  39. package/dist/engine/GraphQLRelationAnalyzer.d.ts.map +1 -0
  40. package/dist/engine/GraphQLRelationAnalyzer.js +117 -0
  41. package/dist/engine/GraphQLRelationAnalyzer.js.map +1 -0
  42. package/dist/engine/LenzEngine.d.ts +1 -1
  43. package/dist/engine/LenzEngine.d.ts.map +1 -1
  44. package/dist/engine/LenzEngine.js +33 -13
  45. package/dist/engine/LenzEngine.js.map +1 -1
  46. package/dist/engine/SchemaRelationValidator.d.ts +15 -0
  47. package/dist/engine/SchemaRelationValidator.d.ts.map +1 -0
  48. package/dist/engine/SchemaRelationValidator.js +133 -0
  49. package/dist/engine/SchemaRelationValidator.js.map +1 -0
  50. package/dist/engine/SchemaValidator.d.ts +11 -10
  51. package/dist/engine/SchemaValidator.d.ts.map +1 -1
  52. package/dist/engine/SchemaValidator.js +151 -169
  53. package/dist/engine/SchemaValidator.js.map +1 -1
  54. package/dist/engine/directives.d.ts +10 -0
  55. package/dist/engine/directives.d.ts.map +1 -1
  56. package/dist/engine/directives.js +152 -6
  57. package/dist/engine/directives.js.map +1 -1
  58. package/dist/engine/generators/ClientGenerator.d.ts +7 -0
  59. package/dist/engine/generators/ClientGenerator.d.ts.map +1 -0
  60. package/dist/engine/generators/ClientGenerator.js +386 -0
  61. package/dist/engine/generators/ClientGenerator.js.map +1 -0
  62. package/dist/engine/generators/CrudModuleGenerator.d.ts +10 -0
  63. package/dist/engine/generators/CrudModuleGenerator.d.ts.map +1 -0
  64. package/dist/engine/generators/CrudModuleGenerator.js +141 -0
  65. package/dist/engine/generators/CrudModuleGenerator.js.map +1 -0
  66. package/dist/engine/generators/DelegateGenerator.d.ts +9 -0
  67. package/dist/engine/generators/DelegateGenerator.d.ts.map +1 -0
  68. package/dist/engine/generators/DelegateGenerator.js +453 -0
  69. package/dist/engine/generators/DelegateGenerator.js.map +1 -0
  70. package/dist/engine/generators/DelegateHelpers.d.ts +7 -0
  71. package/dist/engine/generators/DelegateHelpers.d.ts.map +1 -0
  72. package/dist/engine/generators/DelegateHelpers.js +144 -0
  73. package/dist/engine/generators/DelegateHelpers.js.map +1 -0
  74. package/dist/engine/generators/DelegateRelations.d.ts +11 -0
  75. package/dist/engine/generators/DelegateRelations.d.ts.map +1 -0
  76. package/dist/engine/generators/DelegateRelations.js +794 -0
  77. package/dist/engine/generators/DelegateRelations.js.map +1 -0
  78. package/dist/engine/generators/DelegateTemplateBody.d.ts +8 -0
  79. package/dist/engine/generators/DelegateTemplateBody.d.ts.map +1 -0
  80. package/dist/engine/generators/DelegateTemplateBody.js +776 -0
  81. package/dist/engine/generators/DelegateTemplateBody.js.map +1 -0
  82. package/dist/engine/generators/GenerateRuntimeErrors.d.ts +2 -0
  83. package/dist/engine/generators/GenerateRuntimeErrors.d.ts.map +1 -0
  84. package/dist/engine/generators/GenerateRuntimeErrors.js +140 -0
  85. package/dist/engine/generators/GenerateRuntimeErrors.js.map +1 -0
  86. package/dist/engine/generators/GenerateRuntimeIndex.d.ts +2 -0
  87. package/dist/engine/generators/GenerateRuntimeIndex.d.ts.map +1 -0
  88. package/dist/engine/generators/GenerateRuntimeIndex.js +21 -0
  89. package/dist/engine/generators/GenerateRuntimeIndex.js.map +1 -0
  90. package/dist/engine/generators/GenerateRuntimeLogger.d.ts +2 -0
  91. package/dist/engine/generators/GenerateRuntimeLogger.d.ts.map +1 -0
  92. package/dist/engine/generators/GenerateRuntimeLogger.js +125 -0
  93. package/dist/engine/generators/GenerateRuntimeLogger.js.map +1 -0
  94. package/dist/engine/generators/GenerateRuntimePagination.d.ts +2 -0
  95. package/dist/engine/generators/GenerateRuntimePagination.d.ts.map +1 -0
  96. package/dist/engine/generators/GenerateRuntimePagination.js +159 -0
  97. package/dist/engine/generators/GenerateRuntimePagination.js.map +1 -0
  98. package/dist/engine/generators/GenerateRuntimeQuery.d.ts +2 -0
  99. package/dist/engine/generators/GenerateRuntimeQuery.d.ts.map +1 -0
  100. package/dist/engine/generators/GenerateRuntimeQuery.js +427 -0
  101. package/dist/engine/generators/GenerateRuntimeQuery.js.map +1 -0
  102. package/dist/engine/generators/GenerateRuntimeRelations.d.ts +2 -0
  103. package/dist/engine/generators/GenerateRuntimeRelations.d.ts.map +1 -0
  104. package/dist/engine/generators/GenerateRuntimeRelations.js +130 -0
  105. package/dist/engine/generators/GenerateRuntimeRelations.js.map +1 -0
  106. package/dist/engine/generators/RuntimeGenerator.d.ts +16 -0
  107. package/dist/engine/generators/RuntimeGenerator.d.ts.map +1 -0
  108. package/dist/engine/generators/RuntimeGenerator.js +16 -0
  109. package/dist/engine/generators/RuntimeGenerator.js.map +1 -0
  110. package/dist/engine/generators/SDLInputTypesGenerator.d.ts +6 -0
  111. package/dist/engine/generators/SDLInputTypesGenerator.d.ts.map +1 -0
  112. package/dist/engine/generators/SDLInputTypesGenerator.js +763 -0
  113. package/dist/engine/generators/SDLInputTypesGenerator.js.map +1 -0
  114. package/dist/engine/generators/TypeFilterTypes.d.ts +2 -0
  115. package/dist/engine/generators/TypeFilterTypes.d.ts.map +1 -0
  116. package/dist/engine/generators/TypeFilterTypes.js +220 -0
  117. package/dist/engine/generators/TypeFilterTypes.js.map +1 -0
  118. package/dist/engine/generators/TypeGenerator.d.ts +16 -0
  119. package/dist/engine/generators/TypeGenerator.d.ts.map +1 -0
  120. package/dist/engine/generators/TypeGenerator.js +493 -0
  121. package/dist/engine/generators/TypeGenerator.js.map +1 -0
  122. package/dist/engine/generators/helpers.d.ts +13 -0
  123. package/dist/engine/generators/helpers.d.ts.map +1 -0
  124. package/dist/engine/generators/helpers.js +316 -0
  125. package/dist/engine/generators/helpers.js.map +1 -0
  126. package/dist/errors/index.d.ts +3 -0
  127. package/dist/errors/index.d.ts.map +1 -1
  128. package/dist/errors/index.js +11 -1
  129. package/dist/errors/index.js.map +1 -1
  130. package/dist/index.d.ts +0 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +2 -4
  133. package/dist/index.js.map +1 -1
  134. package/package.json +10 -4
@@ -0,0 +1,794 @@
1
+ import { toCamelCase, toCollectionName } from './helpers.js';
2
+ export class DelegateRelations {
3
+ generateCountInIncludeCode(model) {
4
+ const lines = [];
5
+ for (const rel of model.relations) {
6
+ const targetCamel = toCamelCase(rel.target);
7
+ if (rel.type === 'manyToOne' || rel.type === 'oneToOne') {
8
+ if (rel.foreignKey) {
9
+ lines.push(` if (relField === '${rel.field}') {`);
10
+ lines.push(` counts['${rel.field}'] = document.${rel.foreignKey} ? 1 : 0`);
11
+ lines.push(` }`);
12
+ }
13
+ }
14
+ else if (rel.isForeignKeyArray) {
15
+ lines.push(` if (relField === '${rel.field}') {`);
16
+ lines.push(` if (document.${rel.foreignKey} && Array.isArray(document.${rel.foreignKey}) && document.${rel.foreignKey}.length > 0) {`);
17
+ lines.push(` counts['${rel.field}'] = await this.client.${targetCamel}.count({ where: { id: { in: document.${rel.foreignKey} } } });`);
18
+ lines.push(` } else {`);
19
+ lines.push(` counts['${rel.field}'] = 0`);
20
+ lines.push(` }`);
21
+ lines.push(` }`);
22
+ }
23
+ else if (rel.foreignKey && rel.type === 'manyToMany' && rel.joinCollection) {
24
+ lines.push(` if (relField === '${rel.field}') {`);
25
+ lines.push(` counts['${rel.field}'] = await RelationResolver.countManyToMany(`);
26
+ lines.push(` this.client.$db,`);
27
+ lines.push(` '${rel.joinCollection}',`);
28
+ lines.push(` '${toCamelCase(model.name)}Id',`);
29
+ lines.push(` document.id`);
30
+ lines.push(` );`);
31
+ lines.push(` }`);
32
+ }
33
+ else if (rel.foreignKey) {
34
+ lines.push(` if (relField === '${rel.field}') {`);
35
+ lines.push(` counts['${rel.field}'] = await this.client.${targetCamel}.count({ where: { ${rel.foreignKey}: document.id } });`);
36
+ lines.push(` }`);
37
+ }
38
+ }
39
+ return lines.join('\n');
40
+ }
41
+ generateRelationInclusionCode(model) {
42
+ const lines = [];
43
+ for (const relation of model.relations) {
44
+ if (relation.strategy === 'lookup') {
45
+ lines.push(` // Relation: ${relation.field} (${relation.type}) - lookup strategy`);
46
+ lines.push(` if (include.${relation.field} !== undefined) {`);
47
+ lines.push(` const includeOpts = include.${relation.field};`);
48
+ lines.push(` let whereFilter = {};`);
49
+ lines.push(` let sort = null;`);
50
+ lines.push(` let skip = null;`);
51
+ lines.push(` let limit = null;`);
52
+ lines.push(` let select = undefined;`);
53
+ lines.push(` let nestedInclude = undefined;`);
54
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
55
+ lines.push(` whereFilter = includeOpts.where ? QueryBuilder.buildWhere(includeOpts.where) : {};`);
56
+ lines.push(` sort = includeOpts.orderBy ? QueryBuilder.buildSort(includeOpts.orderBy) : null;`);
57
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
58
+ lines.push(` limit = includeOpts.take !== undefined ? includeOpts.take : null;`);
59
+ lines.push(` select = includeOpts.select;`);
60
+ lines.push(` nestedInclude = includeOpts.include;`);
61
+ lines.push(` }`);
62
+ const localField = relation.foreignKey;
63
+ if (!localField) {
64
+ if (relation.type === 'manyToMany' && relation.joinCollection) {
65
+ lines.push(` let ${relation.field}Result = await RelationResolver.resolveManyToMany(`);
66
+ lines.push(` this.client.$db,`);
67
+ lines.push(` '${model.collectionName}',`);
68
+ lines.push(` '${toCollectionName(relation.target)}',`);
69
+ lines.push(` '${relation.joinCollection}',`);
70
+ lines.push(` '${toCamelCase(model.name)}Id',`);
71
+ lines.push(` '${toCamelCase(relation.target)}Id',`);
72
+ lines.push(` document.id,`);
73
+ lines.push(` whereFilter,`);
74
+ lines.push(` sort,`);
75
+ lines.push(` limit,`);
76
+ lines.push(` skip,`);
77
+ lines.push(` select`);
78
+ lines.push(` )`);
79
+ lines.push(` if (nestedInclude && ${relation.field}Result && Array.isArray(${relation.field}Result)) {`);
80
+ lines.push(` ${relation.field}Result = await Promise.all(${relation.field}Result.map(doc =>`);
81
+ lines.push(` this.client.${toCamelCase(relation.target)}.includeRelations(doc, nestedInclude)`);
82
+ lines.push(` ))`);
83
+ lines.push(` }`);
84
+ lines.push(` result.${relation.field} = ${relation.field}Result`);
85
+ }
86
+ else {
87
+ lines.push(` console.warn('lookup strategy not implemented for relation ${relation.field} without foreign key')`);
88
+ lines.push(` result.${relation.field} = []`);
89
+ }
90
+ }
91
+ else if (relation.isForeignKeyArray) {
92
+ lines.push(` const innerPipeline = [`);
93
+ lines.push(` {`);
94
+ lines.push(` $match: {`);
95
+ lines.push(` $expr: {`);
96
+ lines.push(` $in: [`);
97
+ lines.push(` '$_id',`);
98
+ lines.push(` {`);
99
+ lines.push(` $map: {`);
100
+ lines.push(` input: { $ifNull: ['$$ids', []] },`);
101
+ lines.push(` as: 'id',`);
102
+ lines.push(` in: { $convert: { input: '$$id', to: 'objectId', onError: null } }`);
103
+ lines.push(` }`);
104
+ lines.push(` }`);
105
+ lines.push(` ]`);
106
+ lines.push(` }`);
107
+ lines.push(` }`);
108
+ lines.push(` }`);
109
+ lines.push(` ];`);
110
+ lines.push(` if (whereFilter && Object.keys(whereFilter).length > 0) {`);
111
+ lines.push(` innerPipeline.push({ $match: whereFilter });`);
112
+ lines.push(` }`);
113
+ lines.push(` if (sort) {`);
114
+ lines.push(` innerPipeline.push({ $sort: sort });`);
115
+ lines.push(` }`);
116
+ lines.push(` if (skip !== null) {`);
117
+ lines.push(` innerPipeline.push({ $skip: skip });`);
118
+ lines.push(` }`);
119
+ lines.push(` if (limit !== null) {`);
120
+ lines.push(` innerPipeline.push({ $limit: limit });`);
121
+ lines.push(` }`);
122
+ lines.push(` const pipeline = [`);
123
+ lines.push(` { $match: { _id: document._id } },`);
124
+ lines.push(` { $lookup: {`);
125
+ lines.push(` from: '${toCollectionName(relation.target)}',`);
126
+ lines.push(` let: { ids: '$${localField}' },`);
127
+ lines.push(` pipeline: innerPipeline,`);
128
+ lines.push(` as: '${relation.field}_lookup'`);
129
+ lines.push(` } }`);
130
+ lines.push(` ];`);
131
+ lines.push(` const aggResult = await this.collection.aggregate(pipeline).toArray()`);
132
+ lines.push(` if (aggResult.length > 0) {`);
133
+ lines.push(` let ${relation.field}Result = aggResult[0].${relation.field}_lookup`);
134
+ lines.push(` if (nestedInclude && ${relation.field}Result) {`);
135
+ lines.push(` if (Array.isArray(${relation.field}Result)) {`);
136
+ lines.push(` ${relation.field}Result = await Promise.all(${relation.field}Result.map(doc =>`);
137
+ lines.push(` this.client.${toCamelCase(relation.target)}.includeRelations(doc, nestedInclude)`);
138
+ lines.push(` ))`);
139
+ lines.push(` } else {`);
140
+ lines.push(` ${relation.field}Result = await this.client.${toCamelCase(relation.target)}.includeRelations(${relation.field}Result, nestedInclude)`);
141
+ lines.push(` }`);
142
+ lines.push(` }`);
143
+ lines.push(` result.${relation.field} = ${relation.field}Result`);
144
+ lines.push(` } else {`);
145
+ lines.push(` result.${relation.field} = []`);
146
+ lines.push(` }`);
147
+ }
148
+ else if (relation.foreignKeyLocation === 'target') {
149
+ // FK in target (oneToMany) — use source _id and match FK in target
150
+ lines.push(` const innerPipeline = [`);
151
+ lines.push(` {`);
152
+ lines.push(` $match: {`);
153
+ lines.push(` $expr: {`);
154
+ lines.push(` $eq: [`);
155
+ lines.push(` '$${localField}',`);
156
+ lines.push(` { $convert: { input: '$$sourceId', to: 'objectId', onError: null } }`);
157
+ lines.push(` ]`);
158
+ lines.push(` }`);
159
+ lines.push(` }`);
160
+ lines.push(` }`);
161
+ lines.push(` ];`);
162
+ lines.push(` if (whereFilter && Object.keys(whereFilter).length > 0) {`);
163
+ lines.push(` innerPipeline.push({ $match: whereFilter });`);
164
+ lines.push(` }`);
165
+ lines.push(` if (sort) {`);
166
+ lines.push(` innerPipeline.push({ $sort: sort });`);
167
+ lines.push(` }`);
168
+ lines.push(` if (skip !== null) {`);
169
+ lines.push(` innerPipeline.push({ $skip: skip });`);
170
+ lines.push(` }`);
171
+ lines.push(` if (limit !== null) {`);
172
+ lines.push(` innerPipeline.push({ $limit: limit });`);
173
+ lines.push(` }`);
174
+ lines.push(` const pipeline = [`);
175
+ lines.push(` { $match: { _id: document._id } },`);
176
+ lines.push(` { $lookup: {`);
177
+ lines.push(` from: '${toCollectionName(relation.target)}',`);
178
+ lines.push(` let: { sourceId: '$_id' },`);
179
+ lines.push(` pipeline: innerPipeline,`);
180
+ lines.push(` as: '${relation.field}_lookup'`);
181
+ lines.push(` } }`);
182
+ lines.push(` ];`);
183
+ }
184
+ else {
185
+ // FK in source (manyToOne, oneToOne) — use FK value and match target _id
186
+ lines.push(` const innerPipeline = [`);
187
+ lines.push(` {`);
188
+ lines.push(` $match: {`);
189
+ lines.push(` $expr: {`);
190
+ lines.push(` $eq: [`);
191
+ lines.push(` '$_id',`);
192
+ lines.push(` { $convert: { input: '$$localId', to: 'objectId', onError: null } }`);
193
+ lines.push(` ]`);
194
+ lines.push(` }`);
195
+ lines.push(` }`);
196
+ lines.push(` }`);
197
+ lines.push(` ];`);
198
+ lines.push(` if (whereFilter && Object.keys(whereFilter).length > 0) {`);
199
+ lines.push(` innerPipeline.push({ $match: whereFilter });`);
200
+ lines.push(` }`);
201
+ lines.push(` if (sort) {`);
202
+ lines.push(` innerPipeline.push({ $sort: sort });`);
203
+ lines.push(` }`);
204
+ lines.push(` if (skip !== null) {`);
205
+ lines.push(` innerPipeline.push({ $skip: skip });`);
206
+ lines.push(` }`);
207
+ lines.push(` if (limit !== null) {`);
208
+ lines.push(` innerPipeline.push({ $limit: limit });`);
209
+ lines.push(` }`);
210
+ lines.push(` const pipeline = [`);
211
+ lines.push(` { $match: { _id: document._id } },`);
212
+ lines.push(` { $lookup: {`);
213
+ lines.push(` from: '${toCollectionName(relation.target)}',`);
214
+ lines.push(` let: { localId: '$${localField}' },`);
215
+ lines.push(` pipeline: innerPipeline,`);
216
+ lines.push(` as: '${relation.field}_lookup'`);
217
+ lines.push(` } }`);
218
+ lines.push(` ];`);
219
+ }
220
+ const needUnwind = relation.type === 'oneToOne' || relation.type === 'manyToOne';
221
+ if (needUnwind) {
222
+ lines.push(` pipeline.push({ $unwind: { path: '$${relation.field}_lookup', preserveNullAndEmptyArrays: true } });`);
223
+ }
224
+ lines.push(` const aggResult = await this.collection.aggregate(pipeline).toArray()`);
225
+ lines.push(` if (aggResult.length > 0) {`);
226
+ lines.push(` let ${relation.field}Result = aggResult[0].${relation.field}_lookup`);
227
+ lines.push(` if (nestedInclude && ${relation.field}Result) {`);
228
+ lines.push(` if (Array.isArray(${relation.field}Result)) {`);
229
+ lines.push(` ${relation.field}Result = await Promise.all(${relation.field}Result.map(doc =>`);
230
+ lines.push(` this.client.${toCamelCase(relation.target)}.includeRelations(doc, nestedInclude)`);
231
+ lines.push(` ))`);
232
+ lines.push(` } else {`);
233
+ lines.push(` ${relation.field}Result = await this.client.${toCamelCase(relation.target)}.includeRelations(${relation.field}Result, nestedInclude)`);
234
+ lines.push(` }`);
235
+ lines.push(` }`);
236
+ lines.push(` result.${relation.field} = ${relation.field}Result`);
237
+ lines.push(` } else {`);
238
+ lines.push(` result.${relation.field} = ${needUnwind ? 'null' : '[]'}`);
239
+ lines.push(` }`);
240
+ lines.push(` }`);
241
+ }
242
+ else {
243
+ switch (relation.type) {
244
+ case 'oneToMany':
245
+ lines.push(` // Relation: ${relation.field} (oneToMany) - populate strategy`);
246
+ lines.push(` if (include.${relation.field} !== undefined) {`);
247
+ lines.push(` const includeOpts = include.${relation.field};`);
248
+ lines.push(` let whereFilter = {};`);
249
+ lines.push(` let orderBy = null;`);
250
+ lines.push(` let take = null;`);
251
+ lines.push(` let skip = null;`);
252
+ lines.push(` let select = undefined;`);
253
+ lines.push(` let nestedInclude = undefined;`);
254
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
255
+ lines.push(` whereFilter = includeOpts.where || {};`);
256
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
257
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
258
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
259
+ lines.push(` select = includeOpts.select;`);
260
+ lines.push(` nestedInclude = includeOpts.include;`);
261
+ lines.push(` }`);
262
+ lines.push(` if (document.${relation.foreignKey} && Array.isArray(document.${relation.foreignKey})) {`);
263
+ lines.push(` const baseWhere = { id: { in: document.${relation.foreignKey} } };`);
264
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
265
+ lines.push(` const ${relation.field} = await this.client.${toCamelCase(relation.target)}.findMany({`);
266
+ lines.push(` where: finalWhere,`);
267
+ lines.push(` orderBy: orderBy,`);
268
+ lines.push(` take: take,`);
269
+ lines.push(` skip: skip,`);
270
+ lines.push(` select: select,`);
271
+ lines.push(` include: nestedInclude`);
272
+ lines.push(` })`);
273
+ lines.push(` result.${relation.field} = ${relation.field}`);
274
+ if (relation.foreignKeyLocation === 'target') {
275
+ lines.push(` } else if (document.id) {`);
276
+ lines.push(` const baseWhere = { ${relation.foreignKey}: document.id };`);
277
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
278
+ lines.push(` const ${relation.field} = await this.client.${toCamelCase(relation.target)}.findMany({`);
279
+ lines.push(` where: finalWhere,`);
280
+ lines.push(` orderBy: orderBy,`);
281
+ lines.push(` take: take,`);
282
+ lines.push(` skip: skip,`);
283
+ lines.push(` select: select,`);
284
+ lines.push(` include: nestedInclude`);
285
+ lines.push(` })`);
286
+ lines.push(` result.${relation.field} = ${relation.field}`);
287
+ lines.push(` } else {`);
288
+ lines.push(` result.${relation.field} = []`);
289
+ lines.push(` }`);
290
+ }
291
+ else {
292
+ lines.push(` } else {`);
293
+ lines.push(` result.${relation.field} = []`);
294
+ lines.push(` }`);
295
+ }
296
+ lines.push(` }`);
297
+ break;
298
+ case 'manyToOne':
299
+ lines.push(` // Relation: ${relation.field} (manyToOne) - populate strategy`);
300
+ lines.push(` if (include.${relation.field} !== undefined && document.${relation.foreignKey}) {`);
301
+ lines.push(` const includeOpts = include.${relation.field};`);
302
+ lines.push(` let whereFilter = {};`);
303
+ lines.push(` let select = undefined;`);
304
+ lines.push(` let nestedInclude = undefined;`);
305
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
306
+ lines.push(` whereFilter = includeOpts.where || {};`);
307
+ lines.push(` select = includeOpts.select;`);
308
+ lines.push(` nestedInclude = includeOpts.include;`);
309
+ lines.push(` }`);
310
+ lines.push(` const baseWhere = { id: document.${relation.foreignKey} };`);
311
+ lines.push(` const hasWhere = Object.keys(whereFilter).length > 0;`);
312
+ lines.push(` const finalWhere = hasWhere ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
313
+ lines.push(` const findFn = hasWhere ? 'findFirst' : 'findUnique';`);
314
+ lines.push(` const ${relation.field} = await this.client.${toCamelCase(relation.target)}[findFn]({`);
315
+ lines.push(` where: finalWhere,`);
316
+ lines.push(` select: select,`);
317
+ lines.push(` include: nestedInclude`);
318
+ lines.push(` })`);
319
+ lines.push(` result.${relation.field} = ${relation.field}`);
320
+ lines.push(` }`);
321
+ break;
322
+ case 'oneToOne':
323
+ lines.push(` // Relation: ${relation.field} (oneToOne) - populate strategy`);
324
+ lines.push(` if (include.${relation.field} !== undefined && document.${relation.foreignKey}) {`);
325
+ lines.push(` const includeOpts = include.${relation.field};`);
326
+ lines.push(` let whereFilter = {};`);
327
+ lines.push(` let select = undefined;`);
328
+ lines.push(` let nestedInclude = undefined;`);
329
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
330
+ lines.push(` whereFilter = includeOpts.where || {};`);
331
+ lines.push(` select = includeOpts.select;`);
332
+ lines.push(` nestedInclude = includeOpts.include;`);
333
+ lines.push(` }`);
334
+ lines.push(` const baseWhere = { id: document.${relation.foreignKey} };`);
335
+ lines.push(` const hasWhere = Object.keys(whereFilter).length > 0;`);
336
+ lines.push(` const finalWhere = hasWhere ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
337
+ lines.push(` const findFn = hasWhere ? 'findFirst' : 'findUnique';`);
338
+ lines.push(` const ${relation.field} = await this.client.${toCamelCase(relation.target)}[findFn]({`);
339
+ lines.push(` where: finalWhere,`);
340
+ lines.push(` select: select,`);
341
+ lines.push(` include: nestedInclude`);
342
+ lines.push(` })`);
343
+ lines.push(` result.${relation.field} = ${relation.field}`);
344
+ lines.push(` }`);
345
+ break;
346
+ case 'manyToMany':
347
+ lines.push(` // Relation: ${relation.field} (manyToMany) - populate strategy`);
348
+ lines.push(` if (include.${relation.field} !== undefined) {`);
349
+ lines.push(` const includeOpts = include.${relation.field};`);
350
+ lines.push(` let whereFilter = {};`);
351
+ lines.push(` let orderBy = null;`);
352
+ lines.push(` let take = null;`);
353
+ lines.push(` let skip = null;`);
354
+ lines.push(` let select = undefined;`);
355
+ lines.push(` let nestedInclude = undefined;`);
356
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
357
+ lines.push(` whereFilter = includeOpts.where || {};`);
358
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
359
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
360
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
361
+ lines.push(` select = includeOpts.select;`);
362
+ lines.push(` nestedInclude = includeOpts.include;`);
363
+ lines.push(` }`);
364
+ if (relation.foreignKey) {
365
+ lines.push(` if (document.${relation.foreignKey} && Array.isArray(document.${relation.foreignKey})) {`);
366
+ lines.push(` const baseWhere = { id: { in: document.${relation.foreignKey} } };`);
367
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
368
+ lines.push(` const ${relation.field} = await this.client.${toCamelCase(relation.target)}.findMany({`);
369
+ lines.push(` where: finalWhere,`);
370
+ lines.push(` orderBy: orderBy,`);
371
+ lines.push(` take: take,`);
372
+ lines.push(` skip: skip,`);
373
+ lines.push(` select: select,`);
374
+ lines.push(` include: nestedInclude`);
375
+ lines.push(` })`);
376
+ lines.push(` result.${relation.field} = ${relation.field}`);
377
+ lines.push(` } else {`);
378
+ lines.push(` result.${relation.field} = []`);
379
+ lines.push(` }`);
380
+ }
381
+ else if (relation.joinCollection) {
382
+ lines.push(` let mongoWhere = {};`);
383
+ lines.push(` let mongoSort = null;`);
384
+ lines.push(` if (Object.keys(whereFilter).length > 0) {`);
385
+ lines.push(` mongoWhere = QueryBuilder.buildWhere(whereFilter);`);
386
+ lines.push(` }`);
387
+ lines.push(` if (orderBy) {`);
388
+ lines.push(` mongoSort = QueryBuilder.buildSort(orderBy);`);
389
+ lines.push(` }`);
390
+ lines.push(` const ${relation.field} = await RelationResolver.resolveManyToMany(`);
391
+ lines.push(` this.client.$db,`);
392
+ lines.push(` '${model.collectionName}',`);
393
+ lines.push(` '${toCollectionName(relation.target)}',`);
394
+ lines.push(` '${relation.joinCollection}',`);
395
+ lines.push(` '${toCamelCase(model.name)}Id',`);
396
+ lines.push(` '${toCamelCase(relation.target)}Id',`);
397
+ lines.push(` document.id,`);
398
+ lines.push(` mongoWhere,`);
399
+ lines.push(` mongoSort,`);
400
+ lines.push(` take,`);
401
+ lines.push(` skip,`);
402
+ lines.push(` select`);
403
+ lines.push(` )`);
404
+ lines.push(` result.${relation.field} = ${relation.field}`);
405
+ }
406
+ else {
407
+ lines.push(` console.warn('manyToMany relation ${relation.field} has no foreign key or join collection')`);
408
+ lines.push(` result.${relation.field} = []`);
409
+ }
410
+ lines.push(` }`);
411
+ break;
412
+ }
413
+ }
414
+ }
415
+ if (lines.length === 0)
416
+ return '';
417
+ return lines.join('\n');
418
+ }
419
+ /**
420
+ * Generate batch relation inclusion code — replaces N+1 queries with batched queries.
421
+ * Collects all FK values across documents, queries once with $in, and maps results back.
422
+ */
423
+ generateBatchInclusionCode(model) {
424
+ const lines = [];
425
+ const populateRelations = model.relations.filter(r => r.strategy !== 'lookup');
426
+ const lookupRelations = model.relations.filter(r => r.strategy === 'lookup' && (r.foreignKey || r.joinCollection));
427
+ if (populateRelations.length === 0 && lookupRelations.length === 0)
428
+ return '';
429
+ lines.push(` const batchResult = { documents: documents.map(d => ({ ...d })) };`);
430
+ lines.push(` const docs = batchResult.documents;`);
431
+ lines.push(``);
432
+ for (const relation of populateRelations) {
433
+ const targetCamel = toCamelCase(relation.target);
434
+ const localField = relation.foreignKey;
435
+ switch (relation.type) {
436
+ case 'manyToOne':
437
+ case 'oneToOne': {
438
+ // Collect all FK values, batch query, assign back
439
+ lines.push(` // Batch: ${relation.field} (${relation.type})`);
440
+ lines.push(` if (include.${relation.field} !== undefined) {`);
441
+ lines.push(` const includeOpts = include.${relation.field};`);
442
+ lines.push(` let whereFilter = {};`);
443
+ lines.push(` let select = undefined;`);
444
+ lines.push(` let nestedInclude = undefined;`);
445
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
446
+ lines.push(` whereFilter = includeOpts.where || {};`);
447
+ lines.push(` select = includeOpts.select;`);
448
+ lines.push(` nestedInclude = includeOpts.include;`);
449
+ lines.push(` }`);
450
+ lines.push(` const fkValues = docs.map(d => d.${localField}).filter(Boolean);`);
451
+ lines.push(` if (fkValues.length > 0) {`);
452
+ lines.push(` const uniqueFks = [...new Set(fkValues)];`);
453
+ lines.push(` const baseWhere = { id: { in: uniqueFks } };`);
454
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
455
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({ where: finalWhere, select, include: nestedInclude });`);
456
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
457
+ lines.push(` for (const doc of docs) {`);
458
+ lines.push(` doc.${relation.field} = doc.${localField} ? (relatedMap.get(doc.${localField}) || null) : null;`);
459
+ lines.push(` }`);
460
+ lines.push(` } else {`);
461
+ lines.push(` for (const doc of docs) { doc.${relation.field} = null; }`);
462
+ lines.push(` }`);
463
+ lines.push(` }`);
464
+ break;
465
+ }
466
+ case 'oneToMany': {
467
+ // FK is either array in source (lookup-like) or in target (populate)
468
+ if (relation.foreignKeyLocation === 'source' || relation.isForeignKeyArray) {
469
+ // FK array in source — batch by collecting all IDs from arrays
470
+ lines.push(` // Batch: ${relation.field} (oneToMany) - FK array in source`);
471
+ lines.push(` if (include.${relation.field} !== undefined) {`);
472
+ lines.push(` const includeOpts = include.${relation.field};`);
473
+ lines.push(` let whereFilter = {};`);
474
+ lines.push(` let orderBy = null;`);
475
+ lines.push(` let take = null;`);
476
+ lines.push(` let skip = null;`);
477
+ lines.push(` let select = undefined;`);
478
+ lines.push(` let nestedInclude = undefined;`);
479
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
480
+ lines.push(` whereFilter = includeOpts.where || {};`);
481
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
482
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
483
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
484
+ lines.push(` select = includeOpts.select;`);
485
+ lines.push(` nestedInclude = includeOpts.include;`);
486
+ lines.push(` }`);
487
+ lines.push(` const allIds = docs.flatMap(d => d.${localField} || []);`);
488
+ lines.push(` const uniqueIds = [...new Set(allIds)];`);
489
+ lines.push(` if (uniqueIds.length > 0) {`);
490
+ lines.push(` const baseWhere = { id: { in: uniqueIds } };`);
491
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
492
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({`);
493
+ lines.push(` where: finalWhere,`);
494
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
495
+ lines.push(` });`);
496
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
497
+ lines.push(` for (const doc of docs) {`);
498
+ lines.push(` doc.${relation.field} = (doc.${localField} || []).map((id: string) => relatedMap.get(id)).filter(Boolean);`);
499
+ lines.push(` }`);
500
+ lines.push(` } else {`);
501
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
502
+ lines.push(` }`);
503
+ lines.push(` }`);
504
+ }
505
+ else {
506
+ // FK in target — batch by collecting source IDs
507
+ lines.push(` // Batch: ${relation.field} (oneToMany) - FK in target`);
508
+ lines.push(` if (include.${relation.field} !== undefined) {`);
509
+ lines.push(` const includeOpts = include.${relation.field};`);
510
+ lines.push(` let whereFilter = {};`);
511
+ lines.push(` let orderBy = null;`);
512
+ lines.push(` let take = null;`);
513
+ lines.push(` let skip = null;`);
514
+ lines.push(` let select = undefined;`);
515
+ lines.push(` let nestedInclude = undefined;`);
516
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
517
+ lines.push(` whereFilter = includeOpts.where || {};`);
518
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
519
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
520
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
521
+ lines.push(` select = includeOpts.select;`);
522
+ lines.push(` nestedInclude = includeOpts.include;`);
523
+ lines.push(` }`);
524
+ lines.push(` const sourceIds = docs.map(d => d.id).filter(Boolean);`);
525
+ lines.push(` const uniqueSourceIds = [...new Set(sourceIds)];`);
526
+ lines.push(` if (uniqueSourceIds.length > 0) {`);
527
+ lines.push(` const baseWhere = { ${localField}: { in: uniqueSourceIds } };`);
528
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
529
+ lines.push(` let relatedDocs = await this.client.${targetCamel}.findMany({`);
530
+ lines.push(` where: finalWhere,`);
531
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
532
+ lines.push(` });`);
533
+ lines.push(` const groupedMap = new Map();`);
534
+ lines.push(` for (const rd of relatedDocs) {`);
535
+ lines.push(` const key = rd.${localField};`);
536
+ lines.push(` if (!groupedMap.has(key)) groupedMap.set(key, []);`);
537
+ lines.push(` groupedMap.get(key).push(rd);`);
538
+ lines.push(` }`);
539
+ lines.push(` for (const doc of docs) {`);
540
+ lines.push(` doc.${relation.field} = groupedMap.get(doc.id) || [];`);
541
+ lines.push(` }`);
542
+ lines.push(` } else {`);
543
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
544
+ lines.push(` }`);
545
+ lines.push(` }`);
546
+ }
547
+ break;
548
+ }
549
+ case 'manyToMany': {
550
+ if (localField && !relation.joinCollection) {
551
+ // FK array in source — same as oneToMany with FK array
552
+ lines.push(` // Batch: ${relation.field} (manyToMany) - FK array`);
553
+ lines.push(` if (include.${relation.field} !== undefined) {`);
554
+ lines.push(` const includeOpts = include.${relation.field};`);
555
+ lines.push(` let whereFilter = {};`);
556
+ lines.push(` let orderBy = null;`);
557
+ lines.push(` let take = null;`);
558
+ lines.push(` let skip = null;`);
559
+ lines.push(` let select = undefined;`);
560
+ lines.push(` let nestedInclude = undefined;`);
561
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
562
+ lines.push(` whereFilter = includeOpts.where || {};`);
563
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
564
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
565
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
566
+ lines.push(` select = includeOpts.select;`);
567
+ lines.push(` nestedInclude = includeOpts.include;`);
568
+ lines.push(` }`);
569
+ lines.push(` const allIds = docs.flatMap(d => d.${localField} || []);`);
570
+ lines.push(` const uniqueIds = [...new Set(allIds)];`);
571
+ lines.push(` if (uniqueIds.length > 0) {`);
572
+ lines.push(` const baseWhere = { id: { in: uniqueIds } };`);
573
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
574
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({`);
575
+ lines.push(` where: finalWhere,`);
576
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
577
+ lines.push(` });`);
578
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
579
+ lines.push(` for (const doc of docs) {`);
580
+ lines.push(` doc.${relation.field} = (doc.${localField} || []).map((id: string) => relatedMap.get(id)).filter(Boolean);`);
581
+ lines.push(` }`);
582
+ lines.push(` } else {`);
583
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
584
+ lines.push(` }`);
585
+ lines.push(` }`);
586
+ }
587
+ else if (relation.joinCollection) {
588
+ lines.push(` // Batch: ${relation.field} (manyToMany) - join collection`);
589
+ lines.push(` if (include.${relation.field} !== undefined) {`);
590
+ lines.push(` const includeOpts = include.${relation.field};`);
591
+ lines.push(` let whereFilter = {};`);
592
+ lines.push(` let orderBy = null;`);
593
+ lines.push(` let take = null;`);
594
+ lines.push(` let skip = null;`);
595
+ lines.push(` let select = undefined;`);
596
+ lines.push(` let nestedInclude = undefined;`);
597
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
598
+ lines.push(` whereFilter = includeOpts.where || {};`);
599
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
600
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
601
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
602
+ lines.push(` select = includeOpts.select;`);
603
+ lines.push(` nestedInclude = includeOpts.include;`);
604
+ lines.push(` }`);
605
+ lines.push(` const sourceIds = docs.map((d: any) => d.id).filter(Boolean);`);
606
+ lines.push(` const uniqueSourceIds = [...new Set(sourceIds)];`);
607
+ lines.push(` if (uniqueSourceIds.length > 0) {`);
608
+ lines.push(` const joinCol = this.client.$db.collection('${relation.joinCollection}');`);
609
+ lines.push(` const connections = await joinCol.find({`);
610
+ lines.push(` '${toCamelCase(model.name)}Id': { $in: uniqueSourceIds }`);
611
+ lines.push(` }).toArray();`);
612
+ lines.push(` const targetIds = [...new Set(connections.map((c: any) => c.${toCamelCase(relation.target)}Id).filter(Boolean))];`);
613
+ lines.push(` if (targetIds.length > 0) {`);
614
+ lines.push(` const baseWhere = { id: { in: targetIds } };`);
615
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
616
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({`);
617
+ lines.push(` where: finalWhere,`);
618
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
619
+ lines.push(` });`);
620
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
621
+ lines.push(` const connectionMap = new Map();`);
622
+ lines.push(` for (const conn of connections) {`);
623
+ lines.push(` const sid = conn.${toCamelCase(model.name)}Id;`);
624
+ lines.push(` if (!connectionMap.has(sid)) connectionMap.set(sid, []);`);
625
+ lines.push(` connectionMap.get(sid).push(conn.${toCamelCase(relation.target)}Id);`);
626
+ lines.push(` }`);
627
+ lines.push(` for (const doc of docs) {`);
628
+ lines.push(` const connIds = connectionMap.get(doc.id) || [];`);
629
+ lines.push(` doc.${relation.field} = connIds.map((id: string) => relatedMap.get(id)).filter(Boolean);`);
630
+ lines.push(` }`);
631
+ lines.push(` } else {`);
632
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
633
+ lines.push(` }`);
634
+ lines.push(` } else {`);
635
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
636
+ lines.push(` }`);
637
+ lines.push(` }`);
638
+ }
639
+ break;
640
+ }
641
+ }
642
+ }
643
+ // Lookup strategy batch — replaces N+1 per-document aggregation with batch query
644
+ for (const relation of lookupRelations) {
645
+ const targetCamel = toCamelCase(relation.target);
646
+ if (relation.joinCollection) {
647
+ // Join collection — query join table, collect target IDs, query target
648
+ lines.push(` // Batch (lookup): ${relation.field} (manyToMany join collection)`);
649
+ lines.push(` if (include.${relation.field} !== undefined) {`);
650
+ lines.push(` const includeOpts = include.${relation.field};`);
651
+ lines.push(` let whereFilter = {};`);
652
+ lines.push(` let orderBy = null;`);
653
+ lines.push(` let take = null;`);
654
+ lines.push(` let skip = null;`);
655
+ lines.push(` let select = undefined;`);
656
+ lines.push(` let nestedInclude = undefined;`);
657
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
658
+ lines.push(` whereFilter = includeOpts.where || {};`);
659
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
660
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
661
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
662
+ lines.push(` select = includeOpts.select;`);
663
+ lines.push(` nestedInclude = includeOpts.include;`);
664
+ lines.push(` }`);
665
+ lines.push(` const sourceIds = docs.map((d: any) => d.id).filter(Boolean);`);
666
+ lines.push(` const uniqueSourceIds = [...new Set(sourceIds)];`);
667
+ lines.push(` if (uniqueSourceIds.length > 0) {`);
668
+ lines.push(` const joinCol = this.client.$db.collection('${relation.joinCollection}');`);
669
+ lines.push(` const connections = await joinCol.find({`);
670
+ lines.push(` '${toCamelCase(model.name)}Id': { $in: uniqueSourceIds }`);
671
+ lines.push(` }).toArray();`);
672
+ lines.push(` const targetIds = [...new Set(connections.map((c: any) => c.${toCamelCase(relation.target)}Id).filter(Boolean))];`);
673
+ lines.push(` if (targetIds.length > 0) {`);
674
+ lines.push(` const baseWhere = { id: { in: targetIds } };`);
675
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
676
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({`);
677
+ lines.push(` where: finalWhere,`);
678
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
679
+ lines.push(` });`);
680
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
681
+ lines.push(` const connectionMap = new Map();`);
682
+ lines.push(` for (const conn of connections) {`);
683
+ lines.push(` const sid = conn.${toCamelCase(model.name)}Id;`);
684
+ lines.push(` if (!connectionMap.has(sid)) connectionMap.set(sid, []);`);
685
+ lines.push(` connectionMap.get(sid).push(conn.${toCamelCase(relation.target)}Id);`);
686
+ lines.push(` }`);
687
+ lines.push(` for (const doc of docs) {`);
688
+ lines.push(` const connIds = connectionMap.get(doc.id) || [];`);
689
+ lines.push(` doc.${relation.field} = connIds.map((id: string) => relatedMap.get(id)).filter(Boolean);`);
690
+ lines.push(` }`);
691
+ lines.push(` } else {`);
692
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
693
+ lines.push(` }`);
694
+ lines.push(` } else {`);
695
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
696
+ lines.push(` }`);
697
+ lines.push(` }`);
698
+ }
699
+ else {
700
+ const localField = relation.foreignKey;
701
+ if (relation.isForeignKeyArray) {
702
+ // FK array in source — collect all IDs, query once, map back
703
+ lines.push(` // Batch (lookup): ${relation.field}`);
704
+ lines.push(` if (include.${relation.field} !== undefined) {`);
705
+ lines.push(` const includeOpts = include.${relation.field};`);
706
+ lines.push(` let whereFilter = {};`);
707
+ lines.push(` let orderBy = null;`);
708
+ lines.push(` let take = null;`);
709
+ lines.push(` let skip = null;`);
710
+ lines.push(` let select = undefined;`);
711
+ lines.push(` let nestedInclude = undefined;`);
712
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
713
+ lines.push(` whereFilter = includeOpts.where || {};`);
714
+ lines.push(` orderBy = includeOpts.orderBy || null;`);
715
+ lines.push(` take = includeOpts.take !== undefined ? includeOpts.take : null;`);
716
+ lines.push(` skip = includeOpts.skip !== undefined ? includeOpts.skip : null;`);
717
+ lines.push(` select = includeOpts.select;`);
718
+ lines.push(` nestedInclude = includeOpts.include;`);
719
+ lines.push(` }`);
720
+ lines.push(` const allIds = docs.flatMap((d: any) => d.${localField} || []);`);
721
+ lines.push(` const uniqueIds = [...new Set(allIds)];`);
722
+ lines.push(` if (uniqueIds.length > 0) {`);
723
+ lines.push(` const baseWhere = { id: { in: uniqueIds } };`);
724
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
725
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({`);
726
+ lines.push(` where: finalWhere,`);
727
+ lines.push(` orderBy, take, skip, select, include: nestedInclude`);
728
+ lines.push(` });`);
729
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
730
+ lines.push(` for (const doc of docs) {`);
731
+ lines.push(` doc.${relation.field} = (doc.${localField} || []).map((id: string) => relatedMap.get(id)).filter(Boolean);`);
732
+ lines.push(` }`);
733
+ lines.push(` } else {`);
734
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
735
+ lines.push(` }`);
736
+ lines.push(` }`);
737
+ }
738
+ else {
739
+ // Single FK — collect all FK values, query once, map back
740
+ lines.push(` // Batch (lookup): ${relation.field}`);
741
+ lines.push(` if (include.${relation.field} !== undefined) {`);
742
+ lines.push(` const includeOpts = include.${relation.field};`);
743
+ lines.push(` let whereFilter = {};`);
744
+ lines.push(` let select = undefined;`);
745
+ lines.push(` let nestedInclude = undefined;`);
746
+ lines.push(` if (typeof includeOpts === 'object' && includeOpts !== null) {`);
747
+ lines.push(` whereFilter = includeOpts.where || {};`);
748
+ lines.push(` select = includeOpts.select;`);
749
+ lines.push(` nestedInclude = includeOpts.include;`);
750
+ lines.push(` }`);
751
+ if (relation.foreignKeyLocation === 'target') {
752
+ lines.push(` const sourceIds = docs.map((d: any) => d.id).filter(Boolean);`);
753
+ lines.push(` if (sourceIds.length > 0) {`);
754
+ lines.push(` const uniqueIds = [...new Set(sourceIds)];`);
755
+ lines.push(` const baseWhere = { ${relation.foreignKey}: { in: uniqueIds } };`);
756
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
757
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({ where: finalWhere, select, include: nestedInclude });`);
758
+ lines.push(` const groupedMap = new Map();`);
759
+ lines.push(` for (const r of relatedDocs) {`);
760
+ lines.push(` const key = String(r.${relation.foreignKey});`);
761
+ lines.push(` if (!groupedMap.has(key)) groupedMap.set(key, []);`);
762
+ lines.push(` groupedMap.get(key).push(r);`);
763
+ lines.push(` }`);
764
+ lines.push(` for (const doc of docs) {`);
765
+ lines.push(` doc.${relation.field} = groupedMap.get(doc.id) || [];`);
766
+ lines.push(` }`);
767
+ lines.push(` } else {`);
768
+ lines.push(` for (const doc of docs) { doc.${relation.field} = []; }`);
769
+ lines.push(` }`);
770
+ }
771
+ else {
772
+ lines.push(` const fkValues = docs.map((d: any) => d.${localField}).filter(Boolean);`);
773
+ lines.push(` if (fkValues.length > 0) {`);
774
+ lines.push(` const uniqueFks = [...new Set(fkValues)];`);
775
+ lines.push(` const baseWhere = { id: { in: uniqueFks } };`);
776
+ lines.push(` const finalWhere = Object.keys(whereFilter).length > 0 ? { AND: [baseWhere, whereFilter] } : baseWhere;`);
777
+ lines.push(` const relatedDocs = await this.client.${targetCamel}.findMany({ where: finalWhere, select, include: nestedInclude });`);
778
+ lines.push(` const relatedMap = new Map(relatedDocs.map((r: any) => [r.id, r]));`);
779
+ lines.push(` for (const doc of docs) {`);
780
+ lines.push(` doc.${relation.field} = doc.${localField} ? (relatedMap.get(doc.${localField}) || null) : null;`);
781
+ lines.push(` }`);
782
+ lines.push(` } else {`);
783
+ lines.push(` for (const doc of docs) { doc.${relation.field} = null; }`);
784
+ lines.push(` }`);
785
+ }
786
+ lines.push(` }`);
787
+ }
788
+ }
789
+ }
790
+ lines.push(` return batchResult.documents;`);
791
+ return lines.join('\n');
792
+ }
793
+ }
794
+ //# sourceMappingURL=DelegateRelations.js.map