@isograph/react 0.3.0 → 0.4.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 (164) hide show
  1. package/.turbo/turbo-compile-libs.log +5 -0
  2. package/dist/core/FragmentReference.d.ts +17 -8
  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 +30 -35
  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 +6 -7
  9. package/dist/core/PromiseWrapper.d.ts.map +1 -1
  10. package/dist/core/PromiseWrapper.js +6 -12
  11. package/dist/core/areEqualWithDeepComparison.d.ts +1 -3
  12. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
  13. package/dist/core/areEqualWithDeepComparison.js +16 -2
  14. package/dist/core/brand.d.ts +2 -0
  15. package/dist/core/brand.d.ts.map +1 -0
  16. package/dist/core/brand.js +2 -0
  17. package/dist/core/cache.d.ts +16 -24
  18. package/dist/core/cache.d.ts.map +1 -1
  19. package/dist/core/cache.js +105 -72
  20. package/dist/core/check.d.ts +11 -7
  21. package/dist/core/check.d.ts.map +1 -1
  22. package/dist/core/check.js +2 -2
  23. package/dist/core/componentCache.d.ts +1 -1
  24. package/dist/core/componentCache.d.ts.map +1 -1
  25. package/dist/core/componentCache.js +27 -31
  26. package/dist/core/entrypoint.d.ts +43 -28
  27. package/dist/core/entrypoint.d.ts.map +1 -1
  28. package/dist/core/garbageCollection.d.ts +5 -6
  29. package/dist/core/garbageCollection.d.ts.map +1 -1
  30. package/dist/core/garbageCollection.js +1 -1
  31. package/dist/core/logging.d.ts +23 -15
  32. package/dist/core/logging.d.ts.map +1 -1
  33. package/dist/core/logging.js +8 -5
  34. package/dist/core/makeNetworkRequest.d.ts +5 -5
  35. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  36. package/dist/core/makeNetworkRequest.js +113 -28
  37. package/dist/core/read.d.ts +16 -11
  38. package/dist/core/read.d.ts.map +1 -1
  39. package/dist/core/read.js +468 -305
  40. package/dist/core/reader.d.ts +33 -37
  41. package/dist/core/reader.d.ts.map +1 -1
  42. package/dist/core/startUpdate.d.ts +8 -0
  43. package/dist/core/startUpdate.d.ts.map +1 -0
  44. package/dist/core/startUpdate.js +163 -0
  45. package/dist/core/util.d.ts +3 -0
  46. package/dist/core/util.d.ts.map +1 -1
  47. package/dist/index.d.ts +18 -15
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +9 -1
  50. package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -10
  51. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
  52. package/dist/loadable-hooks/useClientSideDefer.js +2 -2
  53. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +8 -15
  54. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
  55. package/dist/loadable-hooks/useConnectionSpecPagination.js +6 -4
  56. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -2
  57. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -1
  58. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +4 -6
  59. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
  60. package/dist/loadable-hooks/useImperativeLoadableField.js +1 -1
  61. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +6 -13
  62. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
  63. package/dist/loadable-hooks/useSkipLimitPagination.js +11 -9
  64. package/dist/react/FragmentReader.d.ts +7 -14
  65. package/dist/react/FragmentReader.d.ts.map +1 -1
  66. package/dist/react/FragmentReader.js +3 -30
  67. package/dist/react/FragmentRenderer.d.ts +15 -0
  68. package/dist/react/FragmentRenderer.d.ts.map +1 -0
  69. package/dist/react/FragmentRenderer.js +35 -0
  70. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -1
  71. package/dist/react/LoadableFieldReader.d.ts +12 -0
  72. package/dist/react/LoadableFieldReader.d.ts.map +1 -0
  73. package/dist/react/LoadableFieldReader.js +10 -0
  74. package/dist/react/LoadableFieldRenderer.d.ts +13 -0
  75. package/dist/react/LoadableFieldRenderer.d.ts.map +1 -0
  76. package/dist/react/LoadableFieldRenderer.js +37 -0
  77. package/dist/react/useImperativeReference.d.ts +7 -10
  78. package/dist/react/useImperativeReference.d.ts.map +1 -1
  79. package/dist/react/useImperativeReference.js +8 -9
  80. package/dist/react/useLazyReference.d.ts +4 -7
  81. package/dist/react/useLazyReference.d.ts.map +1 -1
  82. package/dist/react/useLazyReference.js +26 -5
  83. package/dist/react/useReadAndSubscribe.d.ts +3 -9
  84. package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
  85. package/dist/react/useReadAndSubscribe.js +7 -3
  86. package/dist/react/useRerenderOnChange.d.ts +1 -1
  87. package/dist/react/useRerenderOnChange.d.ts.map +1 -1
  88. package/dist/react/useResult.d.ts +3 -6
  89. package/dist/react/useResult.d.ts.map +1 -1
  90. package/dist/react/useResult.js +10 -8
  91. package/isograph.config.json +1 -0
  92. package/package.json +6 -6
  93. package/src/core/FragmentReference.ts +40 -16
  94. package/src/core/IsographEnvironment.ts +57 -39
  95. package/src/core/PromiseWrapper.ts +15 -18
  96. package/src/core/areEqualWithDeepComparison.ts +22 -2
  97. package/src/core/brand.ts +18 -0
  98. package/src/core/cache.ts +153 -113
  99. package/src/core/check.ts +17 -12
  100. package/src/core/componentCache.ts +47 -50
  101. package/src/core/entrypoint.ts +66 -21
  102. package/src/core/garbageCollection.ts +9 -9
  103. package/src/core/logging.ts +39 -25
  104. package/src/core/makeNetworkRequest.ts +212 -34
  105. package/src/core/read.ts +728 -440
  106. package/src/core/reader.ts +46 -29
  107. package/src/core/startUpdate.ts +334 -0
  108. package/src/core/util.ts +4 -0
  109. package/src/index.ts +89 -8
  110. package/src/loadable-hooks/useClientSideDefer.ts +11 -10
  111. package/src/loadable-hooks/useConnectionSpecPagination.ts +27 -13
  112. package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
  113. package/src/loadable-hooks/useImperativeLoadableField.ts +10 -12
  114. package/src/loadable-hooks/useSkipLimitPagination.ts +38 -19
  115. package/src/react/FragmentReader.tsx +23 -39
  116. package/src/react/FragmentRenderer.tsx +46 -0
  117. package/src/react/IsographEnvironmentProvider.tsx +1 -1
  118. package/src/react/LoadableFieldReader.tsx +40 -0
  119. package/src/react/LoadableFieldRenderer.tsx +41 -0
  120. package/src/react/useImperativeReference.ts +49 -27
  121. package/src/react/useLazyReference.ts +62 -14
  122. package/src/react/useReadAndSubscribe.ts +17 -9
  123. package/src/react/useRerenderOnChange.ts +2 -2
  124. package/src/react/useResult.ts +22 -8
  125. package/src/tests/__isograph/Economist/link/output_type.ts +2 -0
  126. package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +28 -0
  127. package/src/tests/__isograph/Node/link/output_type.ts +3 -0
  128. package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +31 -0
  129. package/src/tests/__isograph/Query/linkedUpdate/normalization_ast.ts +95 -0
  130. package/src/tests/__isograph/Query/linkedUpdate/output_type.ts +3 -0
  131. package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +51 -0
  132. package/src/tests/__isograph/Query/linkedUpdate/query_text.ts +20 -0
  133. package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +93 -0
  134. package/src/tests/__isograph/Query/meName/entrypoint.ts +8 -29
  135. package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
  136. package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
  137. package/src/tests/__isograph/Query/meName/resolver_reader.ts +5 -0
  138. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +8 -67
  139. package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
  140. package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
  141. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +10 -0
  142. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +8 -34
  143. package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
  144. package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
  145. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +5 -0
  146. package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +31 -0
  147. package/src/tests/__isograph/Query/startUpdate/normalization_ast.ts +51 -0
  148. package/src/tests/__isograph/Query/startUpdate/output_type.ts +3 -0
  149. package/src/tests/__isograph/Query/startUpdate/param_type.ts +26 -0
  150. package/src/tests/__isograph/Query/startUpdate/parameters_type.ts +3 -0
  151. package/src/tests/__isograph/Query/startUpdate/query_text.ts +11 -0
  152. package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +55 -0
  153. package/src/tests/__isograph/Query/subquery/entrypoint.ts +8 -44
  154. package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
  155. package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
  156. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +7 -0
  157. package/src/tests/__isograph/iso.ts +24 -3
  158. package/src/tests/__isograph/tsconfig.json +8 -0
  159. package/src/tests/garbageCollection.test.ts +10 -8
  160. package/src/tests/meNameSuccessor.ts +1 -1
  161. package/src/tests/nodeQuery.ts +2 -1
  162. package/src/tests/normalizeData.test.ts +1 -2
  163. package/src/tests/startUpdate.test.ts +205 -0
  164. package/tsconfig.pkg.json +1 -2
