@@ -44,7 +44,7 @@ interface PendingFetchItem {
44
44
resolver : Deferred < any > ;
45
45
options : FindOptions ;
46
46
trace ?: unknown ;
47
- promise : Promise < StableRecordIdentifier > ;
47
+ promise : Promise < StableExistingRecordIdentifier > ;
48
48
}
49
49
50
50
interface PendingSaveItem {
@@ -60,7 +60,7 @@ export default class FetchManager {
60
60
declare isDestroyed : boolean ;
61
61
declare requestCache : RequestStateService ;
62
62
// fetches pending in the runloop, waiting to be coalesced
63
- declare _pendingFetch : Map < string , PendingFetchItem [ ] > ;
63
+ declare _pendingFetch : Map < string , Map < StableExistingRecordIdentifier , PendingFetchItem [ ] > > ;
64
64
declare _store : Store ;
65
65
66
66
constructor ( store : Store ) {
@@ -114,7 +114,7 @@ export default class FetchManager {
114
114
identifier : StableExistingRecordIdentifier ,
115
115
options : FindOptions ,
116
116
request : StoreRequestInfo
117
- ) : Promise < StableRecordIdentifier > {
117
+ ) : Promise < StableExistingRecordIdentifier > {
118
118
let query : FindRecordQuery = {
119
119
op : 'findRecord' ,
120
120
recordIdentifier : identifier ,
@@ -193,13 +193,21 @@ export default class FetchManager {
193
193
} ) ;
194
194
}
195
195
196
- let fetches = this . _pendingFetch ;
196
+ let fetchesByType = this . _pendingFetch ;
197
+ let fetchesById = fetchesByType . get ( modelName ) ;
197
198
198
- if ( ! fetches . has ( modelName ) ) {
199
- fetches . set ( modelName , [ ] ) ;
199
+ if ( ! fetchesById ) {
200
+ fetchesById = new Map ( ) ;
201
+ fetchesByType . set ( modelName , fetchesById ) ;
200
202
}
201
203
202
- ( fetches . get ( modelName ) as PendingFetchItem [ ] ) . push ( pendingFetchItem ) ;
204
+ let requestsForIdentifier = fetchesById . get ( identifier ) ;
205
+ if ( ! requestsForIdentifier ) {
206
+ requestsForIdentifier = [ ] ;
207
+ fetchesById . set ( identifier , requestsForIdentifier ) ;
208
+ }
209
+
210
+ requestsForIdentifier . push ( pendingFetchItem ) ;
203
211
204
212
if ( TESTING ) {
205
213
if ( ! request . disableTestWaiter ) {
@@ -214,14 +222,12 @@ export default class FetchManager {
214
222
return promise ;
215
223
}
216
224
217
- getPendingFetch ( identifier : StableRecordIdentifier , options : FindOptions ) {
218
- let pendingFetches = this . _pendingFetch . get ( identifier . type ) ;
225
+ getPendingFetch ( identifier : StableExistingRecordIdentifier , options : FindOptions ) {
226
+ let pendingFetches = this . _pendingFetch . get ( identifier . type ) ?. get ( identifier ) ;
219
227
220
228
// We already have a pending fetch for this
221
229
if ( pendingFetches ) {
222
- let matchingPendingFetch = pendingFetches . find (
223
- ( fetch ) => fetch . identifier === identifier && isSameRequest ( options , fetch . options )
224
- ) ;
230
+ let matchingPendingFetch = pendingFetches . find ( ( fetch ) => isSameRequest ( options , fetch . options ) ) ;
225
231
if ( matchingPendingFetch ) {
226
232
return matchingPendingFetch . promise ;
227
233
}
@@ -239,15 +245,15 @@ export default class FetchManager {
239
245
}
240
246
241
247
fetchDataIfNeededForIdentifier (
242
- identifier : StableRecordIdentifier ,
248
+ identifier : StableExistingRecordIdentifier ,
243
249
options : FindOptions = { } ,
244
250
request : StoreRequestInfo
245
- ) : Promise < StableRecordIdentifier > {
251
+ ) : Promise < StableExistingRecordIdentifier > {
246
252
// pre-loading will change the isEmpty value
247
253
const isEmpty = _isEmpty ( this . _store . _instanceCache , identifier ) ;
248
254
const isLoading = _isLoading ( this . _store . _instanceCache , identifier ) ;
249
255
250
- let promise : Promise < StableRecordIdentifier > ;
256
+ let promise : Promise < StableExistingRecordIdentifier > ;
251
257
if ( isEmpty ) {
252
258
assertIdentifierHasId ( identifier ) ;
253
259
@@ -296,12 +302,47 @@ function _isLoading(cache: InstanceCache, identifier: StableRecordIdentifier): b
296
302
) ;
297
303
}
298
304
305
+ function includesSatisfies ( current : undefined | string | string [ ] , existing : undefined | string | string [ ] ) : boolean {
306
+ // if we have no includes we are good
307
+ if ( ! current ?. length ) {
308
+ return true ;
309
+ }
310
+
311
+ // if we are here we have includes,
312
+ // and if existing has no includes then we will need a new request
313
+ if ( ! existing ?. length ) {
314
+ return false ;
315
+ }
316
+
317
+ const arrCurrent = ( Array . isArray ( current ) ? current : current . split ( ',' ) ) . sort ( ) ;
318
+ const arrExisting = ( Array . isArray ( existing ) ? existing : existing . split ( ',' ) ) . sort ( ) ;
319
+
320
+ // includes are identical
321
+ if ( arrCurrent . join ( ',' ) === arrExisting . join ( ',' ) ) {
322
+ return true ;
323
+ }
324
+
325
+ // if all of current includes are in existing includes then we are good
326
+ // so if we find one that is not in existing then we need a new request
327
+ for ( let i = 0 ; i < arrCurrent . length ; i ++ ) {
328
+ if ( ! arrExisting . includes ( arrCurrent [ i ] ) ) {
329
+ return false ;
330
+ }
331
+ }
332
+
333
+ return true ;
334
+ }
335
+
336
+ function optionsSatisfies ( current : object | undefined , existing : object | undefined ) : boolean {
337
+ return ! current || current === existing || Object . keys ( current ) . length === 0 ;
338
+ }
339
+
299
340
// this function helps resolve whether we have a pending request that we should use instead
300
341
function isSameRequest ( options : FindOptions = { } , existingOptions : FindOptions = { } ) {
301
- let includedMatches = ! options . include || options . include === existingOptions . include ;
302
- let adapterOptionsMatches = options . adapterOptions === existingOptions . adapterOptions ;
303
-
304
- return includedMatches && adapterOptionsMatches ;
342
+ return (
343
+ optionsSatisfies ( options . adapterOptions , existingOptions . adapterOptions ) &&
344
+ includesSatisfies ( options . include , existingOptions . include )
345
+ ) ;
305
346
}
306
347
307
348
function _findMany (
@@ -499,35 +540,57 @@ function _processCoalescedGroup(
499
540
}
500
541
}
501
542
502
- function _flushPendingFetchForType ( store : Store , pendingFetchItems : PendingFetchItem [ ] , modelName : string ) {
543
+ function _flushPendingFetchForType (
544
+ store : Store ,
545
+ pendingFetchMap : Map < StableExistingRecordIdentifier , PendingFetchItem [ ] > ,
546
+ modelName : string
547
+ ) {
503
548
let adapter = store . adapterFor ( modelName ) ;
504
549
let shouldCoalesce = ! ! adapter . findMany && adapter . coalesceFindRequests ;
505
- let totalItems = pendingFetchItems . length ;
506
550
507
551
if ( shouldCoalesce ) {
508
- let snapshots = new Array < Snapshot > ( totalItems ) ;
509
- let fetchMap = new Map < Snapshot , PendingFetchItem > ( ) ;
510
- for ( let i = 0 ; i < totalItems ; i ++ ) {
511
- let fetchItem = pendingFetchItems [ i ] ;
512
- snapshots [ i ] = store . _fetchManager . createSnapshot ( fetchItem . identifier , fetchItem . options ) ;
513
- fetchMap . set ( snapshots [ i ] , fetchItem ) ;
514
- }
552
+ const pendingFetchItems : PendingFetchItem [ ] = [ ] ;
553
+ pendingFetchMap . forEach ( ( requestsForIdentifier , identifier ) => {
554
+ if ( requestsForIdentifier . length > 1 ) {
555
+ return ;
556
+ }
515
557
516
- let groups : Snapshot [ ] [ ] ;
517
- if ( adapter . groupRecordsForFindMany ) {
518
- groups = adapter . groupRecordsForFindMany ( store , snapshots ) ;
519
- } else {
520
- groups = [ snapshots ] ;
521
- }
558
+ // remove this entry from the map so it's not processed again
559
+ pendingFetchMap . delete ( identifier ) ;
560
+ pendingFetchItems . push ( requestsForIdentifier [ 0 ] ) ;
561
+ } ) ;
522
562
523
- for ( let i = 0 , l = groups . length ; i < l ; i ++ ) {
524
- _processCoalescedGroup ( store , fetchMap , groups [ i ] , adapter , modelName ) ;
525
- }
526
- } else {
527
- for ( let i = 0 ; i < totalItems ; i ++ ) {
528
- void _fetchRecord ( store , adapter , pendingFetchItems [ i ] ) ;
563
+ let totalItems = pendingFetchItems . length ;
564
+
565
+ if ( totalItems > 1 ) {
566
+ let snapshots = new Array < Snapshot > ( totalItems ) ;
567
+ let fetchMap = new Map < Snapshot , PendingFetchItem > ( ) ;
568
+ for ( let i = 0 ; i < totalItems ; i ++ ) {
569
+ let fetchItem = pendingFetchItems [ i ] ;
570
+ snapshots [ i ] = store . _fetchManager . createSnapshot ( fetchItem . identifier , fetchItem . options ) ;
571
+ fetchMap . set ( snapshots [ i ] , fetchItem ) ;
572
+ }
573
+
574
+ let groups : Snapshot [ ] [ ] ;
575
+ if ( adapter . groupRecordsForFindMany ) {
576
+ groups = adapter . groupRecordsForFindMany ( store , snapshots ) ;
577
+ } else {
578
+ groups = [ snapshots ] ;
579
+ }
580
+
581
+ for ( let i = 0 , l = groups . length ; i < l ; i ++ ) {
582
+ _processCoalescedGroup ( store , fetchMap , groups [ i ] , adapter , modelName ) ;
583
+ }
584
+ } else if ( totalItems === 1 ) {
585
+ void _fetchRecord ( store , adapter , pendingFetchItems [ 0 ] ) ;
529
586
}
530
587
}
588
+
589
+ pendingFetchMap . forEach ( ( pendingFetchItems ) => {
590
+ pendingFetchItems . forEach ( ( pendingFetchItem ) => {
591
+ void _fetchRecord ( store , adapter , pendingFetchItem ) ;
592
+ } ) ;
593
+ } ) ;
531
594
}
532
595
533
596
function _flushPendingSave ( store : Store , pending : PendingSaveItem ) {
0 commit comments