@isograph/react 0.3.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 +16 -7
- package/dist/core/FragmentReference.d.ts.map +1 -1
- package/dist/core/FragmentReference.js +3 -12
- package/dist/core/IsographEnvironment.d.ts +17 -27
- package/dist/core/IsographEnvironment.d.ts.map +1 -1
- package/dist/core/IsographEnvironment.js +4 -0
- package/dist/core/PromiseWrapper.d.ts +3 -4
- package/dist/core/PromiseWrapper.d.ts.map +1 -1
- package/dist/core/PromiseWrapper.js +5 -4
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.js +16 -0
- package/dist/core/cache.d.ts +11 -20
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +60 -45
- package/dist/core/check.d.ts +9 -5
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/check.js +2 -2
- package/dist/core/componentCache.d.ts +1 -1
- package/dist/core/componentCache.d.ts.map +1 -1
- package/dist/core/componentCache.js +27 -31
- package/dist/core/entrypoint.d.ts +23 -26
- package/dist/core/entrypoint.d.ts.map +1 -1
- package/dist/core/garbageCollection.d.ts +3 -4
- package/dist/core/garbageCollection.d.ts.map +1 -1
- package/dist/core/garbageCollection.js +1 -1
- package/dist/core/logging.d.ts +12 -13
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/logging.js +8 -5
- package/dist/core/makeNetworkRequest.d.ts +5 -5
- package/dist/core/makeNetworkRequest.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.js +107 -22
- package/dist/core/read.d.ts +15 -10
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +398 -304
- package/dist/core/reader.d.ts +24 -32
- package/dist/core/reader.d.ts.map +1 -1
- 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 +3 -0
- package/dist/core/util.d.ts.map +1 -1
- package/dist/index.d.ts +16 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -10
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
- package/dist/loadable-hooks/useClientSideDefer.js +2 -2
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +8 -15
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.js +6 -4
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -2
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +4 -6
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.js +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +6 -13
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.js +11 -9
- package/dist/react/FragmentReader.d.ts +2 -3
- package/dist/react/FragmentReader.d.ts.map +1 -1
- package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -1
- package/dist/react/useImperativeReference.d.ts +7 -10
- package/dist/react/useImperativeReference.d.ts.map +1 -1
- package/dist/react/useImperativeReference.js +2 -3
- package/dist/react/useLazyReference.d.ts +4 -7
- package/dist/react/useLazyReference.d.ts.map +1 -1
- package/dist/react/useLazyReference.js +26 -5
- package/dist/react/useReadAndSubscribe.d.ts +3 -9
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
- package/dist/react/useReadAndSubscribe.js +7 -3
- package/dist/react/useRerenderOnChange.d.ts +1 -1
- package/dist/react/useRerenderOnChange.d.ts.map +1 -1
- package/dist/react/useResult.d.ts +3 -6
- package/dist/react/useResult.d.ts.map +1 -1
- package/dist/react/useResult.js +10 -8
- package/isograph.config.json +1 -0
- package/package.json +7 -6
- package/src/core/FragmentReference.ts +30 -15
- package/src/core/IsographEnvironment.ts +39 -31
- package/src/core/PromiseWrapper.ts +3 -3
- package/src/core/areEqualWithDeepComparison.ts +20 -0
- package/src/core/cache.ts +105 -72
- package/src/core/check.ts +13 -8
- package/src/core/componentCache.ts +45 -52
- package/src/core/entrypoint.ts +34 -16
- package/src/core/garbageCollection.ts +6 -6
- package/src/core/logging.ts +24 -22
- package/src/core/makeNetworkRequest.ts +183 -30
- package/src/core/read.ts +618 -435
- package/src/core/reader.ts +37 -24
- package/src/core/startUpdate.ts +28 -0
- package/src/core/util.ts +4 -0
- package/src/index.ts +82 -9
- package/src/loadable-hooks/useClientSideDefer.ts +11 -10
- package/src/loadable-hooks/useConnectionSpecPagination.ts +26 -13
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
- package/src/loadable-hooks/useImperativeLoadableField.ts +10 -12
- package/src/loadable-hooks/useSkipLimitPagination.ts +37 -19
- package/src/react/FragmentReader.tsx +3 -3
- package/src/react/IsographEnvironmentProvider.tsx +1 -1
- package/src/react/useImperativeReference.ts +40 -19
- package/src/react/useLazyReference.ts +62 -14
- package/src/react/useReadAndSubscribe.ts +17 -9
- package/src/react/useRerenderOnChange.ts +2 -2
- package/src/react/useResult.ts +21 -8
- package/src/tests/__isograph/Query/meName/entrypoint.ts +4 -28
- package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
- package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +4 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +4 -66
- package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
- package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +7 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +4 -33
- package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
- package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +4 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +4 -43
- package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
- package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +5 -0
- package/src/tests/__isograph/iso.ts +3 -2
- package/src/tests/garbageCollection.test.ts +10 -8
- package/src/tests/meNameSuccessor.ts +1 -1
- package/src/tests/nodeQuery.ts +2 -1
- package/src/tests/normalizeData.test.ts +1 -1
- package/tsconfig.pkg.json +1 -2
package/src/core/read.ts
CHANGED
@@ -1,39 +1,51 @@
|
|
1
|
+
import { CleanupFn } from '@isograph/disposable-types';
|
1
2
|
import {
|
2
3
|
getParentRecordKey,
|
3
4
|
insertIfNotExists,
|
4
5
|
onNextChangeToRecord,
|
5
6
|
type EncounteredIds,
|
6
7
|
} from './cache';
|
8
|
+
import { FetchOptions } from './check';
|
7
9
|
import { getOrCreateCachedComponent } from './componentCache';
|
8
10
|
import {
|
9
11
|
IsographEntrypoint,
|
10
12
|
RefetchQueryNormalizationArtifactWrapper,
|
13
|
+
type ReaderWithRefetchQueries,
|
11
14
|
} from './entrypoint';
|
12
15
|
import {
|
16
|
+
ExtractData,
|
13
17
|
FragmentReference,
|
14
18
|
Variables,
|
15
|
-
|
16
|
-
ExtractParameters,
|
19
|
+
type UnknownTReadFromStore,
|
17
20
|
} from './FragmentReference';
|
18
21
|
import {
|
19
22
|
assertLink,
|
20
23
|
getOrLoadIsographArtifact,
|
21
24
|
IsographEnvironment,
|
25
|
+
type DataTypeValue,
|
22
26
|
type Link,
|
27
|
+
type StoreRecord,
|
23
28
|
} from './IsographEnvironment';
|
29
|
+
import { logMessage } from './logging';
|
24
30
|
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
25
31
|
import {
|
26
32
|
getPromiseState,
|
27
33
|
PromiseWrapper,
|
28
34
|
readPromise,
|
35
|
+
Result,
|
29
36
|
wrapPromise,
|
30
37
|
wrapResolvedValue,
|
31
38
|
} from './PromiseWrapper';
|
32
|
-
import {
|
39
|
+
import {
|
40
|
+
ReaderAst,
|
41
|
+
type ReaderImperativelyLoadedField,
|
42
|
+
type ReaderLinkedField,
|
43
|
+
type ReaderLoadableField,
|
44
|
+
type ReaderNonLoadableResolverField,
|
45
|
+
type ReaderScalarField,
|
46
|
+
} from './reader';
|
47
|
+
import { getOrCreateCachedStartUpdate } from './startUpdate';
|
33
48
|
import { Arguments } from './util';
|
34
|
-
import { logMessage } from './logging';
|
35
|
-
import { CleanupFn } from '@isograph/disposable-types';
|
36
|
-
import { FetchOptions } from './check';
|
37
49
|
|
38
50
|
export type WithEncounteredRecords<T> = {
|
39
51
|
readonly encounteredRecords: EncounteredIds;
|
@@ -41,7 +53,7 @@ export type WithEncounteredRecords<T> = {
|
|
41
53
|
};
|
42
54
|
|
43
55
|
export function readButDoNotEvaluate<
|
44
|
-
TReadFromStore extends
|
56
|
+
TReadFromStore extends UnknownTReadFromStore,
|
45
57
|
>(
|
46
58
|
environment: IsographEnvironment,
|
47
59
|
fragmentReference: FragmentReference<TReadFromStore, unknown>,
|
@@ -49,6 +61,7 @@ export function readButDoNotEvaluate<
|
|
49
61
|
): WithEncounteredRecords<TReadFromStore> {
|
50
62
|
const mutableEncounteredRecords: EncounteredIds = new Map();
|
51
63
|
|
64
|
+
// TODO consider moving this to the outside
|
52
65
|
const readerWithRefetchQueries = readPromise(
|
53
66
|
fragmentReference.readerWithRefetchQueries,
|
54
67
|
);
|
@@ -64,14 +77,16 @@ export function readButDoNotEvaluate<
|
|
64
77
|
mutableEncounteredRecords,
|
65
78
|
);
|
66
79
|
|
67
|
-
logMessage(environment, {
|
80
|
+
logMessage(environment, () => ({
|
68
81
|
kind: 'DoneReading',
|
69
82
|
response,
|
70
|
-
|
83
|
+
fieldName: readerWithRefetchQueries.readerArtifact.fieldName,
|
84
|
+
root: fragmentReference.root,
|
85
|
+
}));
|
71
86
|
|
72
87
|
if (response.kind === 'MissingData') {
|
73
88
|
// There are two cases here that we care about:
|
74
|
-
// 1. the network request is in flight, we haven't
|
89
|
+
// 1. the network request is in flight, we haven't suspended on it, and we want
|
75
90
|
// to throw if it errors out. So, networkRequestOptions.suspendIfInFlight === false
|
76
91
|
// and networkRequestOptions.throwOnNetworkError === true.
|
77
92
|
// 2. everything else
|
@@ -83,7 +98,21 @@ export function readButDoNotEvaluate<
|
|
83
98
|
!networkRequestOptions.suspendIfInFlight &&
|
84
99
|
networkRequestOptions.throwOnNetworkError
|
85
100
|
) {
|
86
|
-
//
|
101
|
+
// What are we doing here? If the network response has errored out, we can do
|
102
|
+
// two things: throw a rejected promise, or throw an error. Both work identically
|
103
|
+
// in the browser. However, during initial SSR on NextJS, throwing a rejected
|
104
|
+
// promise results in an infinite loop (including re-issuing the query until the
|
105
|
+
// process OOM's or something.) Hence, we throw an error.
|
106
|
+
|
107
|
+
// TODO investigate why we cannot check against NOT_SET here and we have to cast
|
108
|
+
const result = fragmentReference.networkRequest.result as Result<
|
109
|
+
any,
|
110
|
+
any
|
111
|
+
>;
|
112
|
+
if (result.kind === 'Err') {
|
113
|
+
throw new Error('NetworkError', { cause: result.error });
|
114
|
+
}
|
115
|
+
|
87
116
|
throw new Promise((resolve, reject) => {
|
88
117
|
onNextChangeToRecord(environment, response.recordLink).then(resolve);
|
89
118
|
fragmentReference.networkRequest.promise.catch(reject);
|
@@ -98,12 +127,13 @@ export function readButDoNotEvaluate<
|
|
98
127
|
}
|
99
128
|
}
|
100
129
|
|
101
|
-
export type
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
130
|
+
export type ReadDataResultSuccess<Data> = {
|
131
|
+
readonly kind: 'Success';
|
132
|
+
readonly data: Data;
|
133
|
+
};
|
134
|
+
|
135
|
+
export type ReadDataResult<Data> =
|
136
|
+
| ReadDataResultSuccess<Data>
|
107
137
|
| {
|
108
138
|
readonly kind: 'MissingData';
|
109
139
|
readonly reason: string;
|
@@ -115,12 +145,12 @@ function readData<TReadFromStore>(
|
|
115
145
|
environment: IsographEnvironment,
|
116
146
|
ast: ReaderAst<TReadFromStore>,
|
117
147
|
root: Link,
|
118
|
-
variables:
|
148
|
+
variables: Variables,
|
119
149
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
120
150
|
networkRequest: PromiseWrapper<void, any>,
|
121
151
|
networkRequestOptions: NetworkRequestReaderOptions,
|
122
152
|
mutableEncounteredRecords: EncounteredIds,
|
123
|
-
): ReadDataResult<TReadFromStore
|
153
|
+
): ReadDataResult<ExtractData<TReadFromStore>> {
|
124
154
|
const encounteredIds = insertIfNotExists(
|
125
155
|
mutableEncounteredRecords,
|
126
156
|
root.__typename,
|
@@ -139,7 +169,6 @@ function readData<TReadFromStore>(
|
|
139
169
|
return {
|
140
170
|
kind: 'Success',
|
141
171
|
data: null as any,
|
142
|
-
encounteredRecords: mutableEncounteredRecords,
|
143
172
|
};
|
144
173
|
}
|
145
174
|
|
@@ -148,156 +177,49 @@ function readData<TReadFromStore>(
|
|
148
177
|
for (const field of ast) {
|
149
178
|
switch (field.kind) {
|
150
179
|
case 'Scalar': {
|
151
|
-
const
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
if (value === undefined) {
|
156
|
-
return {
|
157
|
-
kind: 'MissingData',
|
158
|
-
reason:
|
159
|
-
'No value for ' + storeRecordName + ' on root ' + root.__link,
|
160
|
-
recordLink: root,
|
161
|
-
};
|
180
|
+
const data = readScalarFieldData(field, storeRecord, root, variables);
|
181
|
+
|
182
|
+
if (data.kind === 'MissingData') {
|
183
|
+
return data;
|
162
184
|
}
|
163
|
-
target[field.alias ?? field.fieldName] =
|
185
|
+
target[field.alias ?? field.fieldName] = data.data;
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
case 'Link': {
|
189
|
+
target[field.alias] = root;
|
164
190
|
break;
|
165
191
|
}
|
166
192
|
case 'Linked': {
|
167
|
-
const
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
reason:
|
177
|
-
'No link for ' +
|
178
|
-
storeRecordName +
|
179
|
-
' on root ' +
|
180
|
-
root.__link +
|
181
|
-
'. Link is ' +
|
182
|
-
JSON.stringify(item),
|
183
|
-
recordLink: root,
|
184
|
-
};
|
185
|
-
} else if (link === null) {
|
186
|
-
results.push(null);
|
187
|
-
continue;
|
188
|
-
}
|
189
|
-
|
190
|
-
const result = readData(
|
193
|
+
const data = readLinkedFieldData(
|
194
|
+
environment,
|
195
|
+
field,
|
196
|
+
storeRecord,
|
197
|
+
root,
|
198
|
+
variables,
|
199
|
+
networkRequest,
|
200
|
+
(ast, root) =>
|
201
|
+
readData(
|
191
202
|
environment,
|
192
|
-
|
193
|
-
|
203
|
+
ast,
|
204
|
+
root,
|
194
205
|
variables,
|
195
206
|
nestedRefetchQueries,
|
196
207
|
networkRequest,
|
197
208
|
networkRequestOptions,
|
198
209
|
mutableEncounteredRecords,
|
199
|
-
)
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
reason:
|
204
|
-
'Missing data for ' +
|
205
|
-
storeRecordName +
|
206
|
-
' on root ' +
|
207
|
-
root.__link +
|
208
|
-
'. Link is ' +
|
209
|
-
JSON.stringify(item),
|
210
|
-
nestedReason: result,
|
211
|
-
recordLink: result.recordLink,
|
212
|
-
};
|
213
|
-
}
|
214
|
-
results.push(result.data);
|
215
|
-
}
|
216
|
-
target[field.alias ?? field.fieldName] = results;
|
217
|
-
break;
|
218
|
-
}
|
219
|
-
let link = assertLink(value);
|
220
|
-
|
221
|
-
if (field.condition) {
|
222
|
-
const data = readData(
|
223
|
-
environment,
|
224
|
-
field.condition.readerAst,
|
225
|
-
root,
|
226
|
-
variables,
|
227
|
-
nestedRefetchQueries,
|
228
|
-
networkRequest,
|
229
|
-
networkRequestOptions,
|
230
|
-
mutableEncounteredRecords,
|
231
|
-
);
|
232
|
-
if (data.kind === 'MissingData') {
|
233
|
-
return {
|
234
|
-
kind: 'MissingData',
|
235
|
-
reason:
|
236
|
-
'Missing data for ' +
|
237
|
-
storeRecordName +
|
238
|
-
' on root ' +
|
239
|
-
root.__link,
|
240
|
-
nestedReason: data,
|
241
|
-
recordLink: data.recordLink,
|
242
|
-
};
|
243
|
-
}
|
244
|
-
const condition = field.condition.resolver({
|
245
|
-
data: data.data,
|
246
|
-
parameters: {},
|
247
|
-
});
|
248
|
-
if (condition === true) {
|
249
|
-
link = root;
|
250
|
-
} else if (condition === false) {
|
251
|
-
link = null;
|
252
|
-
} else {
|
253
|
-
link = condition;
|
254
|
-
}
|
255
|
-
}
|
256
|
-
|
257
|
-
if (link === undefined) {
|
258
|
-
// TODO make this configurable, and also generated and derived from the schema
|
259
|
-
const missingFieldHandler = environment.missingFieldHandler;
|
260
|
-
|
261
|
-
const altLink = missingFieldHandler?.(
|
262
|
-
storeRecord,
|
263
|
-
root,
|
264
|
-
field.fieldName,
|
265
|
-
field.arguments,
|
266
|
-
variables,
|
267
|
-
);
|
268
|
-
logMessage(environment, {
|
269
|
-
kind: 'MissingFieldHandlerCalled',
|
270
|
-
root,
|
271
|
-
storeRecord,
|
272
|
-
fieldName: field.fieldName,
|
273
|
-
arguments: field.arguments,
|
274
|
-
variables,
|
275
|
-
});
|
276
|
-
|
277
|
-
if (altLink === undefined) {
|
278
|
-
return {
|
279
|
-
kind: 'MissingData',
|
280
|
-
reason:
|
281
|
-
'No link for ' +
|
282
|
-
storeRecordName +
|
283
|
-
' on root ' +
|
284
|
-
root.__link +
|
285
|
-
'. Link is ' +
|
286
|
-
JSON.stringify(value),
|
287
|
-
recordLink: root,
|
288
|
-
};
|
289
|
-
} else {
|
290
|
-
link = altLink;
|
291
|
-
}
|
292
|
-
} else if (link === null) {
|
293
|
-
target[field.alias ?? field.fieldName] = null;
|
294
|
-
break;
|
210
|
+
),
|
211
|
+
);
|
212
|
+
if (data.kind === 'MissingData') {
|
213
|
+
return data;
|
295
214
|
}
|
296
|
-
|
297
|
-
|
215
|
+
target[field.alias ?? field.fieldName] = data.data;
|
216
|
+
break;
|
217
|
+
}
|
218
|
+
case 'ImperativelyLoadedField': {
|
219
|
+
const data = readImperativelyLoadedField(
|
298
220
|
environment,
|
299
|
-
field
|
300
|
-
|
221
|
+
field,
|
222
|
+
root,
|
301
223
|
variables,
|
302
224
|
nestedRefetchQueries,
|
303
225
|
networkRequest,
|
@@ -305,306 +227,45 @@ function readData<TReadFromStore>(
|
|
305
227
|
mutableEncounteredRecords,
|
306
228
|
);
|
307
229
|
if (data.kind === 'MissingData') {
|
308
|
-
return
|
309
|
-
kind: 'MissingData',
|
310
|
-
reason:
|
311
|
-
'Missing data for ' + storeRecordName + ' on root ' + root.__link,
|
312
|
-
nestedReason: data,
|
313
|
-
recordLink: data.recordLink,
|
314
|
-
};
|
230
|
+
return data;
|
315
231
|
}
|
316
|
-
target[field.alias
|
232
|
+
target[field.alias] = data.data;
|
317
233
|
break;
|
318
234
|
}
|
319
|
-
case '
|
320
|
-
|
321
|
-
// id field).
|
322
|
-
const data = readData(
|
235
|
+
case 'Resolver': {
|
236
|
+
const data = readResolverFieldData(
|
323
237
|
environment,
|
324
|
-
field
|
238
|
+
field,
|
325
239
|
root,
|
326
240
|
variables,
|
327
|
-
|
328
|
-
[],
|
329
|
-
// This is probably indicative of the fact that we are doing redundant checks
|
330
|
-
// on the status of this network request...
|
241
|
+
nestedRefetchQueries,
|
331
242
|
networkRequest,
|
332
243
|
networkRequestOptions,
|
333
244
|
mutableEncounteredRecords,
|
334
245
|
);
|
335
246
|
if (data.kind === 'MissingData') {
|
336
|
-
return
|
337
|
-
kind: 'MissingData',
|
338
|
-
reason:
|
339
|
-
'Missing data for ' + field.alias + ' on root ' + root.__link,
|
340
|
-
nestedReason: data,
|
341
|
-
recordLink: data.recordLink,
|
342
|
-
};
|
343
|
-
} else {
|
344
|
-
const refetchQueryIndex = field.refetchQuery;
|
345
|
-
const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
|
346
|
-
if (refetchQuery == null) {
|
347
|
-
throw new Error(
|
348
|
-
'refetchQuery is null in RefetchField. This is indicative of a bug in Isograph.',
|
349
|
-
);
|
350
|
-
}
|
351
|
-
const refetchQueryArtifact = refetchQuery.artifact;
|
352
|
-
const allowedVariables = refetchQuery.allowedVariables;
|
353
|
-
|
354
|
-
// Second, we allow the user to call the resolver, which will ultimately
|
355
|
-
// use the resolver reader AST to get the resolver parameters.
|
356
|
-
target[field.alias] = (args: any) => [
|
357
|
-
// Stable id
|
358
|
-
root.__link + '__' + field.name,
|
359
|
-
// Fetcher
|
360
|
-
field.refetchReaderArtifact.resolver(
|
361
|
-
environment,
|
362
|
-
refetchQueryArtifact,
|
363
|
-
data.data,
|
364
|
-
filterVariables({ ...args, ...variables }, allowedVariables),
|
365
|
-
root,
|
366
|
-
// TODO these params should be removed
|
367
|
-
null,
|
368
|
-
[],
|
369
|
-
),
|
370
|
-
];
|
371
|
-
}
|
372
|
-
break;
|
373
|
-
}
|
374
|
-
case 'Resolver': {
|
375
|
-
const usedRefetchQueries = field.usedRefetchQueries;
|
376
|
-
const resolverRefetchQueries = usedRefetchQueries.map((index) => {
|
377
|
-
const resolverRefetchQuery = nestedRefetchQueries[index];
|
378
|
-
if (resolverRefetchQuery == null) {
|
379
|
-
throw new Error(
|
380
|
-
'resolverRefetchQuery is null in Resolver. This is indicative of a bug in Isograph.',
|
381
|
-
);
|
382
|
-
}
|
383
|
-
return resolverRefetchQuery;
|
384
|
-
});
|
385
|
-
|
386
|
-
switch (field.readerArtifact.kind) {
|
387
|
-
case 'EagerReaderArtifact': {
|
388
|
-
const data = readData(
|
389
|
-
environment,
|
390
|
-
field.readerArtifact.readerAst,
|
391
|
-
root,
|
392
|
-
generateChildVariableMap(variables, field.arguments),
|
393
|
-
resolverRefetchQueries,
|
394
|
-
networkRequest,
|
395
|
-
networkRequestOptions,
|
396
|
-
mutableEncounteredRecords,
|
397
|
-
);
|
398
|
-
if (data.kind === 'MissingData') {
|
399
|
-
return {
|
400
|
-
kind: 'MissingData',
|
401
|
-
reason:
|
402
|
-
'Missing data for ' + field.alias + ' on root ' + root.__link,
|
403
|
-
nestedReason: data,
|
404
|
-
recordLink: data.recordLink,
|
405
|
-
};
|
406
|
-
} else {
|
407
|
-
const firstParameter = {
|
408
|
-
data: data.data,
|
409
|
-
parameters: variables,
|
410
|
-
};
|
411
|
-
target[field.alias] =
|
412
|
-
field.readerArtifact.resolver(firstParameter);
|
413
|
-
}
|
414
|
-
break;
|
415
|
-
}
|
416
|
-
case 'ComponentReaderArtifact': {
|
417
|
-
target[field.alias] = getOrCreateCachedComponent(
|
418
|
-
environment,
|
419
|
-
field.readerArtifact.componentName,
|
420
|
-
{
|
421
|
-
kind: 'FragmentReference',
|
422
|
-
readerWithRefetchQueries: wrapResolvedValue({
|
423
|
-
kind: 'ReaderWithRefetchQueries',
|
424
|
-
readerArtifact: field.readerArtifact,
|
425
|
-
nestedRefetchQueries: resolverRefetchQueries,
|
426
|
-
}),
|
427
|
-
root,
|
428
|
-
variables: generateChildVariableMap(variables, field.arguments),
|
429
|
-
networkRequest,
|
430
|
-
} as const,
|
431
|
-
networkRequestOptions,
|
432
|
-
);
|
433
|
-
break;
|
434
|
-
}
|
435
|
-
default: {
|
436
|
-
let _: never = field.readerArtifact;
|
437
|
-
_;
|
438
|
-
throw new Error('Unexpected kind');
|
439
|
-
}
|
247
|
+
return data;
|
440
248
|
}
|
249
|
+
target[field.alias] = data.data;
|
441
250
|
break;
|
442
251
|
}
|
443
252
|
case 'LoadablySelectedField': {
|
444
|
-
const
|
253
|
+
const data = readLoadablySelectedFieldData(
|
445
254
|
environment,
|
446
|
-
field
|
255
|
+
field,
|
447
256
|
root,
|
448
257
|
variables,
|
449
|
-
// Refetch fields just read the id, and don't need refetch query artifacts
|
450
|
-
[],
|
451
258
|
networkRequest,
|
452
259
|
networkRequestOptions,
|
453
260
|
mutableEncounteredRecords,
|
454
261
|
);
|
455
|
-
if (
|
456
|
-
return
|
457
|
-
kind: 'MissingData',
|
458
|
-
reason:
|
459
|
-
'Missing data for ' + field.alias + ' on root ' + root.__link,
|
460
|
-
nestedReason: refetchReaderParams,
|
461
|
-
recordLink: refetchReaderParams.recordLink,
|
462
|
-
};
|
463
|
-
} else {
|
464
|
-
target[field.alias] = (args: any, fetchOptions?: FetchOptions) => {
|
465
|
-
// TODO we should use the reader AST for this
|
466
|
-
const includeReadOutData = (variables: any, readOutData: any) => {
|
467
|
-
variables.id = readOutData.id;
|
468
|
-
return variables;
|
469
|
-
};
|
470
|
-
const localVariables = includeReadOutData(
|
471
|
-
args ?? {},
|
472
|
-
refetchReaderParams.data,
|
473
|
-
);
|
474
|
-
writeQueryArgsToVariables(
|
475
|
-
localVariables,
|
476
|
-
field.queryArguments,
|
477
|
-
variables,
|
478
|
-
);
|
479
|
-
|
480
|
-
return [
|
481
|
-
// Stable id
|
482
|
-
root.__typename +
|
483
|
-
':' +
|
484
|
-
root.__link +
|
485
|
-
'/' +
|
486
|
-
field.name +
|
487
|
-
'/' +
|
488
|
-
stableStringifyArgs(localVariables),
|
489
|
-
// Fetcher
|
490
|
-
() => {
|
491
|
-
const fragmentReferenceAndDisposeFromEntrypoint = (
|
492
|
-
entrypoint: IsographEntrypoint<any, any>,
|
493
|
-
): [FragmentReference<any, any>, CleanupFn] => {
|
494
|
-
const [networkRequest, disposeNetworkRequest] =
|
495
|
-
maybeMakeNetworkRequest(
|
496
|
-
environment,
|
497
|
-
entrypoint,
|
498
|
-
localVariables,
|
499
|
-
fetchOptions,
|
500
|
-
);
|
501
|
-
|
502
|
-
const fragmentReference: FragmentReference<any, any> = {
|
503
|
-
kind: 'FragmentReference',
|
504
|
-
readerWithRefetchQueries: wrapResolvedValue({
|
505
|
-
kind: 'ReaderWithRefetchQueries',
|
506
|
-
readerArtifact:
|
507
|
-
entrypoint.readerWithRefetchQueries.readerArtifact,
|
508
|
-
nestedRefetchQueries:
|
509
|
-
entrypoint.readerWithRefetchQueries
|
510
|
-
.nestedRefetchQueries,
|
511
|
-
} as const),
|
512
|
-
|
513
|
-
// TODO localVariables is not guaranteed to have an id field
|
514
|
-
root,
|
515
|
-
variables: localVariables,
|
516
|
-
networkRequest,
|
517
|
-
};
|
518
|
-
return [fragmentReference, disposeNetworkRequest];
|
519
|
-
};
|
520
|
-
|
521
|
-
if (field.entrypoint.kind === 'Entrypoint') {
|
522
|
-
return fragmentReferenceAndDisposeFromEntrypoint(
|
523
|
-
field.entrypoint,
|
524
|
-
);
|
525
|
-
} else {
|
526
|
-
const isographArtifactPromiseWrapper =
|
527
|
-
getOrLoadIsographArtifact(
|
528
|
-
environment,
|
529
|
-
field.entrypoint.typeAndField,
|
530
|
-
field.entrypoint.loader,
|
531
|
-
);
|
532
|
-
const state = getPromiseState(isographArtifactPromiseWrapper);
|
533
|
-
if (state.kind === 'Ok') {
|
534
|
-
return fragmentReferenceAndDisposeFromEntrypoint(
|
535
|
-
state.value,
|
536
|
-
);
|
537
|
-
} else {
|
538
|
-
// Promise is pending or thrown
|
539
|
-
|
540
|
-
let entrypointLoaderState:
|
541
|
-
| {
|
542
|
-
kind: 'EntrypointNotLoaded';
|
543
|
-
}
|
544
|
-
| {
|
545
|
-
kind: 'NetworkRequestStarted';
|
546
|
-
disposeNetworkRequest: CleanupFn;
|
547
|
-
}
|
548
|
-
| { kind: 'Disposed' } = { kind: 'EntrypointNotLoaded' };
|
549
|
-
|
550
|
-
const networkRequest = wrapPromise(
|
551
|
-
isographArtifactPromiseWrapper.promise.then(
|
552
|
-
(entrypoint) => {
|
553
|
-
if (
|
554
|
-
entrypointLoaderState.kind === 'EntrypointNotLoaded'
|
555
|
-
) {
|
556
|
-
const [networkRequest, disposeNetworkRequest] =
|
557
|
-
maybeMakeNetworkRequest(
|
558
|
-
environment,
|
559
|
-
entrypoint,
|
560
|
-
localVariables,
|
561
|
-
fetchOptions,
|
562
|
-
);
|
563
|
-
entrypointLoaderState = {
|
564
|
-
kind: 'NetworkRequestStarted',
|
565
|
-
disposeNetworkRequest,
|
566
|
-
};
|
567
|
-
return networkRequest.promise;
|
568
|
-
}
|
569
|
-
},
|
570
|
-
),
|
571
|
-
);
|
572
|
-
const readerWithRefetchPromise =
|
573
|
-
isographArtifactPromiseWrapper.promise.then(
|
574
|
-
(entrypoint) => entrypoint.readerWithRefetchQueries,
|
575
|
-
);
|
576
|
-
|
577
|
-
const fragmentReference: FragmentReference<any, any> = {
|
578
|
-
kind: 'FragmentReference',
|
579
|
-
readerWithRefetchQueries: wrapPromise(
|
580
|
-
readerWithRefetchPromise,
|
581
|
-
),
|
582
|
-
|
583
|
-
// TODO localVariables is not guaranteed to have an id field
|
584
|
-
root,
|
585
|
-
variables: localVariables,
|
586
|
-
networkRequest,
|
587
|
-
};
|
588
|
-
|
589
|
-
return [
|
590
|
-
fragmentReference,
|
591
|
-
() => {
|
592
|
-
if (
|
593
|
-
entrypointLoaderState.kind === 'NetworkRequestStarted'
|
594
|
-
) {
|
595
|
-
entrypointLoaderState.disposeNetworkRequest();
|
596
|
-
}
|
597
|
-
entrypointLoaderState = { kind: 'Disposed' };
|
598
|
-
},
|
599
|
-
];
|
600
|
-
}
|
601
|
-
}
|
602
|
-
},
|
603
|
-
];
|
604
|
-
};
|
262
|
+
if (data.kind === 'MissingData') {
|
263
|
+
return data;
|
605
264
|
}
|
265
|
+
target[field.alias] = data.data;
|
606
266
|
break;
|
607
267
|
}
|
268
|
+
|
608
269
|
default: {
|
609
270
|
// Ensure we have covered all variants
|
610
271
|
let _: never = field;
|
@@ -616,7 +277,172 @@ function readData<TReadFromStore>(
|
|
616
277
|
return {
|
617
278
|
kind: 'Success',
|
618
279
|
data: target as any,
|
619
|
-
|
280
|
+
};
|
281
|
+
}
|
282
|
+
|
283
|
+
export function readLoadablySelectedFieldData(
|
284
|
+
environment: IsographEnvironment,
|
285
|
+
field: ReaderLoadableField,
|
286
|
+
root: Link,
|
287
|
+
variables: Variables,
|
288
|
+
networkRequest: PromiseWrapper<void, any>,
|
289
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
290
|
+
mutableEncounteredRecords: EncounteredIds,
|
291
|
+
): ReadDataResult<unknown> {
|
292
|
+
const refetchReaderParams = readData(
|
293
|
+
environment,
|
294
|
+
field.refetchReaderAst,
|
295
|
+
root,
|
296
|
+
variables,
|
297
|
+
// Refetch fields just read the id, and don't need refetch query artifacts
|
298
|
+
[],
|
299
|
+
networkRequest,
|
300
|
+
networkRequestOptions,
|
301
|
+
mutableEncounteredRecords,
|
302
|
+
);
|
303
|
+
|
304
|
+
if (refetchReaderParams.kind === 'MissingData') {
|
305
|
+
return {
|
306
|
+
kind: 'MissingData',
|
307
|
+
reason: 'Missing data for ' + field.alias + ' on root ' + root.__link,
|
308
|
+
nestedReason: refetchReaderParams,
|
309
|
+
recordLink: refetchReaderParams.recordLink,
|
310
|
+
};
|
311
|
+
}
|
312
|
+
|
313
|
+
return {
|
314
|
+
kind: 'Success',
|
315
|
+
data: (
|
316
|
+
args: any,
|
317
|
+
// TODO get the associated type for FetchOptions from the loadably selected field
|
318
|
+
fetchOptions?: FetchOptions<any>,
|
319
|
+
) => {
|
320
|
+
// TODO we should use the reader AST for this
|
321
|
+
const includeReadOutData = (variables: any, readOutData: any) => {
|
322
|
+
variables.id = readOutData.id;
|
323
|
+
return variables;
|
324
|
+
};
|
325
|
+
const localVariables = includeReadOutData(
|
326
|
+
args ?? {},
|
327
|
+
refetchReaderParams.data,
|
328
|
+
);
|
329
|
+
writeQueryArgsToVariables(
|
330
|
+
localVariables,
|
331
|
+
field.queryArguments,
|
332
|
+
variables,
|
333
|
+
);
|
334
|
+
|
335
|
+
return [
|
336
|
+
// Stable id
|
337
|
+
root.__typename +
|
338
|
+
':' +
|
339
|
+
root.__link +
|
340
|
+
'/' +
|
341
|
+
field.name +
|
342
|
+
'/' +
|
343
|
+
stableStringifyArgs(localVariables),
|
344
|
+
// Fetcher
|
345
|
+
() => {
|
346
|
+
const fragmentReferenceAndDisposeFromEntrypoint = (
|
347
|
+
entrypoint: IsographEntrypoint<any, any, any>,
|
348
|
+
): [FragmentReference<any, any>, CleanupFn] => {
|
349
|
+
const [networkRequest, disposeNetworkRequest] =
|
350
|
+
maybeMakeNetworkRequest(
|
351
|
+
environment,
|
352
|
+
entrypoint,
|
353
|
+
localVariables,
|
354
|
+
fetchOptions,
|
355
|
+
);
|
356
|
+
|
357
|
+
const fragmentReference: FragmentReference<any, any> = {
|
358
|
+
kind: 'FragmentReference',
|
359
|
+
readerWithRefetchQueries: wrapResolvedValue({
|
360
|
+
kind: 'ReaderWithRefetchQueries',
|
361
|
+
readerArtifact:
|
362
|
+
entrypoint.readerWithRefetchQueries.readerArtifact,
|
363
|
+
nestedRefetchQueries:
|
364
|
+
entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
365
|
+
} as const),
|
366
|
+
|
367
|
+
// TODO localVariables is not guaranteed to have an id field
|
368
|
+
root,
|
369
|
+
variables: localVariables,
|
370
|
+
networkRequest,
|
371
|
+
};
|
372
|
+
return [fragmentReference, disposeNetworkRequest];
|
373
|
+
};
|
374
|
+
|
375
|
+
if (field.entrypoint.kind === 'Entrypoint') {
|
376
|
+
return fragmentReferenceAndDisposeFromEntrypoint(field.entrypoint);
|
377
|
+
} else {
|
378
|
+
const isographArtifactPromiseWrapper = getOrLoadIsographArtifact(
|
379
|
+
environment,
|
380
|
+
field.entrypoint.typeAndField,
|
381
|
+
field.entrypoint.loader,
|
382
|
+
);
|
383
|
+
const state = getPromiseState(isographArtifactPromiseWrapper);
|
384
|
+
if (state.kind === 'Ok') {
|
385
|
+
return fragmentReferenceAndDisposeFromEntrypoint(state.value);
|
386
|
+
} else {
|
387
|
+
// Promise is pending or thrown
|
388
|
+
|
389
|
+
let entrypointLoaderState:
|
390
|
+
| {
|
391
|
+
kind: 'EntrypointNotLoaded';
|
392
|
+
}
|
393
|
+
| {
|
394
|
+
kind: 'NetworkRequestStarted';
|
395
|
+
disposeNetworkRequest: CleanupFn;
|
396
|
+
}
|
397
|
+
| { kind: 'Disposed' } = { kind: 'EntrypointNotLoaded' };
|
398
|
+
|
399
|
+
const networkRequest = wrapPromise(
|
400
|
+
isographArtifactPromiseWrapper.promise.then((entrypoint) => {
|
401
|
+
if (entrypointLoaderState.kind === 'EntrypointNotLoaded') {
|
402
|
+
const [networkRequest, disposeNetworkRequest] =
|
403
|
+
maybeMakeNetworkRequest(
|
404
|
+
environment,
|
405
|
+
entrypoint,
|
406
|
+
localVariables,
|
407
|
+
fetchOptions,
|
408
|
+
);
|
409
|
+
entrypointLoaderState = {
|
410
|
+
kind: 'NetworkRequestStarted',
|
411
|
+
disposeNetworkRequest,
|
412
|
+
};
|
413
|
+
return networkRequest.promise;
|
414
|
+
}
|
415
|
+
}),
|
416
|
+
);
|
417
|
+
const readerWithRefetchPromise =
|
418
|
+
isographArtifactPromiseWrapper.promise.then(
|
419
|
+
(entrypoint) => entrypoint.readerWithRefetchQueries,
|
420
|
+
);
|
421
|
+
|
422
|
+
const fragmentReference: FragmentReference<any, any> = {
|
423
|
+
kind: 'FragmentReference',
|
424
|
+
readerWithRefetchQueries: wrapPromise(readerWithRefetchPromise),
|
425
|
+
|
426
|
+
// TODO localVariables is not guaranteed to have an id field
|
427
|
+
root,
|
428
|
+
variables: localVariables,
|
429
|
+
networkRequest,
|
430
|
+
};
|
431
|
+
|
432
|
+
return [
|
433
|
+
fragmentReference,
|
434
|
+
() => {
|
435
|
+
if (entrypointLoaderState.kind === 'NetworkRequestStarted') {
|
436
|
+
entrypointLoaderState.disposeNetworkRequest();
|
437
|
+
}
|
438
|
+
entrypointLoaderState = { kind: 'Disposed' };
|
439
|
+
},
|
440
|
+
];
|
441
|
+
}
|
442
|
+
}
|
443
|
+
},
|
444
|
+
];
|
445
|
+
},
|
620
446
|
};
|
621
447
|
}
|
622
448
|
|
@@ -643,7 +469,9 @@ function generateChildVariableMap(
|
|
643
469
|
type Writable<T> = { -readonly [P in keyof T]: T[P] };
|
644
470
|
const childVars: Writable<Variables> = {};
|
645
471
|
for (const [name, value] of fieldArguments) {
|
646
|
-
if (value.kind === '
|
472
|
+
if (value.kind === 'Object') {
|
473
|
+
childVars[name] = generateChildVariableMap(variables, value.value);
|
474
|
+
} else if (value.kind === 'Variable') {
|
647
475
|
const variable = variables[value.name];
|
648
476
|
// Variable could be null if it was not provided but has a default case,
|
649
477
|
// so we allow the loop to continue rather than throwing an error.
|
@@ -667,6 +495,14 @@ function writeQueryArgsToVariables(
|
|
667
495
|
}
|
668
496
|
for (const [name, argType] of queryArgs) {
|
669
497
|
switch (argType.kind) {
|
498
|
+
case 'Object': {
|
499
|
+
writeQueryArgsToVariables(
|
500
|
+
(targetVariables[name] = {}),
|
501
|
+
argType.value,
|
502
|
+
variables,
|
503
|
+
);
|
504
|
+
break;
|
505
|
+
}
|
670
506
|
case 'Variable': {
|
671
507
|
targetVariables[name] = variables[argType.name];
|
672
508
|
break;
|
@@ -692,6 +528,287 @@ function writeQueryArgsToVariables(
|
|
692
528
|
}
|
693
529
|
}
|
694
530
|
|
531
|
+
export function readResolverFieldData(
|
532
|
+
environment: IsographEnvironment,
|
533
|
+
field: ReaderNonLoadableResolverField,
|
534
|
+
root: Link,
|
535
|
+
variables: Variables,
|
536
|
+
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
537
|
+
networkRequest: PromiseWrapper<void, any>,
|
538
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
539
|
+
mutableEncounteredRecords: EncounteredIds,
|
540
|
+
): ReadDataResult<unknown> {
|
541
|
+
const usedRefetchQueries = field.usedRefetchQueries;
|
542
|
+
const resolverRefetchQueries = usedRefetchQueries.map((index) => {
|
543
|
+
const resolverRefetchQuery = nestedRefetchQueries[index];
|
544
|
+
if (resolverRefetchQuery == null) {
|
545
|
+
throw new Error(
|
546
|
+
'resolverRefetchQuery is null in Resolver. This is indicative of a bug in Isograph.',
|
547
|
+
);
|
548
|
+
}
|
549
|
+
return resolverRefetchQuery;
|
550
|
+
});
|
551
|
+
|
552
|
+
const readerWithRefetchQueries = {
|
553
|
+
kind: 'ReaderWithRefetchQueries',
|
554
|
+
readerArtifact: field.readerArtifact,
|
555
|
+
nestedRefetchQueries: resolverRefetchQueries,
|
556
|
+
} satisfies ReaderWithRefetchQueries<any, any>;
|
557
|
+
|
558
|
+
const fragment = {
|
559
|
+
kind: 'FragmentReference',
|
560
|
+
readerWithRefetchQueries: wrapResolvedValue(readerWithRefetchQueries),
|
561
|
+
root,
|
562
|
+
variables: generateChildVariableMap(variables, field.arguments),
|
563
|
+
networkRequest,
|
564
|
+
} satisfies FragmentReference<any, any>;
|
565
|
+
|
566
|
+
switch (field.readerArtifact.kind) {
|
567
|
+
case 'EagerReaderArtifact': {
|
568
|
+
const data = readData(
|
569
|
+
environment,
|
570
|
+
field.readerArtifact.readerAst,
|
571
|
+
root,
|
572
|
+
generateChildVariableMap(variables, field.arguments),
|
573
|
+
resolverRefetchQueries,
|
574
|
+
networkRequest,
|
575
|
+
networkRequestOptions,
|
576
|
+
mutableEncounteredRecords,
|
577
|
+
);
|
578
|
+
if (data.kind === 'MissingData') {
|
579
|
+
return {
|
580
|
+
kind: 'MissingData',
|
581
|
+
reason: 'Missing data for ' + field.alias + ' on root ' + root.__link,
|
582
|
+
nestedReason: data,
|
583
|
+
recordLink: data.recordLink,
|
584
|
+
};
|
585
|
+
}
|
586
|
+
const firstParameter = {
|
587
|
+
data: data.data,
|
588
|
+
parameters: variables,
|
589
|
+
startUpdate: field.readerArtifact.hasUpdatable
|
590
|
+
? getOrCreateCachedStartUpdate(
|
591
|
+
environment,
|
592
|
+
fragment,
|
593
|
+
readerWithRefetchQueries.readerArtifact.fieldName,
|
594
|
+
)
|
595
|
+
: undefined,
|
596
|
+
};
|
597
|
+
return {
|
598
|
+
kind: 'Success',
|
599
|
+
data: field.readerArtifact.resolver(firstParameter),
|
600
|
+
};
|
601
|
+
}
|
602
|
+
case 'ComponentReaderArtifact': {
|
603
|
+
return {
|
604
|
+
kind: 'Success',
|
605
|
+
data: getOrCreateCachedComponent(
|
606
|
+
environment,
|
607
|
+
field.readerArtifact.fieldName,
|
608
|
+
fragment,
|
609
|
+
networkRequestOptions,
|
610
|
+
),
|
611
|
+
};
|
612
|
+
}
|
613
|
+
default: {
|
614
|
+
let _: never = field.readerArtifact;
|
615
|
+
_;
|
616
|
+
throw new Error('Unexpected kind');
|
617
|
+
}
|
618
|
+
}
|
619
|
+
}
|
620
|
+
|
621
|
+
export function readScalarFieldData(
|
622
|
+
field: ReaderScalarField,
|
623
|
+
storeRecord: StoreRecord,
|
624
|
+
root: Link,
|
625
|
+
variables: Variables,
|
626
|
+
): ReadDataResult<string | number | boolean | Link | DataTypeValue[] | null> {
|
627
|
+
const storeRecordName = getParentRecordKey(field, variables);
|
628
|
+
const value = storeRecord[storeRecordName];
|
629
|
+
// TODO consider making scalars into discriminated unions. This probably has
|
630
|
+
// to happen for when we handle errors.
|
631
|
+
if (value === undefined) {
|
632
|
+
return {
|
633
|
+
kind: 'MissingData',
|
634
|
+
reason: 'No value for ' + storeRecordName + ' on root ' + root.__link,
|
635
|
+
recordLink: root,
|
636
|
+
};
|
637
|
+
}
|
638
|
+
return { kind: 'Success', data: value };
|
639
|
+
}
|
640
|
+
|
641
|
+
export function readLinkedFieldData(
|
642
|
+
environment: IsographEnvironment,
|
643
|
+
field: ReaderLinkedField,
|
644
|
+
storeRecord: StoreRecord,
|
645
|
+
root: Link,
|
646
|
+
variables: Variables,
|
647
|
+
networkRequest: PromiseWrapper<void, any>,
|
648
|
+
|
649
|
+
readData: <TReadFromStore>(
|
650
|
+
ast: ReaderAst<TReadFromStore>,
|
651
|
+
root: Link,
|
652
|
+
) => ReadDataResult<object>,
|
653
|
+
): ReadDataResult<unknown> {
|
654
|
+
const storeRecordName = getParentRecordKey(field, variables);
|
655
|
+
const value = storeRecord[storeRecordName];
|
656
|
+
if (Array.isArray(value)) {
|
657
|
+
const results = [];
|
658
|
+
for (const item of value) {
|
659
|
+
const link = assertLink(item);
|
660
|
+
if (link === undefined) {
|
661
|
+
return {
|
662
|
+
kind: 'MissingData',
|
663
|
+
reason:
|
664
|
+
'No link for ' +
|
665
|
+
storeRecordName +
|
666
|
+
' on root ' +
|
667
|
+
root.__link +
|
668
|
+
'. Link is ' +
|
669
|
+
JSON.stringify(item),
|
670
|
+
recordLink: root,
|
671
|
+
};
|
672
|
+
} else if (link === null) {
|
673
|
+
results.push(null);
|
674
|
+
continue;
|
675
|
+
}
|
676
|
+
|
677
|
+
const result = readData(field.selections, link);
|
678
|
+
if (result.kind === 'MissingData') {
|
679
|
+
return {
|
680
|
+
kind: 'MissingData',
|
681
|
+
reason:
|
682
|
+
'Missing data for ' +
|
683
|
+
storeRecordName +
|
684
|
+
' on root ' +
|
685
|
+
root.__link +
|
686
|
+
'. Link is ' +
|
687
|
+
JSON.stringify(item),
|
688
|
+
nestedReason: result,
|
689
|
+
recordLink: result.recordLink,
|
690
|
+
};
|
691
|
+
}
|
692
|
+
results.push(result.data);
|
693
|
+
}
|
694
|
+
return {
|
695
|
+
kind: 'Success',
|
696
|
+
data: results,
|
697
|
+
};
|
698
|
+
}
|
699
|
+
let link = assertLink(value);
|
700
|
+
|
701
|
+
if (field.condition) {
|
702
|
+
const data = readData(field.condition.readerAst, root);
|
703
|
+
if (data.kind === 'MissingData') {
|
704
|
+
return {
|
705
|
+
kind: 'MissingData',
|
706
|
+
reason:
|
707
|
+
'Missing data for ' + storeRecordName + ' on root ' + root.__link,
|
708
|
+
nestedReason: data,
|
709
|
+
recordLink: data.recordLink,
|
710
|
+
};
|
711
|
+
}
|
712
|
+
|
713
|
+
const readerWithRefetchQueries = {
|
714
|
+
kind: 'ReaderWithRefetchQueries',
|
715
|
+
readerArtifact: field.condition,
|
716
|
+
// TODO this is wrong
|
717
|
+
// should map field.condition.usedRefetchQueries
|
718
|
+
// but it doesn't exist
|
719
|
+
nestedRefetchQueries: [],
|
720
|
+
} satisfies ReaderWithRefetchQueries<any, any>;
|
721
|
+
|
722
|
+
const fragment = {
|
723
|
+
kind: 'FragmentReference',
|
724
|
+
readerWithRefetchQueries: wrapResolvedValue(readerWithRefetchQueries),
|
725
|
+
root,
|
726
|
+
variables: generateChildVariableMap(
|
727
|
+
variables,
|
728
|
+
// TODO this is wrong
|
729
|
+
// should use field.condition.variables
|
730
|
+
// but it doesn't exist
|
731
|
+
[],
|
732
|
+
),
|
733
|
+
networkRequest,
|
734
|
+
} satisfies FragmentReference<any, any>;
|
735
|
+
|
736
|
+
const condition = field.condition.resolver({
|
737
|
+
data: data.data,
|
738
|
+
parameters: {},
|
739
|
+
...(field.condition.hasUpdatable
|
740
|
+
? {
|
741
|
+
startUpdate: getOrCreateCachedStartUpdate(
|
742
|
+
environment,
|
743
|
+
fragment,
|
744
|
+
readerWithRefetchQueries.readerArtifact.fieldName,
|
745
|
+
),
|
746
|
+
}
|
747
|
+
: undefined),
|
748
|
+
});
|
749
|
+
if (condition === true) {
|
750
|
+
link = root;
|
751
|
+
} else if (condition === false) {
|
752
|
+
link = null;
|
753
|
+
} else {
|
754
|
+
link = condition;
|
755
|
+
}
|
756
|
+
}
|
757
|
+
|
758
|
+
if (link === undefined) {
|
759
|
+
// TODO make this configurable, and also generated and derived from the schema
|
760
|
+
const missingFieldHandler = environment.missingFieldHandler;
|
761
|
+
|
762
|
+
const altLink = missingFieldHandler?.(
|
763
|
+
storeRecord,
|
764
|
+
root,
|
765
|
+
field.fieldName,
|
766
|
+
field.arguments,
|
767
|
+
variables,
|
768
|
+
);
|
769
|
+
logMessage(environment, () => ({
|
770
|
+
kind: 'MissingFieldHandlerCalled',
|
771
|
+
root,
|
772
|
+
storeRecord,
|
773
|
+
fieldName: field.fieldName,
|
774
|
+
arguments: field.arguments,
|
775
|
+
variables,
|
776
|
+
}));
|
777
|
+
|
778
|
+
if (altLink === undefined) {
|
779
|
+
return {
|
780
|
+
kind: 'MissingData',
|
781
|
+
reason:
|
782
|
+
'No link for ' +
|
783
|
+
storeRecordName +
|
784
|
+
' on root ' +
|
785
|
+
root.__link +
|
786
|
+
'. Link is ' +
|
787
|
+
JSON.stringify(value),
|
788
|
+
recordLink: root,
|
789
|
+
};
|
790
|
+
} else {
|
791
|
+
link = altLink;
|
792
|
+
}
|
793
|
+
} else if (link === null) {
|
794
|
+
return {
|
795
|
+
kind: 'Success',
|
796
|
+
data: null,
|
797
|
+
};
|
798
|
+
}
|
799
|
+
const targetId = link;
|
800
|
+
const data = readData(field.selections, targetId);
|
801
|
+
if (data.kind === 'MissingData') {
|
802
|
+
return {
|
803
|
+
kind: 'MissingData',
|
804
|
+
reason: 'Missing data for ' + storeRecordName + ' on root ' + root.__link,
|
805
|
+
nestedReason: data,
|
806
|
+
recordLink: data.recordLink,
|
807
|
+
};
|
808
|
+
}
|
809
|
+
return data;
|
810
|
+
}
|
811
|
+
|
695
812
|
export type NetworkRequestReaderOptions = {
|
696
813
|
suspendIfInFlight: boolean;
|
697
814
|
throwOnNetworkError: boolean;
|
@@ -720,3 +837,69 @@ function stableStringifyArgs(args: object) {
|
|
720
837
|
}
|
721
838
|
return s;
|
722
839
|
}
|
840
|
+
|
841
|
+
export function readImperativelyLoadedField(
|
842
|
+
environment: IsographEnvironment,
|
843
|
+
field: ReaderImperativelyLoadedField,
|
844
|
+
root: Link,
|
845
|
+
variables: Variables,
|
846
|
+
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
847
|
+
networkRequest: PromiseWrapper<void, any>,
|
848
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
849
|
+
mutableEncounteredRecords: EncounteredIds,
|
850
|
+
): ReadDataResult<unknown> {
|
851
|
+
// First, we read the data using the refetch reader AST (i.e. read out the
|
852
|
+
// id field).
|
853
|
+
const data = readData(
|
854
|
+
environment,
|
855
|
+
field.refetchReaderArtifact.readerAst,
|
856
|
+
root,
|
857
|
+
variables,
|
858
|
+
// Refetch fields just read the id, and don't need refetch query artifacts
|
859
|
+
[],
|
860
|
+
// This is probably indicative of the fact that we are doing redundant checks
|
861
|
+
// on the status of this network request...
|
862
|
+
networkRequest,
|
863
|
+
networkRequestOptions,
|
864
|
+
mutableEncounteredRecords,
|
865
|
+
);
|
866
|
+
if (data.kind === 'MissingData') {
|
867
|
+
return {
|
868
|
+
kind: 'MissingData',
|
869
|
+
reason: 'Missing data for ' + field.alias + ' on root ' + root.__link,
|
870
|
+
nestedReason: data,
|
871
|
+
recordLink: data.recordLink,
|
872
|
+
};
|
873
|
+
} else {
|
874
|
+
const refetchQueryIndex = field.refetchQuery;
|
875
|
+
const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
|
876
|
+
if (refetchQuery == null) {
|
877
|
+
throw new Error(
|
878
|
+
'refetchQuery is null in RefetchField. This is indicative of a bug in Isograph.',
|
879
|
+
);
|
880
|
+
}
|
881
|
+
const refetchQueryArtifact = refetchQuery.artifact;
|
882
|
+
const allowedVariables = refetchQuery.allowedVariables;
|
883
|
+
|
884
|
+
// Second, we allow the user to call the resolver, which will ultimately
|
885
|
+
// use the resolver reader AST to get the resolver parameters.
|
886
|
+
return {
|
887
|
+
kind: 'Success',
|
888
|
+
data: (args: any) => [
|
889
|
+
// Stable id
|
890
|
+
root.__link + '__' + field.name,
|
891
|
+
// Fetcher
|
892
|
+
field.refetchReaderArtifact.resolver(
|
893
|
+
environment,
|
894
|
+
refetchQueryArtifact,
|
895
|
+
data.data,
|
896
|
+
filterVariables({ ...args, ...variables }, allowedVariables),
|
897
|
+
root,
|
898
|
+
// TODO these params should be removed
|
899
|
+
null,
|
900
|
+
[],
|
901
|
+
),
|
902
|
+
],
|
903
|
+
};
|
904
|
+
}
|
905
|
+
}
|