package/src/core/cache.ts CHANGED
@@ -3,56 +3,52 @@ import {
3
3
  ItemCleanupPair,
4
4
  ParentCache,
5
5
  } from '@isograph/react-disposable-state';
6
- import {
7
- DataId,
8
- Link,
9
- ROOT_ID,
10
- StoreRecord,
11
- type IsographEnvironment,
12
- DataTypeValue,
13
- getLink,
14
- FragmentSubscription,
15
- type TypeName,
16
- } from './IsographEnvironment';
17
6
  import {
18
7
  IsographEntrypoint,
19
- NormalizationAst,
20
8
  NormalizationInlineFragment,
21
9
  NormalizationLinkedField,
22
10
  NormalizationScalarField,
23
- RefetchQueryNormalizationArtifactWrapper,
11
+ type NormalizationAst,
12
+ type NormalizationAstLoader,
13
+ type NormalizationAstNodes,
24
14
  } from '../core/entrypoint';
25
- import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
26
- import { Argument, ArgumentValue } from './util';
27
- import { WithEncounteredRecords, readButDoNotEvaluate } from './read';
15
+ import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
16
+ import { FetchOptions } from './check';
28
17
  import {
18
+ ExtractParameters,
29
19
  FragmentReference,
30
20
  Variables,
31
- ExtractParameters,
21
+ type UnknownTReadFromStore,
22
+ type VariableValue,
32
23
  } from './FragmentReference';
