@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.
Files changed (128) hide show
  1. package/.turbo/turbo-compile-typescript.log +4 -0
  2. package/dist/core/FragmentReference.d.ts +16 -7
  3. package/dist/core/FragmentReference.d.ts.map +1 -1
  4. package/dist/core/FragmentReference.js +3 -12
  5. package/dist/core/IsographEnvironment.d.ts +17 -27
  6. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  7. package/dist/core/IsographEnvironment.js +4 -0
  8. package/dist/core/PromiseWrapper.d.ts +3 -4
  9. package/dist/core/PromiseWrapper.d.ts.map +1 -1
  10. package/dist/core/PromiseWrapper.js +5 -4
  11. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
  12. package/dist/core/areEqualWithDeepComparison.js +16 -0
  13. package/dist/core/cache.d.ts +11 -20
  14. package/dist/core/cache.d.ts.map +1 -1
  15. package/dist/core/cache.js +60 -45
  16. package/dist/core/check.d.ts +9 -5
  17. package/dist/core/check.d.ts.map +1 -1
  18. package/dist/core/check.js +2 -2
  19. package/dist/core/componentCache.d.ts +1 -1
  20. package/dist/core/componentCache.d.ts.map +1 -1
  21. package/dist/core/componentCache.js +27 -31
  22. package/dist/core/entrypoint.d.ts +23 -26
  23. package/dist/core/entrypoint.d.ts.map +1 -1
  24. package/dist/core/garbageCollection.d.ts +3 -4
  25. package/dist/core/garbageCollection.d.ts.map +1 -1
  26. package/dist/core/garbageCollection.js +1 -1
  27. package/dist/core/logging.d.ts +12 -13
  28. package/dist/core/logging.d.ts.map +1 -1
  29. package/dist/core/logging.js +8 -5
  30. package/dist/core/makeNetworkRequest.d.ts +5 -5
  31. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  32. package/dist/core/makeNetworkRequest.js +107 -22
  33. package/dist/core/read.d.ts +15 -10
  34. package/dist/core/read.d.ts.map +1 -1
  35. package/dist/core/read.js +398 -304
  36. package/dist/core/reader.d.ts +24 -32
  37. package/dist/core/reader.d.ts.map +1 -1
  38. package/dist/core/startUpdate.d.ts +5 -0
  39. package/dist/core/startUpdate.d.ts.map +1 -0
  40. package/dist/core/startUpdate.js +15 -0
  41. package/dist/core/util.d.ts +3 -0
  42. package/dist/core/util.d.ts.map +1 -1
  43. package/dist/index.d.ts +16 -16
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +2 -1
  46. package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -10
  47. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
  48. package/dist/loadable-hooks/useClientSideDefer.js +2 -2
  49. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +8 -15
  50. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
  51. package/dist/loadable-hooks/useConnectionSpecPagination.js +6 -4
  52. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -2
  53. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -1
  54. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +4 -6
  55. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
  56. package/dist/loadable-hooks/useImperativeLoadableField.js +1 -1
  57. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +6 -13
  58. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
  59. package/dist/loadable-hooks/useSkipLimitPagination.js +11 -9
  60. package/dist/react/FragmentReader.d.ts +2 -3
  61. package/dist/react/FragmentReader.d.ts.map +1 -1
  62. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -1
  63. package/dist/react/useImperativeReference.d.ts +7 -10
  64. package/dist/react/useImperativeReference.d.ts.map +1 -1
  65. package/dist/react/useImperativeReference.js +2 -3
  66. package/dist/react/useLazyReference.d.ts +4 -7
  67. package/dist/react/useLazyReference.d.ts.map +1 -1
  68. package/dist/react/useLazyReference.js +26 -5
  69. package/dist/react/useReadAndSubscribe.d.ts +3 -9
  70. package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
  71. package/dist/react/useReadAndSubscribe.js +7 -3
  72. package/dist/react/useRerenderOnChange.d.ts +1 -1
  73. package/dist/react/useRerenderOnChange.d.ts.map +1 -1
  74. package/dist/react/useResult.d.ts +3 -6
  75. package/dist/react/useResult.d.ts.map +1 -1
  76. package/dist/react/useResult.js +10 -8
  77. package/isograph.config.json +1 -0
  78. package/package.json +7 -6
  79. package/src/core/FragmentReference.ts +30 -15
  80. package/src/core/IsographEnvironment.ts +39 -31
  81. package/src/core/PromiseWrapper.ts +3 -3
  82. package/src/core/areEqualWithDeepComparison.ts +20 -0
  83. package/src/core/cache.ts +105 -72
  84. package/src/core/check.ts +13 -8
  85. package/src/core/componentCache.ts +45 -52
  86. package/src/core/entrypoint.ts +34 -16
  87. package/src/core/garbageCollection.ts +6 -6
  88. package/src/core/logging.ts +24 -22
  89. package/src/core/makeNetworkRequest.ts +183 -30
  90. package/src/core/read.ts +618 -435
  91. package/src/core/reader.ts +37 -24
  92. package/src/core/startUpdate.ts +28 -0
  93. package/src/core/util.ts +4 -0
  94. package/src/index.ts +82 -9
  95. package/src/loadable-hooks/useClientSideDefer.ts +11 -10
  96. package/src/loadable-hooks/useConnectionSpecPagination.ts +26 -13
  97. package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
  98. package/src/loadable-hooks/useImperativeLoadableField.ts +10 -12
  99. package/src/loadable-hooks/useSkipLimitPagination.ts +37 -19
  100. package/src/react/FragmentReader.tsx +3 -3
  101. package/src/react/IsographEnvironmentProvider.tsx +1 -1
  102. package/src/react/useImperativeReference.ts +40 -19
  103. package/src/react/useLazyReference.ts +62 -14
  104. package/src/react/useReadAndSubscribe.ts +17 -9
  105. package/src/react/useRerenderOnChange.ts +2 -2
  106. package/src/react/useResult.ts +21 -8
  107. package/src/tests/__isograph/Query/meName/entrypoint.ts +4 -28
  108. package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
  109. package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
  110. package/src/tests/__isograph/Query/meName/resolver_reader.ts +4 -0
  111. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +4 -66
  112. package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
  113. package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
  114. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +7 -0
  115. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +4 -33
  116. package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
  117. package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
  118. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +4 -0
  119. package/src/tests/__isograph/Query/subquery/entrypoint.ts +4 -43
  120. package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
  121. package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
  122. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +5 -0
  123. package/src/tests/__isograph/iso.ts +3 -2
  124. package/src/tests/garbageCollection.test.ts +10 -8
  125. package/src/tests/meNameSuccessor.ts +1 -1
  126. package/src/tests/nodeQuery.ts +2 -1
  127. package/src/tests/normalizeData.test.ts +1 -1
  128. 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
