react-relay 13.2.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReactRelayContext.js +1 -1
- package/ReactRelayFragmentContainer.js.flow +7 -4
- package/ReactRelayPaginationContainer.js.flow +13 -8
- package/ReactRelayQueryFetcher.js.flow +1 -0
- package/ReactRelayQueryRenderer.js.flow +6 -2
- package/ReactRelayRefetchContainer.js.flow +10 -3
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +3 -3
- package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +2 -2
- package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +2 -2
- package/buildReactRelayContainer.js.flow +2 -2
- package/hooks.js +1 -1
- package/index.js +1 -1
- package/legacy.js +1 -1
- package/lib/ReactRelayQueryFetcher.js +1 -0
- package/lib/ReactRelayQueryRenderer.js +0 -1
- package/lib/readContext.js +2 -1
- package/lib/relay-hooks/FragmentResource.js +52 -10
- package/lib/relay-hooks/HooksImplementation.js +29 -0
- package/lib/relay-hooks/MatchContainer.js +1 -0
- package/lib/relay-hooks/QueryResource.js +2 -1
- package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +203 -56
- package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +254 -109
- package/lib/relay-hooks/react-cache/useFragment_REACT_CACHE.js +51 -0
- package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +13 -2
- package/lib/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js +125 -0
- package/lib/relay-hooks/useFragment.js +15 -1
- package/lib/relay-hooks/useLazyLoadQuery.js +18 -2
- package/lib/relay-hooks/useMutation.js +4 -5
- package/lib/relay-hooks/usePreloadedQuery.js +18 -2
- package/package.json +2 -2
- package/react-relay-hooks.js +2 -2
- package/react-relay-hooks.min.js +2 -2
- package/react-relay-legacy.js +2 -2
- package/react-relay-legacy.min.js +2 -2
- package/react-relay.js +2 -2
- package/react-relay.min.js +2 -2
- package/readContext.js.flow +1 -0
- package/relay-hooks/FragmentResource.js.flow +55 -9
- package/relay-hooks/HooksImplementation.js.flow +45 -0
- package/relay-hooks/MatchContainer.js.flow +8 -1
- package/relay-hooks/QueryResource.js.flow +4 -2
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +2 -2
- package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +2 -2
- package/relay-hooks/loadQuery.js.flow +2 -1
- package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +245 -64
- package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +242 -99
- package/relay-hooks/react-cache/useFragment_REACT_CACHE.js.flow +74 -0
- package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +10 -4
- package/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js.flow +153 -0
- package/relay-hooks/useFragment.js.flow +17 -10
- package/relay-hooks/useLazyLoadQuery.js.flow +38 -3
- package/relay-hooks/useMutation.js.flow +3 -3
- package/relay-hooks/usePreloadedQuery.js.flow +30 -2
- package/relay-hooks/useRefetchableFragmentNode.js.flow +26 -11
- package/relay-hooks/useSubscription.js.flow +14 -8
@@ -15,19 +15,25 @@
|
|
15
15
|
|
16
16
|
import type {
|
17
17
|
FetchPolicy,
|
18
|
+
GraphQLResponse,
|
18
19
|
IEnvironment,
|
20
|
+
Observable,
|
19
21
|
OperationDescriptor,
|
20
22
|
ReaderFragment,
|
21
23
|
RenderPolicy,
|
22
24
|
} from 'relay-runtime';
|
23
25
|
|
26
|
+
const SuspenseResource = require('../SuspenseResource');
|
24
27
|
const {getCacheForType, getCacheSignal} = require('./RelayReactCache');
|
25
28
|
const invariant = require('invariant');
|
26
29
|
const {
|
30
|
+
RelayFeatureFlags,
|
27
31
|
__internal: {fetchQuery: fetchQueryInternal},
|
28
32
|
} = require('relay-runtime');
|
29
33
|
const warning = require('warning');
|
30
34
|
|
35
|
+
type QueryCacheCommitable = () => () => void;
|
36
|
+
|
31
37
|
type QueryResult = {|
|
32
38
|
fragmentNode: ReaderFragment,
|
33
39
|
fragmentRef: mixed,
|
@@ -36,26 +42,96 @@ type QueryResult = {|
|
|
36
42
|
// Note that the status of a cache entry will be 'resolved' when partial
|
37
43
|
// rendering is allowed, even if a fetch is ongoing. The pending status
|
38
44
|
// is specifically to indicate that we should suspend.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
// Note also that the retainCount is different from the retain count of
|
46
|
+
// an operation, which is maintained by the Environment. This retain
|
47
|
+
// count is used in Legacy Timeouts mode to count how many components
|
48
|
+
// are mounted that use the entry, plus one count for the temporary retain
|
49
|
+
// before any components have mounted. It is unused when Legacy Timeouts
|
50
|
+
// mode is off.
|
51
|
+
type QueryCacheEntryStatus =
|
52
|
+
| {|
|
53
|
+
status: 'resolved',
|
54
|
+
result: QueryResult,
|
55
|
+
|}
|
56
|
+
| {|
|
57
|
+
status: 'pending',
|
58
|
+
promise: Promise<void>,
|
59
|
+
|}
|
60
|
+
| {|
|
61
|
+
status: 'rejected',
|
62
|
+
error: Error,
|
63
|
+
|};
|
43
64
|
|
44
|
-
type
|
65
|
+
type QueryCacheEntry = {|
|
66
|
+
...QueryCacheEntryStatus,
|
67
|
+
onCommit: QueryCacheCommitable,
|
68
|
+
suspenseResource: SuspenseResource | null,
|
69
|
+
|};
|
45
70
|
|
46
71
|
const DEFAULT_FETCH_POLICY = 'store-or-network';
|
47
72
|
|
73
|
+
type QueryCacheKey = string;
|
74
|
+
|
75
|
+
class QueryCache {
|
76
|
+
_map: Map<IEnvironment, Map<QueryCacheKey, QueryCacheEntry>>;
|
77
|
+
|
78
|
+
constructor() {
|
79
|
+
this._map = new Map();
|
80
|
+
}
|
81
|
+
|
82
|
+
get(environment: IEnvironment, key: QueryCacheKey): QueryCacheEntry | void {
|
83
|
+
let forEnv = this._map.get(environment);
|
84
|
+
if (!forEnv) {
|
85
|
+
forEnv = new Map();
|
86
|
+
this._map.set(environment, forEnv);
|
87
|
+
}
|
88
|
+
return forEnv.get(key);
|
89
|
+
}
|
90
|
+
|
91
|
+
set(
|
92
|
+
environment: IEnvironment,
|
93
|
+
key: QueryCacheKey,
|
94
|
+
value: QueryCacheEntry,
|
95
|
+
): void {
|
96
|
+
let forEnv = this._map.get(environment);
|
97
|
+
if (!forEnv) {
|
98
|
+
forEnv = new Map();
|
99
|
+
this._map.set(environment, forEnv);
|
100
|
+
}
|
101
|
+
forEnv.set(key, value);
|
102
|
+
}
|
103
|
+
|
104
|
+
delete(environment: IEnvironment, key: QueryCacheKey): void {
|
105
|
+
const forEnv = this._map.get(environment);
|
106
|
+
if (!forEnv) {
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
forEnv.delete(key);
|
110
|
+
if (forEnv.size === 0) {
|
111
|
+
this._map.delete(environment);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
48
116
|
function createQueryCache(): QueryCache {
|
49
|
-
return new
|
117
|
+
return new QueryCache();
|
50
118
|
}
|
51
119
|
|
120
|
+
const noopOnCommit = () => {
|
121
|
+
return () => undefined;
|
122
|
+
};
|
123
|
+
|
124
|
+
const noopPromise = new Promise(() => {});
|
125
|
+
|
52
126
|
function getQueryCacheKey(
|
53
127
|
operation: OperationDescriptor,
|
54
128
|
fetchPolicy: FetchPolicy,
|
55
129
|
renderPolicy: RenderPolicy,
|
56
|
-
|
57
|
-
|
58
|
-
return
|
130
|
+
fetchKey?: ?string | ?number,
|
131
|
+
): QueryCacheKey {
|
132
|
+
return `${fetchPolicy}-${renderPolicy}-${operation.request.identifier}-${
|
133
|
+
fetchKey ?? ''
|
134
|
+
}`;
|
59
135
|
}
|
60
136
|
|
61
137
|
function constructQueryResult(operation: OperationDescriptor): QueryResult {
|
@@ -72,14 +148,28 @@ function constructQueryResult(operation: OperationDescriptor): QueryResult {
|
|
72
148
|
};
|
73
149
|
}
|
74
150
|
|
151
|
+
function makeInitialCacheEntry() {
|
152
|
+
return {
|
153
|
+
status: 'pending',
|
154
|
+
promise: noopPromise,
|
155
|
+
onCommit: noopOnCommit,
|
156
|
+
suspenseResource: null,
|
157
|
+
};
|
158
|
+
}
|
159
|
+
|
75
160
|
function getQueryResultOrFetchQuery_REACT_CACHE(
|
76
161
|
environment: IEnvironment,
|
77
162
|
queryOperationDescriptor: OperationDescriptor,
|
78
|
-
|
79
|
-
|
80
|
-
|
163
|
+
options?: {|
|
164
|
+
fetchPolicy?: FetchPolicy,
|
165
|
+
renderPolicy?: RenderPolicy,
|
166
|
+
fetchKey?: ?string | ?number,
|
167
|
+
fetchObservable?: Observable<GraphQLResponse>,
|
168
|
+
|},
|
169
|
+
): [QueryResult, QueryCacheCommitable] {
|
170
|
+
const fetchPolicy = options?.fetchPolicy ?? DEFAULT_FETCH_POLICY;
|
81
171
|
const renderPolicy =
|
82
|
-
|
172
|
+
options?.renderPolicy ?? environment.UNSTABLE_getDefaultRenderPolicy();
|
83
173
|
|
84
174
|
const cache = getCacheForType(createQueryCache);
|
85
175
|
|
@@ -87,43 +177,111 @@ function getQueryResultOrFetchQuery_REACT_CACHE(
|
|
87
177
|
queryOperationDescriptor,
|
88
178
|
fetchPolicy,
|
89
179
|
renderPolicy,
|
180
|
+
options?.fetchKey,
|
90
181
|
);
|
91
182
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
);
|
104
|
-
|
105
|
-
|
106
|
-
//
|
107
|
-
//
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
183
|
+
const initialEntry = cache.get(environment, cacheKey);
|
184
|
+
|
185
|
+
function updateCache(
|
186
|
+
updater: QueryCacheEntryStatus => QueryCacheEntryStatus,
|
187
|
+
) {
|
188
|
+
let currentEntry = cache.get(environment, cacheKey);
|
189
|
+
if (!currentEntry) {
|
190
|
+
currentEntry = makeInitialCacheEntry();
|
191
|
+
cache.set(environment, cacheKey, currentEntry);
|
192
|
+
}
|
193
|
+
// $FlowExpectedError[prop-missing] Extra properties are passed in -- this is fine
|
194
|
+
const newStatus: {...} = updater(currentEntry);
|
195
|
+
// $FlowExpectedError[cannot-spread-inexact] Flow cannot understand that this is valid...
|
196
|
+
cache.set(environment, cacheKey, {...currentEntry, ...newStatus});
|
197
|
+
// ... but we can because QueryCacheEntry spreads QueryCacheEntryStatus, so spreading
|
198
|
+
// a QueryCacheEntryStatus into a QueryCacheEntry will result in a valid QueryCacheEntry.
|
199
|
+
}
|
200
|
+
|
201
|
+
// Initiate a query to fetch the data if needed:
|
202
|
+
if (RelayFeatureFlags.USE_REACT_CACHE_LEGACY_TIMEOUTS) {
|
203
|
+
let entry;
|
204
|
+
if (initialEntry === undefined) {
|
205
|
+
onCacheMiss(
|
206
|
+
environment,
|
207
|
+
queryOperationDescriptor,
|
208
|
+
fetchPolicy,
|
209
|
+
renderPolicy,
|
210
|
+
updateCache,
|
211
|
+
options?.fetchObservable,
|
212
|
+
);
|
213
|
+
const createdEntry = cache.get(environment, cacheKey);
|
214
|
+
invariant(
|
215
|
+
createdEntry !== undefined,
|
216
|
+
'An entry should have been created by onCacheMiss. This is a bug in Relay.',
|
217
|
+
);
|
218
|
+
entry = createdEntry;
|
219
|
+
} else {
|
220
|
+
entry = initialEntry;
|
221
|
+
}
|
222
|
+
if (!entry.suspenseResource) {
|
223
|
+
entry.suspenseResource = new SuspenseResource(() => {
|
224
|
+
const retention = environment.retain(queryOperationDescriptor);
|
225
|
+
return {
|
226
|
+
dispose: () => {
|
227
|
+
retention.dispose();
|
228
|
+
cache.delete(environment, cacheKey);
|
229
|
+
},
|
230
|
+
};
|
231
|
+
});
|
232
|
+
}
|
233
|
+
if (entry.onCommit === noopOnCommit) {
|
234
|
+
entry.onCommit = () => {
|
235
|
+
invariant(
|
236
|
+
entry.suspenseResource,
|
237
|
+
'SuspenseResource should have been initialized. This is a bug in Relay.',
|
238
|
+
);
|
239
|
+
const retention = entry.suspenseResource.permanentRetain(environment);
|
240
|
+
return () => {
|
241
|
+
retention.dispose();
|
242
|
+
};
|
243
|
+
};
|
244
|
+
}
|
245
|
+
entry.suspenseResource.temporaryRetain(environment);
|
246
|
+
} else {
|
247
|
+
if (initialEntry === undefined) {
|
248
|
+
// This is the behavior we eventually want: We retain the query until the
|
249
|
+
// presiding Cache component unmounts, at which point the AbortSignal
|
250
|
+
// will be triggered.
|
251
|
+
onCacheMiss(
|
252
|
+
environment,
|
253
|
+
queryOperationDescriptor,
|
254
|
+
fetchPolicy,
|
255
|
+
renderPolicy,
|
256
|
+
updateCache,
|
257
|
+
options?.fetchObservable,
|
258
|
+
);
|
259
|
+
|
260
|
+
// Since this is the first time rendering, retain the query. React will
|
261
|
+
// trigger the abort signal when this cache entry is no longer needed.
|
262
|
+
const retention = environment.retain(queryOperationDescriptor);
|
263
|
+
|
264
|
+
const dispose = () => {
|
113
265
|
retention.dispose();
|
114
|
-
cache.delete(cacheKey);
|
115
|
-
}
|
116
|
-
|
117
|
-
|
266
|
+
cache.delete(environment, cacheKey);
|
267
|
+
};
|
268
|
+
const abortSignal = getCacheSignal();
|
269
|
+
abortSignal.addEventListener('abort', dispose, {once: true});
|
270
|
+
}
|
118
271
|
}
|
119
272
|
|
273
|
+
const entry = cache.get(environment, cacheKey); // could be a different entry now if synchronously resolved
|
274
|
+
invariant(
|
275
|
+
entry !== undefined,
|
276
|
+
'An entry should have been created by onCacheMiss. This is a bug in Relay.',
|
277
|
+
);
|
120
278
|
switch (entry.status) {
|
121
279
|
case 'pending':
|
122
280
|
throw entry.promise;
|
123
281
|
case 'rejected':
|
124
282
|
throw entry.error;
|
125
283
|
case 'resolved':
|
126
|
-
return entry.result;
|
284
|
+
return [entry.result, entry.onCommit];
|
127
285
|
}
|
128
286
|
invariant(false, 'switch statement should be exhaustive');
|
129
287
|
}
|
@@ -133,8 +291,9 @@ function onCacheMiss(
|
|
133
291
|
operation: OperationDescriptor,
|
134
292
|
fetchPolicy: FetchPolicy,
|
135
293
|
renderPolicy: RenderPolicy,
|
136
|
-
updateCache:
|
137
|
-
|
294
|
+
updateCache: ((QueryCacheEntryStatus) => QueryCacheEntryStatus) => void,
|
295
|
+
customFetchObservable?: Observable<GraphQLResponse>,
|
296
|
+
): void {
|
138
297
|
// NB: Besides checking if the data is available, calling `check` will write missing
|
139
298
|
// data to the store using any missing data handlers specified in the environment.
|
140
299
|
const queryAvailability = environment.check(operation);
|
@@ -169,25 +328,46 @@ function onCacheMiss(
|
|
169
328
|
}
|
170
329
|
}
|
171
330
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
331
|
+
if (shouldFetch) {
|
332
|
+
executeOperationAndKeepUpToDate(
|
333
|
+
environment,
|
334
|
+
operation,
|
335
|
+
updateCache,
|
336
|
+
customFetchObservable,
|
337
|
+
);
|
338
|
+
updateCache(existing => {
|
339
|
+
switch (existing.status) {
|
340
|
+
case 'resolved':
|
341
|
+
return existing;
|
342
|
+
case 'rejected':
|
343
|
+
return existing;
|
344
|
+
case 'pending':
|
345
|
+
return shouldRenderNow
|
346
|
+
? {
|
347
|
+
status: 'resolved',
|
348
|
+
result: constructQueryResult(operation),
|
349
|
+
}
|
350
|
+
: existing;
|
351
|
+
}
|
352
|
+
});
|
177
353
|
} else {
|
178
354
|
invariant(
|
179
|
-
|
180
|
-
'Should either fetch or render
|
355
|
+
shouldRenderNow,
|
356
|
+
'Should either fetch or be willing to render. This is a bug in Relay.',
|
181
357
|
);
|
182
|
-
|
358
|
+
updateCache(_existing => ({
|
359
|
+
status: 'resolved',
|
360
|
+
result: constructQueryResult(operation),
|
361
|
+
}));
|
183
362
|
}
|
184
363
|
}
|
185
364
|
|
186
365
|
function executeOperationAndKeepUpToDate(
|
187
366
|
environment: IEnvironment,
|
188
367
|
operation: OperationDescriptor,
|
189
|
-
updateCache:
|
190
|
-
|
368
|
+
updateCache: ((QueryCacheEntryStatus) => QueryCacheEntryStatus) => void,
|
369
|
+
customFetchObservable?: Observable<GraphQLResponse>,
|
370
|
+
) {
|
191
371
|
let resolvePromise;
|
192
372
|
const promise = new Promise(r => {
|
193
373
|
resolvePromise = r;
|
@@ -198,12 +378,16 @@ function executeOperationAndKeepUpToDate(
|
|
198
378
|
let isFirstPayload = true;
|
199
379
|
|
200
380
|
// FIXME We may still need to cancel network requests for live queries.
|
201
|
-
const fetchObservable =
|
381
|
+
const fetchObservable =
|
382
|
+
customFetchObservable ?? fetchQueryInternal(environment, operation);
|
202
383
|
fetchObservable.subscribe({
|
203
384
|
start: subscription => {},
|
204
385
|
error: error => {
|
205
386
|
if (isFirstPayload) {
|
206
|
-
updateCache(
|
387
|
+
updateCache(_existing => ({
|
388
|
+
status: 'rejected',
|
389
|
+
error,
|
390
|
+
}));
|
207
391
|
} else {
|
208
392
|
// TODO:T92030819 Remove this warning and actually throw the network error
|
209
393
|
// To complete this task we need to have a way of precisely tracking suspendable points
|
@@ -220,24 +404,21 @@ function executeOperationAndKeepUpToDate(
|
|
220
404
|
},
|
221
405
|
next: response => {
|
222
406
|
// Stop suspending on the first payload because of streaming, defer, etc.
|
223
|
-
updateCache({
|
224
|
-
status: 'resolved',
|
225
|
-
result: constructQueryResult(operation),
|
226
|
-
});
|
227
|
-
resolvePromise();
|
228
|
-
isFirstPayload = false;
|
229
|
-
},
|
230
|
-
complete: () => {
|
231
|
-
updateCache({
|
407
|
+
updateCache(_existing => ({
|
232
408
|
status: 'resolved',
|
233
409
|
result: constructQueryResult(operation),
|
234
|
-
});
|
410
|
+
}));
|
235
411
|
resolvePromise();
|
236
412
|
isFirstPayload = false;
|
237
413
|
},
|
238
414
|
});
|
239
415
|
|
240
|
-
|
416
|
+
// If the above subscription yields a value synchronously, then one of the updates
|
417
|
+
// above will have already happened and we'll now be in a resolved or rejected state.
|
418
|
+
// But in the usual case, we save the promise to the entry here:
|
419
|
+
updateCache(existing =>
|
420
|
+
existing.status === 'pending' ? {status: 'pending', promise} : existing,
|
421
|
+
);
|
241
422
|
}
|
242
423
|
|
243
424
|
module.exports = getQueryResultOrFetchQuery_REACT_CACHE;
|