33
- import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
34
- import { maybeMakeNetworkRequest } from './makeNetworkRequest';
35
- import { wrapResolvedValue } from './PromiseWrapper';
24
+ import {
25
+ DataId,
26
+ DataTypeValue,
27
+ FragmentSubscription,
28
+ getLink,
29
+ ROOT_ID,
30
+ StoreLink,
31
+ StoreRecord,
32
+ type IsographEnvironment,
33
+ type TypeName,
34
+ } from './IsographEnvironment';
36
35
  import { logMessage } from './logging';
37
- import { FetchOptions } from './check';
36
+ import { maybeMakeNetworkRequest } from './makeNetworkRequest';
37
+ import { wrapPromise, wrapResolvedValue } from './PromiseWrapper';
38
+ import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
39
+ import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
40
+ import { Argument, ArgumentValue } from './util';
38
41
 
39
42
  export const TYPENAME_FIELD_NAME = '__typename';
40
43
 
41
44
  export function getOrCreateItemInSuspenseCache<
42
- TReadFromStore extends { parameters: object; data: object },
45
+ TReadFromStore extends UnknownTReadFromStore,
43
46
  TClientFieldValue,
44
47
  >(
45
48
  environment: IsographEnvironment,
46
49
  index: string,
47
50
  factory: Factory<FragmentReference<TReadFromStore, TClientFieldValue>>,
48
51
  ): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
49
- // TODO this is probably a useless message, we should remove it
50
- logMessage(environment, {
51
- kind: 'GettingSuspenseCacheItem',
52
- index,
53
- availableCacheItems: Object.keys(environment.fragmentCache),
54
- found: !!environment.fragmentCache[index],
55
- });
56
52
  if (environment.fragmentCache[index] == null) {
57
53
  environment.fragmentCache[index] = new ParentCache(factory);
58
54
  }
@@ -83,23 +79,44 @@ export function stableCopy<T>(value: T): T {
83
79
  }
84
80
 
85
81
  export function getOrCreateCacheForArtifact<
86
- TReadFromStore extends { parameters: object; data: object },
82
+ TReadFromStore extends UnknownTReadFromStore,
87
83
  TClientFieldValue,
84
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
88
85
  >(
89
86
  environment: IsographEnvironment,
90
- entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
87
+ entrypoint: IsographEntrypoint<
88
+ TReadFromStore,
89
+ TClientFieldValue,
90
+ TNormalizationAst
91
+ >,
91
92
  variables: ExtractParameters<TReadFromStore>,
92
- fetchOptions?: FetchOptions,
93
+ fetchOptions?: FetchOptions<TClientFieldValue>,
93
94
  ): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
