@@ -124,7 +124,90 @@ export function groupInversedOrMappedKeysByEntity<T extends AnyEntity<T>>(
124
124
return entitiesMap ;
125
125
}
126
126
127
+ function allKeysArePK < K extends object > (
128
+ keys : Array < EntityKey < K > > | undefined ,
129
+ primaryKeys : Array < EntityKey < K > > ,
130
+ ) : boolean {
131
+ if ( keys == null ) {
132
+ return false ;
133
+ }
134
+ if ( keys . length !== primaryKeys . length ) {
135
+ return false ;
136
+ }
137
+ for ( const key of keys ) {
138
+ if ( ! primaryKeys . includes ( key ) ) {
139
+ return false ;
140
+ }
141
+ }
142
+ return true ;
143
+ }
144
+
145
+ // {id: 5, name: "a"} returns false because contains additional fields
146
+ // Returns true for all PK formats including {id: 1} or {owner: 1, recipient: 2}
147
+ function isPK < K extends object > ( filter : FilterQueryDataloader < K > , meta : EntityMetadata < K > ) : boolean {
148
+ if ( meta == null ) {
149
+ return false ;
150
+ }
151
+ if ( meta . compositePK ) {
152
+ // COMPOSITE
153
+ if ( Array . isArray ( filter ) ) {
154
+ // PK or PK[] or object[]
155
+ // [1, 2]
156
+ // [[1, 2], [3, 4]]
157
+ // [{owner: 1, recipient: 2}, {owner: 3, recipient: 4}]
158
+ // [{owner: 1, recipient: 2, sex: 0}, {owner: 3, recipient: 4, sex: 1}]
159
+ if ( Utils . isPrimaryKey ( filter , meta . compositePK ) ) {
160
+ // PK
161
+ return true ;
162
+ }
163
+ if ( Utils . isPrimaryKey ( filter [ 0 ] , meta . compositePK ) ) {
164
+ // PK[]
165
+ return true ;
166
+ }
167
+ const keys = typeof filter [ 0 ] === "object" ? ( Object . keys ( filter [ 0 ] ) as Array < EntityKey < K > > ) : undefined ;
168
+ if ( allKeysArePK ( keys , meta . primaryKeys ) ) {
169
+ // object is PK or PK[]
170
+ return true ;
171
+ }
172
+ } else {
173
+ // object
174
+ // {owner: 1, recipient: 2, sex: 0}
175
+ const keys = typeof filter === "object" ? ( Object . keys ( filter ) as Array < EntityKey < K > > ) : undefined ;
176
+ if ( allKeysArePK ( keys , meta . primaryKeys ) ) {
177
+ // object is PK
178
+ return true ;
179
+ }
180
+ }
181
+ } else {
182
+ // NOT COMPOSITE
183
+ if ( Array . isArray ( filter ) ) {
184
+ // PK[]
185
+ // [1, 2]
186
+ // [{id: 1}, {id: 2}] NOT POSSIBLE FOR NON COMPOSITE
187
+ if ( Utils . isPrimaryKey ( filter [ 0 ] ) ) {
188
+ return true ;
189
+ }
190
+ } else {
191
+ // PK or object
192
+ // 1
193
+ // {id: [1, 2], sex: 0} or {id: 1, sex: 0}
194
+ if ( Utils . isPrimaryKey ( filter ) ) {
195
+ // PK
196
+ return true ;
197
+ }
198
+ const keys =
199
+ typeof filter === "object" ? ( Object . keys ( filter ) as [ EntityKey < K > , ...Array < EntityKey < K > > ] ) : undefined ;
200
+ if ( keys ?. length === 1 && keys [ 0 ] === meta . primaryKeys [ 0 ] ) {
201
+ // object is PK
202
+ return true ;
203
+ }
204
+ }
205
+ }
206
+ return false ;
207
+ }
208
+
127
209
// Call this fn only if keyProp.targetMeta != null otherwise you will get false positives
210
+ // Returns only PKs in short-hand format like 1 or [1, 1] not {id: 1} or {owner: 1, recipient: 2}
128
211
function getPKs < K extends object > (
129
212
filter : FilterQueryDataloader < K > ,
130
213
meta : EntityMetadata < K > ,
@@ -180,20 +263,20 @@ COMPOSITE PK:
180
263
const asc = ( a : string , b : string ) : number => a . localeCompare ( b ) ;
181
264
const notNull = ( el : string | undefined ) : boolean => el != null ;
182
265
183
- function getNewFiltersAndMapKeys < K extends object > (
266
+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
184
267
cur : FilterQueryDataloader < K > ,
185
268
meta : EntityMetadata < K > ,
186
269
entityName : string ,
187
- ) : Array < [ FilterQueryDataloader < K > , string ] > ;
188
- function getNewFiltersAndMapKeys < K extends object > (
270
+ ) : Array < [ FilterQueryDataloader < K > , string , Set < string > ? ] > ;
271
+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
189
272
cur : FilterQueryDataloader < K > ,
190
273
meta : EntityMetadata < K > ,
191
- ) : [ FilterQueryDataloader < K > , string ] ;
192
- function getNewFiltersAndMapKeys < K extends object > (
274
+ ) : [ FilterQueryDataloader < K > , string , string ? ] ;
275
+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
193
276
cur : FilterQueryDataloader < K > ,
194
277
meta : EntityMetadata < K > ,
195
278
entityName ?: string ,
196
- ) : [ FilterQueryDataloader < K > , string ] | Array < [ FilterQueryDataloader < K > , string ] > {
279
+ ) : [ FilterQueryDataloader < K > , string , string ? ] | Array < [ FilterQueryDataloader < K > , string , Set < string > ? ] > {
197
280
const PKs = getPKs ( cur , meta ) ;
198
281
if ( PKs != null ) {
199
282
const res : [ FilterQueryDataloader < K > , string ] = [
@@ -206,6 +289,8 @@ function getNewFiltersAndMapKeys<K extends object>(
206
289
} else {
207
290
const newFilter : any = { } ;
208
291
const keys : string [ ] = [ ] ;
292
+ const populateSet = new Set < string > ( ) ;
293
+ let computedPopulate : string | undefined ;
209
294
if ( Array . isArray ( cur ) ) {
210
295
// COMPOSITE PKs like [{owner: 1, recipient: 2}, {recipient: 4, owner: 3}]
211
296
for ( const key of meta . primaryKeys ) {
@@ -223,27 +308,42 @@ function getNewFiltersAndMapKeys<K extends object>(
223
308
// Using $or at the top level means that we can treat it as two separate queries and filter results from either of them
224
309
if ( key === "$or" && entityName != null ) {
225
310
return ( value as Array < FilterQueryDataloader < K > > )
226
- . map ( ( el ) => getNewFiltersAndMapKeys ( el , meta , entityName ) )
311
+ . map ( ( el ) => getNewFiltersMapKeysAndMandatoryPopulate ( el , meta , entityName ) )
227
312
. flat ( ) ;
228
313
}
229
314
const keyProp = meta . properties [ key as EntityKey < K > ] ;
230
315
if ( keyProp == null ) {
231
316
throw new Error ( `Cannot find properties for ${ key } ` ) ;
232
317
}
233
318
if ( keyProp . targetMeta == null ) {
319
+ // Our current key might lead to a scalar (thus we don't need to populate anything)
320
+ // or to an explicited PK like {id: 1, name: 'a'}
234
321
newFilter [ key ] = Array . isArray ( value ) ? value : [ value ] ;
235
322
keys . push ( key ) ;
236
323
} else {
237
- const [ subFilter , subKey ] = getNewFiltersAndMapKeys ( value , keyProp . targetMeta ) ;
324
+ // Our current key points to either a Reference or a Collection
325
+ const [ subFilter , subKey , furtherPop ] = getNewFiltersMapKeysAndMandatoryPopulate ( value , keyProp . targetMeta ) ;
238
326
newFilter [ key ] = subFilter ;
239
327
keys . push ( `${ key } :${ subKey } ` ) ;
328
+ // We need to populate all Collections and all the References
329
+ // where we further match non-PKs properties
330
+ if ( keyProp . ref !== true || ! isPK ( value , keyProp . targetMeta ) ) {
331
+ computedPopulate = furtherPop == null ? `${ key } ` : `${ key } .${ furtherPop } ` ;
332
+ if ( entityName != null ) {
333
+ populateSet . add ( computedPopulate ) ;
334
+ } else {
335
+ // We return computedPopulate as the third element of the array
336
+ }
337
+ }
240
338
}
241
339
}
242
340
const res : [ FilterQueryDataloader < K > , string ] = [
243
341
newFilter ,
244
342
[ entityName , `{${ keys . sort ( asc ) . join ( "," ) } }` ] . filter ( notNull ) . join ( "|" ) ,
245
343
] ;
246
- return entityName == null ? res : [ res ] ;
344
+ return entityName == null
345
+ ? [ ...res , computedPopulate ]
346
+ : [ [ ...res , populateSet . size === 0 ? undefined : populateSet ] ] ;
247
347
}
248
348
}
249
349
}
@@ -259,7 +359,7 @@ function updateQueryFilter<K extends object, P extends string = never>(
259
359
newQueryMap ?: boolean ,
260
360
) : void {
261
361
if ( options ?. populate != null && accOptions != null && accOptions . populate !== true ) {
262
- if ( Array . isArray ( options . populate ) && options . populate [ 0 ] === "*" ) {
362
+ if ( Array . isArray ( options . populate ) && options . populate . includes ( "*" ) ) {
263
363
accOptions . populate = true ;
264
364
} else if ( Array . isArray ( options . populate ) ) {
265
365
if ( accOptions . populate == null ) {
@@ -276,14 +376,54 @@ function updateQueryFilter<K extends object, P extends string = never>(
276
376
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
277
377
const curValue = ( cur as Record < string , any [ ] > ) [ key ] ! ;
278
378
if ( Array . isArray ( value ) ) {
279
- value . push ( ...curValue . reduce < any [ ] > ( ( acc , cur ) => acc . concat ( cur ) , [ ] ) ) ;
379
+ // value.push(...curValue.reduce<any[]>((acc, cur) => acc.concat(cur), []));
380
+ value . push ( ...structuredClone ( curValue ) ) ;
280
381
} else {
281
382
updateQueryFilter ( [ value ] , curValue ) ;
282
383
}
283
384
}
284
385
}
285
386
}
286
387
388
+ // The least amount of populate necessary to map the dataloader results to their original queries
389
+ export function getMandatoryPopulate < K extends object > (
390
+ cur : FilterQueryDataloader < K > ,
391
+ meta : EntityMetadata < K > ,
392
+ ) : string | undefined ;
393
+ export function getMandatoryPopulate < K extends object > (
394
+ cur : FilterQueryDataloader < K > ,
395
+ meta : EntityMetadata < K > ,
396
+ populate : Set < any > ,
397
+ ) : void ;
398
+ export function getMandatoryPopulate < K extends object > (
399
+ cur : FilterQueryDataloader < K > ,
400
+ meta : EntityMetadata < K > ,
401
+ populate ?: Set < any > ,
402
+ ) : any {
403
+ for ( const [ key , value ] of Object . entries ( cur ) ) {
404
+ const keyProp = meta . properties [ key as EntityKey < K > ] ;
405
+ if ( keyProp == null ) {
406
+ throw new Error ( `Cannot find properties for ${ key } ` ) ;
407
+ }
408
+ // If our current key leads to scalar we don't need to populate anything
409
+ if ( keyProp . targetMeta != null ) {
410
+ // Our current key points to either a Reference or a Collection
411
+ // We need to populate all Collections
412
+ // We also need to populate References whenever we have to further match non-PKs properties
413
+ const PKs = getPKs ( value , keyProp . targetMeta ) ;
414
+ if ( keyProp . ref !== true || PKs == null ) {
415
+ const furtherPopulate = getMandatoryPopulate ( value , keyProp . targetMeta ) ;
416
+ const computedPopulate = furtherPopulate == null ? `${ key } ` : `${ key } .${ furtherPopulate } ` ;
417
+ if ( populate != null ) {
418
+ populate . add ( computedPopulate ) ;
419
+ } else {
420
+ return computedPopulate ;
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+
287
427
export interface DataloaderFind < K extends object , Hint extends string = never , Fields extends string = never > {
288
428
entityName : string ;
289
429
meta : EntityMetadata < K > ;
@@ -299,13 +439,13 @@ export function groupFindQueriesByOpts(
299
439
const queriesMap = new Map < string , [ FilterQueryDataloader < any > , { populate ?: true | Set < any > } ?] > ( ) ;
300
440
for ( const dataloaderFind of dataloaderFinds ) {
301
441
const { entityName, meta, filter, options } = dataloaderFind ;
302
- const filtersAndKeys = getNewFiltersAndMapKeys ( filter , meta , entityName ) ;
442
+ const filtersAndKeys = getNewFiltersMapKeysAndMandatoryPopulate ( filter , meta , entityName ) ;
303
443
dataloaderFind . filtersAndKeys = [ ] ;
304
- filtersAndKeys . forEach ( ( [ newFilter , key ] ) => {
444
+ filtersAndKeys . forEach ( ( [ newFilter , key , mandatoryPopulate ] ) => {
305
445
dataloaderFind . filtersAndKeys ?. push ( { key, newFilter } ) ;
306
446
let queryMap = queriesMap . get ( key ) ;
307
447
if ( queryMap == null ) {
308
- queryMap = [ structuredClone ( newFilter ) , { } ] ;
448
+ queryMap = [ structuredClone ( newFilter ) , { ... ( mandatoryPopulate != null && { populate : mandatoryPopulate } ) } ] ;
309
449
updateQueryFilter ( queryMap , newFilter , options , true ) ;
310
450
queriesMap . set ( key , queryMap ) ;
311
451
} else {
@@ -382,6 +522,9 @@ export function optsMapToQueries<Entity extends object>(
382
522
populate : options . populate === true ? [ "*" ] : Array . from ( options . populate ) ,
383
523
} ) ,
384
524
} satisfies Pick < FindOptions < any , any > , "populate" > ;
525
+ console . log ( "entityName" , entityName ) ;
526
+ console . log ( "filter" , filter ) ;
527
+ console . log ( "findOptions" , findOptions ) ;
385
528
const entities = await em . find ( entityName , filter , findOptions ) ;
386
529
return [ key , entities ] ;
387
530
} ) ;
0 commit comments