@@ -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 > ,
@@ -259,7 +342,7 @@ function updateQueryFilter<K extends object, P extends string = never>(
259
342
newQueryMap ?: boolean ,
260
343
) : void {
261
344
if ( options ?. populate != null && accOptions != null && accOptions . populate !== true ) {
262
- if ( Array . isArray ( options . populate ) && options . populate [ 0 ] === "*" ) {
345
+ if ( Array . isArray ( options . populate ) && options . populate . includes ( "*" ) ) {
263
346
accOptions . populate = true ;
264
347
} else if ( Array . isArray ( options . populate ) ) {
265
348
if ( accOptions . populate == null ) {
@@ -276,14 +359,56 @@ function updateQueryFilter<K extends object, P extends string = never>(
276
359
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
277
360
const curValue = ( cur as Record < string , any [ ] > ) [ key ] ! ;
278
361
if ( Array . isArray ( value ) ) {
279
- value . push ( ...curValue . reduce < any [ ] > ( ( acc , cur ) => acc . concat ( cur ) , [ ] ) ) ;
362
+ // value.push(...curValue.reduce<any[]>((acc, cur) => acc.concat(cur), []));
363
+ value . push ( ...structuredClone ( curValue ) ) ;
280
364
} else {
281
365
updateQueryFilter ( [ value ] , curValue ) ;
282
366
}
283
367
}
284
368
}
285
369
}
286
370
371
+ // The least amount of populate necessary to map the dataloader results to their original queries
372
+ function getMandatoryPopulate < K extends object > (
373
+ cur : FilterQueryDataloader < K > ,
374
+ meta : EntityMetadata < K > ,
375
+ ) : string | undefined ;
376
+ function getMandatoryPopulate < K extends object > (
377
+ cur : FilterQueryDataloader < K > ,
378
+ meta : EntityMetadata < K > ,
379
+ options : { populate ?: Set < any > } ,
380
+ ) : void ;
381
+ function getMandatoryPopulate < K extends object > (
382
+ cur : FilterQueryDataloader < K > ,
383
+ meta : EntityMetadata < K > ,
384
+ options ?: { populate ?: Set < any > } ,
385
+ ) : any {
386
+ for ( const [ key , value ] of Object . entries ( cur ) ) {
387
+ const keyProp = meta . properties [ key as EntityKey < K > ] ;
388
+ if ( keyProp == null ) {
389
+ throw new Error ( `Cannot find properties for ${ key } ` ) ;
390
+ }
391
+ // If our current key leads to scalar we don't need to populate anything
392
+ if ( keyProp . targetMeta != null ) {
393
+ // Our current key points to either a Reference or a Collection
394
+ // We need to populate all Collections
395
+ // We also need to populate References whenever we have to further match non-PKs properties
396
+ if ( keyProp . ref !== true || ! isPK ( value , keyProp . targetMeta ) ) {
397
+ const furtherPop = getMandatoryPopulate ( value , keyProp . targetMeta ) ;
398
+ const computedPopulate = furtherPop == null ? `${ key } ` : `${ key } .${ furtherPop } ` ;
399
+ if ( options != null ) {
400
+ if ( options . populate == null ) {
401
+ options . populate = new Set ( ) ;
402
+ }
403
+ options . populate . add ( computedPopulate ) ;
404
+ } else {
405
+ return computedPopulate ;
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+
287
412
export interface DataloaderFind < K extends object , Hint extends string = never , Fields extends string = never > {
288
413
entityName : string ;
289
414
meta : EntityMetadata < K > ;
@@ -305,7 +430,9 @@ export function groupFindQueriesByOpts(
305
430
dataloaderFind . filtersAndKeys ?. push ( { key, newFilter } ) ;
306
431
let queryMap = queriesMap . get ( key ) ;
307
432
if ( queryMap == null ) {
308
- queryMap = [ structuredClone ( newFilter ) , { } ] ;
433
+ const queryMapOpts = { } ;
434
+ queryMap = [ structuredClone ( newFilter ) , queryMapOpts ] ;
435
+ getMandatoryPopulate ( newFilter , meta , queryMapOpts ) ;
309
436
updateQueryFilter ( queryMap , newFilter , options , true ) ;
310
437
queriesMap . set ( key , queryMap ) ;
311
438
} else {
@@ -348,6 +475,7 @@ export function getFindBatchLoadFn<Entity extends object>(
348
475
for ( const [ key , value ] of Object . entries ( filter ) ) {
349
476
const entityValue = entity [ key as keyof K ] ;
350
477
if ( Array . isArray ( value ) ) {
478
+ // Our current filter is an array
351
479
if ( Array . isArray ( entityValue ) ) {
352
480
// Collection
353
481
if ( ! value . every ( ( el ) => entityValue . includes ( el ) ) ) {
@@ -360,8 +488,10 @@ export function getFindBatchLoadFn<Entity extends object>(
360
488
}
361
489
}
362
490
} else {
363
- // Object: recursion
364
- if ( ! filterResult ( entityValue as object , value ) ) {
491
+ // Our current filter is an object
492
+ if ( entityValue instanceof Collection ) {
493
+ entityValue . find ( ( entity ) => filterResult ( entity , value ) ) ;
494
+ } else if ( ! filterResult ( entityValue as object , value ) ) {
365
495
return false ;
366
496
}
367
497
}
0 commit comments