94
- const cacheKey =
95
- entrypoint.networkRequestInfo.queryText +
96
- JSON.stringify(stableCopy(variables));
95
+ let cacheKey = '';
96
+ switch (entrypoint.networkRequestInfo.operation.kind) {
97
+ case 'Operation':
98
+ cacheKey =
99
+ entrypoint.networkRequestInfo.operation.text +
100
+ JSON.stringify(stableCopy(variables));
101
+ break;
102
+ case 'PersistedOperation':
103
+ cacheKey =
104
+ entrypoint.networkRequestInfo.operation.operationId +
105
+ JSON.stringify(stableCopy(variables));
106
+ break;
107
+ }
97
108
  const factory = () => {
109
+ const readerWithRefetchQueries =
110
+ entrypoint.readerWithRefetchQueries.kind ===
111
+ 'ReaderWithRefetchQueriesLoader'
112
+ ? wrapPromise(entrypoint.readerWithRefetchQueries.loader())
113
+ : wrapResolvedValue(entrypoint.readerWithRefetchQueries);
98
114
  const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
99
115
  environment,
100
116
  entrypoint,
101
117
  variables,
102
- fetchOptions,
118
+ readerWithRefetchQueries,
119
+ fetchOptions ?? null,
103
120
  );
104
121
 
105
122
  const itemCleanupPair: ItemCleanupPair<
