-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
store.ts
242 lines (206 loc) · 8.13 KB
/
store.ts
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import {
ApolloAction,
isQueryInitAction,
isQueryResultAction,
isQueryErrorAction,
isQueryResultClientAction,
isQueryStopAction,
isStoreResetAction,
StoreResetAction,
} from '../actions';
import {
graphQLResultHasError,
} from '../data/storeUtils';
import {
DocumentNode,
SelectionSetNode,
GraphQLError,
} from 'graphql';
import { isEqual } from '../util/isEqual';
import { NetworkStatus } from './networkStatus';
export interface QueryStore {
[queryId: string]: QueryStoreValue;
}
export type QueryStoreValue = {
queryString: string;
document: DocumentNode;
variables: Object;
previousVariables: Object | null;
networkStatus: NetworkStatus;
networkError: Error | null;
graphQLErrors: GraphQLError[];
lastRequestId: number;
metadata: any;
};
export interface SelectionSetWithRoot {
id: string;
typeName: string;
selectionSet: SelectionSetNode;
}
export function queries(
previousState: QueryStore = {},
action: ApolloAction,
): QueryStore {
if (isQueryInitAction(action)) {
const newState = { ...previousState } as QueryStore;
const previousQuery = previousState[action.queryId];
if (previousQuery && previousQuery.queryString !== action.queryString) {
// XXX we're throwing an error here to catch bugs where a query gets overwritten by a new one.
// we should implement a separate action for refetching so that QUERY_INIT may never overwrite
// an existing query (see also: https://github.com/apollostack/apollo-client/issues/732)
throw new Error('Internal Error: may not update existing query string in store');
}
let isSetVariables = false;
let previousVariables: Object | null = null;
if (
action.storePreviousVariables &&
previousQuery &&
previousQuery.networkStatus !== NetworkStatus.loading
// if the previous query was still loading, we don't want to remember it at all.
) {
if (!isEqual(previousQuery.variables, action.variables)) {
isSetVariables = true;
previousVariables = previousQuery.variables;
}
}
// TODO break this out into a separate function
let newNetworkStatus = NetworkStatus.loading;
if (isSetVariables) {
newNetworkStatus = NetworkStatus.setVariables;
} else if (action.isPoll) {
newNetworkStatus = NetworkStatus.poll;
} else if (action.isRefetch) {
newNetworkStatus = NetworkStatus.refetch;
// TODO: can we determine setVariables here if it's a refetch and the variables have changed?
} else if (action.isPoll) {
newNetworkStatus = NetworkStatus.poll;
}
// XXX right now if QUERY_INIT is fired twice, like in a refetch situation, we just overwrite
// the store. We probably want a refetch action instead, because I suspect that if you refetch
// before the initial fetch is done, you'll get an error.
newState[action.queryId] = {
queryString: action.queryString,
document: action.document,
variables: action.variables,
previousVariables,
networkError: null,
graphQLErrors: [],
networkStatus: newNetworkStatus,
lastRequestId: action.requestId,
metadata: action.metadata,
};
// If the action had a `moreForQueryId` property then we need to set the
// network status on that query as well to `fetchMore`.
//
// We have a complement to this if statement in the query result and query
// error action branch, but importantly *not* in the client result branch.
// This is because the implementation of `fetchMore` *always* sets
// `fetchPolicy` to `network-only` so we would never have a client result.
if (typeof action.fetchMoreForQueryId === 'string') {
newState[action.fetchMoreForQueryId] = {
// We assume that that both a query with id `action.moreForQueryId`
// already exists and that it is not `action.queryId`. This is a safe
// assumption given how we set `moreForQueryId`.
...previousState[action.fetchMoreForQueryId],
// We set the network status to `fetchMore` here overwriting any
// network status that currently exists. This is how network statuses
// are set normally, so it makes sense to set it this way here as well.
networkStatus: NetworkStatus.fetchMore,
};
}
return newState;
} else if (isQueryResultAction(action)) {
if (!previousState[action.queryId]) {
return previousState;
}
// Ignore results from old requests
if (action.requestId < previousState[action.queryId].lastRequestId) {
return previousState;
}
const newState = { ...previousState } as QueryStore;
const resultHasGraphQLErrors = graphQLResultHasError(action.result);
newState[action.queryId] = {
...previousState[action.queryId],
networkError: null,
graphQLErrors: resultHasGraphQLErrors ? action.result.errors : [],
previousVariables: null,
networkStatus: NetworkStatus.ready,
};
// If we have a `fetchMoreForQueryId` then we need to update the network
// status for that query. See the branch for query initialization for more
// explanation about this process.
if (typeof action.fetchMoreForQueryId === 'string') {
newState[action.fetchMoreForQueryId] = {
...previousState[action.fetchMoreForQueryId],
networkStatus: NetworkStatus.ready,
};
}
return newState;
} else if (isQueryErrorAction(action)) {
if (!previousState[action.queryId]) {
return previousState;
}
// Ignore results from old requests
if (action.requestId < previousState[action.queryId].lastRequestId) {
return previousState;
}
const newState = { ...previousState } as QueryStore;
newState[action.queryId] = {
...previousState[action.queryId],
networkError: action.error,
networkStatus: NetworkStatus.error,
};
// If we have a `fetchMoreForQueryId` then we need to update the network
// status for that query. See the branch for query initialization for more
// explanation about this process.
if (typeof action.fetchMoreForQueryId === 'string') {
newState[action.fetchMoreForQueryId] = {
...previousState[action.fetchMoreForQueryId],
networkError: action.error,
networkStatus: NetworkStatus.error,
};
}
return newState;
} else if (isQueryResultClientAction(action)) {
if (!previousState[action.queryId]) {
return previousState;
}
const newState = { ...previousState } as QueryStore;
newState[action.queryId] = {
...previousState[action.queryId],
networkError: null,
previousVariables: null,
// XXX I'm not sure what exactly action.complete really means. I assume it means we have the complete result
// and do not need to hit the server. Not sure when we'd fire this action if the result is not complete, so that bears explanation.
// We should write that down somewhere.
networkStatus: action.complete ? NetworkStatus.ready : NetworkStatus.loading,
};
return newState;
} else if (isQueryStopAction(action)) {
const newState = { ...previousState } as QueryStore;
delete newState[action.queryId];
return newState;
} else if (isStoreResetAction(action)) {
return resetQueryState(previousState, action);
}
return previousState;
}
// Returns the new query state after we receive a store reset action.
// Note that we don't remove the query state for the query IDs that are associated with watchQuery()
// observables. This is because these observables are simply refetched and not
// errored in the event of a store reset.
function resetQueryState(state: QueryStore, action: StoreResetAction): QueryStore {
const observableQueryIds = action.observableQueryIds;
// keep only the queries with query ids that are associated with observables
const newQueries = Object.keys(state).filter((queryId) => {
return (observableQueryIds.indexOf(queryId) > -1);
}).reduce((res, key) => {
// XXX set loading to true so listeners don't trigger unless they want results with partial data
res[key] = {
...state[key],
networkStatus: NetworkStatus.loading,
};
return res;
}, {} as QueryStore);
return newQueries;
}