-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathselectors.js
130 lines (113 loc) · 3.59 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
121
122
123
124
125
126
127
128
129
130
/**
* External dependencies
*/
import createSelector from 'rememo';
import EquivalentKeyMap from 'equivalent-key-map';
/**
* Internal dependencies
*/
import getQueryParts from './get-query-parts';
/**
* 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 } = getQueryParts(
query
);
let itemIds;
if ( Array.isArray( include ) && ! stableKey ) {
// If the parsed query yields a set of IDs, but otherwise no filtering,
// it's safe to consider targeted item IDs as the include set. This
// doesn't guarantee that those objects have been queried, which is
// accounted for below in the loop `null` return.
itemIds = include;
// TODO: Avoid storing the empty stable string in reducer, since it
// can be computed dynamically here always.
} else if ( state.queries[ stableKey ] ) {
itemIds = state.queries[ 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;
}
if ( ! state.items.hasOwnProperty( itemId ) ) {
return null;
}
const item = state.items[ itemId ];
let filteredItem;
if ( Array.isArray( fields ) ) {
filteredItem = {};
for ( let f = 0; f < fields.length; f++ ) {
// Abort the entire request if a field is missing from the item.
// This accounts for the fact that queried items are stored by
// stable key without an associated fields query. Other requests
// may have included fewer fields properties.
const field = fields[ f ];
if ( ! item.hasOwnProperty( field ) ) {
return null;
}
filteredItem[ field ] = item[ field ];
}
} else {
// If expecting a complete item, validate that completeness, or
// otherwise abort.
if ( ! state.itemIsComplete[ 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;
} );