@@ -107,12 +124,7 @@ export function getOrCreateCacheForArtifact<
107
124
  > = [
108
125
  {
109
126
  kind: 'FragmentReference',
110
- readerWithRefetchQueries: wrapResolvedValue({
111
- kind: 'ReaderWithRefetchQueries',
112
- readerArtifact: entrypoint.readerWithRefetchQueries.readerArtifact,
113
- nestedRefetchQueries:
114
- entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
115
- }),
127
+ readerWithRefetchQueries,
116
128
  root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
117
129
  variables,
118
130
  networkRequest: networkRequest,
@@ -124,8 +136,8 @@ export function getOrCreateCacheForArtifact<
124
136
  return getOrCreateItemInSuspenseCache(environment, cacheKey, factory);
125
137
  }
126
138
 
127
- type NetworkResponseScalarValue = string | number | boolean;
128
- type NetworkResponseValue =
139
+ export type NetworkResponseScalarValue = string | number | boolean;
140
+ export type NetworkResponseValue =
129
141
  | NetworkResponseScalarValue
130
142
  | null
131
143
  | NetworkResponseObject
@@ -142,20 +154,19 @@ export type NetworkResponseObject = {
142
154
 
143
155
  export function normalizeData(
144
156
  environment: IsographEnvironment,
145
- normalizationAst: NormalizationAst,
157
+ normalizationAst: NormalizationAstNodes,
146
158
  networkResponse: NetworkResponseObject,
147
159
  variables: Variables,
148
- nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
149
- root: Link,
160
+ root: StoreLink,
150
161
  ): EncounteredIds {
151
162
  const encounteredIds: EncounteredIds = new Map();
152
163
 
153
- logMessage(environment, {
164
+ logMessage(environment, () => ({
154
165
  kind: 'AboutToNormalize',
155
166
  normalizationAst,
156
167
  networkResponse,
157
168
  variables,
158
- });
169
+ }));
159
170
 
160
171
  const recordsById = (environment.store[root.__typename] ??= {});
161
172
  const newStoreRecord = (recordsById[root.__link] ??= {});
@@ -167,15 +178,14 @@ export function normalizeData(
167
178
  newStoreRecord,
168
179
  root,
169
180
  variables,
170
- nestedRefetchQueries,
171
181
  encounteredIds,
172
182
  );
173
183
 
174
- logMessage(environment, {
184
+ logMessage(environment, () => ({
175
185
  kind: 'AfterNormalization',
176
186
  store: environment.store,
177
187
  encounteredIds,
178
- });
188
+ }));
179
189
 
180
190
  callSubscriptions(environment, encounteredIds);
181
191
  return encounteredIds;
@@ -195,7 +205,7 @@ export function subscribeToAnyChange(
195
205
 
196
206
  export function subscribeToAnyChangesToRecord(
197
207
  environment: IsographEnvironment,
198
- recordLink: Link,
208
+ recordLink: StoreLink,
199
209
  callback: () => void,
200
210
  ): () => void {
201
211
  const subscription = {
@@ -208,9 +218,7 @@ export function subscribeToAnyChangesToRecord(
208
218
  }
209
219
 
210
220
  // TODO we should re-read and call callback if the value has changed
211
- export function subscribe<
212
- TReadFromStore extends { parameters: object; data: object },
213
- >(
221
+ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
214
222
  environment: IsographEnvironment,
215
223
  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
216
224
  fragmentReference: FragmentReference<TReadFromStore, any>,
@@ -232,7 +240,7 @@ export function subscribe<
232
240
 
233
241
  export function onNextChangeToRecord(
234
242
  environment: IsographEnvironment,
235
- recordLink: Link,
243
+ recordLink: StoreLink,
236
244
  ): Promise<void> {
237
245
  return new Promise((resolve) => {
238
246
  const unsubscribe = subscribeToAnyChangesToRecord(
@@ -251,20 +259,28 @@ export function onNextChangeToRecord(
251
259
  //
252
260
  // That's probably okay to ignore. We don't, however, want to prevent
253
261
  // updating other subscriptions if one subscription had missing data.
254
- function withErrorHandling<T>(f: (t: T) => void): (t: T) => void {
262
+ function withErrorHandling<T>(
263
+ environment: IsographEnvironment,
264
+ f: (t: T) => void,
265
+ ): (t: T) => void {
255
266
  return (t) => {
256
267
  try {
257
268
  return f(t);
258
- } catch {}
269
+ } catch (e) {
270
+ logMessage(environment, () => ({
271
+ kind: 'ErrorEncounteredInWithErrorHandling',
272
+ error: e,
273
+ }));
274
+ }
259
275
  };
260
276
  }
261
277
 
262
- function callSubscriptions(
278
+ export function callSubscriptions(
263
279
  environment: IsographEnvironment,
264
280
  recordsEncounteredWhenNormalizing: EncounteredIds,
265
281
  ) {
266
282
  environment.subscriptions.forEach(
267
- withErrorHandling((subscription) => {
283
+ withErrorHandling(environment, (subscription) => {
268
284
  switch (subscription.kind) {
269
285
  case 'FragmentSubscription': {
270
286
  // TODO if there are multiple components subscribed to the same
@@ -303,14 +319,14 @@ function callSubscriptions(
303
319
  newEncounteredDataAndRecords.item,
304
320
  );
305
321
 
306
- logMessage(environment, {
322
+ logMessage(environment, () => ({
307
323
  kind: 'DeepEqualityCheck',
308
324
  fragmentReference: subscription.fragmentReference,
309
325
  old: subscription.encounteredDataAndRecords.item,
310
326
  new: newEncounteredDataAndRecords.item,
311
327
  deeplyEqual:
312
328
  mergedItem === subscription.encounteredDataAndRecords.item,
313
- });
329
+ }));
314
330
 
315
331
  if (mergedItem !== subscription.encounteredDataAndRecords.item) {
316
332
  subscription.callback(newEncounteredDataAndRecords);
@@ -376,12 +392,11 @@ export type EncounteredIds = Map<TypeName, Set<DataId>>;
376
392
  */
377
393
  function normalizeDataIntoRecord(
378
394
  environment: IsographEnvironment,
379
- normalizationAst: NormalizationAst,
395
+ normalizationAst: NormalizationAstNodes,
380
396
  networkResponseParentRecord: NetworkResponseObject,
381
397
  targetParentRecord: StoreRecord,
382
- targetParentRecordLink: Link,
398
+ targetParentRecordLink: StoreLink,
383
399
  variables: Variables,
384
- nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
385
400
  mutableEncounteredIds: EncounteredIds,
386
401
  ): RecordHasBeenUpdated {
387
402
  let recordHasBeenUpdated = false;
@@ -406,7 +421,6 @@ function normalizeDataIntoRecord(
406
421
  targetParentRecord,
407
422
  targetParentRecordLink,
408
423
  variables,
409
- nestedRefetchQueries,
410
424
  mutableEncounteredIds,
411
425
  );
412
426
  recordHasBeenUpdated =
@@ -421,7 +435,6 @@ function normalizeDataIntoRecord(
421
435
  targetParentRecord,
422
436
  targetParentRecordLink,
423
437
  variables,
424
- nestedRefetchQueries,
425
438
  mutableEncounteredIds,
426
439
  );
427
440
  recordHasBeenUpdated =
@@ -437,7 +450,7 @@ function normalizeDataIntoRecord(
437
450
  }
438
451
  }
439
452
  if (recordHasBeenUpdated) {
440
- let encounteredRecordsIds = insertIfNotExists(
453
+ let encounteredRecordsIds = insertEmptySetIfMissing(
441
454
  mutableEncounteredIds,
442
455
  targetParentRecordLink.__typename,
443
456
  );
@@ -447,7 +460,7 @@ function normalizeDataIntoRecord(
447
460
  return recordHasBeenUpdated;
448
461
  }
449
462
 
450
- export function insertIfNotExists<K, V>(map: Map<K, Set<V>>, key: K) {
463
+ export function insertEmptySetIfMissing<K, V>(map: Map<K, Set<V>>, key: K) {
451
464
  let result = map.get(key);
452
465
  if (result === undefined) {
453
466
  result = new Set();
@@ -487,9 +500,8 @@ function normalizeLinkedField(
487
500
  astNode: NormalizationLinkedField,
488
501
  networkResponseParentRecord: NetworkResponseObject,
489
502
  targetParentRecord: StoreRecord,
490
- targetParentRecordLink: Link,
503
+ targetParentRecordLink: StoreLink,
491
504
  variables: Variables,
492
- nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
493
505
  mutableEncounteredIds: EncounteredIds,
494
506
  ): RecordHasBeenUpdated {
495
507
  const networkResponseKey = getNetworkResponseKey(astNode);
@@ -513,7 +525,7 @@ function normalizeLinkedField(
513
525
 
514
526
  if (Array.isArray(networkResponseData)) {
515
527
  // TODO check astNode.plural or the like
516
- const dataIds: (Link | null)[] = [];
528
+ const dataIds: (StoreLink | null)[] = [];
517
529
  for (let i = 0; i < networkResponseData.length; i++) {
518
530
  const networkResponseObject = networkResponseData[i];
519
531
  if (networkResponseObject == null) {
@@ -527,7 +539,6 @@ function normalizeLinkedField(
527
539
  targetParentRecordLink,
528
540
  variables,
529
541
  i,
530
- nestedRefetchQueries,
531
542
  mutableEncounteredIds,
532
543
  );
533
544
 
@@ -554,7 +565,6 @@ function normalizeLinkedField(
554
565
  targetParentRecordLink,
555
566
  variables,
556
567
  null,
557
- nestedRefetchQueries,
558
568
  mutableEncounteredIds,
559
569
  );
560
570
 
@@ -586,9 +596,8 @@ function normalizeInlineFragment(
586
596
  astNode: NormalizationInlineFragment,
587
597
  networkResponseParentRecord: NetworkResponseObject,
588
598
  targetParentRecord: StoreRecord,
589
- targetParentRecordLink: Link,
599
+ targetParentRecordLink: StoreLink,
590
600
  variables: Variables,
591
- nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
592
601
  mutableEncounteredIds: EncounteredIds,
593
602
  ): RecordHasBeenUpdated {
594
603
  const typeToRefineTo = astNode.type;
@@ -600,7 +609,6 @@ function normalizeInlineFragment(
600
609
  targetParentRecord,
601
610
  targetParentRecordLink,
602
611
  variables,
603
- nestedRefetchQueries,
604
612
  mutableEncounteredIds,
605
613
  );
606
614
  return hasBeenModified;
@@ -610,7 +618,7 @@ function normalizeInlineFragment(
610
618
 
611
619
  function dataIdsAreTheSame(
612
620
  existingValue: DataTypeValue,
613
- newDataIds: (Link | null)[],
621
+ newDataIds: (StoreLink | null)[],
614
622
  ): boolean {
615
623
  if (Array.isArray(existingValue)) {
616
624
  if (newDataIds.length !== existingValue.length) {
@@ -635,10 +643,9 @@ function normalizeNetworkResponseObject(
635
643
  environment: IsographEnvironment,
636
644
  astNode: NormalizationLinkedField,
637
645
  networkResponseData: NetworkResponseObject,
638
- targetParentRecordLink: Link,
646
+ targetParentRecordLink: StoreLink,
639
647
  variables: Variables,
640
648
  index: number | null,
641
- nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
642
649
  mutableEncounteredIds: EncounteredIds,
643
650
  ): DataId /* The id of the modified or newly created item */ {
644
651
  const newStoreRecordId = getDataIdOfNetworkResponse(
@@ -668,7 +675,6 @@ function normalizeNetworkResponseObject(
668
675
  newStoreRecord,
669
676
  { __link: newStoreRecordId, __typename: __typename },
670
677
  variables,
671
- nestedRefetchQueries,
672
678
  mutableEncounteredIds,
673
679
  );
674
680
 
@@ -724,8 +730,19 @@ export function getParentRecordKey(
724
730
  function getStoreKeyChunkForArgumentValue(
725
731
  argumentValue: ArgumentValue,
726
732
  variables: Variables,
727
- ) {
733
+ ): VariableValue {
728
734
  switch (argumentValue.kind) {
735
+ case 'Object': {
736
+ return Object.fromEntries(
737
+ argumentValue.value.map(([argumentName, argumentValue]) => {
738
+ return [
739
+ argumentName,
740
+ // substitute variables
741
+ getStoreKeyChunkForArgumentValue(argumentValue, variables),
742
+ ];
743
+ }),
744
+ );
745
+ }
729
746
  case 'Literal': {
730
747
  return argumentValue.value;
731
748
  }
@@ -749,8 +766,14 @@ function getStoreKeyChunkForArgumentValue(
749
766
  }
750
767
 
751
768
  function getStoreKeyChunkForArgument(argument: Argument, variables: Variables) {
752
- const chunk = getStoreKeyChunkForArgumentValue(argument[1], variables);
753
- return `${FIRST_SPLIT_KEY}${argument[0]}${SECOND_SPLIT_KEY}${chunk}`;
769
+ const [argumentName, argumentValue] = argument;
770
+ let chunk = getStoreKeyChunkForArgumentValue(argumentValue, variables);
771
+
772
+ if (typeof chunk === 'object') {
773
+ chunk = JSON.stringify(stableCopy(chunk));
774
+ }
775
+
776
+ return `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${chunk}`;
754
777
  }
755
778
 
756
779
  function getNetworkResponseKey(
@@ -758,47 +781,64 @@ function getNetworkResponseKey(
758
781
  ): string {
759
782
  let networkResponseKey = astNode.fieldName;
760
783
  const fieldParameters = astNode.arguments;
784
+
761
785
  if (fieldParameters != null) {
762
- for (const fieldParameter of fieldParameters) {
763
- const [argumentName, argumentValue] = fieldParameter;
764
- let argumentValueChunk;
765
- switch (argumentValue.kind) {
766
- case 'Literal': {
767
- argumentValueChunk = 'l_' + argumentValue.value;
768
- break;
769
- }
770
- case 'Variable': {
771
- argumentValueChunk = 'v_' + argumentValue.name;
772
- break;
773
- }
774
- case 'String': {
775
- argumentValueChunk = 's_' + argumentValue.value;
776
- break;
777
- }
778
- case 'Enum': {
779
- argumentValueChunk = 'e_' + argumentValue.value;
780
- break;
781
- }
782
- default: {
783
- // Ensure we have covered all variants
784
- let _: never = argumentValue;
785
- _;
786
- throw new Error('Unexpected case');
787
- }
788
- }
786
+ for (const [argumentName, argumentValue] of fieldParameters) {
787
+ let argumentValueChunk = getArgumentValueChunk(argumentValue);
789
788
  networkResponseKey += `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${argumentValueChunk}`;
790
789
  }
791
790
  }
791
+
792
792
  return networkResponseKey;
793
793
  }
794
794
 
795
+ function getArgumentValueChunk(argumentValue: ArgumentValue): string {
796
+ switch (argumentValue.kind) {
797
+ case 'Object': {
798
+ return (
799
+ 'o_' +
800
+ argumentValue.value
801
+ .map(([argumentName, argumentValue]) => {
802
+ return (
803
+ argumentName +
804
+ THIRD_SPLIT_KEY +
805
+ getArgumentValueChunk(argumentValue)
806
+ );
807
+ })
808
+ .join('_') +
809
+ '_c'
810
+ );
811
+ }
812
+ case 'Literal': {
813
+ return 'l_' + argumentValue.value;
814
+ }
815
+ case 'Variable': {
816
+ return 'v_' + argumentValue.name;
817
+ }
818
+ case 'String': {
819
+ // replace all non-word characters (alphanumeric & underscore) with underscores
820
+ return 's_' + argumentValue.value.replaceAll(/\W/g, '_');
821
+ }
822
+ case 'Enum': {
823
+ return 'e_' + argumentValue.value;
824
+ }
825
+ default: {
826
+ // Ensure we have covered all variants
827
+ let _: never = argumentValue;
828
+ _;
829
+ throw new Error('Unexpected case');
830
+ }
831
+ }
832
+ }
833
+
795
834
  // an alias might be pullRequests____first___first____after___cursor
796
835
  export const FIRST_SPLIT_KEY = '____';
797
836
  export const SECOND_SPLIT_KEY = '___';
837
+ export const THIRD_SPLIT_KEY = '__';
798
838
 
799
839
  // Returns a key to look up an item in the store
800
840
  function getDataIdOfNetworkResponse(
801
- parentRecordLink: Link,
841
+ parentRecordLink: StoreLink,
802
842
  dataToNormalize: NetworkResponseObject,
803
843
  astNode: NormalizationLinkedField,
804
844
  variables: Variables,
package/src/core/check.ts CHANGED
@@ -1,38 +1,43 @@
1
1
  import { getParentRecordKey } from './cache';
2
- import { NormalizationAst } from './entrypoint';
2
+ import { NormalizationAstNodes } from './entrypoint';
3
3
  import { Variables } from './FragmentReference';
4
4
  import {
5
5
  getLink,
6
6
  IsographEnvironment,
7
- Link,
7
+ StoreLink,
8
8
  StoreRecord,
9
9
  } from './IsographEnvironment';
10
10
  import { logMessage } from './logging';
11
11
 
12
- export type ShouldFetch = 'Yes' | 'No' | 'IfNecessary';
12
+ export type ShouldFetch = RequiredShouldFetch | 'IfNecessary';
13
+ export type RequiredShouldFetch = 'Yes' | 'No';
13
14
 
14
15
  export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary';
15
16
 
16
- export type FetchOptions = {
17
+ export type FetchOptions<TReadOutData> = {
17
18
  shouldFetch?: ShouldFetch;
18
- onComplete?: () => void;
19
+ onComplete?: (data: TReadOutData) => void;
19
20
  onError?: () => void;
20
21
  };
21
22
 
23
+ export type RequiredFetchOptions<TReadOutData> = {
24
+ shouldFetch: RequiredShouldFetch;
25
+ } & FetchOptions<TReadOutData>;
26
+
22
27
  export type CheckResult =
23
28
  | {
24
29
  kind: 'EnoughData';
25
30
  }
26
31
  | {
27
32
  kind: 'MissingData';
28
- record: Link;
33
+ record: StoreLink;
29
34
  };
30
35
 
31
36
  export function check(
32
37
  environment: IsographEnvironment,
33
- normalizationAst: NormalizationAst,
38
+ normalizationAst: NormalizationAstNodes,
34
39
  variables: Variables,
35
- root: Link,
40
+ root: StoreLink,
36
41
  ): CheckResult {
37
42
  const recordsById = (environment.store[root.__typename] ??= {});
38
43
  const newStoreRecord = (recordsById[root.__link] ??= {});
@@ -44,19 +49,19 @@ export function check(
44
49
  newStoreRecord,
45
50
  root,
46
51
  );
47
- logMessage(environment, {
52
+ logMessage(environment, () => ({
48
53
  kind: 'EnvironmentCheck',
49
54
  result: checkResult,
50
- });
55
+ }));
51
56
  return checkResult;
52
57
  }
53
58
 
54
59
  function checkFromRecord(
55
60
  environment: IsographEnvironment,
56
- normalizationAst: NormalizationAst,
61
+ normalizationAst: NormalizationAstNodes,
57
62
  variables: Variables,
58
63
  record: StoreRecord,
59
- recordLink: Link,
64
+ recordLink: StoreLink,
60
65
  ): CheckResult {
61
66
  normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
62
67
  switch (normalizationAstNode.kind) {