@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/read.ts
CHANGED
@@ -1,41 +1,67 @@
|
|
1
|
-
import { CleanupFn } from '@isograph/
|
2
|
-
import {
|
1
|
+
import { CleanupFn } from '@isograph/disposable-types';
|
2
|
+
import {
|
3
|
+
getParentRecordKey,
|
4
|
+
insertIfNotExists,
|
5
|
+
onNextChangeToRecord,
|
6
|
+
type EncounteredIds,
|
7
|
+
} from './cache';
|
8
|
+
import { FetchOptions } from './check';
|
3
9
|
import { getOrCreateCachedComponent } from './componentCache';
|
4
10
|
import {
|
5
11
|
IsographEntrypoint,
|
6
12
|
RefetchQueryNormalizationArtifactWrapper,
|
13
|
+
type ReaderWithRefetchQueries,
|
7
14
|
} from './entrypoint';
|
8
|
-
import {
|
15
|
+
import {
|
16
|
+
ExtractData,
|
17
|
+
FragmentReference,
|
18
|
+
Variables,
|
19
|
+
type UnknownTReadFromStore,
|
20
|
+
} from './FragmentReference';
|
9
21
|
import {
|
10
22
|
assertLink,
|
11
|
-
DataId,
|
12
|
-
defaultMissingFieldHandler,
|
13
23
|
getOrLoadIsographArtifact,
|
14
24
|
IsographEnvironment,
|
25
|
+
type DataTypeValue,
|
26
|
+
type Link,
|
27
|
+
type StoreRecord,
|
15
28
|
} from './IsographEnvironment';
|
16
|
-
import {
|
29
|
+
import { logMessage } from './logging';
|
30
|
+
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
17
31
|
import {
|
18
32
|
getPromiseState,
|
19
33
|
PromiseWrapper,
|
20
34
|
readPromise,
|
35
|
+
Result,
|
21
36
|
wrapPromise,
|
22
37
|
wrapResolvedValue,
|
23
38
|
} from './PromiseWrapper';
|
24
|
-
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';
|
25
48
|
import { Arguments } from './util';
|
26
49
|
|
27
50
|
export type WithEncounteredRecords<T> = {
|
28
|
-
readonly encounteredRecords:
|
29
|
-
readonly item: T
|
51
|
+
readonly encounteredRecords: EncounteredIds;
|
52
|
+
readonly item: ExtractData<T>;
|
30
53
|
};
|
31
54
|
|
32
|
-
export function readButDoNotEvaluate<
|
55
|
+
export function readButDoNotEvaluate<
|
56
|
+
TReadFromStore extends UnknownTReadFromStore,
|
57
|
+
>(
|
33
58
|
environment: IsographEnvironment,
|
34
59
|
fragmentReference: FragmentReference<TReadFromStore, unknown>,
|
35
60
|
networkRequestOptions: NetworkRequestReaderOptions,
|
36
61
|
): WithEncounteredRecords<TReadFromStore> {
|
37
|
-
const mutableEncounteredRecords = new
|
62
|
+
const mutableEncounteredRecords: EncounteredIds = new Map();
|
38
63
|
|
64
|
+
// TODO consider moving this to the outside
|
39
65
|
const readerWithRefetchQueries = readPromise(
|
40
66
|
fragmentReference.readerWithRefetchQueries,
|
41
67
|
);
|
@@ -50,13 +76,17 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
|
50
76
|
networkRequestOptions,
|
51
77
|
mutableEncounteredRecords,
|
52
78
|
);
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
79
|
+
|
80
|
+
logMessage(environment, () => ({
|
81
|
+
kind: 'DoneReading',
|
82
|
+
response,
|
83
|
+
fieldName: readerWithRefetchQueries.readerArtifact.fieldName,
|
84
|
+
root: fragmentReference.root,
|
85
|
+
}));
|
86
|
+
|
57
87
|
if (response.kind === 'MissingData') {
|
58
88
|
// There are two cases here that we care about:
|
59
|
-
// 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
|
60
90
|
// to throw if it errors out. So, networkRequestOptions.suspendIfInFlight === false
|
61
91
|
// and networkRequestOptions.throwOnNetworkError === true.
|
62
92
|
// 2. everything else
|
@@ -68,13 +98,27 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
|
68
98
|
!networkRequestOptions.suspendIfInFlight &&
|
69
99
|
networkRequestOptions.throwOnNetworkError
|
70
100
|
) {
|
71
|
-
//
|
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
|
+
|
72
116
|
throw new Promise((resolve, reject) => {
|
73
|
-
|
117
|
+
onNextChangeToRecord(environment, response.recordLink).then(resolve);
|
74
118
|
fragmentReference.networkRequest.promise.catch(reject);
|
75
119
|
});
|
76
120
|
}
|
77
|
-
throw
|
121
|
+
throw onNextChangeToRecord(environment, response.recordLink);
|
78
122
|
} else {
|
79
123
|
return {
|
80
124
|
encounteredRecords: mutableEncounteredRecords,
|
@@ -83,34 +127,41 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
|
83
127
|
}
|
84
128
|
}
|
85
129
|
|
86
|
-
type
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
130
|
+
export type ReadDataResultSuccess<Data> = {
|
131
|
+
readonly kind: 'Success';
|
132
|
+
readonly data: Data;
|
133
|
+
};
|
134
|
+
|
135
|
+
export type ReadDataResult<Data> =
|
136
|
+
| ReadDataResultSuccess<Data>
|
92
137
|
| {
|
93
138
|
readonly kind: 'MissingData';
|
94
139
|
readonly reason: string;
|
95
140
|
readonly nestedReason?: ReadDataResult<unknown>;
|
141
|
+
readonly recordLink: Link;
|
96
142
|
};
|
97
143
|
|
98
144
|
function readData<TReadFromStore>(
|
99
145
|
environment: IsographEnvironment,
|
100
146
|
ast: ReaderAst<TReadFromStore>,
|
101
|
-
root:
|
147
|
+
root: Link,
|
102
148
|
variables: Variables,
|
103
149
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
104
150
|
networkRequest: PromiseWrapper<void, any>,
|
105
151
|
networkRequestOptions: NetworkRequestReaderOptions,
|
106
|
-
mutableEncounteredRecords:
|
107
|
-
): ReadDataResult<TReadFromStore
|
108
|
-
|
109
|
-
|
152
|
+
mutableEncounteredRecords: EncounteredIds,
|
153
|
+
): ReadDataResult<ExtractData<TReadFromStore>> {
|
154
|
+
const encounteredIds = insertIfNotExists(
|
155
|
+
mutableEncounteredRecords,
|
156
|
+
root.__typename,
|
157
|
+
);
|
158
|
+
encounteredIds.add(root.__link);
|
159
|
+
let storeRecord = environment.store[root.__typename]?.[root.__link];
|
110
160
|
if (storeRecord === undefined) {
|
111
161
|
return {
|
112
162
|
kind: 'MissingData',
|
113
|
-
reason: 'No record for root ' + root,
|
163
|
+
reason: 'No record for root ' + root.__link,
|
164
|
+
recordLink: root,
|
114
165
|
};
|
115
166
|
}
|
116
167
|
|
@@ -118,7 +169,6 @@ function readData<TReadFromStore>(
|
|
118
169
|
return {
|
119
170
|
kind: 'Success',
|
120
171
|
data: null as any,
|
121
|
-
encounteredRecords: mutableEncounteredRecords,
|
122
172
|
};
|
123
173
|
}
|
124
174
|
|
@@ -127,382 +177,95 @@ function readData<TReadFromStore>(
|
|
127
177
|
for (const field of ast) {
|
128
178
|
switch (field.kind) {
|
129
179
|
case 'Scalar': {
|
130
|
-
const
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
if (value === undefined) {
|
135
|
-
return {
|
136
|
-
kind: 'MissingData',
|
137
|
-
reason: 'No value for ' + storeRecordName + ' on root ' + root,
|
138
|
-
};
|
180
|
+
const data = readScalarFieldData(field, storeRecord, root, variables);
|
181
|
+
|
182
|
+
if (data.kind === 'MissingData') {
|
183
|
+
return data;
|
139
184
|
}
|
140
|
-
target[field.alias ?? field.fieldName] =
|
185
|
+
target[field.alias ?? field.fieldName] = data.data;
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
case 'Link': {
|
189
|
+
target[field.alias] = root;
|
141
190
|
break;
|
142
191
|
}
|
143
192
|
case 'Linked': {
|
144
|
-
const
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
reason:
|
154
|
-
'No link for ' +
|
155
|
-
storeRecordName +
|
156
|
-
' on root ' +
|
157
|
-
root +
|
158
|
-
'. Link is ' +
|
159
|
-
JSON.stringify(item),
|
160
|
-
};
|
161
|
-
} else if (link === null) {
|
162
|
-
results.push(null);
|
163
|
-
continue;
|
164
|
-
}
|
165
|
-
const result = readData(
|
193
|
+
const data = readLinkedFieldData(
|
194
|
+
environment,
|
195
|
+
field,
|
196
|
+
storeRecord,
|
197
|
+
root,
|
198
|
+
variables,
|
199
|
+
networkRequest,
|
200
|
+
(ast, root) =>
|
201
|
+
readData(
|
166
202
|
environment,
|
167
|
-
|
168
|
-
|
203
|
+
ast,
|
204
|
+
root,
|
169
205
|
variables,
|
170
206
|
nestedRefetchQueries,
|
171
207
|
networkRequest,
|
172
208
|
networkRequestOptions,
|
173
209
|
mutableEncounteredRecords,
|
174
|
-
)
|
175
|
-
if (result.kind === 'MissingData') {
|
176
|
-
return {
|
177
|
-
kind: 'MissingData',
|
178
|
-
reason:
|
179
|
-
'Missing data for ' +
|
180
|
-
storeRecordName +
|
181
|
-
' on root ' +
|
182
|
-
root +
|
183
|
-
'. Link is ' +
|
184
|
-
JSON.stringify(item),
|
185
|
-
nestedReason: result,
|
186
|
-
};
|
187
|
-
}
|
188
|
-
results.push(result.data);
|
189
|
-
}
|
190
|
-
target[field.alias ?? field.fieldName] = results;
|
191
|
-
break;
|
192
|
-
}
|
193
|
-
let link = assertLink(value);
|
194
|
-
if (link === undefined) {
|
195
|
-
// TODO make this configurable, and also generated and derived from the schema
|
196
|
-
const missingFieldHandler =
|
197
|
-
environment.missingFieldHandler ?? defaultMissingFieldHandler;
|
198
|
-
const altLink = missingFieldHandler(
|
199
|
-
storeRecord,
|
200
|
-
root,
|
201
|
-
field.fieldName,
|
202
|
-
field.arguments,
|
203
|
-
variables,
|
204
|
-
);
|
205
|
-
if (altLink === undefined) {
|
206
|
-
return {
|
207
|
-
kind: 'MissingData',
|
208
|
-
reason:
|
209
|
-
'No link for ' +
|
210
|
-
storeRecordName +
|
211
|
-
' on root ' +
|
212
|
-
root +
|
213
|
-
'. Link is ' +
|
214
|
-
JSON.stringify(value),
|
215
|
-
};
|
216
|
-
} else {
|
217
|
-
link = altLink;
|
218
|
-
}
|
219
|
-
} else if (link === null) {
|
220
|
-
target[field.alias ?? field.fieldName] = null;
|
221
|
-
break;
|
222
|
-
}
|
223
|
-
const targetId = link.__link;
|
224
|
-
const data = readData(
|
225
|
-
environment,
|
226
|
-
field.selections,
|
227
|
-
targetId,
|
228
|
-
variables,
|
229
|
-
nestedRefetchQueries,
|
230
|
-
networkRequest,
|
231
|
-
networkRequestOptions,
|
232
|
-
mutableEncounteredRecords,
|
210
|
+
),
|
233
211
|
);
|
234
212
|
if (data.kind === 'MissingData') {
|
235
|
-
return
|
236
|
-
kind: 'MissingData',
|
237
|
-
reason: 'Missing data for ' + storeRecordName + ' on root ' + root,
|
238
|
-
nestedReason: data,
|
239
|
-
};
|
213
|
+
return data;
|
240
214
|
}
|
241
215
|
target[field.alias ?? field.fieldName] = data.data;
|
242
216
|
break;
|
243
217
|
}
|
244
218
|
case 'ImperativelyLoadedField': {
|
245
|
-
|
246
|
-
// id field).
|
247
|
-
const data = readData(
|
219
|
+
const data = readImperativelyLoadedField(
|
248
220
|
environment,
|
249
|
-
field
|
221
|
+
field,
|
250
222
|
root,
|
251
223
|
variables,
|
252
|
-
|
253
|
-
[],
|
254
|
-
// This is probably indicative of the fact that we are doing redundant checks
|
255
|
-
// on the status of this network request...
|
224
|
+
nestedRefetchQueries,
|
256
225
|
networkRequest,
|
257
226
|
networkRequestOptions,
|
258
227
|
mutableEncounteredRecords,
|
259
228
|
);
|
260
229
|
if (data.kind === 'MissingData') {
|
261
|
-
return
|
262
|
-
kind: 'MissingData',
|
263
|
-
reason: 'Missing data for ' + field.alias + ' on root ' + root,
|
264
|
-
nestedReason: data,
|
265
|
-
};
|
266
|
-
} else {
|
267
|
-
const refetchQueryIndex = field.refetchQuery;
|
268
|
-
if (refetchQueryIndex == null) {
|
269
|
-
throw new Error('refetchQuery is null in RefetchField');
|
270
|
-
}
|
271
|
-
const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
|
272
|
-
const refetchQueryArtifact = refetchQuery.artifact;
|
273
|
-
const allowedVariables = refetchQuery.allowedVariables;
|
274
|
-
|
275
|
-
// Second, we allow the user to call the resolver, which will ultimately
|
276
|
-
// use the resolver reader AST to get the resolver parameters.
|
277
|
-
target[field.alias] = (args: any) => [
|
278
|
-
// Stable id
|
279
|
-
root + '__' + field.name,
|
280
|
-
// Fetcher
|
281
|
-
field.refetchReaderArtifact.resolver(
|
282
|
-
environment,
|
283
|
-
refetchQueryArtifact,
|
284
|
-
data.data,
|
285
|
-
filterVariables({ ...args, ...variables }, allowedVariables),
|
286
|
-
root,
|
287
|
-
// TODO these params should be removed
|
288
|
-
null,
|
289
|
-
[],
|
290
|
-
),
|
291
|
-
];
|
230
|
+
return data;
|
292
231
|
}
|
232
|
+
target[field.alias] = data.data;
|
293
233
|
break;
|
294
234
|
}
|
295
235
|
case 'Resolver': {
|
296
|
-
const
|
297
|
-
|
298
|
-
|
236
|
+
const data = readResolverFieldData(
|
237
|
+
environment,
|
238
|
+
field,
|
239
|
+
root,
|
240
|
+
variables,
|
241
|
+
nestedRefetchQueries,
|
242
|
+
networkRequest,
|
243
|
+
networkRequestOptions,
|
244
|
+
mutableEncounteredRecords,
|
299
245
|
);
|
300
|
-
|
301
|
-
|
302
|
-
case 'EagerReaderArtifact': {
|
303
|
-
const data = readData(
|
304
|
-
environment,
|
305
|
-
field.readerArtifact.readerAst,
|
306
|
-
root,
|
307
|
-
variables,
|
308
|
-
resolverRefetchQueries,
|
309
|
-
networkRequest,
|
310
|
-
networkRequestOptions,
|
311
|
-
mutableEncounteredRecords,
|
312
|
-
);
|
313
|
-
if (data.kind === 'MissingData') {
|
314
|
-
return {
|
315
|
-
kind: 'MissingData',
|
316
|
-
reason: 'Missing data for ' + field.alias + ' on root ' + root,
|
317
|
-
nestedReason: data,
|
318
|
-
};
|
319
|
-
} else {
|
320
|
-
target[field.alias] = field.readerArtifact.resolver(data.data);
|
321
|
-
}
|
322
|
-
break;
|
323
|
-
}
|
324
|
-
case 'ComponentReaderArtifact': {
|
325
|
-
target[field.alias] = getOrCreateCachedComponent(
|
326
|
-
environment,
|
327
|
-
field.readerArtifact.componentName,
|
328
|
-
{
|
329
|
-
kind: 'FragmentReference',
|
330
|
-
readerWithRefetchQueries: wrapResolvedValue({
|
331
|
-
kind: 'ReaderWithRefetchQueries',
|
332
|
-
readerArtifact: field.readerArtifact,
|
333
|
-
nestedRefetchQueries: resolverRefetchQueries,
|
334
|
-
}),
|
335
|
-
root,
|
336
|
-
variables: generateChildVariableMap(variables, field.arguments),
|
337
|
-
networkRequest,
|
338
|
-
} as const,
|
339
|
-
networkRequestOptions,
|
340
|
-
);
|
341
|
-
break;
|
342
|
-
}
|
343
|
-
default: {
|
344
|
-
let _: never = field.readerArtifact;
|
345
|
-
_;
|
346
|
-
throw new Error('Unexpected kind');
|
347
|
-
}
|
246
|
+
if (data.kind === 'MissingData') {
|
247
|
+
return data;
|
348
248
|
}
|
249
|
+
target[field.alias] = data.data;
|
349
250
|
break;
|
350
251
|
}
|
351
252
|
case 'LoadablySelectedField': {
|
352
|
-
const
|
253
|
+
const data = readLoadablySelectedFieldData(
|
353
254
|
environment,
|
354
|
-
field
|
255
|
+
field,
|
355
256
|
root,
|
356
257
|
variables,
|
357
|
-
// Refetch fields just read the id, and don't need refetch query artifacts
|
358
|
-
[],
|
359
258
|
networkRequest,
|
360
259
|
networkRequestOptions,
|
361
260
|
mutableEncounteredRecords,
|
362
261
|
);
|
363
|
-
if (
|
364
|
-
return
|
365
|
-
kind: 'MissingData',
|
366
|
-
reason: 'Missing data for ' + field.alias + ' on root ' + root,
|
367
|
-
nestedReason: refetchReaderParams,
|
368
|
-
};
|
369
|
-
} else {
|
370
|
-
target[field.alias] = (args: any) => {
|
371
|
-
// TODO we should use the reader AST for this
|
372
|
-
const includeReadOutData = (variables: any, readOutData: any) => {
|
373
|
-
variables.id = readOutData.id;
|
374
|
-
return variables;
|
375
|
-
};
|
376
|
-
const localVariables = includeReadOutData(
|
377
|
-
args ?? {},
|
378
|
-
refetchReaderParams.data,
|
379
|
-
);
|
380
|
-
writeQueryArgsToVariables(
|
381
|
-
localVariables,
|
382
|
-
field.queryArguments,
|
383
|
-
variables,
|
384
|
-
);
|
385
|
-
|
386
|
-
return [
|
387
|
-
// Stable id
|
388
|
-
root +
|
389
|
-
'/' +
|
390
|
-
field.name +
|
391
|
-
'/' +
|
392
|
-
stableStringifyArgs(localVariables),
|
393
|
-
// Fetcher
|
394
|
-
() => {
|
395
|
-
const fragmentReferenceAndDisposeFromEntrypoint = (
|
396
|
-
entrypoint: IsographEntrypoint<any, any>,
|
397
|
-
): [FragmentReference<any, any>, CleanupFn] => {
|
398
|
-
const [networkRequest, disposeNetworkRequest] =
|
399
|
-
makeNetworkRequest(environment, entrypoint, localVariables);
|
400
|
-
|
401
|
-
const fragmentReference: FragmentReference<any, any> = {
|
402
|
-
kind: 'FragmentReference',
|
403
|
-
readerWithRefetchQueries: wrapResolvedValue({
|
404
|
-
kind: 'ReaderWithRefetchQueries',
|
405
|
-
readerArtifact:
|
406
|
-
entrypoint.readerWithRefetchQueries.readerArtifact,
|
407
|
-
nestedRefetchQueries:
|
408
|
-
entrypoint.readerWithRefetchQueries
|
409
|
-
.nestedRefetchQueries,
|
410
|
-
} as const),
|
411
|
-
|
412
|
-
// TODO localVariables is not guaranteed to have an id field
|
413
|
-
root: localVariables.id,
|
414
|
-
variables: localVariables,
|
415
|
-
networkRequest,
|
416
|
-
};
|
417
|
-
return [fragmentReference, disposeNetworkRequest];
|
418
|
-
};
|
419
|
-
|
420
|
-
if (field.entrypoint.kind === 'Entrypoint') {
|
421
|
-
return fragmentReferenceAndDisposeFromEntrypoint(
|
422
|
-
field.entrypoint,
|
423
|
-
);
|
424
|
-
} else {
|
425
|
-
const isographArtifactPromiseWrapper =
|
426
|
-
getOrLoadIsographArtifact(
|
427
|
-
environment,
|
428
|
-
field.entrypoint.typeAndField,
|
429
|
-
field.entrypoint.loader,
|
430
|
-
);
|
431
|
-
const state = getPromiseState(isographArtifactPromiseWrapper);
|
432
|
-
if (state.kind === 'Ok') {
|
433
|
-
return fragmentReferenceAndDisposeFromEntrypoint(
|
434
|
-
state.value,
|
435
|
-
);
|
436
|
-
} else {
|
437
|
-
// Promise is pending or thrown
|
438
|
-
|
439
|
-
let entrypointLoaderState:
|
440
|
-
| {
|
441
|
-
kind: 'EntrypointNotLoaded';
|
442
|
-
}
|
443
|
-
| {
|
444
|
-
kind: 'NetworkRequestStarted';
|
445
|
-
disposeNetworkRequest: CleanupFn;
|
446
|
-
}
|
447
|
-
| { kind: 'Disposed' } = { kind: 'EntrypointNotLoaded' };
|
448
|
-
|
449
|
-
const networkRequest = wrapPromise(
|
450
|
-
isographArtifactPromiseWrapper.promise.then(
|
451
|
-
(entrypoint) => {
|
452
|
-
if (
|
453
|
-
entrypointLoaderState.kind === 'EntrypointNotLoaded'
|
454
|
-
) {
|
455
|
-
const [networkRequest, disposeNetworkRequest] =
|
456
|
-
makeNetworkRequest(
|
457
|
-
environment,
|
458
|
-
entrypoint,
|
459
|
-
localVariables,
|
460
|
-
);
|
461
|
-
entrypointLoaderState = {
|
462
|
-
kind: 'NetworkRequestStarted',
|
463
|
-
disposeNetworkRequest,
|
464
|
-
};
|
465
|
-
return networkRequest.promise;
|
466
|
-
}
|
467
|
-
},
|
468
|
-
),
|
469
|
-
);
|
470
|
-
const readerWithRefetchPromise =
|
471
|
-
isographArtifactPromiseWrapper.promise.then(
|
472
|
-
(entrypoint) => entrypoint.readerWithRefetchQueries,
|
473
|
-
);
|
474
|
-
|
475
|
-
const fragmentReference: FragmentReference<any, any> = {
|
476
|
-
kind: 'FragmentReference',
|
477
|
-
readerWithRefetchQueries: wrapPromise(
|
478
|
-
readerWithRefetchPromise,
|
479
|
-
),
|
480
|
-
|
481
|
-
// TODO localVariables is not guaranteed to have an id field
|
482
|
-
root: localVariables.id,
|
483
|
-
variables: localVariables,
|
484
|
-
networkRequest,
|
485
|
-
};
|
486
|
-
|
487
|
-
return [
|
488
|
-
fragmentReference,
|
489
|
-
() => {
|
490
|
-
if (
|
491
|
-
entrypointLoaderState.kind === 'NetworkRequestStarted'
|
492
|
-
) {
|
493
|
-
entrypointLoaderState.disposeNetworkRequest();
|
494
|
-
}
|
495
|
-
entrypointLoaderState = { kind: 'Disposed' };
|
496
|
-
},
|
497
|
-
];
|
498
|
-
}
|
499
|
-
}
|
500
|
-
},
|
501
|
-
];
|
502
|
-
};
|
262
|
+
if (data.kind === 'MissingData') {
|
263
|
+
return data;
|
503
264
|
}
|
265
|
+
target[field.alias] = data.data;
|
504
266
|
break;
|
505
267
|
}
|
268
|
+
|
506
269
|
default: {
|
507
270
|
// Ensure we have covered all variants
|
508
271
|
let _: never = field;
|
@@ -514,7 +277,172 @@ function readData<TReadFromStore>(
|
|
514
277
|
return {
|
515
278
|
kind: 'Success',
|
516
279
|
data: target as any,
|
517
|
-
|
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
|
+
},
|
518
446
|
};
|
519
447
|
}
|
520
448
|
|
@@ -541,8 +469,15 @@ function generateChildVariableMap(
|
|
541
469
|
type Writable<T> = { -readonly [P in keyof T]: T[P] };
|
542
470
|
const childVars: Writable<Variables> = {};
|
543
471
|
for (const [name, value] of fieldArguments) {
|
544
|
-
if (value.kind === '
|
545
|
-
childVars[name] = variables
|
472
|
+
if (value.kind === 'Object') {
|
473
|
+
childVars[name] = generateChildVariableMap(variables, value.value);
|
474
|
+
} else if (value.kind === 'Variable') {
|
475
|
+
const variable = variables[value.name];
|
476
|
+
// Variable could be null if it was not provided but has a default case,
|
477
|
+
// so we allow the loop to continue rather than throwing an error.
|
478
|
+
if (variable != null) {
|
479
|
+
childVars[name] = variable;
|
480
|
+
}
|
546
481
|
} else {
|
547
482
|
childVars[name] = value.value;
|
548
483
|
}
|
@@ -560,6 +495,14 @@ function writeQueryArgsToVariables(
|
|
560
495
|
}
|
561
496
|
for (const [name, argType] of queryArgs) {
|
562
497
|
switch (argType.kind) {
|
498
|
+
case 'Object': {
|
499
|
+
writeQueryArgsToVariables(
|
500
|
+
(targetVariables[name] = {}),
|
501
|
+
argType.value,
|
502
|
+
variables,
|
503
|
+
);
|
504
|
+
break;
|
505
|
+
}
|
563
506
|
case 'Variable': {
|
564
507
|
targetVariables[name] = variables[argType.name];
|
565
508
|
break;
|
@@ -585,6 +528,287 @@ function writeQueryArgsToVariables(
|
|
585
528
|
}
|
586
529
|
}
|
587
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
|
+
|
588
812
|
export type NetworkRequestReaderOptions = {
|
589
813
|
suspendIfInFlight: boolean;
|
590
814
|
throwOnNetworkError: boolean;
|
@@ -603,7 +827,7 @@ export function getNetworkRequestOptionsWithDefaults(
|
|
603
827
|
// TODO call stableStringifyArgs on the variable values, as well.
|
604
828
|
// This doesn't matter for now, since we are just using primitive values
|
605
829
|
// in the demo.
|
606
|
-
function stableStringifyArgs(args:
|
830
|
+
function stableStringifyArgs(args: object) {
|
607
831
|
const keys = Object.keys(args);
|
608
832
|
keys.sort();
|
609
833
|
let s = '';
|
@@ -613,3 +837,69 @@ function stableStringifyArgs(args: Object) {
|
|
613
837
|
}
|
614
838
|
return s;
|
615
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
|
+
}
|