@isograph/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/core/FragmentReference.d.ts +14 -4
  2. package/dist/core/FragmentReference.d.ts.map +1 -0
  3. package/dist/core/FragmentReference.js +2 -3
  4. package/dist/core/IsographEnvironment.d.ts +28 -10
  5. package/dist/core/IsographEnvironment.d.ts.map +1 -0
  6. package/dist/core/IsographEnvironment.js +15 -22
  7. package/dist/core/PromiseWrapper.d.ts +1 -0
  8. package/dist/core/PromiseWrapper.d.ts.map +1 -0
  9. package/dist/core/PromiseWrapper.js +4 -5
  10. package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
  11. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
  12. package/dist/core/areEqualWithDeepComparison.js +73 -39
  13. package/dist/core/cache.d.ts +26 -10
  14. package/dist/core/cache.d.ts.map +1 -0
  15. package/dist/core/cache.js +160 -98
  16. package/dist/core/check.d.ts +18 -0
  17. package/dist/core/check.d.ts.map +1 -0
  18. package/dist/core/check.js +127 -0
  19. package/dist/core/componentCache.d.ts +1 -1
  20. package/dist/core/componentCache.d.ts.map +1 -0
  21. package/dist/core/componentCache.js +14 -14
  22. package/dist/core/entrypoint.d.ts +27 -8
  23. package/dist/core/entrypoint.d.ts.map +1 -0
  24. package/dist/core/entrypoint.js +1 -2
  25. package/dist/core/garbageCollection.d.ts +3 -1
  26. package/dist/core/garbageCollection.d.ts.map +1 -0
  27. package/dist/core/garbageCollection.js +48 -15
  28. package/dist/core/logging.d.ts +69 -0
  29. package/dist/core/logging.d.ts.map +1 -0
  30. package/dist/core/logging.js +19 -0
  31. package/dist/core/makeNetworkRequest.d.ts +4 -1
  32. package/dist/core/makeNetworkRequest.d.ts.map +1 -0
  33. package/dist/core/makeNetworkRequest.js +71 -15
  34. package/dist/core/read.d.ts +20 -5
  35. package/dist/core/read.d.ts.map +1 -0
  36. package/dist/core/read.js +104 -41
  37. package/dist/core/reader.d.ts +34 -10
  38. package/dist/core/reader.d.ts.map +1 -0
  39. package/dist/core/util.d.ts +2 -0
  40. package/dist/core/util.d.ts.map +1 -0
  41. package/dist/index.d.ts +10 -5
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +10 -2
  44. package/dist/loadable-hooks/useClientSideDefer.d.ts +15 -3
  45. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
  46. package/dist/loadable-hooks/useClientSideDefer.js +4 -6
  47. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
  48. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
  49. package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
  50. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -0
  51. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
  52. package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
  53. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -5
  54. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
  55. package/dist/loadable-hooks/useImperativeLoadableField.js +3 -4
  56. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +18 -24
  57. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
  58. package/dist/loadable-hooks/useSkipLimitPagination.js +88 -44
  59. package/dist/react/FragmentReader.d.ts +7 -4
  60. package/dist/react/FragmentReader.d.ts.map +1 -0
  61. package/dist/react/FragmentReader.js +4 -2
  62. package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
  63. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
  64. package/dist/react/IsographEnvironmentProvider.js +3 -3
  65. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
  66. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
  67. package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
  68. package/dist/react/useImperativeReference.d.ts +8 -3
  69. package/dist/react/useImperativeReference.d.ts.map +1 -0
  70. package/dist/react/useImperativeReference.js +4 -5
  71. package/dist/react/useLazyReference.d.ts +7 -2
  72. package/dist/react/useLazyReference.d.ts.map +1 -0
  73. package/dist/react/useLazyReference.js +11 -4
  74. package/dist/react/useReadAndSubscribe.d.ts +12 -3
  75. package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
  76. package/dist/react/useReadAndSubscribe.js +6 -7
  77. package/dist/react/useRerenderOnChange.d.ts +6 -1
  78. package/dist/react/useRerenderOnChange.d.ts.map +1 -0
  79. package/dist/react/useRerenderOnChange.js +3 -4
  80. package/dist/react/useResult.d.ts +5 -1
  81. package/dist/react/useResult.d.ts.map +1 -0
  82. package/dist/react/useResult.js +8 -5
  83. package/{src/tests/isograph.config.json → isograph.config.json} +1 -1
  84. package/package.json +12 -8
  85. package/{src/tests/schema.graphql → schema.graphql} +1 -0
  86. package/src/core/FragmentReference.ts +17 -5
  87. package/src/core/IsographEnvironment.ts +38 -29
  88. package/src/core/areEqualWithDeepComparison.ts +76 -42
  89. package/src/core/cache.ts +237 -123
  90. package/src/core/check.ts +207 -0
  91. package/src/core/componentCache.ts +18 -17
  92. package/src/core/entrypoint.ts +15 -8
  93. package/src/core/garbageCollection.ts +71 -20
  94. package/src/core/logging.ts +116 -0
  95. package/src/core/makeNetworkRequest.ts +89 -13
  96. package/src/core/read.ts +162 -55
  97. package/src/core/reader.ts +40 -13
  98. package/src/core/util.ts +4 -0
  99. package/src/index.ts +14 -1
  100. package/src/loadable-hooks/useClientSideDefer.ts +45 -15
  101. package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
  102. package/src/loadable-hooks/useImperativeLoadableField.ts +36 -10
  103. package/src/loadable-hooks/useSkipLimitPagination.ts +231 -90
  104. package/src/react/FragmentReader.tsx +13 -4
  105. package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
  106. package/src/react/useImperativeReference.ts +18 -7
  107. package/src/react/useLazyReference.ts +24 -4
  108. package/src/react/useReadAndSubscribe.ts +20 -5
  109. package/src/react/useRerenderOnChange.ts +6 -1
  110. package/src/react/useResult.ts +10 -2
  111. package/src/tests/__isograph/Query/meName/entrypoint.ts +7 -2
  112. package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
  113. package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
  114. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +9 -2
  115. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
  116. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
  117. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +13 -2
  118. package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
  119. package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
  120. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
  121. package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
  122. package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
  123. package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
  124. package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
  125. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
  126. package/src/tests/__isograph/iso.ts +22 -11
  127. package/src/tests/garbageCollection.test.ts +45 -39
  128. package/src/tests/meNameSuccessor.ts +8 -3
  129. package/src/tests/nodeQuery.ts +6 -4
  130. package/src/tests/normalizeData.test.ts +120 -0
  131. package/src/tests/tsconfig.json +3 -3
  132. package/tsconfig.json +2 -2
  133. package/tsconfig.pkg.json +6 -1
  134. package/vitest.config.ts +20 -0
