@isograph/react 0.4.2 → 0.5.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/.turbo/turbo-compile-libs.log +2 -2
- package/dist/core/FragmentReference.d.ts +4 -2
- package/dist/core/FragmentReference.d.ts.map +1 -1
- package/dist/core/FragmentReference.js +2 -2
- package/dist/core/IsographEnvironment.d.ts +19 -11
- package/dist/core/IsographEnvironment.d.ts.map +1 -1
- package/dist/core/IsographEnvironment.js +27 -2
- package/dist/core/PromiseWrapper.d.ts +13 -7
- package/dist/core/PromiseWrapper.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.js +5 -0
- package/dist/core/brand.d.ts +17 -0
- package/dist/core/brand.d.ts.map +1 -1
- package/dist/core/cache.d.ts +10 -7
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +102 -74
- package/dist/core/check.d.ts +8 -4
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/check.js +10 -7
- package/dist/core/componentCache.d.ts +1 -1
- package/dist/core/componentCache.d.ts.map +1 -1
- package/dist/core/componentCache.js +6 -4
- package/dist/core/entrypoint.d.ts +17 -7
- package/dist/core/entrypoint.d.ts.map +1 -1
- package/dist/core/garbageCollection.d.ts +8 -2
- package/dist/core/garbageCollection.d.ts.map +1 -1
- package/dist/core/garbageCollection.js +36 -14
- package/dist/core/logging.d.ts +16 -3
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.d.ts +4 -2
- package/dist/core/makeNetworkRequest.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.js +115 -38
- package/dist/core/optimisticProxy.d.ts +59 -0
- package/dist/core/optimisticProxy.d.ts.map +1 -0
- package/dist/core/optimisticProxy.js +399 -0
- package/dist/core/read.d.ts +3 -2
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +158 -123
- package/dist/core/reader.d.ts +10 -5
- package/dist/core/reader.d.ts.map +1 -1
- package/dist/core/startUpdate.d.ts +3 -2
- package/dist/core/startUpdate.d.ts.map +1 -1
- package/dist/core/startUpdate.js +35 -36
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/loadable-hooks/useClientSideDefer.d.ts +9 -4
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
- package/dist/loadable-hooks/useClientSideDefer.js +34 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +5 -3
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.js +27 -13
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.js +1 -1
- package/dist/react/FragmentReader.d.ts +2 -1
- package/dist/react/FragmentReader.d.ts.map +1 -1
- package/dist/react/FragmentRenderer.d.ts +2 -1
- package/dist/react/FragmentRenderer.d.ts.map +1 -1
- package/dist/react/LoadableFieldReader.d.ts +9 -3
- package/dist/react/LoadableFieldReader.d.ts.map +1 -1
- package/dist/react/LoadableFieldReader.js +40 -1
- package/dist/react/LoadableFieldRenderer.d.ts +9 -3
- package/dist/react/LoadableFieldRenderer.d.ts.map +1 -1
- package/dist/react/LoadableFieldRenderer.js +36 -1
- package/dist/react/useImperativeReference.d.ts +4 -3
- package/dist/react/useImperativeReference.d.ts.map +1 -1
- package/dist/react/useImperativeReference.js +3 -5
- package/dist/react/useLazyReference.d.ts +2 -1
- package/dist/react/useLazyReference.d.ts.map +1 -1
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
- package/dist/react/useReadAndSubscribe.js +1 -3
- package/dist/react/useResult.d.ts.map +1 -1
- package/dist/react/useResult.js +6 -5
- package/package.json +16 -17
- package/src/core/FragmentReference.ts +10 -4
- package/src/core/IsographEnvironment.ts +59 -13
- package/src/core/PromiseWrapper.ts +14 -7
- package/src/core/areEqualWithDeepComparison.ts +5 -0
- package/src/core/brand.ts +18 -0
- package/src/core/cache.ts +186 -91
- package/src/core/check.ts +21 -10
- package/src/core/componentCache.ts +8 -4
- package/src/core/entrypoint.ts +35 -6
- package/src/core/garbageCollection.ts +61 -19
- package/src/core/logging.ts +15 -3
- package/src/core/makeNetworkRequest.ts +307 -74
- package/src/core/optimisticProxy.ts +563 -0
- package/src/core/read.ts +233 -163
- package/src/core/reader.ts +11 -7
- package/src/core/startUpdate.ts +47 -32
- package/src/index.ts +2 -1
- package/src/loadable-hooks/useClientSideDefer.ts +76 -26
- package/src/loadable-hooks/useConnectionSpecPagination.ts +34 -17
- package/src/loadable-hooks/useImperativeLoadableField.ts +2 -2
- package/src/loadable-hooks/useSkipLimitPagination.ts +2 -3
- package/src/react/FragmentReader.tsx +3 -1
- package/src/react/FragmentRenderer.tsx +8 -1
- package/src/react/LoadableFieldReader.tsx +123 -12
- package/src/react/LoadableFieldRenderer.tsx +122 -12
- package/src/react/useImperativeReference.ts +20 -11
- package/src/react/useLazyReference.ts +17 -6
- package/src/react/useReadAndSubscribe.ts +1 -8
- package/src/react/useResult.ts +9 -11
- package/src/tests/__isograph/Node/__link/output_type.ts +3 -0
- package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +3 -3
- package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +4 -4
- package/src/tests/__isograph/Query/linkedUpdate/raw_response_type.ts +13 -0
- package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +6 -6
- package/src/tests/__isograph/Query/meName/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/meName/raw_response_type.ts +7 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +2 -2
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/meNameSuccessor/raw_response_type.ts +14 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +4 -4
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/nodeField/raw_response_type.ts +7 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +2 -2
- package/src/tests/__isograph/Query/normalizeUndefinedField/entrypoint.ts +33 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/normalization_ast.ts +25 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/output_type.ts +3 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/param_type.ts +9 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/query_text.ts +6 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/raw_response_type.ts +7 -0
- package/src/tests/__isograph/Query/normalizeUndefinedField/resolver_reader.ts +38 -0
- package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/startUpdate/raw_response_type.ts +8 -0
- package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +3 -3
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +3 -1
- package/src/tests/__isograph/Query/subquery/raw_response_type.ts +9 -0
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +3 -3
- package/src/tests/__isograph/iso.ts +11 -1
- package/src/tests/garbageCollection.test.ts +8 -5
- package/src/tests/meNameSuccessor.ts +6 -3
- package/src/tests/nodeQuery.ts +4 -2
- package/src/tests/normalizeData.test.ts +89 -15
- package/src/tests/optimisticProxy.test.ts +860 -0
- package/src/tests/startUpdate.test.ts +8 -6
- package/vitest.config.ts +10 -1
- package/src/tests/__isograph/Economist/link/output_type.ts +0 -2
- package/src/tests/__isograph/Node/link/output_type.ts +0 -3
package/src/core/cache.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
DataTypeValue,
|
|
27
27
|
FragmentSubscription,
|
|
28
28
|
getLink,
|
|
29
|
+
getOrLoadReaderWithRefetchQueries,
|
|
29
30
|
ROOT_ID,
|
|
30
31
|
StoreLink,
|
|
31
32
|
StoreRecord,
|
|
@@ -33,8 +34,15 @@ import {
|
|
|
33
34
|
type TypeName,
|
|
34
35
|
} from './IsographEnvironment';
|
|
35
36
|
import { logMessage } from './logging';
|
|
36
|
-
import {
|
|
37
|
-
|
|
37
|
+
import {
|
|
38
|
+
maybeMakeNetworkRequest,
|
|
39
|
+
retainQueryWithoutMakingNetworkRequest,
|
|
40
|
+
} from './makeNetworkRequest';
|
|
41
|
+
import {
|
|
42
|
+
addNetworkResponseStoreLayer,
|
|
43
|
+
getMutableStoreRecordProxy,
|
|
44
|
+
type StoreLayerWithData,
|
|
45
|
+
} from './optimisticProxy';
|
|
38
46
|
import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
|
|
39
47
|
import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
|
|
40
48
|
import { Argument, ArgumentValue } from './util';
|
|
@@ -65,7 +73,7 @@ export function stableCopy<T>(value: T): T {
|
|
|
65
73
|
if (!value || typeof value !== 'object') {
|
|
66
74
|
return value;
|
|
67
75
|
}
|
|
68
|
-
if (
|
|
76
|
+
if (isArray(value)) {
|
|
69
77
|
// @ts-ignore
|
|
70
78
|
return value.map(stableCopy);
|
|
71
79
|
}
|
|
@@ -82,15 +90,17 @@ export function getOrCreateCacheForArtifact<
|
|
|
82
90
|
TReadFromStore extends UnknownTReadFromStore,
|
|
83
91
|
TClientFieldValue,
|
|
84
92
|
TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
|
|
93
|
+
TRawResponseType extends NetworkResponseObject,
|
|
85
94
|
>(
|
|
86
95
|
environment: IsographEnvironment,
|
|
87
96
|
entrypoint: IsographEntrypoint<
|
|
88
97
|
TReadFromStore,
|
|
89
98
|
TClientFieldValue,
|
|
90
|
-
TNormalizationAst
|
|
99
|
+
TNormalizationAst,
|
|
100
|
+
TRawResponseType
|
|
91
101
|
>,
|
|
92
102
|
variables: ExtractParameters<TReadFromStore>,
|
|
93
|
-
fetchOptions?: FetchOptions<TClientFieldValue>,
|
|
103
|
+
fetchOptions?: FetchOptions<TClientFieldValue, TRawResponseType>,
|
|
94
104
|
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
|
95
105
|
let cacheKey = '';
|
|
96
106
|
switch (entrypoint.networkRequestInfo.operation.kind) {
|
|
@@ -106,11 +116,11 @@ export function getOrCreateCacheForArtifact<
|
|
|
106
116
|
break;
|
|
107
117
|
}
|
|
108
118
|
const factory = () => {
|
|
109
|
-
const readerWithRefetchQueries =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
const { fieldName, readerArtifactKind, readerWithRefetchQueries } =
|
|
120
|
+
getOrLoadReaderWithRefetchQueries(
|
|
121
|
+
environment,
|
|
122
|
+
entrypoint.readerWithRefetchQueries,
|
|
123
|
+
);
|
|
114
124
|
const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
|
|
115
125
|
environment,
|
|
116
126
|
entrypoint,
|
|
@@ -125,6 +135,8 @@ export function getOrCreateCacheForArtifact<
|
|
|
125
135
|
{
|
|
126
136
|
kind: 'FragmentReference',
|
|
127
137
|
readerWithRefetchQueries,
|
|
138
|
+
fieldName,
|
|
139
|
+
readerArtifactKind,
|
|
128
140
|
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
129
141
|
variables,
|
|
130
142
|
networkRequest: networkRequest,
|
|
@@ -141,26 +153,26 @@ export type NetworkResponseValue =
|
|
|
141
153
|
| NetworkResponseScalarValue
|
|
142
154
|
| null
|
|
143
155
|
| NetworkResponseObject
|
|
144
|
-
| (NetworkResponseObject | null)[]
|
|
145
|
-
| (NetworkResponseScalarValue | null)[];
|
|
156
|
+
| readonly (NetworkResponseObject | null)[]
|
|
157
|
+
| readonly (NetworkResponseScalarValue | null)[];
|
|
146
158
|
|
|
147
159
|
export type NetworkResponseObject = {
|
|
148
160
|
// N.B. undefined is here to support optional id's, but
|
|
149
161
|
// undefined should not *actually* be present in the network response.
|
|
150
|
-
[index: string]: undefined | NetworkResponseValue;
|
|
151
|
-
id?: DataId;
|
|
152
|
-
__typename?: TypeName;
|
|
162
|
+
readonly [index: string]: undefined | NetworkResponseValue;
|
|
163
|
+
readonly id?: DataId;
|
|
164
|
+
readonly __typename?: TypeName;
|
|
153
165
|
};
|
|
154
166
|
|
|
155
167
|
export function normalizeData(
|
|
156
168
|
environment: IsographEnvironment,
|
|
169
|
+
storeLayer: StoreLayerWithData,
|
|
157
170
|
normalizationAst: NormalizationAstNodes,
|
|
158
171
|
networkResponse: NetworkResponseObject,
|
|
159
172
|
variables: Variables,
|
|
160
173
|
root: StoreLink,
|
|
174
|
+
encounteredIds: EncounteredIds,
|
|
161
175
|
): EncounteredIds {
|
|
162
|
-
const encounteredIds: EncounteredIds = new Map();
|
|
163
|
-
|
|
164
176
|
logMessage(environment, () => ({
|
|
165
177
|
kind: 'AboutToNormalize',
|
|
166
178
|
normalizationAst,
|
|
@@ -168,11 +180,11 @@ export function normalizeData(
|
|
|
168
180
|
variables,
|
|
169
181
|
}));
|
|
170
182
|
|
|
171
|
-
const
|
|
172
|
-
const newStoreRecord = (recordsById[root.__link] ??= {});
|
|
183
|
+
const newStoreRecord = getMutableStoreRecordProxy(storeLayer, root);
|
|
173
184
|
|
|
174
185
|
normalizeDataIntoRecord(
|
|
175
186
|
environment,
|
|
187
|
+
storeLayer,
|
|
176
188
|
normalizationAst,
|
|
177
189
|
networkResponse,
|
|
178
190
|
newStoreRecord,
|
|
@@ -181,13 +193,6 @@ export function normalizeData(
|
|
|
181
193
|
encounteredIds,
|
|
182
194
|
);
|
|
183
195
|
|
|
184
|
-
logMessage(environment, () => ({
|
|
185
|
-
kind: 'AfterNormalization',
|
|
186
|
-
store: environment.store,
|
|
187
|
-
encounteredIds,
|
|
188
|
-
}));
|
|
189
|
-
|
|
190
|
-
callSubscriptions(environment, encounteredIds);
|
|
191
196
|
return encounteredIds;
|
|
192
197
|
}
|
|
193
198
|
|
|
@@ -217,7 +222,6 @@ export function subscribeToAnyChangesToRecord(
|
|
|
217
222
|
return () => environment.subscriptions.delete(subscription);
|
|
218
223
|
}
|
|
219
224
|
|
|
220
|
-
// TODO we should re-read and call callback if the value has changed
|
|
221
225
|
export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
|
222
226
|
environment: IsographEnvironment,
|
|
223
227
|
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
|
@@ -234,6 +238,13 @@ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
|
|
234
238
|
fragmentReference,
|
|
235
239
|
readerAst,
|
|
236
240
|
};
|
|
241
|
+
|
|
242
|
+
// subscribe is called in an effect. (We should actually subscribe during the
|
|
243
|
+
// initial render.) Because it's called in an effect, we might have missed some
|
|
244
|
+
// changes since the initial render! So, at this point, we re-read and call the
|
|
245
|
+
// subscription (i.e. re-render) if the fragment data has changed.
|
|
246
|
+
callSubscriptionIfDataChanged(environment, fragmentSubscription);
|
|
247
|
+
|
|
237
248
|
environment.subscriptions.add(fragmentSubscription);
|
|
238
249
|
return () => environment.subscriptions.delete(fragmentSubscription);
|
|
239
250
|
}
|
|
@@ -292,53 +303,7 @@ export function callSubscriptions(
|
|
|
292
303
|
subscription.encounteredDataAndRecords.encounteredRecords,
|
|
293
304
|
)
|
|
294
305
|
) {
|
|
295
|
-
|
|
296
|
-
environment,
|
|
297
|
-
subscription.fragmentReference,
|
|
298
|
-
// Is this wrong?
|
|
299
|
-
// Reasons to think no:
|
|
300
|
-
// - we are only updating the read-out value, and the network
|
|
301
|
-
// options only affect whether we throw.
|
|
302
|
-
// - the component will re-render, and re-throw on its own, anyway.
|
|
303
|
-
//
|
|
304
|
-
// Reasons to think not:
|
|
305
|
-
// - it seems more efficient to suspend here and not update state,
|
|
306
|
-
// if we expect that the component will just throw anyway
|
|
307
|
-
// - consistency
|
|
308
|
-
// - it's also weird, this is called from makeNetworkRequest, where
|
|
309
|
-
// we don't currently pass network request options
|
|
310
|
-
{
|
|
311
|
-
suspendIfInFlight: false,
|
|
312
|
-
throwOnNetworkError: false,
|
|
313
|
-
},
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
const mergedItem = mergeObjectsUsingReaderAst(
|
|
317
|
-
subscription.readerAst,
|
|
318
|
-
subscription.encounteredDataAndRecords.item,
|
|
319
|
-
newEncounteredDataAndRecords.item,
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
logMessage(environment, () => ({
|
|
323
|
-
kind: 'DeepEqualityCheck',
|
|
324
|
-
fragmentReference: subscription.fragmentReference,
|
|
325
|
-
old: subscription.encounteredDataAndRecords.item,
|
|
326
|
-
new: newEncounteredDataAndRecords.item,
|
|
327
|
-
deeplyEqual:
|
|
328
|
-
mergedItem === subscription.encounteredDataAndRecords.item,
|
|
329
|
-
}));
|
|
330
|
-
|
|
331
|
-
if (mergedItem !== subscription.encounteredDataAndRecords.item) {
|
|
332
|
-
logAnyError(
|
|
333
|
-
environment,
|
|
334
|
-
{ situation: 'calling FragmentSubscription callback' },
|
|
335
|
-
() => {
|
|
336
|
-
subscription.callback(newEncounteredDataAndRecords);
|
|
337
|
-
},
|
|
338
|
-
);
|
|
339
|
-
subscription.encounteredDataAndRecords =
|
|
340
|
-
newEncounteredDataAndRecords;
|
|
341
|
-
}
|
|
306
|
+
callSubscriptionIfDataChanged(environment, subscription);
|
|
342
307
|
}
|
|
343
308
|
return;
|
|
344
309
|
}
|
|
@@ -375,6 +340,59 @@ export function callSubscriptions(
|
|
|
375
340
|
);
|
|
376
341
|
}
|
|
377
342
|
|
|
343
|
+
function callSubscriptionIfDataChanged<
|
|
344
|
+
TReadFromStore extends UnknownTReadFromStore,
|
|
345
|
+
>(
|
|
346
|
+
environment: IsographEnvironment,
|
|
347
|
+
subscription: FragmentSubscription<TReadFromStore>,
|
|
348
|
+
) {
|
|
349
|
+
const newEncounteredDataAndRecords = readButDoNotEvaluate(
|
|
350
|
+
environment,
|
|
351
|
+
subscription.fragmentReference,
|
|
352
|
+
// Is this wrong?
|
|
353
|
+
// Reasons to think no:
|
|
354
|
+
// - we are only updating the read-out value, and the network
|
|
355
|
+
// options only affect whether we throw.
|
|
356
|
+
// - the component will re-render, and re-throw on its own, anyway.
|
|
357
|
+
//
|
|
358
|
+
// Reasons to think not:
|
|
359
|
+
// - it seems more efficient to suspend here and not update state,
|
|
360
|
+
// if we expect that the component will just throw anyway
|
|
361
|
+
// - consistency
|
|
362
|
+
// - it's also weird, this is called from makeNetworkRequest, where
|
|
363
|
+
// we don't currently pass network request options
|
|
364
|
+
{
|
|
365
|
+
suspendIfInFlight: false,
|
|
366
|
+
throwOnNetworkError: false,
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const mergedItem = mergeObjectsUsingReaderAst(
|
|
371
|
+
subscription.readerAst,
|
|
372
|
+
subscription.encounteredDataAndRecords.item,
|
|
373
|
+
newEncounteredDataAndRecords.item,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
logMessage(environment, () => ({
|
|
377
|
+
kind: 'DeepEqualityCheck',
|
|
378
|
+
fragmentReference: subscription.fragmentReference,
|
|
379
|
+
old: subscription.encounteredDataAndRecords.item,
|
|
380
|
+
new: newEncounteredDataAndRecords.item,
|
|
381
|
+
deeplyEqual: mergedItem === subscription.encounteredDataAndRecords.item,
|
|
382
|
+
}));
|
|
383
|
+
|
|
384
|
+
if (mergedItem !== subscription.encounteredDataAndRecords.item) {
|
|
385
|
+
logAnyError(
|
|
386
|
+
environment,
|
|
387
|
+
{ situation: 'calling FragmentSubscription callback' },
|
|
388
|
+
() => {
|
|
389
|
+
subscription.callback(newEncounteredDataAndRecords);
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
subscription.encounteredDataAndRecords = newEncounteredDataAndRecords;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
378
396
|
function hasOverlappingIds(
|
|
379
397
|
ids1: EncounteredIds,
|
|
380
398
|
ids2: EncounteredIds,
|
|
@@ -408,6 +426,7 @@ export type EncounteredIds = Map<TypeName, Set<DataId>>;
|
|
|
408
426
|
*/
|
|
409
427
|
function normalizeDataIntoRecord(
|
|
410
428
|
environment: IsographEnvironment,
|
|
429
|
+
storeLayer: StoreLayerWithData,
|
|
411
430
|
normalizationAst: NormalizationAstNodes,
|
|
412
431
|
networkResponseParentRecord: NetworkResponseObject,
|
|
413
432
|
targetParentRecord: StoreRecord,
|
|
@@ -432,6 +451,7 @@ function normalizeDataIntoRecord(
|
|
|
432
451
|
case 'Linked': {
|
|
433
452
|
const linkedFieldResultedInChange = normalizeLinkedField(
|
|
434
453
|
environment,
|
|
454
|
+
storeLayer,
|
|
435
455
|
normalizationNode,
|
|
436
456
|
networkResponseParentRecord,
|
|
437
457
|
targetParentRecord,
|
|
@@ -446,6 +466,7 @@ function normalizeDataIntoRecord(
|
|
|
446
466
|
case 'InlineFragment': {
|
|
447
467
|
const inlineFragmentResultedInChange = normalizeInlineFragment(
|
|
448
468
|
environment,
|
|
469
|
+
storeLayer,
|
|
449
470
|
normalizationNode,
|
|
450
471
|
networkResponseParentRecord,
|
|
451
472
|
targetParentRecord,
|
|
@@ -495,12 +516,14 @@ function normalizeScalarField(
|
|
|
495
516
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
|
496
517
|
const networkResponseData = networkResponseParentRecord[networkResponseKey];
|
|
497
518
|
const parentRecordKey = getParentRecordKey(astNode, variables);
|
|
519
|
+
const existingValue = targetStoreRecord[parentRecordKey];
|
|
498
520
|
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
521
|
+
if (networkResponseData == null) {
|
|
522
|
+
targetStoreRecord[parentRecordKey] = null;
|
|
523
|
+
return existingValue !== null;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (isScalarOrEmptyArray(networkResponseData)) {
|
|
504
527
|
targetStoreRecord[parentRecordKey] = networkResponseData;
|
|
505
528
|
return existingValue !== networkResponseData;
|
|
506
529
|
} else {
|
|
@@ -508,11 +531,16 @@ function normalizeScalarField(
|
|
|
508
531
|
}
|
|
509
532
|
}
|
|
510
533
|
|
|
534
|
+
export function isArray(value: unknown): value is readonly unknown[] {
|
|
535
|
+
return Array.isArray(value);
|
|
536
|
+
}
|
|
537
|
+
|
|
511
538
|
/**
|
|
512
539
|
* Mutate targetParentRecord with a given linked field ast node.
|
|
513
540
|
*/
|
|
514
541
|
function normalizeLinkedField(
|
|
515
542
|
environment: IsographEnvironment,
|
|
543
|
+
storeLayer: StoreLayerWithData,
|
|
516
544
|
astNode: NormalizationLinkedField,
|
|
517
545
|
networkResponseParentRecord: NetworkResponseObject,
|
|
518
546
|
targetParentRecord: StoreRecord,
|
|
@@ -539,7 +567,7 @@ function normalizeLinkedField(
|
|
|
539
567
|
);
|
|
540
568
|
}
|
|
541
569
|
|
|
542
|
-
if (
|
|
570
|
+
if (isArray(networkResponseData)) {
|
|
543
571
|
// TODO check astNode.plural or the like
|
|
544
572
|
const dataIds: (StoreLink | null)[] = [];
|
|
545
573
|
for (let i = 0; i < networkResponseData.length; i++) {
|
|
@@ -550,6 +578,7 @@ function normalizeLinkedField(
|
|
|
550
578
|
}
|
|
551
579
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
|
552
580
|
environment,
|
|
581
|
+
storeLayer,
|
|
553
582
|
astNode,
|
|
554
583
|
networkResponseObject,
|
|
555
584
|
targetParentRecordLink,
|
|
@@ -576,6 +605,7 @@ function normalizeLinkedField(
|
|
|
576
605
|
} else {
|
|
577
606
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
|
578
607
|
environment,
|
|
608
|
+
storeLayer,
|
|
579
609
|
astNode,
|
|
580
610
|
networkResponseData,
|
|
581
611
|
targetParentRecordLink,
|
|
@@ -609,6 +639,7 @@ function normalizeLinkedField(
|
|
|
609
639
|
*/
|
|
610
640
|
function normalizeInlineFragment(
|
|
611
641
|
environment: IsographEnvironment,
|
|
642
|
+
storeLayer: StoreLayerWithData,
|
|
612
643
|
astNode: NormalizationInlineFragment,
|
|
613
644
|
networkResponseParentRecord: NetworkResponseObject,
|
|
614
645
|
targetParentRecord: StoreRecord,
|
|
@@ -620,6 +651,7 @@ function normalizeInlineFragment(
|
|
|
620
651
|
if (networkResponseParentRecord[TYPENAME_FIELD_NAME] === typeToRefineTo) {
|
|
621
652
|
const hasBeenModified = normalizeDataIntoRecord(
|
|
622
653
|
environment,
|
|
654
|
+
storeLayer,
|
|
623
655
|
astNode.selections,
|
|
624
656
|
networkResponseParentRecord,
|
|
625
657
|
targetParentRecord,
|
|
@@ -636,7 +668,7 @@ function dataIdsAreTheSame(
|
|
|
636
668
|
existingValue: DataTypeValue,
|
|
637
669
|
newDataIds: (StoreLink | null)[],
|
|
638
670
|
): boolean {
|
|
639
|
-
if (
|
|
671
|
+
if (isArray(existingValue)) {
|
|
640
672
|
if (newDataIds.length !== existingValue.length) {
|
|
641
673
|
return false;
|
|
642
674
|
}
|
|
@@ -657,6 +689,7 @@ function dataIdsAreTheSame(
|
|
|
657
689
|
|
|
658
690
|
function normalizeNetworkResponseObject(
|
|
659
691
|
environment: IsographEnvironment,
|
|
692
|
+
storeLayer: StoreLayerWithData,
|
|
660
693
|
astNode: NormalizationLinkedField,
|
|
661
694
|
networkResponseData: NetworkResponseObject,
|
|
662
695
|
targetParentRecordLink: StoreLink,
|
|
@@ -681,15 +714,16 @@ function normalizeNetworkResponseObject(
|
|
|
681
714
|
);
|
|
682
715
|
}
|
|
683
716
|
|
|
684
|
-
const
|
|
685
|
-
const newStoreRecord = (
|
|
717
|
+
const link = { __link: newStoreRecordId, __typename };
|
|
718
|
+
const newStoreRecord = getMutableStoreRecordProxy(storeLayer, link);
|
|
686
719
|
|
|
687
720
|
normalizeDataIntoRecord(
|
|
688
721
|
environment,
|
|
722
|
+
storeLayer,
|
|
689
723
|
astNode.selections,
|
|
690
724
|
networkResponseData,
|
|
691
725
|
newStoreRecord,
|
|
692
|
-
|
|
726
|
+
link,
|
|
693
727
|
variables,
|
|
694
728
|
mutableEncounteredIds,
|
|
695
729
|
);
|
|
@@ -698,12 +732,13 @@ function normalizeNetworkResponseObject(
|
|
|
698
732
|
}
|
|
699
733
|
|
|
700
734
|
function isScalarOrEmptyArray(
|
|
701
|
-
data:
|
|
702
|
-
): data is
|
|
735
|
+
data: NetworkResponseValue,
|
|
736
|
+
): data is
|
|
737
|
+
| NetworkResponseScalarValue
|
|
738
|
+
| readonly (NetworkResponseScalarValue | null)[] {
|
|
703
739
|
// N.B. empty arrays count as empty arrays of scalar fields.
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
return (data as any).every((x: any) => isScalarOrEmptyArray(x));
|
|
740
|
+
if (isArray(data)) {
|
|
741
|
+
return data.every((x) => isScalarOrEmptyArray(x));
|
|
707
742
|
}
|
|
708
743
|
const isScalarValue =
|
|
709
744
|
data === null ||
|
|
@@ -713,8 +748,10 @@ function isScalarOrEmptyArray(
|
|
|
713
748
|
return isScalarValue;
|
|
714
749
|
}
|
|
715
750
|
|
|
716
|
-
function isNullOrEmptyArray(
|
|
717
|
-
|
|
751
|
+
function isNullOrEmptyArray(
|
|
752
|
+
data: unknown,
|
|
753
|
+
): data is readonly never[] | null[] | null {
|
|
754
|
+
if (isArray(data)) {
|
|
718
755
|
if (data.length === 0) {
|
|
719
756
|
return true;
|
|
720
757
|
}
|
|
@@ -889,3 +926,61 @@ function getDataIdOfNetworkResponse(
|
|
|
889
926
|
}
|
|
890
927
|
return storeKey;
|
|
891
928
|
}
|
|
929
|
+
|
|
930
|
+
export function writeData<
|
|
931
|
+
TReadFromStore extends UnknownTReadFromStore,
|
|
932
|
+
TRawResponseType extends NetworkResponseObject,
|
|
933
|
+
TClientFieldValue,
|
|
934
|
+
>(
|
|
935
|
+
environment: IsographEnvironment,
|
|
936
|
+
entrypoint: IsographEntrypoint<
|
|
937
|
+
TReadFromStore,
|
|
938
|
+
TClientFieldValue,
|
|
939
|
+
NormalizationAst,
|
|
940
|
+
TRawResponseType
|
|
941
|
+
>,
|
|
942
|
+
data: TRawResponseType,
|
|
943
|
+
variables: ExtractParameters<TReadFromStore>,
|
|
944
|
+
): ItemCleanupPair<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
|
945
|
+
const encounteredIds: EncounteredIds = new Map();
|
|
946
|
+
environment.store = addNetworkResponseStoreLayer(environment.store);
|
|
947
|
+
normalizeData(
|
|
948
|
+
environment,
|
|
949
|
+
environment.store,
|
|
950
|
+
entrypoint.networkRequestInfo.normalizationAst.selections,
|
|
951
|
+
data,
|
|
952
|
+
variables,
|
|
953
|
+
{ __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
954
|
+
encounteredIds,
|
|
955
|
+
);
|
|
956
|
+
logMessage(environment, () => ({
|
|
957
|
+
kind: 'AfterNormalization',
|
|
958
|
+
store: environment.store,
|
|
959
|
+
encounteredIds,
|
|
960
|
+
}));
|
|
961
|
+
|
|
962
|
+
callSubscriptions(environment, encounteredIds);
|
|
963
|
+
|
|
964
|
+
const { fieldName, readerArtifactKind, readerWithRefetchQueries } =
|
|
965
|
+
getOrLoadReaderWithRefetchQueries(
|
|
966
|
+
environment,
|
|
967
|
+
entrypoint.readerWithRefetchQueries,
|
|
968
|
+
);
|
|
969
|
+
const [networkRequest, disposeNetworkRequest] =
|
|
970
|
+
retainQueryWithoutMakingNetworkRequest(environment, entrypoint, variables);
|
|
971
|
+
|
|
972
|
+
return [
|
|
973
|
+
{
|
|
974
|
+
kind: 'FragmentReference',
|
|
975
|
+
readerWithRefetchQueries,
|
|
976
|
+
fieldName,
|
|
977
|
+
readerArtifactKind,
|
|
978
|
+
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
979
|
+
variables,
|
|
980
|
+
networkRequest,
|
|
981
|
+
},
|
|
982
|
+
() => {
|
|
983
|
+
disposeNetworkRequest();
|
|
984
|
+
},
|
|
985
|
+
];
|
|
986
|
+
}
|
package/src/core/check.ts
CHANGED
|
@@ -8,21 +8,28 @@ import {
|
|
|
8
8
|
StoreRecord,
|
|
9
9
|
} from './IsographEnvironment';
|
|
10
10
|
import { logMessage } from './logging';
|
|
11
|
+
import { getStoreRecordProxy } from './optimisticProxy';
|
|
11
12
|
|
|
12
13
|
export type ShouldFetch = RequiredShouldFetch | 'IfNecessary';
|
|
13
14
|
export type RequiredShouldFetch = 'Yes' | 'No';
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
shouldFetch?: ShouldFetch;
|
|
18
|
+
type FetchOptionsShared<TReadOutData> = {
|
|
19
19
|
onComplete?: (data: TReadOutData) => void;
|
|
20
20
|
onError?: () => void;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export
|
|
23
|
+
export interface FetchOptions<TReadOutData, TRawResponseType>
|
|
24
|
+
extends FetchOptionsShared<TReadOutData> {
|
|
25
|
+
shouldFetch?: ShouldFetch;
|
|
26
|
+
optimisticNetworkResponse?: TRawResponseType;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RequiredFetchOptions<TReadOutData>
|
|
30
|
+
extends FetchOptionsShared<TReadOutData> {
|
|
24
31
|
shouldFetch: RequiredShouldFetch;
|
|
25
|
-
}
|
|
32
|
+
}
|
|
26
33
|
|
|
27
34
|
export type CheckResult =
|
|
28
35
|
| {
|
|
@@ -39,8 +46,14 @@ export function check(
|
|
|
39
46
|
variables: Variables,
|
|
40
47
|
root: StoreLink,
|
|
41
48
|
): CheckResult {
|
|
42
|
-
const
|
|
43
|
-
|
|
49
|
+
const newStoreRecord = getStoreRecordProxy(environment.store, root);
|
|
50
|
+
|
|
51
|
+
if (newStoreRecord == null) {
|
|
52
|
+
return {
|
|
53
|
+
kind: 'MissingData',
|
|
54
|
+
record: root,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
44
57
|
|
|
45
58
|
const checkResult = checkFromRecord(
|
|
46
59
|
environment,
|
|
@@ -107,8 +120,7 @@ function checkFromRecord(
|
|
|
107
120
|
);
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
const linkedRecord =
|
|
111
|
-
environment.store[link.__typename]?.[link.__link];
|
|
123
|
+
const linkedRecord = getStoreRecordProxy(environment.store, link);
|
|
112
124
|
|
|
113
125
|
if (linkedRecord === undefined) {
|
|
114
126
|
return {
|
|
@@ -141,8 +153,7 @@ function checkFromRecord(
|
|
|
141
153
|
);
|
|
142
154
|
}
|
|
143
155
|
|
|
144
|
-
const linkedRecord =
|
|
145
|
-
environment.store[link.__typename]?.[link.__link];
|
|
156
|
+
const linkedRecord = getStoreRecordProxy(environment.store, link);
|
|
146
157
|
|
|
147
158
|
if (linkedRecord === undefined) {
|
|
148
159
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
|
|
2
|
+
import { maybeUnwrapNetworkRequest } from '../react/useResult';
|
|
2
3
|
import {
|
|
3
4
|
FragmentReference,
|
|
4
5
|
stableIdForFragmentReference,
|
|
@@ -11,7 +12,6 @@ import { createStartUpdate } from './startUpdate';
|
|
|
11
12
|
|
|
12
13
|
export function getOrCreateCachedComponent(
|
|
13
14
|
environment: IsographEnvironment,
|
|
14
|
-
componentName: string,
|
|
15
15
|
fragmentReference: FragmentReference<any, any>,
|
|
16
16
|
networkRequestOptions: NetworkRequestReaderOptions,
|
|
17
17
|
): React.FC<any> {
|
|
@@ -23,9 +23,13 @@ export function getOrCreateCachedComponent(
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
return (environment.componentCache[
|
|
26
|
-
stableIdForFragmentReference(fragmentReference
|
|
26
|
+
stableIdForFragmentReference(fragmentReference)
|
|
27
27
|
] ??= (() => {
|
|
28
28
|
function Component(additionalRuntimeProps: { [key: string]: any }) {
|
|
29
|
+
maybeUnwrapNetworkRequest(
|
|
30
|
+
fragmentReference.networkRequest,
|
|
31
|
+
networkRequestOptions,
|
|
32
|
+
);
|
|
29
33
|
const readerWithRefetchQueries = readPromise(
|
|
30
34
|
fragmentReference.readerWithRefetchQueries,
|
|
31
35
|
);
|
|
@@ -38,7 +42,7 @@ export function getOrCreateCachedComponent(
|
|
|
38
42
|
|
|
39
43
|
logMessage(environment, () => ({
|
|
40
44
|
kind: 'ComponentRerendered',
|
|
41
|
-
componentName,
|
|
45
|
+
componentName: fragmentReference.fieldName,
|
|
42
46
|
rootLink: fragmentReference.root,
|
|
43
47
|
}));
|
|
44
48
|
|
|
@@ -54,7 +58,7 @@ export function getOrCreateCachedComponent(
|
|
|
54
58
|
);
|
|
55
59
|
}
|
|
56
60
|
const idString = `(type: ${fragmentReference.root.__typename}, id: ${fragmentReference.root.__link})`;
|
|
57
|
-
Component.displayName = `${
|
|
61
|
+
Component.displayName = `${fragmentReference.fieldName} ${idString} @component`;
|
|
58
62
|
return Component;
|
|
59
63
|
})());
|
|
60
64
|
}
|