@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/cache.ts CHANGED
@@ -3,56 +3,53 @@ 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
11
  RefetchQueryNormalizationArtifactWrapper,
12
+ type NormalizationAst,
13
+ type NormalizationAstLoader,
14
+ type NormalizationAstNodes,
24
15
  } from '../core/entrypoint';
25
- import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
26
- import { Argument, ArgumentValue } from './util';
27
- import { WithEncounteredRecords, readButDoNotEvaluate } from './read';
16
+ import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
17
+ import { FetchOptions } from './check';
28
18
  import {
19
+ ExtractParameters,
29
20
  FragmentReference,
30
21
  Variables,
31
- ExtractParameters,
22
+ type UnknownTReadFromStore,
23
+ type VariableValue,
32
24
  } from './FragmentReference';
33
- import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
25
+ import {
26
+ DataId,
27
+ DataTypeValue,
28
+ FragmentSubscription,
29
+ getLink,
30
+ Link,
31
+ ROOT_ID,
32
+ StoreRecord,
33
+ type IsographEnvironment,
34
+ type TypeName,
35
+ } from './IsographEnvironment';
36
+ import { logMessage } from './logging';
34
37
  import { maybeMakeNetworkRequest } from './makeNetworkRequest';
35
38
  import { wrapResolvedValue } from './PromiseWrapper';
36
- import { logMessage } from './logging';
37
- import { FetchOptions } from './check';
39
+ import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
40
+ import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
41
+ import { Argument, ArgumentValue } from './util';
38
42
 
39
43
  export const TYPENAME_FIELD_NAME = '__typename';
40
44
 
41
45
  export function getOrCreateItemInSuspenseCache<
42
- TReadFromStore extends { parameters: object; data: object },
46
+ TReadFromStore extends UnknownTReadFromStore,
43
47
  TClientFieldValue,
44
48
  >(
45
49
  environment: IsographEnvironment,
46
50
  index: string,
47
51
  factory: Factory<FragmentReference<TReadFromStore, TClientFieldValue>>,
48
52
  ): 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
53
  if (environment.fragmentCache[index] == null) {
57
54
  environment.fragmentCache[index] = new ParentCache(factory);
58
55
  }
@@ -83,13 +80,18 @@ export function stableCopy<T>(value: T): T {
83
80
  }
84
81
 
85
82
  export function getOrCreateCacheForArtifact<
86
- TReadFromStore extends { parameters: object; data: object },
83
+ TReadFromStore extends UnknownTReadFromStore,
87
84
  TClientFieldValue,
85
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
88
86
  >(
89
87
  environment: IsographEnvironment,
90
- entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
88
+ entrypoint: IsographEntrypoint<
89
+ TReadFromStore,
90
+ TClientFieldValue,
91
+ TNormalizationAst
92
+ >,
91
93
  variables: ExtractParameters<TReadFromStore>,
92
- fetchOptions?: FetchOptions,
94
+ fetchOptions?: FetchOptions<TClientFieldValue>,
93
95
  ): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
94
96
  const cacheKey =
95
97
  entrypoint.networkRequestInfo.queryText +
@@ -124,8 +126,8 @@ export function getOrCreateCacheForArtifact<
124
126
  return getOrCreateItemInSuspenseCache(environment, cacheKey, factory);
125
127
  }
126
128
 
127
- type NetworkResponseScalarValue = string | number | boolean;
128
- type NetworkResponseValue =
129
+ export type NetworkResponseScalarValue = string | number | boolean;
130
+ export type NetworkResponseValue =
129
131
  | NetworkResponseScalarValue
130
132
  | null
131
133
  | NetworkResponseObject
