@isograph/react 0.2.0 → 0.3.1
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-typescript.log +4 -0
- package/dist/core/FragmentReference.d.ts +25 -6
- package/dist/core/FragmentReference.d.ts.map +1 -0
- package/dist/core/FragmentReference.js +3 -13
- package/dist/core/IsographEnvironment.d.ts +34 -26
- package/dist/core/IsographEnvironment.d.ts.map +1 -0
- package/dist/core/IsographEnvironment.js +19 -22
- package/dist/core/PromiseWrapper.d.ts +4 -4
- package/dist/core/PromiseWrapper.d.ts.map +1 -0
- package/dist/core/PromiseWrapper.js +9 -9
- package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
- package/dist/core/areEqualWithDeepComparison.js +89 -39
- package/dist/core/cache.d.ts +20 -13
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +205 -128
- package/dist/core/check.d.ts +22 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +127 -0
- package/dist/core/componentCache.d.ts +2 -2
- package/dist/core/componentCache.d.ts.map +1 -0
- package/dist/core/componentCache.js +28 -32
- package/dist/core/entrypoint.d.ts +31 -15
- package/dist/core/entrypoint.d.ts.map +1 -0
- package/dist/core/entrypoint.js +1 -2
- package/dist/core/garbageCollection.d.ts +6 -5
- package/dist/core/garbageCollection.d.ts.map +1 -0
- package/dist/core/garbageCollection.js +49 -16
- package/dist/core/logging.d.ts +68 -0
- package/dist/core/logging.d.ts.map +1 -0
- package/dist/core/logging.js +22 -0
- package/dist/core/makeNetworkRequest.d.ts +6 -3
- package/dist/core/makeNetworkRequest.d.ts.map +1 -0
- package/dist/core/makeNetworkRequest.js +160 -19
- package/dist/core/read.d.ts +25 -5
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +416 -259
- package/dist/core/reader.d.ts +31 -15
- package/dist/core/reader.d.ts.map +1 -0
- package/dist/core/startUpdate.d.ts +5 -0
- package/dist/core/startUpdate.d.ts.map +1 -0
- package/dist/core/startUpdate.js +15 -0
- package/dist/core/util.d.ts +5 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -2
- package/dist/loadable-hooks/useClientSideDefer.d.ts +9 -3
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
- package/dist/loadable-hooks/useClientSideDefer.js +6 -8
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +27 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.js +162 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +2 -2
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -7
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +4 -5
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +13 -26
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +93 -47
- package/dist/react/FragmentReader.d.ts +6 -4
- package/dist/react/FragmentReader.d.ts.map +1 -0
- package/dist/react/FragmentReader.js +4 -2
- package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
- package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
- package/dist/react/IsographEnvironmentProvider.js +3 -3
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
- package/dist/react/useImperativeReference.d.ts +8 -6
- package/dist/react/useImperativeReference.d.ts.map +1 -0
- package/dist/react/useImperativeReference.js +6 -8
- package/dist/react/useLazyReference.d.ts +5 -3
- package/dist/react/useLazyReference.d.ts.map +1 -0
- package/dist/react/useLazyReference.js +34 -6
- package/dist/react/useReadAndSubscribe.d.ts +6 -3
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
- package/dist/react/useReadAndSubscribe.js +13 -10
- package/dist/react/useRerenderOnChange.d.ts +7 -2
- package/dist/react/useRerenderOnChange.d.ts.map +1 -0
- package/dist/react/useRerenderOnChange.js +3 -4
- package/dist/react/useResult.d.ts +4 -3
- package/dist/react/useResult.d.ts.map +1 -0
- package/dist/react/useResult.js +14 -9
- package/isograph.config.json +8 -0
- package/package.json +14 -9
- package/{src/tests/schema.graphql → schema.graphql} +1 -0
- package/src/core/FragmentReference.ts +44 -17
- package/src/core/IsographEnvironment.ts +67 -50
- package/src/core/PromiseWrapper.ts +3 -3
- package/src/core/areEqualWithDeepComparison.ts +95 -41
- package/src/core/cache.ts +316 -169
- package/src/core/check.ts +212 -0
- package/src/core/componentCache.ts +40 -46
- package/src/core/entrypoint.ts +41 -16
- package/src/core/garbageCollection.ts +77 -26
- package/src/core/logging.ts +118 -0
- package/src/core/makeNetworkRequest.ts +249 -20
- package/src/core/read.ts +658 -368
- package/src/core/reader.ts +61 -21
- package/src/core/startUpdate.ts +28 -0
- package/src/core/util.ts +8 -0
- package/src/index.ts +94 -8
- package/src/loadable-hooks/useClientSideDefer.ts +48 -17
- package/src/loadable-hooks/useConnectionSpecPagination.ts +344 -0
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
- package/src/loadable-hooks/useImperativeLoadableField.ts +36 -12
- package/src/loadable-hooks/useSkipLimitPagination.ts +253 -94
- package/src/react/FragmentReader.tsx +15 -6
- package/src/react/IsographEnvironmentProvider.tsx +1 -1
- package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
- package/src/react/useImperativeReference.ts +50 -18
- package/src/react/useLazyReference.ts +79 -11
- package/src/react/useReadAndSubscribe.ts +33 -10
- package/src/react/useRerenderOnChange.ts +7 -2
- package/src/react/useResult.ts +30 -9
- package/src/tests/__isograph/Query/meName/entrypoint.ts +10 -29
- package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
- package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
- package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +5 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +10 -65
- package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
- package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +10 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +10 -28
- package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
- package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
- package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +5 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +28 -0
- package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
- package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
- package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +52 -0
- package/src/tests/__isograph/iso.ts +24 -12
- package/src/tests/garbageCollection.test.ts +53 -45
- package/src/tests/meNameSuccessor.ts +8 -3
- package/src/tests/nodeQuery.ts +7 -4
- package/src/tests/normalizeData.test.ts +120 -0
- package/src/tests/tsconfig.json +3 -3
- package/tsconfig.json +2 -2
- package/tsconfig.pkg.json +7 -3
- package/vitest.config.ts +20 -0
- package/src/tests/isograph.config.json +0 -7
package/src/core/cache.ts
CHANGED
@@ -3,50 +3,53 @@ import {
|
|
3
3
|
ItemCleanupPair,
|
4
4
|
ParentCache,
|
5
5
|
} from '@isograph/react-disposable-state';
|
6
|
-
import {
|
7
|
-
DataId,
|
8
|
-
ROOT_ID,
|
9
|
-
StoreRecord,
|
10
|
-
Link,
|
11
|
-
type IsographEnvironment,
|
12
|
-
DataTypeValue,
|
13
|
-
getLink,
|
14
|
-
FragmentSubscription,
|
15
|
-
} from './IsographEnvironment';
|
16
6
|
import {
|
17
7
|
IsographEntrypoint,
|
18
|
-
NormalizationAst,
|
19
8
|
NormalizationInlineFragment,
|
20
9
|
NormalizationLinkedField,
|
21
10
|
NormalizationScalarField,
|
22
11
|
RefetchQueryNormalizationArtifactWrapper,
|
12
|
+
type NormalizationAst,
|
13
|
+
type NormalizationAstLoader,
|
14
|
+
type NormalizationAstNodes,
|
23
15
|
} from '../core/entrypoint';
|
24
|
-
import {
|
25
|
-
import {
|
26
|
-
import {
|
27
|
-
|
28
|
-
|
29
|
-
|
16
|
+
import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
|
17
|
+
import { FetchOptions } from './check';
|
18
|
+
import {
|
19
|
+
ExtractParameters,
|
20
|
+
FragmentReference,
|
21
|
+
Variables,
|
22
|
+
type UnknownTReadFromStore,
|
23
|
+
type VariableValue,
|
24
|
+
} from './FragmentReference';
|
25
|
+
import {
|
26
|
+
DataId,
|
27
|
+
DataTypeValue,
|
28
|
+
FragmentSubscription,
|
29
|
+
getLink,
|
30
|
+
Link,
|
31
|
+
ROOT_ID,
|
32
|
+
StoreRecord,
|
33
|
+
type IsographEnvironment,
|
34
|
+
type TypeName,
|
35
|
+
} from './IsographEnvironment';
|
36
|
+
import { logMessage } from './logging';
|
37
|
+
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
30
38
|
import { wrapResolvedValue } from './PromiseWrapper';
|
39
|
+
import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
|
40
|
+
import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
|
41
|
+
import { Argument, ArgumentValue } from './util';
|
31
42
|
|
32
|
-
const TYPENAME_FIELD_NAME = '__typename';
|
43
|
+
export const TYPENAME_FIELD_NAME = '__typename';
|
33
44
|
|
34
45
|
export function getOrCreateItemInSuspenseCache<
|
35
|
-
TReadFromStore extends
|
46
|
+
TReadFromStore extends UnknownTReadFromStore,
|
36
47
|
TClientFieldValue,
|
37
48
|
>(
|
38
49
|
environment: IsographEnvironment,
|
39
50
|
index: string,
|
40
51
|
factory: Factory<FragmentReference<TReadFromStore, TClientFieldValue>>,
|
41
52
|
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
42
|
-
// @ts-expect-error
|
43
|
-
if (typeof window !== 'undefined' && window.__LOG) {
|
44
|
-
console.log('getting cache for', {
|
45
|
-
index,
|
46
|
-
cache: Object.keys(environment.fragmentCache),
|
47
|
-
found: !!environment.fragmentCache[index],
|
48
|
-
});
|
49
|
-
}
|
50
53
|
if (environment.fragmentCache[index] == null) {
|
51
54
|
environment.fragmentCache[index] = new ParentCache(factory);
|
52
55
|
}
|
@@ -77,20 +80,30 @@ export function stableCopy<T>(value: T): T {
|
|
77
80
|
}
|
78
81
|
|
79
82
|
export function getOrCreateCacheForArtifact<
|
80
|
-
TReadFromStore extends
|
83
|
+
TReadFromStore extends UnknownTReadFromStore,
|
81
84
|
TClientFieldValue,
|
85
|
+
TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
|
82
86
|
>(
|
83
87
|
environment: IsographEnvironment,
|
84
|
-
entrypoint: IsographEntrypoint<
|
85
|
-
|
88
|
+
entrypoint: IsographEntrypoint<
|
89
|
+
TReadFromStore,
|
90
|
+
TClientFieldValue,
|
91
|
+
TNormalizationAst
|
92
|
+
>,
|
93
|
+
variables: ExtractParameters<TReadFromStore>,
|
94
|
+
fetchOptions?: FetchOptions<TClientFieldValue>,
|
86
95
|
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
87
|
-
const cacheKey =
|
96
|
+
const cacheKey =
|
97
|
+
entrypoint.networkRequestInfo.queryText +
|
98
|
+
JSON.stringify(stableCopy(variables));
|
88
99
|
const factory = () => {
|
89
|
-
const [networkRequest, disposeNetworkRequest] =
|
100
|
+
const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
|
90
101
|
environment,
|
91
102
|
entrypoint,
|
92
103
|
variables,
|
104
|
+
fetchOptions,
|
93
105
|
);
|
106
|
+
|
94
107
|
const itemCleanupPair: ItemCleanupPair<
|
95
108
|
FragmentReference<TReadFromStore, TClientFieldValue>
|
96
109
|
> = [
|
@@ -102,7 +115,7 @@ export function getOrCreateCacheForArtifact<
|
|
102
115
|
nestedRefetchQueries:
|
103
116
|
entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
104
117
|
}),
|
105
|
-
root: ROOT_ID,
|
118
|
+
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
106
119
|
variables,
|
107
120
|
networkRequest: networkRequest,
|
108
121
|
},
|
@@ -113,56 +126,59 @@ export function getOrCreateCacheForArtifact<
|
|
113
126
|
return getOrCreateItemInSuspenseCache(environment, cacheKey, factory);
|
114
127
|
}
|
115
128
|
|
116
|
-
type NetworkResponseScalarValue = string | number | boolean;
|
117
|
-
type NetworkResponseValue =
|
129
|
+
export type NetworkResponseScalarValue = string | number | boolean;
|
130
|
+
export type NetworkResponseValue =
|
118
131
|
| NetworkResponseScalarValue
|
119
132
|
| null
|
120
133
|
| NetworkResponseObject
|
121
|
-
| NetworkResponseObject[]
|
122
|
-
| NetworkResponseScalarValue[];
|
123
|
-
|
134
|
+
| (NetworkResponseObject | null)[]
|
135
|
+
| (NetworkResponseScalarValue | null)[];
|
136
|
+
|
137
|
+
export type NetworkResponseObject = {
|
124
138
|
// N.B. undefined is here to support optional id's, but
|
125
139
|
// undefined should not *actually* be present in the network response.
|
126
140
|
[index: string]: undefined | NetworkResponseValue;
|
127
141
|
id?: DataId;
|
142
|
+
__typename?: TypeName;
|
128
143
|
};
|
129
144
|
|
130
145
|
export function normalizeData(
|
131
146
|
environment: IsographEnvironment,
|
132
|
-
normalizationAst:
|
147
|
+
normalizationAst: NormalizationAstNodes,
|
133
148
|
networkResponse: NetworkResponseObject,
|
134
149
|
variables: Variables,
|
135
150
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
151
|
+
root: Link,
|
152
|
+
): EncounteredIds {
|
153
|
+
const encounteredIds: EncounteredIds = new Map();
|
154
|
+
|
155
|
+
logMessage(environment, () => ({
|
156
|
+
kind: 'AboutToNormalize',
|
157
|
+
normalizationAst,
|
158
|
+
networkResponse,
|
159
|
+
variables,
|
160
|
+
}));
|
161
|
+
|
162
|
+
const recordsById = (environment.store[root.__typename] ??= {});
|
163
|
+
const newStoreRecord = (recordsById[root.__link] ??= {});
|
164
|
+
|
148
165
|
normalizeDataIntoRecord(
|
149
166
|
environment,
|
150
167
|
normalizationAst,
|
151
168
|
networkResponse,
|
152
|
-
|
153
|
-
|
154
|
-
variables
|
169
|
+
newStoreRecord,
|
170
|
+
root,
|
171
|
+
variables,
|
155
172
|
nestedRefetchQueries,
|
156
173
|
encounteredIds,
|
157
174
|
);
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
}
|
175
|
+
|
176
|
+
logMessage(environment, () => ({
|
177
|
+
kind: 'AfterNormalization',
|
178
|
+
store: environment.store,
|
179
|
+
encounteredIds,
|
180
|
+
}));
|
181
|
+
|
166
182
|
callSubscriptions(environment, encounteredIds);
|
167
183
|
return encounteredIds;
|
168
184
|
}
|
@@ -179,33 +195,54 @@ export function subscribeToAnyChange(
|
|
179
195
|
return () => environment.subscriptions.delete(subscription);
|
180
196
|
}
|
181
197
|
|
198
|
+
export function subscribeToAnyChangesToRecord(
|
199
|
+
environment: IsographEnvironment,
|
200
|
+
recordLink: Link,
|
201
|
+
callback: () => void,
|
202
|
+
): () => void {
|
203
|
+
const subscription = {
|
204
|
+
kind: 'AnyChangesToRecord',
|
205
|
+
recordLink,
|
206
|
+
callback,
|
207
|
+
} as const;
|
208
|
+
environment.subscriptions.add(subscription);
|
209
|
+
return () => environment.subscriptions.delete(subscription);
|
210
|
+
}
|
211
|
+
|
182
212
|
// TODO we should re-read and call callback if the value has changed
|
183
|
-
export function subscribe<TReadFromStore extends
|
213
|
+
export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
184
214
|
environment: IsographEnvironment,
|
185
215
|
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
186
216
|
fragmentReference: FragmentReference<TReadFromStore, any>,
|
187
217
|
callback: (
|
188
218
|
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
189
219
|
) => void,
|
220
|
+
readerAst: ReaderAst<TReadFromStore>,
|
190
221
|
): () => void {
|
191
222
|
const fragmentSubscription: FragmentSubscription<TReadFromStore> = {
|
192
223
|
kind: 'FragmentSubscription',
|
193
224
|
callback,
|
194
225
|
encounteredDataAndRecords,
|
195
226
|
fragmentReference,
|
227
|
+
readerAst,
|
196
228
|
};
|
197
|
-
// @ts-expect-error
|
198
229
|
environment.subscriptions.add(fragmentSubscription);
|
199
|
-
// @ts-expect-error
|
200
230
|
return () => environment.subscriptions.delete(fragmentSubscription);
|
201
231
|
}
|
202
232
|
|
203
|
-
export function
|
233
|
+
export function onNextChangeToRecord(
|
234
|
+
environment: IsographEnvironment,
|
235
|
+
recordLink: Link,
|
236
|
+
): Promise<void> {
|
204
237
|
return new Promise((resolve) => {
|
205
|
-
const unsubscribe =
|
206
|
-
|
207
|
-
|
208
|
-
|
238
|
+
const unsubscribe = subscribeToAnyChangesToRecord(
|
239
|
+
environment,
|
240
|
+
recordLink,
|
241
|
+
() => {
|
242
|
+
unsubscribe();
|
243
|
+
resolve();
|
244
|
+
},
|
245
|
+
);
|
209
246
|
});
|
210
247
|
}
|
211
248
|
|
@@ -224,7 +261,7 @@ function withErrorHandling<T>(f: (t: T) => void): (t: T) => void {
|
|
224
261
|
|
225
262
|
function callSubscriptions(
|
226
263
|
environment: IsographEnvironment,
|
227
|
-
recordsEncounteredWhenNormalizing:
|
264
|
+
recordsEncounteredWhenNormalizing: EncounteredIds,
|
228
265
|
) {
|
229
266
|
environment.subscriptions.forEach(
|
230
267
|
withErrorHandling((subscription) => {
|
@@ -260,36 +297,40 @@ function callSubscriptions(
|
|
260
297
|
},
|
261
298
|
);
|
262
299
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
300
|
+
const mergedItem = mergeObjectsUsingReaderAst(
|
301
|
+
subscription.readerAst,
|
302
|
+
subscription.encounteredDataAndRecords.item,
|
303
|
+
newEncounteredDataAndRecords.item,
|
304
|
+
);
|
305
|
+
|
306
|
+
logMessage(environment, () => ({
|
307
|
+
kind: 'DeepEqualityCheck',
|
308
|
+
fragmentReference: subscription.fragmentReference,
|
309
|
+
old: subscription.encounteredDataAndRecords.item,
|
310
|
+
new: newEncounteredDataAndRecords.item,
|
311
|
+
deeplyEqual:
|
312
|
+
mergedItem === subscription.encounteredDataAndRecords.item,
|
313
|
+
}));
|
314
|
+
|
315
|
+
if (mergedItem !== subscription.encounteredDataAndRecords.item) {
|
278
316
|
subscription.callback(newEncounteredDataAndRecords);
|
279
|
-
} else {
|
280
|
-
// @ts-expect-error
|
281
|
-
if (typeof window !== 'undefined' && window.__LOG) {
|
282
|
-
console.log('Deep equality - Yes', {
|
283
|
-
fragmentReference: subscription.fragmentReference,
|
284
|
-
old: subscription.encounteredDataAndRecords.item,
|
285
|
-
});
|
286
|
-
}
|
287
317
|
}
|
288
318
|
}
|
289
319
|
return;
|
290
320
|
}
|
291
321
|
case 'AnyRecords': {
|
292
|
-
|
322
|
+
subscription.callback();
|
323
|
+
return;
|
324
|
+
}
|
325
|
+
case 'AnyChangesToRecord': {
|
326
|
+
if (
|
327
|
+
recordsEncounteredWhenNormalizing
|
328
|
+
.get(subscription.recordLink.__typename)
|
329
|
+
?.has(subscription.recordLink.__link)
|
330
|
+
) {
|
331
|
+
subscription.callback();
|
332
|
+
}
|
333
|
+
return;
|
293
334
|
}
|
294
335
|
default: {
|
295
336
|
// Ensure we have covered all variants
|
@@ -302,7 +343,25 @@ function callSubscriptions(
|
|
302
343
|
);
|
303
344
|
}
|
304
345
|
|
305
|
-
function hasOverlappingIds(
|
346
|
+
function hasOverlappingIds(
|
347
|
+
ids1: EncounteredIds,
|
348
|
+
ids2: EncounteredIds,
|
349
|
+
): boolean {
|
350
|
+
for (const [typeName, set1] of ids1.entries()) {
|
351
|
+
const set2 = ids2.get(typeName);
|
352
|
+
if (set2 === undefined) {
|
353
|
+
continue;
|
354
|
+
}
|
355
|
+
|
356
|
+
if (isNotDisjointFrom(set1, set2)) {
|
357
|
+
return true;
|
358
|
+
}
|
359
|
+
}
|
360
|
+
return false;
|
361
|
+
}
|
362
|
+
|
363
|
+
// TODO use a polyfill library
|
364
|
+
function isNotDisjointFrom<T>(set1: Set<T>, set2: Set<T>): boolean {
|
306
365
|
for (const id of set1) {
|
307
366
|
if (set2.has(id)) {
|
308
367
|
return true;
|
@@ -311,18 +370,19 @@ function hasOverlappingIds(set1: Set<DataId>, set2: Set<DataId>): boolean {
|
|
311
370
|
return false;
|
312
371
|
}
|
313
372
|
|
373
|
+
export type EncounteredIds = Map<TypeName, Set<DataId>>;
|
314
374
|
/**
|
315
375
|
* Mutate targetParentRecord according to the normalizationAst and networkResponseParentRecord.
|
316
376
|
*/
|
317
377
|
function normalizeDataIntoRecord(
|
318
378
|
environment: IsographEnvironment,
|
319
|
-
normalizationAst:
|
379
|
+
normalizationAst: NormalizationAstNodes,
|
320
380
|
networkResponseParentRecord: NetworkResponseObject,
|
321
381
|
targetParentRecord: StoreRecord,
|
322
|
-
|
382
|
+
targetParentRecordLink: Link,
|
323
383
|
variables: Variables,
|
324
384
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
325
|
-
mutableEncounteredIds:
|
385
|
+
mutableEncounteredIds: EncounteredIds,
|
326
386
|
): RecordHasBeenUpdated {
|
327
387
|
let recordHasBeenUpdated = false;
|
328
388
|
for (const normalizationNode of normalizationAst) {
|
@@ -344,7 +404,7 @@ function normalizeDataIntoRecord(
|
|
344
404
|
normalizationNode,
|
345
405
|
networkResponseParentRecord,
|
346
406
|
targetParentRecord,
|
347
|
-
|
407
|
+
targetParentRecordLink,
|
348
408
|
variables,
|
349
409
|
nestedRefetchQueries,
|
350
410
|
mutableEncounteredIds,
|
@@ -359,7 +419,7 @@ function normalizeDataIntoRecord(
|
|
359
419
|
normalizationNode,
|
360
420
|
networkResponseParentRecord,
|
361
421
|
targetParentRecord,
|
362
|
-
|
422
|
+
targetParentRecordLink,
|
363
423
|
variables,
|
364
424
|
nestedRefetchQueries,
|
365
425
|
mutableEncounteredIds,
|
@@ -377,11 +437,25 @@ function normalizeDataIntoRecord(
|
|
377
437
|
}
|
378
438
|
}
|
379
439
|
if (recordHasBeenUpdated) {
|
380
|
-
|
440
|
+
let encounteredRecordsIds = insertIfNotExists(
|
441
|
+
mutableEncounteredIds,
|
442
|
+
targetParentRecordLink.__typename,
|
443
|
+
);
|
444
|
+
|
445
|
+
encounteredRecordsIds.add(targetParentRecordLink.__link);
|
381
446
|
}
|
382
447
|
return recordHasBeenUpdated;
|
383
448
|
}
|
384
449
|
|
450
|
+
export function insertIfNotExists<K, V>(map: Map<K, Set<V>>, key: K) {
|
451
|
+
let result = map.get(key);
|
452
|
+
if (result === undefined) {
|
453
|
+
result = new Set();
|
454
|
+
map.set(key, result);
|
455
|
+
}
|
456
|
+
return result;
|
457
|
+
}
|
458
|
+
|
385
459
|
type RecordHasBeenUpdated = boolean;
|
386
460
|
function normalizeScalarField(
|
387
461
|
astNode: NormalizationScalarField,
|
@@ -413,10 +487,10 @@ function normalizeLinkedField(
|
|
413
487
|
astNode: NormalizationLinkedField,
|
414
488
|
networkResponseParentRecord: NetworkResponseObject,
|
415
489
|
targetParentRecord: StoreRecord,
|
416
|
-
|
490
|
+
targetParentRecordLink: Link,
|
417
491
|
variables: Variables,
|
418
492
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
419
|
-
mutableEncounteredIds:
|
493
|
+
mutableEncounteredIds: EncounteredIds,
|
420
494
|
): RecordHasBeenUpdated {
|
421
495
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
422
496
|
const networkResponseData = networkResponseParentRecord[networkResponseKey];
|
@@ -428,7 +502,10 @@ function normalizeLinkedField(
|
|
428
502
|
return existingValue !== null;
|
429
503
|
}
|
430
504
|
|
431
|
-
if (
|
505
|
+
if (
|
506
|
+
isScalarOrEmptyArray(networkResponseData) &&
|
507
|
+
!isNullOrEmptyArray(networkResponseData)
|
508
|
+
) {
|
432
509
|
throw new Error(
|
433
510
|
'Unexpected scalar network response when normalizing a linked field',
|
434
511
|
);
|
@@ -436,21 +513,36 @@ function normalizeLinkedField(
|
|
436
513
|
|
437
514
|
if (Array.isArray(networkResponseData)) {
|
438
515
|
// TODO check astNode.plural or the like
|
439
|
-
const dataIds: Link[] = [];
|
516
|
+
const dataIds: (Link | null)[] = [];
|
440
517
|
for (let i = 0; i < networkResponseData.length; i++) {
|
441
518
|
const networkResponseObject = networkResponseData[i];
|
519
|
+
if (networkResponseObject == null) {
|
520
|
+
dataIds.push(null);
|
521
|
+
continue;
|
522
|
+
}
|
442
523
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
443
524
|
environment,
|
444
525
|
astNode,
|
445
526
|
networkResponseObject,
|
446
|
-
|
527
|
+
targetParentRecordLink,
|
447
528
|
variables,
|
448
529
|
i,
|
449
530
|
nestedRefetchQueries,
|
450
531
|
mutableEncounteredIds,
|
451
532
|
);
|
452
533
|
|
453
|
-
|
534
|
+
const __typename =
|
535
|
+
astNode.concreteType ?? networkResponseObject[TYPENAME_FIELD_NAME];
|
536
|
+
if (__typename == null) {
|
537
|
+
throw new Error(
|
538
|
+
'Unexpected missing __typename in network response when normalizing a linked field. ' +
|
539
|
+
'This is indicative of a bug in Isograph.',
|
540
|
+
);
|
541
|
+
}
|
542
|
+
dataIds.push({
|
543
|
+
__link: newStoreRecordId,
|
544
|
+
__typename,
|
545
|
+
});
|
454
546
|
}
|
455
547
|
targetParentRecord[parentRecordKey] = dataIds;
|
456
548
|
return !dataIdsAreTheSame(existingValue, dataIds);
|
@@ -459,18 +551,30 @@ function normalizeLinkedField(
|
|
459
551
|
environment,
|
460
552
|
astNode,
|
461
553
|
networkResponseData,
|
462
|
-
|
554
|
+
targetParentRecordLink,
|
463
555
|
variables,
|
464
556
|
null,
|
465
557
|
nestedRefetchQueries,
|
466
558
|
mutableEncounteredIds,
|
467
559
|
);
|
468
560
|
|
561
|
+
let __typename =
|
562
|
+
astNode.concreteType ?? networkResponseData[TYPENAME_FIELD_NAME];
|
563
|
+
|
564
|
+
if (__typename == null) {
|
565
|
+
throw new Error(
|
566
|
+
'Unexpected missing __typename in network response when normalizing a linked field. ' +
|
567
|
+
'This is indicative of a bug in Isograph.',
|
568
|
+
);
|
569
|
+
}
|
570
|
+
|
469
571
|
targetParentRecord[parentRecordKey] = {
|
470
572
|
__link: newStoreRecordId,
|
573
|
+
__typename,
|
471
574
|
};
|
575
|
+
|
472
576
|
const link = getLink(existingValue);
|
473
|
-
return link?.__link !== newStoreRecordId;
|
577
|
+
return link?.__link !== newStoreRecordId || link.__typename !== __typename;
|
474
578
|
}
|
475
579
|
}
|
476
580
|
|
@@ -482,10 +586,10 @@ function normalizeInlineFragment(
|
|
482
586
|
astNode: NormalizationInlineFragment,
|
483
587
|
networkResponseParentRecord: NetworkResponseObject,
|
484
588
|
targetParentRecord: StoreRecord,
|
485
|
-
|
589
|
+
targetParentRecordLink: Link,
|
486
590
|
variables: Variables,
|
487
591
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
488
|
-
mutableEncounteredIds:
|
592
|
+
mutableEncounteredIds: EncounteredIds,
|
489
593
|
): RecordHasBeenUpdated {
|
490
594
|
const typeToRefineTo = astNode.type;
|
491
595
|
if (networkResponseParentRecord[TYPENAME_FIELD_NAME] === typeToRefineTo) {
|
@@ -494,7 +598,7 @@ function normalizeInlineFragment(
|
|
494
598
|
astNode.selections,
|
495
599
|
networkResponseParentRecord,
|
496
600
|
targetParentRecord,
|
497
|
-
|
601
|
+
targetParentRecordLink,
|
498
602
|
variables,
|
499
603
|
nestedRefetchQueries,
|
500
604
|
mutableEncounteredIds,
|
@@ -506,7 +610,7 @@ function normalizeInlineFragment(
|
|
506
610
|
|
507
611
|
function dataIdsAreTheSame(
|
508
612
|
existingValue: DataTypeValue,
|
509
|
-
newDataIds: Link[],
|
613
|
+
newDataIds: (Link | null)[],
|
510
614
|
): boolean {
|
511
615
|
if (Array.isArray(existingValue)) {
|
512
616
|
if (newDataIds.length !== existingValue.length) {
|
@@ -514,10 +618,11 @@ function dataIdsAreTheSame(
|
|
514
618
|
}
|
515
619
|
for (let i = 0; i < newDataIds.length; i++) {
|
516
620
|
const maybeLink = getLink(existingValue[i]);
|
517
|
-
if (
|
518
|
-
|
519
|
-
|
520
|
-
|
621
|
+
if (
|
622
|
+
newDataIds[i]?.__link !== maybeLink?.__link ||
|
623
|
+
newDataIds[i]?.__typename !== maybeLink?.__typename
|
624
|
+
) {
|
625
|
+
return false;
|
521
626
|
}
|
522
627
|
}
|
523
628
|
return true;
|
@@ -530,29 +635,38 @@ function normalizeNetworkResponseObject(
|
|
530
635
|
environment: IsographEnvironment,
|
531
636
|
astNode: NormalizationLinkedField,
|
532
637
|
networkResponseData: NetworkResponseObject,
|
533
|
-
|
638
|
+
targetParentRecordLink: Link,
|
534
639
|
variables: Variables,
|
535
640
|
index: number | null,
|
536
641
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
537
|
-
mutableEncounteredIds:
|
642
|
+
mutableEncounteredIds: EncounteredIds,
|
538
643
|
): DataId /* The id of the modified or newly created item */ {
|
539
644
|
const newStoreRecordId = getDataIdOfNetworkResponse(
|
540
|
-
|
645
|
+
targetParentRecordLink,
|
541
646
|
networkResponseData,
|
542
647
|
astNode,
|
543
648
|
variables,
|
544
649
|
index,
|
545
650
|
);
|
651
|
+
const __typename =
|
652
|
+
astNode.concreteType ?? networkResponseData[TYPENAME_FIELD_NAME];
|
653
|
+
|
654
|
+
if (__typename == null) {
|
655
|
+
throw new Error(
|
656
|
+
'Unexpected missing __typename in network response object. ' +
|
657
|
+
'This is indicative of a bug in Isograph.',
|
658
|
+
);
|
659
|
+
}
|
546
660
|
|
547
|
-
const
|
548
|
-
|
661
|
+
const recordsById = (environment.store[__typename] ??= {});
|
662
|
+
const newStoreRecord = (recordsById[newStoreRecordId] ??= {});
|
549
663
|
|
550
664
|
normalizeDataIntoRecord(
|
551
665
|
environment,
|
552
666
|
astNode.selections,
|
553
667
|
networkResponseData,
|
554
668
|
newStoreRecord,
|
555
|
-
newStoreRecordId,
|
669
|
+
{ __link: newStoreRecordId, __typename: __typename },
|
556
670
|
variables,
|
557
671
|
nestedRefetchQueries,
|
558
672
|
mutableEncounteredIds,
|
@@ -563,35 +677,29 @@ function normalizeNetworkResponseObject(
|
|
563
677
|
|
564
678
|
function isScalarOrEmptyArray(
|
565
679
|
data: NonNullable<NetworkResponseValue>,
|
566
|
-
): data is NetworkResponseScalarValue | NetworkResponseScalarValue[] {
|
680
|
+
): data is NetworkResponseScalarValue | (NetworkResponseScalarValue | null)[] {
|
567
681
|
// N.B. empty arrays count as empty arrays of scalar fields.
|
568
682
|
if (Array.isArray(data)) {
|
569
683
|
// This is maybe fixed in a new version of Typescript??
|
570
684
|
return (data as any).every((x: any) => isScalarOrEmptyArray(x));
|
571
685
|
}
|
572
686
|
const isScalarValue =
|
687
|
+
data === null ||
|
573
688
|
typeof data === 'string' ||
|
574
689
|
typeof data === 'number' ||
|
575
690
|
typeof data === 'boolean';
|
576
691
|
return isScalarValue;
|
577
692
|
}
|
578
693
|
|
579
|
-
function
|
580
|
-
data: NonNullable<NetworkResponseValue>,
|
581
|
-
): data is NetworkResponseScalarValue | NetworkResponseScalarValue[] {
|
582
|
-
// N.B. empty arrays count as empty arrays of linked fields.
|
694
|
+
function isNullOrEmptyArray(data: unknown): data is never[] | null[] | null {
|
583
695
|
if (Array.isArray(data)) {
|
584
696
|
if (data.length === 0) {
|
585
|
-
return
|
697
|
+
return true;
|
586
698
|
}
|
587
|
-
|
588
|
-
return (data as any).every((x: any) => isScalarOrEmptyArray(x));
|
699
|
+
return data.every((x) => isNullOrEmptyArray(x));
|
589
700
|
}
|
590
|
-
|
591
|
-
|
592
|
-
typeof data === 'number' ||
|
593
|
-
typeof data === 'boolean';
|
594
|
-
return isScalarValue;
|
701
|
+
|
702
|
+
return data === null;
|
595
703
|
}
|
596
704
|
|
597
705
|
export function getParentRecordKey(
|
@@ -616,8 +724,19 @@ export function getParentRecordKey(
|
|
616
724
|
function getStoreKeyChunkForArgumentValue(
|
617
725
|
argumentValue: ArgumentValue,
|
618
726
|
variables: Variables,
|
619
|
-
) {
|
727
|
+
): VariableValue {
|
620
728
|
switch (argumentValue.kind) {
|
729
|
+
case 'Object': {
|
730
|
+
return Object.fromEntries(
|
731
|
+
argumentValue.value.map(([argumentName, argumentValue]) => {
|
732
|
+
return [
|
733
|
+
argumentName,
|
734
|
+
// substitute variables
|
735
|
+
getStoreKeyChunkForArgumentValue(argumentValue, variables),
|
736
|
+
];
|
737
|
+
}),
|
738
|
+
);
|
739
|
+
}
|
621
740
|
case 'Literal': {
|
622
741
|
return argumentValue.value;
|
623
742
|
}
|
@@ -641,7 +760,12 @@ function getStoreKeyChunkForArgumentValue(
|
|
641
760
|
}
|
642
761
|
|
643
762
|
function getStoreKeyChunkForArgument(argument: Argument, variables: Variables) {
|
644
|
-
|
763
|
+
let chunk = getStoreKeyChunkForArgumentValue(argument[1], variables);
|
764
|
+
|
765
|
+
if (typeof chunk === 'object') {
|
766
|
+
chunk = JSON.stringify(stableCopy(chunk));
|
767
|
+
}
|
768
|
+
|
645
769
|
return `${FIRST_SPLIT_KEY}${argument[0]}${SECOND_SPLIT_KEY}${chunk}`;
|
646
770
|
}
|
647
771
|
|
@@ -650,52 +774,75 @@ function getNetworkResponseKey(
|
|
650
774
|
): string {
|
651
775
|
let networkResponseKey = astNode.fieldName;
|
652
776
|
const fieldParameters = astNode.arguments;
|
777
|
+
|
653
778
|
if (fieldParameters != null) {
|
654
|
-
for (const
|
655
|
-
|
656
|
-
let argumentValueChunk;
|
657
|
-
switch (argumentValue.kind) {
|
658
|
-
case 'Literal': {
|
659
|
-
argumentValueChunk = 'l_' + argumentValue.value;
|
660
|
-
break;
|
661
|
-
}
|
662
|
-
case 'Variable': {
|
663
|
-
argumentValueChunk = 'v_' + argumentValue.name;
|
664
|
-
break;
|
665
|
-
}
|
666
|
-
case 'String': {
|
667
|
-
argumentValueChunk = 's_' + argumentValue.value;
|
668
|
-
break;
|
669
|
-
}
|
670
|
-
case 'Enum': {
|
671
|
-
argumentValueChunk = 'e_' + argumentValue.value;
|
672
|
-
break;
|
673
|
-
}
|
674
|
-
default: {
|
675
|
-
// Ensure we have covered all variants
|
676
|
-
let _: never = argumentValue;
|
677
|
-
_;
|
678
|
-
throw new Error('Unexpected case');
|
679
|
-
}
|
680
|
-
}
|
779
|
+
for (const [argumentName, argumentValue] of fieldParameters) {
|
780
|
+
let argumentValueChunk = getArgumentValueChunk(argumentValue);
|
681
781
|
networkResponseKey += `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${argumentValueChunk}`;
|
682
782
|
}
|
683
783
|
}
|
784
|
+
|
684
785
|
return networkResponseKey;
|
685
786
|
}
|
686
787
|
|
788
|
+
function getArgumentValueChunk(argumentValue: ArgumentValue): string {
|
789
|
+
switch (argumentValue.kind) {
|
790
|
+
case 'Object': {
|
791
|
+
return (
|
792
|
+
'o_' +
|
793
|
+
argumentValue.value
|
794
|
+
.map(([argumentName, argumentValue]) => {
|
795
|
+
return (
|
796
|
+
argumentName +
|
797
|
+
THIRD_SPLIT_KEY +
|
798
|
+
getArgumentValueChunk(argumentValue)
|
799
|
+
);
|
800
|
+
})
|
801
|
+
.join('_') +
|
802
|
+
'_c'
|
803
|
+
);
|
804
|
+
}
|
805
|
+
case 'Literal': {
|
806
|
+
return 'l_' + argumentValue.value;
|
807
|
+
}
|
808
|
+
case 'Variable': {
|
809
|
+
return 'v_' + argumentValue.name;
|
810
|
+
}
|
811
|
+
case 'String': {
|
812
|
+
// replace all non-word characters (alphanumeric & underscore) with underscores
|
813
|
+
return 's_' + argumentValue.value.replaceAll(/\W/g, '_');
|
814
|
+
}
|
815
|
+
case 'Enum': {
|
816
|
+
return 'e_' + argumentValue.value;
|
817
|
+
}
|
818
|
+
default: {
|
819
|
+
// Ensure we have covered all variants
|
820
|
+
let _: never = argumentValue;
|
821
|
+
_;
|
822
|
+
throw new Error('Unexpected case');
|
823
|
+
}
|
824
|
+
}
|
825
|
+
}
|
826
|
+
|
687
827
|
// an alias might be pullRequests____first___first____after___cursor
|
688
828
|
export const FIRST_SPLIT_KEY = '____';
|
689
829
|
export const SECOND_SPLIT_KEY = '___';
|
830
|
+
export const THIRD_SPLIT_KEY = '__';
|
690
831
|
|
691
832
|
// Returns a key to look up an item in the store
|
692
833
|
function getDataIdOfNetworkResponse(
|
693
|
-
|
834
|
+
parentRecordLink: Link,
|
694
835
|
dataToNormalize: NetworkResponseObject,
|
695
|
-
astNode: NormalizationLinkedField
|
836
|
+
astNode: NormalizationLinkedField,
|
696
837
|
variables: Variables,
|
697
838
|
index: number | null,
|
698
839
|
): DataId {
|
840
|
+
// If we are dealing with nested Query, use __ROOT as id
|
841
|
+
// TODO do not hard code this value here
|
842
|
+
if (astNode.concreteType === 'Query') {
|
843
|
+
return ROOT_ID;
|
844
|
+
}
|
845
|
+
|
699
846
|
// Check whether the dataToNormalize has an id field. If so, that is the key.
|
700
847
|
// If not, we construct an id from the parentRecordId and the field parameters.
|
701
848
|
|
@@ -704,7 +851,7 @@ function getDataIdOfNetworkResponse(
|
|
704
851
|
return dataId;
|
705
852
|
}
|
706
853
|
|
707
|
-
let storeKey = `${
|
854
|
+
let storeKey = `${parentRecordLink.__typename}:${parentRecordLink.__link}.${astNode.fieldName}`;
|
708
855
|
if (index != null) {
|
709
856
|
storeKey += `.${index}`;
|
710
857
|
}
|