- ExtractData,
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 { ReaderAst } from './reader';
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 { parameters: object; data: object },
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 suspend on it, and we want
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
- // TODO assert that the network request state is not Err
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 ReadDataResult<TReadFromStore> =
102
- | {
103
- readonly kind: 'Success';
104
- readonly data: ExtractData<TReadFromStore>;
105
- readonly encounteredRecords: EncounteredIds;
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: ExtractParameters<TReadFromStore>,
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 storeRecordName = getParentRecordKey(field, variables);
152
- const value = storeRecord[storeRecordName];
153
- // TODO consider making scalars into discriminated unions. This probably has
154
- // to happen for when we handle errors.
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] = value;
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 storeRecordName = getParentRecordKey(field, variables);
168
- const value = storeRecord[storeRecordName];
169
- if (Array.isArray(value)) {
170
- const results = [];
171
- for (const item of value) {
172
- const link = assertLink(item);
173
- if (link === undefined) {
174
- return {
175
- kind: 'MissingData',
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
- field.selections,
193
- link,
203
+ ast,
204
+ root,
194
205
  variables,
195
206
  nestedRefetchQueries,
196
207
  networkRequest,
197
208
  networkRequestOptions,
198
209
  mutableEncounteredRecords,
199
- );
200
- if (result.kind === 'MissingData') {
201
- return {
202
- kind: 'MissingData',
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
- const targetId = link;
297
- const data = readData(
215
+ target[field.alias ?? field.fieldName] = data.data;
216
+ break;
217
+ }
218
+ case 'ImperativelyLoadedField': {
219
+ const data = readImperativelyLoadedField(
298
220
  environment,
299
- field.selections,
300
- targetId,
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 ?? field.fieldName] = data.data;
232
+ target[field.alias] = data.data;
317
233
  break;
318
234
  }
319
- case 'ImperativelyLoadedField': {
320
- // First, we read the data using the refetch reader AST (i.e. read out the
321
- // id field).
322
- const data = readData(
235
+ case 'Resolver': {
236
+ const data = readResolverFieldData(
323
237
  environment,
324
- field.refetchReaderArtifact.readerAst,
238
+ field,
325
239
  root,
326
240
  variables,
327
- // Refetch fields just read the id, and don't need refetch query artifacts
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 refetchReaderParams = readData(
253
+ const data = readLoadablySelectedFieldData(
445
254
  environment,
446
- field.refetchReaderAst,
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 (refetchReaderParams.kind === 'MissingData') {
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
- encounteredRecords: mutableEncounteredRecords,
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 === 'Variable') {
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
+ }