@@ -10,32 +10,90 @@ import {
10
10
  retainQuery,
11
11
  unretainQuery,
12
12
  } from './garbageCollection';
13
- import { IsographEnvironment } from './IsographEnvironment';
14
- import { AnyError, PromiseWrapper, wrapPromise } from './PromiseWrapper';
13
+ import { IsographEnvironment, ROOT_ID } from './IsographEnvironment';
14
+ import {
15
+ AnyError,
16
+ PromiseWrapper,
17
+ wrapPromise,
18
+ wrapResolvedValue,
19
+ } from './PromiseWrapper';
15
20
  import { normalizeData } from './cache';
21
+ import { logMessage } from './logging';
22
+ import { check, DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check';
16
23
 
17
- export function makeNetworkRequest(
24
+ let networkRequestId = 0;
25
+
26
+ export function maybeMakeNetworkRequest(
18
27
  environment: IsographEnvironment,
19
28
  artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
20
29
  variables: Variables,
30
+ fetchOptions?: FetchOptions,
21
31
  ): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
22
- // @ts-expect-error
23
- if (typeof window !== 'undefined' && window.__LOG) {
24
- console.log('make network request', artifact, variables);
32
+ switch (fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE) {
33
+ case 'Yes': {
34
+ return makeNetworkRequest(environment, artifact, variables, fetchOptions);
35
+ }
36
+ case 'No': {
37
+ return [wrapResolvedValue(undefined), () => {}];
38
+ }
39
+ case 'IfNecessary': {
40
+ const result = check(
41
+ environment,
42
+ artifact.networkRequestInfo.normalizationAst,
43
+ variables,
44
+ {
45
+ __link: ROOT_ID,
46
+ __typename: artifact.concreteType,
47
+ },
48
+ );
49
+ if (result.kind === 'EnoughData') {
50
+ return [wrapResolvedValue(undefined), () => {}];
51
+ } else {
52
+ return makeNetworkRequest(
53
+ environment,
54
+ artifact,
55
+ variables,
56
+ fetchOptions,
57
+ );
58
+ }
59
+ }
25
60
  }
61
+ }
62
+
63
+ export function makeNetworkRequest(
64
+ environment: IsographEnvironment,
65
+ artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
66
+ variables: Variables,
67
+ fetchOptions?: FetchOptions,
68
+ ): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
69
+ // TODO this should be a DataId and stored in the store
70
+ const myNetworkRequestId = networkRequestId + '';
71
+ networkRequestId++;
72
+
73
+ logMessage(environment, {
74
+ kind: 'MakeNetworkRequest',
75
+ artifact,
76
+ variables,
77
+ networkRequestId: myNetworkRequestId,
78
+ });
79
+
26
80
  let status: NetworkRequestStatus = {
27
81
  kind: 'UndisposedIncomplete',
28
82
  };
29
83
  // This should be an observable, not a promise
30
84
  const promise = environment
31
- .networkFunction(artifact.queryText, variables)
85
+ .networkFunction(artifact.networkRequestInfo.queryText, variables)
32
86
  .then((networkResponse) => {
33
- // @ts-expect-error
34
- if (typeof window !== 'undefined' && window.__LOG) {
35
- console.log('network response', artifact, networkResponse);
36
- }
87
+ logMessage(environment, {
88
+ kind: 'ReceivedNetworkResponse',
89
+ networkResponse,
90
+ networkRequestId: myNetworkRequestId,
91
+ });
37
92
 
38
93
  if (networkResponse.errors != null) {
94
+ try {
95
+ fetchOptions?.onError?.();
96
+ } catch {}
39
97
  // @ts-expect-error Why are we getting the wrong constructor here?
40
98
  throw new Error('GraphQL network response had errors', {
41
99
  cause: networkResponse,
@@ -43,18 +101,21 @@ export function makeNetworkRequest(
43
101
  }
44
102
 
45
103
  if (status.kind === 'UndisposedIncomplete') {
104
+ const root = { __link: ROOT_ID, __typename: artifact.concreteType };
46
105
  normalizeData(
47
106
  environment,
48
- artifact.normalizationAst,
107
+ artifact.networkRequestInfo.normalizationAst,
49
108
  networkResponse.data ?? {},
50
109
  variables,
51
110
  artifact.kind === 'Entrypoint'
52
111
  ? artifact.readerWithRefetchQueries.nestedRefetchQueries
53
112
  : [],
113
+ root,
54
114
  );
55
115
  const retainedQuery = {
56
- normalizationAst: artifact.normalizationAst,
116
+ normalizationAst: artifact.networkRequestInfo.normalizationAst,
57
117
  variables,
118
+ root,
58
119
  };
59
120
  status = {
60
121
  kind: 'UndisposedComplete',
@@ -62,6 +123,21 @@ export function makeNetworkRequest(
62
123
  };
63
124
  retainQuery(environment, retainedQuery);
64
125
  }
126
+
127
+ try {
128
+ fetchOptions?.onComplete?.();
129
+ } catch {}
130
+ })
131
+ .catch((e) => {
132
+ logMessage(environment, {
133
+ kind: 'ReceivedNetworkError',
134
+ networkRequestId: myNetworkRequestId,
135
+ error: e,
136
+ });
137
+ try {
138
+ fetchOptions?.onError?.();
139
+ } catch {}
140
+ throw e;
65
141
  });
66
142
 
67
143
  const wrapper = wrapPromise(promise);
package/src/core/read.ts CHANGED
@@ -1,19 +1,27 @@
1
- import { CleanupFn } from '@isograph/isograph-disposable-types/dist';
2
- import { getParentRecordKey, onNextChange } from './cache';
1
+ import {
2
+ getParentRecordKey,
3
+ insertIfNotExists,
4
+ onNextChangeToRecord,
5
+ type EncounteredIds,
6
+ } from './cache';
3
7
  import { getOrCreateCachedComponent } from './componentCache';
4
8
  import {
5
9
  IsographEntrypoint,
6
10
  RefetchQueryNormalizationArtifactWrapper,
7
11
  } from './entrypoint';
8
- import { FragmentReference, Variables } from './FragmentReference';
12
+ import {
13
+ FragmentReference,
14
+ Variables,
15
+ ExtractData,
16
+ ExtractParameters,
17
+ } from './FragmentReference';
9
18
  import {
10
19
  assertLink,
11
- DataId,
12
- defaultMissingFieldHandler,
13
20
  getOrLoadIsographArtifact,
14
21
  IsographEnvironment,
22
+ type Link,
15
23
  } from './IsographEnvironment';
16
- import { makeNetworkRequest } from './makeNetworkRequest';
24
+ import { maybeMakeNetworkRequest } from './makeNetworkRequest';
17
25
  import {
18
26
  getPromiseState,
19
27
  PromiseWrapper,
@@ -23,18 +31,23 @@ import {
23
31
  } from './PromiseWrapper';
24
32
  import { ReaderAst } from './reader';
25
33
  import { Arguments } from './util';
34
+ import { logMessage } from './logging';
35
+ import { CleanupFn } from '@isograph/disposable-types';
36
+ import { FetchOptions } from './check';
26
37
 
27
38
  export type WithEncounteredRecords<T> = {
28
- readonly encounteredRecords: Set<DataId>;
29
- readonly item: T;
39
+ readonly encounteredRecords: EncounteredIds;
40
+ readonly item: ExtractData<T>;
30
41
  };
31
42
 
32
- export function readButDoNotEvaluate<TReadFromStore extends Object>(
43
+ export function readButDoNotEvaluate<
44
+ TReadFromStore extends { parameters: object; data: object },
45
+ >(
33
46
  environment: IsographEnvironment,
34
47
  fragmentReference: FragmentReference<TReadFromStore, unknown>,
35
48
  networkRequestOptions: NetworkRequestReaderOptions,
36
49
  ): WithEncounteredRecords<TReadFromStore> {
37
- const mutableEncounteredRecords = new Set<DataId>();
50
+ const mutableEncounteredRecords: EncounteredIds = new Map();
38
51
 
39
52
  const readerWithRefetchQueries = readPromise(
40
53
  fragmentReference.readerWithRefetchQueries,
@@ -50,10 +63,12 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
50
63
  networkRequestOptions,
51
64
  mutableEncounteredRecords,
52
65
  );
53
- // @ts-expect-error
54
- if (typeof window !== 'undefined' && window.__LOG) {
55
- console.log('done reading', { response });
56
- }
66
+
67
+ logMessage(environment, {
68
+ kind: 'DoneReading',
69
+ response,
70
+ });
71
+
57
72
  if (response.kind === 'MissingData') {
58
73
  // There are two cases here that we care about:
59
74
  // 1. the network request is in flight, we haven't suspend on it, and we want
@@ -70,11 +85,11 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
70
85
  ) {
71
86
  // TODO assert that the network request state is not Err
72
87
  throw new Promise((resolve, reject) => {
73
- onNextChange(environment).then(resolve);
88
+ onNextChangeToRecord(environment, response.recordLink).then(resolve);
74
89
  fragmentReference.networkRequest.promise.catch(reject);
75
90
  });
76
91
  }
77
- throw onNextChange(environment);
92
+ throw onNextChangeToRecord(environment, response.recordLink);
78
93
  } else {
79
94
  return {
80
95
  encounteredRecords: mutableEncounteredRecords,
@@ -83,34 +98,40 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
83
98
  }
84
99
  }
85
100
 
86
- type ReadDataResult<TReadFromStore> =
101
+ export type ReadDataResult<TReadFromStore> =
87
102
  | {
88
103
  readonly kind: 'Success';
89
- readonly data: TReadFromStore;
90
- readonly encounteredRecords: Set<DataId>;
104
+ readonly data: ExtractData<TReadFromStore>;
105
+ readonly encounteredRecords: EncounteredIds;
91
106
  }
92
107
  | {
93
108
  readonly kind: 'MissingData';
94
109
  readonly reason: string;
95
110
  readonly nestedReason?: ReadDataResult<unknown>;
111
+ readonly recordLink: Link;
96
112
  };
97
113
 
98
114
  function readData<TReadFromStore>(
99
115
  environment: IsographEnvironment,
100
116
  ast: ReaderAst<TReadFromStore>,
101
- root: DataId,
102
- variables: Variables,
117
+ root: Link,
118
+ variables: ExtractParameters<TReadFromStore>,
103
119
  nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
104
120
  networkRequest: PromiseWrapper<void, any>,
105
121
  networkRequestOptions: NetworkRequestReaderOptions,
106
- mutableEncounteredRecords: Set<DataId>,
122
+ mutableEncounteredRecords: EncounteredIds,
107
123
  ): ReadDataResult<TReadFromStore> {
108
- mutableEncounteredRecords.add(root);
109
- let storeRecord = environment.store[root];
124
+ const encounteredIds = insertIfNotExists(
125
+ mutableEncounteredRecords,
126
+ root.__typename,
127
+ );
128
+ encounteredIds.add(root.__link);
129
+ let storeRecord = environment.store[root.__typename]?.[root.__link];
110
130
  if (storeRecord === undefined) {
111
131
  return {
112
132
  kind: 'MissingData',
113
- reason: 'No record for root ' + root,
133
+ reason: 'No record for root ' + root.__link,
134
+ recordLink: root,
114
135
  };
115
136
  }
116
137
 
@@ -134,7 +155,9 @@ function readData<TReadFromStore>(
134
155
  if (value === undefined) {
135
156
  return {
136
157
  kind: 'MissingData',
137
- reason: 'No value for ' + storeRecordName + ' on root ' + root,
158
+ reason:
159
+ 'No value for ' + storeRecordName + ' on root ' + root.__link,
160
+ recordLink: root,
138
161
  };
139
162
  }
140
163
  target[field.alias ?? field.fieldName] = value;
@@ -154,18 +177,20 @@ function readData<TReadFromStore>(
154
177
  'No link for ' +
155
178
  storeRecordName +
156
179
  ' on root ' +
157
- root +
180
+ root.__link +
158
181
  '. Link is ' +
159
182
  JSON.stringify(item),
183
+ recordLink: root,
160
184
  };
161
185
  } else if (link === null) {
162
186
  results.push(null);
163
187
  continue;
164
188
  }
189
+
165
190
  const result = readData(
166
191
  environment,
167
192
  field.selections,
168
- link.__link,
193
+ link,
169
194
  variables,
170
195
  nestedRefetchQueries,
171
196
  networkRequest,
@@ -179,10 +204,11 @@ function readData<TReadFromStore>(
179
204
  'Missing data for ' +
180
205
  storeRecordName +
181
206
  ' on root ' +
182
- root +
207
+ root.__link +
183
208
  '. Link is ' +
184
209
  JSON.stringify(item),
185
210
  nestedReason: result,
211
+ recordLink: result.recordLink,
186
212
  };
187
213
  }
188
214
  results.push(result.data);
@@ -191,17 +217,63 @@ function readData<TReadFromStore>(
191
217
  break;
192
218
  }
193
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
+
194
257
  if (link === undefined) {
195
258
  // TODO make this configurable, and also generated and derived from the schema
196
- const missingFieldHandler =
197
- environment.missingFieldHandler ?? defaultMissingFieldHandler;
198
- const altLink = missingFieldHandler(
259
+ const missingFieldHandler = environment.missingFieldHandler;
260
+
261
+ const altLink = missingFieldHandler?.(
199
262
  storeRecord,
200
263
  root,
201
264
  field.fieldName,
202
265
  field.arguments,
203
266
  variables,
204
267
  );
268
+ logMessage(environment, {
269
+ kind: 'MissingFieldHandlerCalled',
270
+ root,
271
+ storeRecord,
272
+ fieldName: field.fieldName,
273
+ arguments: field.arguments,
274
+ variables,
275
+ });
276
+
205
277
  if (altLink === undefined) {
206
278
  return {
207
279
  kind: 'MissingData',
@@ -209,9 +281,10 @@ function readData<TReadFromStore>(
209
281
  'No link for ' +
210
282
  storeRecordName +
211
283
  ' on root ' +
212
- root +
284
+ root.__link +
213
285
  '. Link is ' +
214
286
  JSON.stringify(value),
287
+ recordLink: root,
215
288
  };
216
289
  } else {
217
290
  link = altLink;
@@ -220,7 +293,7 @@ function readData<TReadFromStore>(
220
293
  target[field.alias ?? field.fieldName] = null;
221
294
  break;
222
295
  }
223
- const targetId = link.__link;
296
+ const targetId = link;
224
297
  const data = readData(
225
298
  environment,
226
299
  field.selections,
@@ -234,8 +307,10 @@ function readData<TReadFromStore>(
234
307
  if (data.kind === 'MissingData') {
235
308
  return {
236
309
  kind: 'MissingData',
237
- reason: 'Missing data for ' + storeRecordName + ' on root ' + root,
310
+ reason:
311
+ 'Missing data for ' + storeRecordName + ' on root ' + root.__link,
238
312
  nestedReason: data,
313
+ recordLink: data.recordLink,
239
314
  };
240
315
  }
241
316
  target[field.alias ?? field.fieldName] = data.data;
@@ -260,15 +335,19 @@ function readData<TReadFromStore>(
260
335
  if (data.kind === 'MissingData') {
261
336
  return {
262
337
  kind: 'MissingData',
263
- reason: 'Missing data for ' + field.alias + ' on root ' + root,
338
+ reason:
339
+ 'Missing data for ' + field.alias + ' on root ' + root.__link,
264
340
  nestedReason: data,
341
+ recordLink: data.recordLink,
265
342
  };
266
343
  } else {
267
344
  const refetchQueryIndex = field.refetchQuery;
268
- if (refetchQueryIndex == null) {
269
- throw new Error('refetchQuery is null in RefetchField');
270
- }
271
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
+ }
272
351
  const refetchQueryArtifact = refetchQuery.artifact;
273
352
  const allowedVariables = refetchQuery.allowedVariables;
274
353
 
@@ -276,7 +355,7 @@ function readData<TReadFromStore>(
276
355
  // use the resolver reader AST to get the resolver parameters.
277
356
  target[field.alias] = (args: any) => [
278
357
  // Stable id
279
- root + '__' + field.name,
358
+ root.__link + '__' + field.name,
280
359
  // Fetcher
281
360
  field.refetchReaderArtifact.resolver(
282
361
  environment,
@@ -294,9 +373,15 @@ function readData<TReadFromStore>(
294
373
  }
295
374
  case 'Resolver': {
296
375
  const usedRefetchQueries = field.usedRefetchQueries;
297
- const resolverRefetchQueries = usedRefetchQueries.map(
298
- (index) => nestedRefetchQueries[index],
299
- );
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
+ });
300
385
 
301
386
  switch (field.readerArtifact.kind) {
302
387
  case 'EagerReaderArtifact': {
@@ -304,7 +389,7 @@ function readData<TReadFromStore>(
304
389
  environment,
305
390
  field.readerArtifact.readerAst,
306
391
  root,
307
- variables,
392
+ generateChildVariableMap(variables, field.arguments),
308
393
  resolverRefetchQueries,
309
394
  networkRequest,
310
395
  networkRequestOptions,
@@ -313,11 +398,18 @@ function readData<TReadFromStore>(
313
398
  if (data.kind === 'MissingData') {
314
399
  return {
315
400
  kind: 'MissingData',
316
- reason: 'Missing data for ' + field.alias + ' on root ' + root,
401
+ reason:
402
+ 'Missing data for ' + field.alias + ' on root ' + root.__link,
317
403
  nestedReason: data,
404
+ recordLink: data.recordLink,
318
405
  };
319
406
  } else {
320
- target[field.alias] = field.readerArtifact.resolver(data.data);
407
+ const firstParameter = {
408
+ data: data.data,
409
+ parameters: variables,
410
+ };
411
+ target[field.alias] =
412
+ field.readerArtifact.resolver(firstParameter);
321
413
  }
322
414
  break;
323
415
  }
@@ -363,11 +455,13 @@ function readData<TReadFromStore>(
363
455
  if (refetchReaderParams.kind === 'MissingData') {
364
456
  return {
365
457
  kind: 'MissingData',
366
- reason: 'Missing data for ' + field.alias + ' on root ' + root,
458
+ reason:
459
+ 'Missing data for ' + field.alias + ' on root ' + root.__link,
367
460
  nestedReason: refetchReaderParams,
461
+ recordLink: refetchReaderParams.recordLink,
368
462
  };
369
463
  } else {
370
- target[field.alias] = (args: any) => {
464
+ target[field.alias] = (args: any, fetchOptions?: FetchOptions) => {
371
465
  // TODO we should use the reader AST for this
372
466
  const includeReadOutData = (variables: any, readOutData: any) => {
373
467
  variables.id = readOutData.id;
@@ -385,7 +479,9 @@ function readData<TReadFromStore>(
385
479
 
386
480
  return [
387
481
  // Stable id
388
- root +
482
+ root.__typename +
483
+ ':' +
484
+ root.__link +
389
485
  '/' +
390
486
  field.name +
391
487
  '/' +
@@ -396,7 +492,12 @@ function readData<TReadFromStore>(
396
492
  entrypoint: IsographEntrypoint<any, any>,
397
493
  ): [FragmentReference<any, any>, CleanupFn] => {
398
494
  const [networkRequest, disposeNetworkRequest] =
399
- makeNetworkRequest(environment, entrypoint, localVariables);
495
+ maybeMakeNetworkRequest(
496
+ environment,
497
+ entrypoint,
498
+ localVariables,
499
+ fetchOptions,
500
+ );
400
501
 
401
502
  const fragmentReference: FragmentReference<any, any> = {
402
503
  kind: 'FragmentReference',
@@ -410,7 +511,7 @@ function readData<TReadFromStore>(
410
511
  } as const),
411
512
 
412
513
  // TODO localVariables is not guaranteed to have an id field
413
- root: localVariables.id,
514
+ root,
414
515
  variables: localVariables,
415
516
  networkRequest,
416
517
  };
@@ -453,10 +554,11 @@ function readData<TReadFromStore>(
453
554
  entrypointLoaderState.kind === 'EntrypointNotLoaded'
454
555
  ) {
455
556
  const [networkRequest, disposeNetworkRequest] =
456
- makeNetworkRequest(
557
+ maybeMakeNetworkRequest(
457
558
  environment,
458
559
  entrypoint,
459
560
  localVariables,
561
+ fetchOptions,
460
562
  );
461
563
  entrypointLoaderState = {
462
564
  kind: 'NetworkRequestStarted',
@@ -479,7 +581,7 @@ function readData<TReadFromStore>(
479
581
  ),
480
582
 
481
583
  // TODO localVariables is not guaranteed to have an id field
482
- root: localVariables.id,
584
+ root,
483
585
  variables: localVariables,
484
586
  networkRequest,
485
587
  };
@@ -542,7 +644,12 @@ function generateChildVariableMap(
542
644
  const childVars: Writable<Variables> = {};
543
645
  for (const [name, value] of fieldArguments) {
544
646
  if (value.kind === 'Variable') {
545
- childVars[name] = variables[value.name];
647
+ const variable = variables[value.name];
648
+ // Variable could be null if it was not provided but has a default case,
649
+ // so we allow the loop to continue rather than throwing an error.
650
+ if (variable != null) {
651
+ childVars[name] = variable;
652
+ }
546
653
  } else {
547
654
  childVars[name] = value.value;
548
655
  }
@@ -603,7 +710,7 @@ export function getNetworkRequestOptionsWithDefaults(
603
710
  // TODO call stableStringifyArgs on the variable values, as well.
604
711
  // This doesn't matter for now, since we are just using primitive values
605
712
  // in the demo.
606
- function stableStringifyArgs(args: Object) {
713
+ function stableStringifyArgs(args: object) {
607
714
  const keys = Object.keys(args);
608
715
  keys.sort();
609
716
  let s = '';