-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathselectors.js
120 lines (103 loc) · 3.13 KB
/
selectors.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* External dependencies
*/
import createSelector from 'rememo';
import EquivalentKeyMap from 'equivalent-key-map';
/**
* Internal dependencies
*/
import getQueryParts from './get-query-parts';
import { setNestedValue } from '../utils';
/**
* Cache of state keys to EquivalentKeyMap where the inner map tracks queries
* to their resulting items set. WeakMap allows garbage collection on expired
* state references.
*
* @type {WeakMap<Object,EquivalentKeyMap>}
*/
const queriedItemsCacheByState = new WeakMap();
/**
* Returns items for a given query, or null if the items are not known.
*
* @param {Object} state State object.
* @param {?Object} query Optional query.
*
* @return {?Array} Query items.
*/
function getQueriedItemsUncached( state, query ) {
const { stableKey, page, perPage, include, fields, context } =
getQueryParts( query );
let itemIds;
if ( state.queries?.[ context ]?.[ stableKey ] ) {
itemIds = state.queries[ context ][ stableKey ];
}
if ( ! itemIds ) {
return null;
}
const startOffset = perPage === -1 ? 0 : ( page - 1 ) * perPage;
const endOffset =
perPage === -1
? itemIds.length
: Math.min( startOffset + perPage, itemIds.length );
const items = [];
for ( let i = startOffset; i < endOffset; i++ ) {
const itemId = itemIds[ i ];
if ( Array.isArray( include ) && ! include.includes( itemId ) ) {
continue;
}
// Having a target item ID doesn't guarantee that this object has been queried.
if ( ! state.items[ context ]?.hasOwnProperty( itemId ) ) {
return null;
}
const item = state.items[ context ][ itemId ];
let filteredItem;
if ( Array.isArray( fields ) ) {
filteredItem = {};
for ( let f = 0; f < fields.length; f++ ) {
const field = fields[ f ].split( '.' );
let value = item;
field.forEach( ( fieldName ) => {
value = value[ fieldName ];
} );
setNestedValue( filteredItem, field, value );
}
} else {
// If expecting a complete item, validate that completeness, or
// otherwise abort.
if ( ! state.itemIsComplete[ context ]?.[ itemId ] ) {
return null;
}
filteredItem = item;
}
items.push( filteredItem );
}
return items;
}
/**
* Returns items for a given query, or null if the items are not known. Caches
* result both per state (by reference) and per query (by deep equality).
* The caching approach is intended to be durable to query objects which are
* deeply but not referentially equal, since otherwise:
*
* `getQueriedItems( state, {} ) !== getQueriedItems( state, {} )`
*
* @param {Object} state State object.
* @param {?Object} query Optional query.
*
* @return {?Array} Query items.
*/
export const getQueriedItems = createSelector( ( state, query = {} ) => {
let queriedItemsCache = queriedItemsCacheByState.get( state );
if ( queriedItemsCache ) {
const queriedItems = queriedItemsCache.get( query );
if ( queriedItems !== undefined ) {
return queriedItems;
}
} else {
queriedItemsCache = new EquivalentKeyMap();
queriedItemsCacheByState.set( state, queriedItemsCache );
}
const items = getQueriedItemsUncached( state, query );
queriedItemsCache.set( query, items );
return items;
} );