@@ -142,7 +144,7 @@ export type NetworkResponseObject = {
142
144
 
143
145
  export function normalizeData(
144
146
  environment: IsographEnvironment,
145
- normalizationAst: NormalizationAst,
147
+ normalizationAst: NormalizationAstNodes,
146
148
  networkResponse: NetworkResponseObject,
147
149
  variables: Variables,
148
150
  nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
@@ -150,12 +152,12 @@ export function normalizeData(
150
152
  ): EncounteredIds {
151
153
  const encounteredIds: EncounteredIds = new Map();
152
154
 
153
- logMessage(environment, {
155
+ logMessage(environment, () => ({
154
156
  kind: 'AboutToNormalize',
155
157
  normalizationAst,
156
158
  networkResponse,
157
159
  variables,
158
- });
160
+ }));
159
161
 
160
162
  const recordsById = (environment.store[root.__typename] ??= {});
161
163
  const newStoreRecord = (recordsById[root.__link] ??= {});
@@ -171,11 +173,11 @@ export function normalizeData(
171
173
  encounteredIds,
172
174
  );
173
175
 
174
- logMessage(environment, {
176
+ logMessage(environment, () => ({
175
177
  kind: 'AfterNormalization',
176
178
  store: environment.store,
177
179
  encounteredIds,
178
- });
180
+ }));
179
181
 
180
182
  callSubscriptions(environment, encounteredIds);
181
183
  return encounteredIds;
@@ -208,9 +210,7 @@ export function subscribeToAnyChangesToRecord(
208
210
  }
209
211
 
210
212
  // 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
- >(
213
+ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
214
214
  environment: IsographEnvironment,
215
215
  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
216
216
  fragmentReference: FragmentReference<TReadFromStore, any>,
@@ -303,14 +303,14 @@ function callSubscriptions(
303
303
  newEncounteredDataAndRecords.item,
304
304
  );
305
305
 
306
- logMessage(environment, {
306
+ logMessage(environment, () => ({
307
307
  kind: 'DeepEqualityCheck',
308
308
  fragmentReference: subscription.fragmentReference,
309
309
  old: subscription.encounteredDataAndRecords.item,
310
310
  new: newEncounteredDataAndRecords.item,
311
311
  deeplyEqual:
312
312
  mergedItem === subscription.encounteredDataAndRecords.item,
313
- });
313
+ }));
314
314
 
315
315
  if (mergedItem !== subscription.encounteredDataAndRecords.item) {
316
316
  subscription.callback(newEncounteredDataAndRecords);
@@ -376,7 +376,7 @@ export type EncounteredIds = Map<TypeName, Set<DataId>>;
376
376
  */
377
377
  function normalizeDataIntoRecord(
378
378
  environment: IsographEnvironment,
379
- normalizationAst: NormalizationAst,
379
+ normalizationAst: NormalizationAstNodes,
380
380
  networkResponseParentRecord: NetworkResponseObject,
381
381
  targetParentRecord: StoreRecord,
382
382
  targetParentRecordLink: Link,
@@ -724,8 +724,19 @@ export function getParentRecordKey(
724
724
  function getStoreKeyChunkForArgumentValue(
725
725
  argumentValue: ArgumentValue,
726
726
  variables: Variables,
727
- ) {
727
+ ): VariableValue {
728
728
  switch (argumentValue.kind) {
729
+ case 'Object': {
730
+ return Object.fromEntries(
731
+ argumentValue.value.map(([argumentName, argumentValue]) => {
732
+ return [
733
+ argumentName,
734
+ // substitute variables
735
+ getStoreKeyChunkForArgumentValue(argumentValue, variables),
736
+ ];
737
+ }),
738
+ );
739
+ }
729
740
  case 'Literal': {
730
741
  return argumentValue.value;
731
742
  }
@@ -749,7 +760,12 @@ function getStoreKeyChunkForArgumentValue(
749
760
  }
750
761
 
751
762
  function getStoreKeyChunkForArgument(argument: Argument, variables: Variables) {
752
- const chunk = getStoreKeyChunkForArgumentValue(argument[1], variables);
763
+ let chunk = getStoreKeyChunkForArgumentValue(argument[1], variables);
764
+
765
+ if (typeof chunk === 'object') {
766
+ chunk = JSON.stringify(stableCopy(chunk));
767
+ }
768
+
753
769
  return `${FIRST_SPLIT_KEY}${argument[0]}${SECOND_SPLIT_KEY}${chunk}`;
754
770
  }
755
771
 
@@ -758,43 +774,60 @@ function getNetworkResponseKey(
758
774
  ): string {
759
775
  let networkResponseKey = astNode.fieldName;
760
776
  const fieldParameters = astNode.arguments;
777
+
761
778
  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
- }
779
+ for (const [argumentName, argumentValue] of fieldParameters) {
780
+ let argumentValueChunk = getArgumentValueChunk(argumentValue);
789
781
  networkResponseKey += `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${argumentValueChunk}`;
790
782
  }
791
783
  }
784
+
792
785
  return networkResponseKey;
793
786
  }
794
787
 
788
+ function getArgumentValueChunk(argumentValue: ArgumentValue): string {
789
+ switch (argumentValue.kind) {
790
+ case 'Object': {
791
+ return (
792
+ 'o_' +
793
+ argumentValue.value
794
+ .map(([argumentName, argumentValue]) => {
795
+ return (
796
+ argumentName +
797
+ THIRD_SPLIT_KEY +
798
+ getArgumentValueChunk(argumentValue)
799
+ );
800
+ })
801
+ .join('_') +
802
+ '_c'
803
+ );
804
+ }
805
+ case 'Literal': {
806
+ return 'l_' + argumentValue.value;
807
+ }
808
+ case 'Variable': {
809
+ return 'v_' + argumentValue.name;
810
+ }
811
+ case 'String': {
812
+ // replace all non-word characters (alphanumeric & underscore) with underscores
813
+ return 's_' + argumentValue.value.replaceAll(/\W/g, '_');
814
+ }
815
+ case 'Enum': {
816
+ return 'e_' + argumentValue.value;
817
+ }
818
+ default: {
819
+ // Ensure we have covered all variants
820
+ let _: never = argumentValue;
821
+ _;
822
+ throw new Error('Unexpected case');
823
+ }
824
+ }
825
+ }
826
+
795
827
  // an alias might be pullRequests____first___first____after___cursor
796
828
  export const FIRST_SPLIT_KEY = '____';
797
829
  export const SECOND_SPLIT_KEY = '___';
830
+ export const THIRD_SPLIT_KEY = '__';
798
831
 
799
832
  // Returns a key to look up an item in the store
800
833
  function getDataIdOfNetworkResponse(
package/src/core/check.ts CHANGED
@@ -1,5 +1,5 @@
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,
@@ -9,16 +9,21 @@ import {
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';
@@ -30,7 +35,7 @@ export type CheckResult =
30
35
 
31
36
  export function check(
32
37
  environment: IsographEnvironment,
33
- normalizationAst: NormalizationAst,
38
+ normalizationAst: NormalizationAstNodes,
34
39
  variables: Variables,
35
40
  root: Link,
36
41
  ): CheckResult {
@@ -44,16 +49,16 @@ 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
64
  recordLink: Link,
@@ -1,10 +1,13 @@
1
- import { stableCopy } from './cache';
2
- import { IsographEnvironment } from './IsographEnvironment';
3
- import { FragmentReference } from './FragmentReference';
4
1
  import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
5
- import { NetworkRequestReaderOptions } from './read';
6
- import { readPromise } from './PromiseWrapper';
2
+ import {
3
+ FragmentReference,
4
+ stableIdForFragmentReference,
5
+ } from './FragmentReference';
6
+ import { IsographEnvironment } from './IsographEnvironment';
7
7
  import { logMessage } from './logging';
8
+ import { readPromise } from './PromiseWrapper';
9
+ import { NetworkRequestReaderOptions } from './read';
10
+ import { createStartUpdate } from './startUpdate';
8
11
 
9
12
  export function getOrCreateCachedComponent(
10
13
  environment: IsographEnvironment,
@@ -12,51 +15,41 @@ export function getOrCreateCachedComponent(
12
15
  fragmentReference: FragmentReference<any, any>,
13
16
  networkRequestOptions: NetworkRequestReaderOptions,
14
17
  ): React.FC<any> {
15
- // cachedComponentsById is a three layer cache: id, then component name, then
16
- // stringified args. These three, together, uniquely identify a read at a given
17
- // time.
18
- const cachedComponentsById = environment.componentCache;
19
-
20
- const recordLink = fragmentReference.root.__link;
21
-
22
- const componentsByName = (cachedComponentsById[recordLink] ??= {});
23
-
24
- componentsByName[componentName] = componentsByName[componentName] ?? {};
25
- const byArgs = componentsByName[componentName];
26
-
27
- const stringifiedArgs = JSON.stringify(
28
- stableCopy(fragmentReference.variables),
29
- );
30
- byArgs[stringifiedArgs] =
31
- byArgs[stringifiedArgs] ??
32
- (() => {
33
- function Component(additionalRuntimeProps: { [key: string]: any }) {
34
- const readerWithRefetchQueries = readPromise(
35
- fragmentReference.readerWithRefetchQueries,
36
- );
37
-
38
- const data = useReadAndSubscribe(
39
- fragmentReference,
40
- networkRequestOptions,
41
- readerWithRefetchQueries.readerArtifact.readerAst,
42
- );
43
-
44
- logMessage(environment, {
45
- kind: 'ComponentRerendered',
46
- componentName,
47
- rootLink: fragmentReference.root,
48
- });
49
-
50
- return readerWithRefetchQueries.readerArtifact.resolver(
51
- {
52
- data,
53
- parameters: fragmentReference.variables,
54
- },
55
- additionalRuntimeProps,
56
- );
57
- }
58
- Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
59
- return Component;
60
- })();
61
- return byArgs[stringifiedArgs];
18
+ // We create startUpdate outside of component to make it stable
19
+ const startUpdate = createStartUpdate(environment, fragmentReference);
20
+
21
+ return (environment.componentCache[
22
+ stableIdForFragmentReference(fragmentReference, componentName)
23
+ ] ??= (() => {
24
+ function Component(additionalRuntimeProps: { [key: string]: any }) {
25
+ const readerWithRefetchQueries = readPromise(
26
+ fragmentReference.readerWithRefetchQueries,
27
+ );
28
+
29
+ const data = useReadAndSubscribe(
30
+ fragmentReference,
31
+ networkRequestOptions,
32
+ readerWithRefetchQueries.readerArtifact.readerAst,
33
+ );
34
+
35
+ logMessage(environment, () => ({
36
+ kind: 'ComponentRerendered',
37
+ componentName,
38
+ rootLink: fragmentReference.root,
39
+ }));
40
+
41
+ return readerWithRefetchQueries.readerArtifact.resolver(
42
+ {
43
+ data,
44
+ parameters: fragmentReference.variables,
45
+ startUpdate: readerWithRefetchQueries.readerArtifact.hasUpdatable
46
+ ? startUpdate
47
+ : undefined,
48
+ },
49
+ additionalRuntimeProps,
50
+ );
51
+ }
52
+ Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
53
+ return Component;
54
+ })());
62
55
  }
@@ -1,9 +1,10 @@
1
+ import type { UnknownTReadFromStore } from './FragmentReference';
1
2
  import type { TypeName } from './IsographEnvironment';
2
3
  import { TopLevelReaderArtifact } from './reader';
3
4
  import { Arguments } from './util';
4
5
 
5
6
  export type ReaderWithRefetchQueries<
6
- TReadFromStore extends { parameters: object; data: object },
7
+ TReadFromStore extends UnknownTReadFromStore,
7
8
  TClientFieldValue,
8
9
  > = {
9
10
  readonly kind: 'ReaderWithRefetchQueries';
@@ -16,18 +17,19 @@ export type ReaderWithRefetchQueries<
16
17
  readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
17
18
  };
18
19
 
19
- export type NetworkRequestInfo = {
20
+ export type NetworkRequestInfo<TNormalizationAst> = {
20
21
  readonly kind: 'NetworkRequestInfo';
21
22
  readonly queryText: string;
22
- readonly normalizationAst: NormalizationAst;
23
+ readonly normalizationAst: TNormalizationAst;
23
24
  };
24
25
  // This type should be treated as an opaque type.
25
26
  export type IsographEntrypoint<
26
- TReadFromStore extends { parameters: object; data: object },
27
+ TReadFromStore extends UnknownTReadFromStore,
27
28
  TClientFieldValue,
29
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
28
30
  > = {
29
31
  readonly kind: 'Entrypoint';
30
- readonly networkRequestInfo: NetworkRequestInfo;
32
+ readonly networkRequestInfo: NetworkRequestInfo<TNormalizationAst>;
31
33
  readonly readerWithRefetchQueries: ReaderWithRefetchQueries<
32
34
  TReadFromStore,
33
35
  TClientFieldValue
@@ -36,13 +38,13 @@ export type IsographEntrypoint<
36
38
  };
37
39
 
38
40
  export type IsographEntrypointLoader<
39
- TReadFromStore extends { parameters: object; data: object },
41
+ TReadFromStore extends UnknownTReadFromStore,
40
42
  TClientFieldValue,
41
43
  > = {
42
44
  readonly kind: 'EntrypointLoader';
43
45
  readonly typeAndField: string;
44
46
  readonly loader: () => Promise<
45
- IsographEntrypoint<TReadFromStore, TClientFieldValue>
47
+ IsographEntrypoint<TReadFromStore, TClientFieldValue, NormalizationAst>
46
48
  >;
47
49
  };
48
50
 
@@ -50,7 +52,18 @@ export type NormalizationAstNode =
50
52
  | NormalizationScalarField
51
53
  | NormalizationLinkedField
52
54
  | NormalizationInlineFragment;
53
- export type NormalizationAst = ReadonlyArray<NormalizationAstNode>;
55
+
56
+ export type NormalizationAstNodes = ReadonlyArray<NormalizationAstNode>;
57
+
58
+ export type NormalizationAst = {
59
+ readonly kind: 'NormalizationAst';
60
+ readonly selections: NormalizationAstNodes;
61
+ };
62
+
63
+ export type NormalizationAstLoader = {
64
+ readonly kind: 'NormalizationAstLoader';
65
+ readonly loader: () => Promise<NormalizationAst>;
66
+ };
54
67
 
55
68
  export type NormalizationScalarField = {
56
69
  readonly kind: 'Scalar';
@@ -62,20 +75,20 @@ export type NormalizationLinkedField = {
62
75
  readonly kind: 'Linked';
63
76
  readonly fieldName: string;
64
77
  readonly arguments: Arguments | null;
65
- readonly selections: NormalizationAst;
78
+ readonly selections: NormalizationAstNodes;
66
79
  readonly concreteType: TypeName | null;
67
80
  };
68
81
 
69
82
  export type NormalizationInlineFragment = {
70
83
  readonly kind: 'InlineFragment';
71
84
  readonly type: string;
72
- readonly selections: NormalizationAst;
85
+ readonly selections: NormalizationAstNodes;
73
86
  };
74
87
 
75
88
  // This is more like an entrypoint, but one specifically for a refetch query/mutation
76
89
  export type RefetchQueryNormalizationArtifact = {
77
90
  readonly kind: 'RefetchQuery';
78
- readonly networkRequestInfo: NetworkRequestInfo;
91
+ readonly networkRequestInfo: NetworkRequestInfo<NormalizationAst>;
79
92
  readonly concreteType: TypeName;
80
93
  };
81
94
 
@@ -86,21 +99,26 @@ export type RefetchQueryNormalizationArtifactWrapper = {
86
99
  };
87
100
 
88
101
  export function assertIsEntrypoint<
89
- TReadFromStore extends { parameters: object; data: object },
102
+ TReadFromStore extends UnknownTReadFromStore,
90
103
  TClientFieldValue,
104
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
91
105
  >(
92
106
  value:
93
- | IsographEntrypoint<TReadFromStore, TClientFieldValue>
107
+ | IsographEntrypoint<TReadFromStore, TClientFieldValue, TNormalizationAst>
94
108
  | ((_: any) => any)
95
109
  // Temporarily, allow any here. Once we automatically provide
96
110
  // types to entrypoints, we probably don't need this.
97
111
  | any,
98
- ): asserts value is IsographEntrypoint<TReadFromStore, TClientFieldValue> {
112
+ ): asserts value is IsographEntrypoint<
113
+ TReadFromStore,
114
+ TClientFieldValue,
115
+ TNormalizationAst
116
+ > {
99
117
  if (typeof value === 'function') throw new Error('Not a string');
100
118
  }
101
119
 
102
120
  export type ExtractReadFromStore<Type> =
103
- Type extends IsographEntrypoint<infer X, any> ? X : never;
121
+ Type extends IsographEntrypoint<infer X, any, any> ? X : never;
104
122
  export type ExtractResolverResult<Type> =
105
- Type extends IsographEntrypoint<any, infer X> ? X : never;
123
+ Type extends IsographEntrypoint<any, infer X, any> ? X : never;
106
124
  export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
@@ -1,23 +1,23 @@
1
+ import { getParentRecordKey } from './cache';
2
+ import { NormalizationAstNodes } from './entrypoint';
1
3
  import { Variables } from './FragmentReference';
2
4
  import {
5
+ assertLink,
3
6
  DataId,
4
7
  IsographEnvironment,
5
8
  IsographStore,
6
9
  StoreRecord,
7
- assertLink,
8
10
  type Link,
9
11
  type TypeName,
10
12
  } from './IsographEnvironment';
11
- import { getParentRecordKey } from './cache';
12
- import { NormalizationAst } from './entrypoint';
13
13
 
14
14
  export type RetainedQuery = {
15
- readonly normalizationAst: NormalizationAst;
15
+ readonly normalizationAst: NormalizationAstNodes;
16
16
  readonly variables: {};
17
17
  readonly root: Link;
18
18
  };
19
19
 
20
- type DidUnretainSomeQuery = boolean;
20
+ export type DidUnretainSomeQuery = boolean;
21
21
  export function unretainQuery(
22
22
  environment: IsographEnvironment,
23
23
  retainedQuery: RetainedQuery,
@@ -108,7 +108,7 @@ function recordReachableIdsFromRecord(
108
108
  store: IsographStore,
109
109
  currentRecord: StoreRecord,
110
110
  mutableRetainedIds: RetainedIds,
111
- selections: NormalizationAst,
111
+ selections: NormalizationAstNodes,
112
112
  variables: Variables | null,
113
113
  ) {
114
114
  for (const selection of selections) {