@isograph/react 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.turbo/turbo-compile-typescript.log +4 -0
  2. package/dist/core/FragmentReference.d.ts +25 -6
  3. package/dist/core/FragmentReference.d.ts.map +1 -0
  4. package/dist/core/FragmentReference.js +3 -13
  5. package/dist/core/IsographEnvironment.d.ts +34 -26
  6. package/dist/core/IsographEnvironment.d.ts.map +1 -0
  7. package/dist/core/IsographEnvironment.js +19 -22
  8. package/dist/core/PromiseWrapper.d.ts +4 -4
  9. package/dist/core/PromiseWrapper.d.ts.map +1 -0
  10. package/dist/core/PromiseWrapper.js +9 -9
  11. package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
  12. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
  13. package/dist/core/areEqualWithDeepComparison.js +89 -39
  14. package/dist/core/cache.d.ts +20 -13
  15. package/dist/core/cache.d.ts.map +1 -0
  16. package/dist/core/cache.js +205 -128
  17. package/dist/core/check.d.ts +22 -0
  18. package/dist/core/check.d.ts.map +1 -0
  19. package/dist/core/check.js +127 -0
  20. package/dist/core/componentCache.d.ts +2 -2
  21. package/dist/core/componentCache.d.ts.map +1 -0
  22. package/dist/core/componentCache.js +28 -32
  23. package/dist/core/entrypoint.d.ts +31 -15
  24. package/dist/core/entrypoint.d.ts.map +1 -0
  25. package/dist/core/entrypoint.js +1 -2
  26. package/dist/core/garbageCollection.d.ts +6 -5
  27. package/dist/core/garbageCollection.d.ts.map +1 -0
  28. package/dist/core/garbageCollection.js +49 -16
  29. package/dist/core/logging.d.ts +68 -0
  30. package/dist/core/logging.d.ts.map +1 -0
  31. package/dist/core/logging.js +22 -0
  32. package/dist/core/makeNetworkRequest.d.ts +6 -3
  33. package/dist/core/makeNetworkRequest.d.ts.map +1 -0
  34. package/dist/core/makeNetworkRequest.js +160 -19
  35. package/dist/core/read.d.ts +25 -5
  36. package/dist/core/read.d.ts.map +1 -0
  37. package/dist/core/read.js +416 -259
  38. package/dist/core/reader.d.ts +31 -15
  39. package/dist/core/reader.d.ts.map +1 -0
  40. package/dist/core/startUpdate.d.ts +5 -0
  41. package/dist/core/startUpdate.d.ts.map +1 -0
  42. package/dist/core/startUpdate.js +15 -0
  43. package/dist/core/util.d.ts +5 -0
  44. package/dist/core/util.d.ts.map +1 -0
  45. package/dist/index.d.ts +19 -14
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +11 -2
  48. package/dist/loadable-hooks/useClientSideDefer.d.ts +9 -3
  49. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
  50. package/dist/loadable-hooks/useClientSideDefer.js +6 -8
  51. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +27 -0
  52. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
  53. package/dist/loadable-hooks/useConnectionSpecPagination.js +162 -0
  54. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +2 -2
  55. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
  56. package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
  57. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -7
  58. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
  59. package/dist/loadable-hooks/useImperativeLoadableField.js +4 -5
  60. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +13 -26
  61. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
  62. package/dist/loadable-hooks/useSkipLimitPagination.js +93 -47
  63. package/dist/react/FragmentReader.d.ts +6 -4
  64. package/dist/react/FragmentReader.d.ts.map +1 -0
  65. package/dist/react/FragmentReader.js +4 -2
  66. package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
  67. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
  68. package/dist/react/IsographEnvironmentProvider.js +3 -3
  69. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
  70. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
  71. package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
  72. package/dist/react/useImperativeReference.d.ts +8 -6
  73. package/dist/react/useImperativeReference.d.ts.map +1 -0
  74. package/dist/react/useImperativeReference.js +6 -8
  75. package/dist/react/useLazyReference.d.ts +5 -3
  76. package/dist/react/useLazyReference.d.ts.map +1 -0
  77. package/dist/react/useLazyReference.js +34 -6
  78. package/dist/react/useReadAndSubscribe.d.ts +6 -3
  79. package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
  80. package/dist/react/useReadAndSubscribe.js +13 -10
  81. package/dist/react/useRerenderOnChange.d.ts +7 -2
  82. package/dist/react/useRerenderOnChange.d.ts.map +1 -0
  83. package/dist/react/useRerenderOnChange.js +3 -4
  84. package/dist/react/useResult.d.ts +4 -3
  85. package/dist/react/useResult.d.ts.map +1 -0
  86. package/dist/react/useResult.js +14 -9
  87. package/isograph.config.json +8 -0
  88. package/package.json +14 -9
  89. package/{src/tests/schema.graphql → schema.graphql} +1 -0
  90. package/src/core/FragmentReference.ts +44 -17
  91. package/src/core/IsographEnvironment.ts +67 -50
  92. package/src/core/PromiseWrapper.ts +3 -3
  93. package/src/core/areEqualWithDeepComparison.ts +95 -41
  94. package/src/core/cache.ts +316 -169
  95. package/src/core/check.ts +212 -0
  96. package/src/core/componentCache.ts +40 -46
  97. package/src/core/entrypoint.ts +41 -16
  98. package/src/core/garbageCollection.ts +77 -26
  99. package/src/core/logging.ts +118 -0
  100. package/src/core/makeNetworkRequest.ts +249 -20
  101. package/src/core/read.ts +658 -368
  102. package/src/core/reader.ts +61 -21
  103. package/src/core/startUpdate.ts +28 -0
  104. package/src/core/util.ts +8 -0
  105. package/src/index.ts +94 -8
  106. package/src/loadable-hooks/useClientSideDefer.ts +48 -17
  107. package/src/loadable-hooks/useConnectionSpecPagination.ts +344 -0
  108. package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
  109. package/src/loadable-hooks/useImperativeLoadableField.ts +36 -12
  110. package/src/loadable-hooks/useSkipLimitPagination.ts +253 -94
  111. package/src/react/FragmentReader.tsx +15 -6
  112. package/src/react/IsographEnvironmentProvider.tsx +1 -1
  113. package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
  114. package/src/react/useImperativeReference.ts +50 -18
  115. package/src/react/useLazyReference.ts +79 -11
  116. package/src/react/useReadAndSubscribe.ts +33 -10
  117. package/src/react/useRerenderOnChange.ts +7 -2
  118. package/src/react/useResult.ts +30 -9
  119. package/src/tests/__isograph/Query/meName/entrypoint.ts +10 -29
  120. package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
  121. package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
  122. package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
  123. package/src/tests/__isograph/Query/meName/resolver_reader.ts +5 -0
  124. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +10 -65
  125. package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
  126. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
  127. package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
  128. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +10 -0
  129. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +10 -28
  130. package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
  131. package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
  132. package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
  133. package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
  134. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +5 -0
  135. package/src/tests/__isograph/Query/subquery/entrypoint.ts +28 -0
  136. package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
  137. package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
  138. package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
  139. package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
  140. package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
  141. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +52 -0
  142. package/src/tests/__isograph/iso.ts +24 -12
  143. package/src/tests/garbageCollection.test.ts +53 -45
  144. package/src/tests/meNameSuccessor.ts +8 -3
  145. package/src/tests/nodeQuery.ts +7 -4
  146. package/src/tests/normalizeData.test.ts +120 -0
  147. package/src/tests/tsconfig.json +3 -3
  148. package/tsconfig.json +2 -2
  149. package/tsconfig.pkg.json +7 -3
  150. package/vitest.config.ts +20 -0
  151. package/src/tests/isograph.config.json +0 -7
@@ -0,0 +1,212 @@
1
+ import { getParentRecordKey } from './cache';
2
+ import { NormalizationAstNodes } from './entrypoint';
3
+ import { Variables } from './FragmentReference';
4
+ import {
5
+ getLink,
6
+ IsographEnvironment,
7
+ Link,
8
+ StoreRecord,
9
+ } from './IsographEnvironment';
10
+ import { logMessage } from './logging';
11
+
12
+ export type ShouldFetch = RequiredShouldFetch | 'IfNecessary';
13
+ export type RequiredShouldFetch = 'Yes' | 'No';
14
+
15
+ export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary';
16
+
17
+ export type FetchOptions<TReadOutData> = {
18
+ shouldFetch?: ShouldFetch;
19
+ onComplete?: (data: TReadOutData) => void;
20
+ onError?: () => void;
21
+ };
22
+
23
+ export type RequiredFetchOptions<TReadOutData> = {
24
+ shouldFetch: RequiredShouldFetch;
25
+ } & FetchOptions<TReadOutData>;
26
+
27
+ export type CheckResult =
28
+ | {
29
+ kind: 'EnoughData';
30
+ }
31
+ | {
32
+ kind: 'MissingData';
33
+ record: Link;
34
+ };
35
+
36
+ export function check(
37
+ environment: IsographEnvironment,
38
+ normalizationAst: NormalizationAstNodes,
39
+ variables: Variables,
40
+ root: Link,
41
+ ): CheckResult {
42
+ const recordsById = (environment.store[root.__typename] ??= {});
43
+ const newStoreRecord = (recordsById[root.__link] ??= {});
44
+
45
+ const checkResult = checkFromRecord(
46
+ environment,
47
+ normalizationAst,
48
+ variables,
49
+ newStoreRecord,
50
+ root,
51
+ );
52
+ logMessage(environment, () => ({
53
+ kind: 'EnvironmentCheck',
54
+ result: checkResult,
55
+ }));
56
+ return checkResult;
57
+ }
58
+
59
+ function checkFromRecord(
60
+ environment: IsographEnvironment,
61
+ normalizationAst: NormalizationAstNodes,
62
+ variables: Variables,
63
+ record: StoreRecord,
64
+ recordLink: Link,
65
+ ): CheckResult {
66
+ normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
67
+ switch (normalizationAstNode.kind) {
68
+ case 'Scalar': {
69
+ const parentRecordKey = getParentRecordKey(
70
+ normalizationAstNode,
71
+ variables,
72
+ );
73
+ const scalarValue = record[parentRecordKey];
74
+
75
+ // null means the value is known to be missing, so it must
76
+ // be exactly undefined
77
+ if (scalarValue === undefined) {
78
+ return {
79
+ kind: 'MissingData',
80
+ record: recordLink,
81
+ };
82
+ }
83
+ continue normalizationAstLoop;
84
+ }
85
+ case 'Linked': {
86
+ const parentRecordKey = getParentRecordKey(
87
+ normalizationAstNode,
88
+ variables,
89
+ );
90
+
91
+ const linkedValue = record[parentRecordKey];
92
+
93
+ if (linkedValue === undefined) {
94
+ return {
95
+ kind: 'MissingData',
96
+ record: recordLink,
97
+ };
98
+ } else if (linkedValue === null) {
99
+ continue;
100
+ } else if (Array.isArray(linkedValue)) {
101
+ arrayItemsLoop: for (const item of linkedValue) {
102
+ const link = getLink(item);
103
+ if (link === null) {
104
+ throw new Error(
105
+ 'Unexpected non-link in the Isograph store. ' +
106
+ 'This is indicative of a bug in Isograph.',
107
+ );
108
+ }
109
+
110
+ const linkedRecord =
111
+ environment.store[link.__typename]?.[link.__link];
112
+
113
+ if (linkedRecord === undefined) {
114
+ return {
115
+ kind: 'MissingData',
116
+ record: link,
117
+ };
118
+ } else if (linkedRecord === null) {
119
+ continue arrayItemsLoop;
120
+ } else {
121
+ // TODO in __DEV__ assert linkedRecord is an object
122
+ const result = checkFromRecord(
123
+ environment,
124
+ normalizationAstNode.selections,
125
+ variables,
126
+ linkedRecord,
127
+ link,
128
+ );
129
+
130
+ if (result.kind === 'MissingData') {
131
+ return result;
132
+ }
133
+ }
134
+ }
135
+ } else {
136
+ const link = getLink(linkedValue);
137
+ if (link === null) {
138
+ throw new Error(
139
+ 'Unexpected non-link in the Isograph store. ' +
140
+ 'This is indicative of a bug in Isograph.',
141
+ );
142
+ }
143
+
144
+ const linkedRecord =
145
+ environment.store[link.__typename]?.[link.__link];
146
+
147
+ if (linkedRecord === undefined) {
148
+ return {
149
+ kind: 'MissingData',
150
+ record: link,
151
+ };
152
+ } else if (linkedRecord === null) {
153
+ continue normalizationAstLoop;
154
+ } else {
155
+ // TODO in __DEV__ assert linkedRecord is an object
156
+ const result = checkFromRecord(
157
+ environment,
158
+ normalizationAstNode.selections,
159
+ variables,
160
+ linkedRecord,
161
+ link,
162
+ );
163
+
164
+ if (result.kind === 'MissingData') {
165
+ return result;
166
+ }
167
+ }
168
+ }
169
+
170
+ continue normalizationAstLoop;
171
+ }
172
+ case 'InlineFragment': {
173
+ const existingRecordTypename = record['__typename'];
174
+
175
+ if (
176
+ existingRecordTypename == null ||
177
+ existingRecordTypename !== normalizationAstNode.type
178
+ ) {
179
+ return {
180
+ kind: 'MissingData',
181
+ record: recordLink,
182
+ };
183
+ }
184
+
185
+ const result = checkFromRecord(
186
+ environment,
187
+ normalizationAstNode.selections,
188
+ variables,
189
+ record,
190
+ recordLink,
191
+ );
192
+
193
+ if (result.kind === 'MissingData') {
194
+ return result;
195
+ }
196
+
197
+ continue normalizationAstLoop;
198
+ }
199
+ default: {
200
+ let _: never = normalizationAstNode;
201
+ _;
202
+ throw new Error(
203
+ 'Unexpected case. This is indicative of a bug in Isograph.',
204
+ );
205
+ }
206
+ }
207
+ }
208
+
209
+ return {
210
+ kind: 'EnoughData',
211
+ };
212
+ }
@@ -1,9 +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';
2
+ import {
3
+ FragmentReference,
4
+ stableIdForFragmentReference,
5
+ } from './FragmentReference';
6
+ import { IsographEnvironment } from './IsographEnvironment';
7
+ import { logMessage } from './logging';
6
8
  import { readPromise } from './PromiseWrapper';
9
+ import { NetworkRequestReaderOptions } from './read';
10
+ import { createStartUpdate } from './startUpdate';
7
11
 
8
12
  export function getOrCreateCachedComponent(
9
13
  environment: IsographEnvironment,
@@ -11,51 +15,41 @@ export function getOrCreateCachedComponent(
11
15
  fragmentReference: FragmentReference<any, any>,
12
16
  networkRequestOptions: NetworkRequestReaderOptions,
13
17
  ): React.FC<any> {
14
- // cachedComponentsById is a three layer cache: id, then component name, then
15
- // stringified args. These three, together, uniquely identify a read at a given
16
- // time.
17
- const cachedComponentsById = environment.componentCache;
18
-
19
- cachedComponentsById[fragmentReference.root] =
20
- cachedComponentsById[fragmentReference.root] ?? {};
21
- const componentsByName = cachedComponentsById[fragmentReference.root];
22
-
23
- componentsByName[componentName] = componentsByName[componentName] ?? {};
24
- const byArgs = componentsByName[componentName];
18
+ // We create startUpdate outside of component to make it stable
19
+ const startUpdate = createStartUpdate(environment, fragmentReference);
25
20
 
26
- const stringifiedArgs = JSON.stringify(
27
- stableCopy(fragmentReference.variables),
28
- );
29
- byArgs[stringifiedArgs] =
30
- byArgs[stringifiedArgs] ??
31
- (() => {
32
- function Component(additionalRuntimeProps: { [key: string]: any }) {
33
- const data = useReadAndSubscribe(
34
- fragmentReference,
35
- networkRequestOptions,
36
- );
21
+ return (environment.componentCache[
22
+ stableIdForFragmentReference(fragmentReference, componentName)
23
+ ] ??= (() => {
24
+ function Component(additionalRuntimeProps: { [key: string]: any }) {
25
+ const readerWithRefetchQueries = readPromise(
26
+ fragmentReference.readerWithRefetchQueries,
27
+ );
37
28
 
38
- // @ts-expect-error
39
- if (typeof window !== 'undefined' && window.__LOG) {
40
- console.log(
41
- 'Component re-rendered: ' +
42
- componentName +
43
- ' ' +
44
- fragmentReference.root,
45
- );
46
- }
29
+ const data = useReadAndSubscribe(
30
+ fragmentReference,
31
+ networkRequestOptions,
32
+ readerWithRefetchQueries.readerArtifact.readerAst,
33
+ );
47
34
 
48
- const readerWithRefetchQueries = readPromise(
49
- fragmentReference.readerWithRefetchQueries,
50
- );
35
+ logMessage(environment, () => ({
36
+ kind: 'ComponentRerendered',
37
+ componentName,
38
+ rootLink: fragmentReference.root,
39
+ }));
51
40
 
52
- return readerWithRefetchQueries.readerArtifact.resolver(
41
+ return readerWithRefetchQueries.readerArtifact.resolver(
42
+ {
53
43
  data,
54
- additionalRuntimeProps,
55
- );
56
- }
57
- Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
58
- return Component;
59
- })();
60
- return byArgs[stringifiedArgs];
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
+ })());
61
55
  }
@@ -1,8 +1,10 @@
1
+ import type { UnknownTReadFromStore } from './FragmentReference';
2
+ import type { TypeName } from './IsographEnvironment';
1
3
  import { TopLevelReaderArtifact } from './reader';
2
4
  import { Arguments } from './util';
3
5
 
4
6
  export type ReaderWithRefetchQueries<
5
- TReadFromStore extends Object,
7
+ TReadFromStore extends UnknownTReadFromStore,
6
8
  TClientFieldValue,
7
9
  > = {
8
10
  readonly kind: 'ReaderWithRefetchQueries';
@@ -15,28 +17,34 @@ export type ReaderWithRefetchQueries<
15
17
  readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
16
18
  };
17
19
 
20
+ export type NetworkRequestInfo<TNormalizationAst> = {
21
+ readonly kind: 'NetworkRequestInfo';
22
+ readonly queryText: string;
23
+ readonly normalizationAst: TNormalizationAst;
24
+ };
18
25
  // This type should be treated as an opaque type.
19
26
  export type IsographEntrypoint<
20
- TReadFromStore extends Object,
27
+ TReadFromStore extends UnknownTReadFromStore,
21
28
  TClientFieldValue,
29
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
22
30
  > = {
23
31
  readonly kind: 'Entrypoint';
24
- readonly queryText: string;
25
- readonly normalizationAst: NormalizationAst;
32
+ readonly networkRequestInfo: NetworkRequestInfo<TNormalizationAst>;
26
33
  readonly readerWithRefetchQueries: ReaderWithRefetchQueries<
27
34
  TReadFromStore,
28
35
  TClientFieldValue
29
36
  >;
37
+ readonly concreteType: TypeName;
30
38
  };
31
39
 
32
40
  export type IsographEntrypointLoader<
33
- TReadFromStore extends Object,
41
+ TReadFromStore extends UnknownTReadFromStore,
34
42
  TClientFieldValue,
35
43
  > = {
36
44
  readonly kind: 'EntrypointLoader';
37
45
  readonly typeAndField: string;
38
46
  readonly loader: () => Promise<
39
- IsographEntrypoint<TReadFromStore, TClientFieldValue>
47
+ IsographEntrypoint<TReadFromStore, TClientFieldValue, NormalizationAst>
40
48
  >;
41
49
  };
42
50
 
@@ -44,7 +52,18 @@ export type NormalizationAstNode =
44
52
  | NormalizationScalarField
45
53
  | NormalizationLinkedField
46
54
  | NormalizationInlineFragment;
47
- 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
+ };
48
67
 
49
68
  export type NormalizationScalarField = {
50
69
  readonly kind: 'Scalar';
@@ -56,20 +75,21 @@ export type NormalizationLinkedField = {
56
75
  readonly kind: 'Linked';
57
76
  readonly fieldName: string;
58
77
  readonly arguments: Arguments | null;
59
- readonly selections: NormalizationAst;
78
+ readonly selections: NormalizationAstNodes;
79
+ readonly concreteType: TypeName | null;
60
80
  };
61
81
 
62
82
  export type NormalizationInlineFragment = {
63
83
  readonly kind: 'InlineFragment';
64
84
  readonly type: string;
65
- readonly selections: NormalizationAst;
85
+ readonly selections: NormalizationAstNodes;
66
86
  };
67
87
 
68
88
  // This is more like an entrypoint, but one specifically for a refetch query/mutation
69
89
  export type RefetchQueryNormalizationArtifact = {
70
90
  readonly kind: 'RefetchQuery';
71
- readonly queryText: string;
72
- readonly normalizationAst: NormalizationAst;
91
+ readonly networkRequestInfo: NetworkRequestInfo<NormalizationAst>;
92
+ readonly concreteType: TypeName;
73
93
  };
74
94
 
75
95
  // TODO rename
@@ -79,21 +99,26 @@ export type RefetchQueryNormalizationArtifactWrapper = {
79
99
  };
80
100
 
81
101
  export function assertIsEntrypoint<
82
- TReadFromStore extends Object,
102
+ TReadFromStore extends UnknownTReadFromStore,
83
103
  TClientFieldValue,
104
+ TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
84
105
  >(
85
106
  value:
86
- | IsographEntrypoint<TReadFromStore, TClientFieldValue>
107
+ | IsographEntrypoint<TReadFromStore, TClientFieldValue, TNormalizationAst>
87
108
  | ((_: any) => any)
88
109
  // Temporarily, allow any here. Once we automatically provide
89
110
  // types to entrypoints, we probably don't need this.
90
111
  | any,
91
- ): asserts value is IsographEntrypoint<TReadFromStore, TClientFieldValue> {
112
+ ): asserts value is IsographEntrypoint<
113
+ TReadFromStore,
114
+ TClientFieldValue,
115
+ TNormalizationAst
116
+ > {
92
117
  if (typeof value === 'function') throw new Error('Not a string');
93
118
  }
94
119
 
95
120
  export type ExtractReadFromStore<Type> =
96
- Type extends IsographEntrypoint<infer X, any> ? X : never;
121
+ Type extends IsographEntrypoint<infer X, any, any> ? X : never;
97
122
  export type ExtractResolverResult<Type> =
98
- Type extends IsographEntrypoint<any, infer X> ? X : never;
123
+ Type extends IsographEntrypoint<any, infer X, any> ? X : never;
99
124
  export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
@@ -1,21 +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
- ROOT_ID,
7
9
  StoreRecord,
8
- assertLink,
10
+ type Link,
11
+ type TypeName,
9
12
  } from './IsographEnvironment';
10
- import { getParentRecordKey } from './cache';
11
- import { NormalizationAst } from './entrypoint';
12
13
 
13
14
  export type RetainedQuery = {
14
- readonly normalizationAst: NormalizationAst;
15
+ readonly normalizationAst: NormalizationAstNodes;
15
16
  readonly variables: {};
17
+ readonly root: Link;
16
18
  };
17
19
 
18
- type DidUnretainSomeQuery = boolean;
20
+ export type DidUnretainSomeQuery = boolean;
19
21
  export function unretainQuery(
20
22
  environment: IsographEnvironment,
21
23
  retainedQuery: RetainedQuery,
@@ -42,7 +44,7 @@ export function retainQuery(
42
44
  }
43
45
 
44
46
  export function garbageCollectEnvironment(environment: IsographEnvironment) {
45
- const retainedIds = new Set<DataId>([ROOT_ID]);
47
+ const retainedIds: RetainedIds = {};
46
48
 
47
49
  for (const query of environment.retainedQueries) {
48
50
  recordReachableIds(environment.store, query, retainedIds);
@@ -51,32 +53,62 @@ export function garbageCollectEnvironment(environment: IsographEnvironment) {
51
53
  recordReachableIds(environment.store, query, retainedIds);
52
54
  }
53
55
 
54
- for (const dataId in environment.store) {
55
- if (!retainedIds.has(dataId)) {
56
- delete environment.store[dataId];
56
+ for (const typeName in environment.store) {
57
+ const dataById = environment.store[typeName];
58
+ if (dataById == null) continue;
59
+ const retainedTypeIds = retainedIds[typeName];
60
+
61
+ // delete all objects
62
+ if (retainedTypeIds == undefined || retainedTypeIds.size == 0) {
63
+ delete environment.store[typeName];
64
+ continue;
65
+ }
66
+
67
+ for (const dataId in dataById) {
68
+ if (!retainedTypeIds.has(dataId)) {
69
+ delete dataById[dataId];
70
+ }
71
+ }
72
+
73
+ if (Object.keys(dataById).length === 0) {
74
+ delete environment.store[typeName];
57
75
  }
58
76
  }
59
77
  }
60
78
 
79
+ interface RetainedIds {
80
+ [typeName: TypeName]: Set<DataId>;
81
+ }
82
+
61
83
  function recordReachableIds(
62
84
  store: IsographStore,
63
85
  retainedQuery: RetainedQuery,
64
- mutableRetainedIds: Set<DataId>,
86
+ mutableRetainedIds: RetainedIds,
65
87
  ) {
66
- recordReachableIdsFromRecord(
67
- store,
68
- store[ROOT_ID],
69
- mutableRetainedIds,
70
- retainedQuery.normalizationAst,
71
- retainedQuery.variables,
72
- );
88
+ const record =
89
+ store[retainedQuery.root.__typename]?.[retainedQuery.root.__link];
90
+
91
+ const retainedRecordsIds = (mutableRetainedIds[
92
+ retainedQuery.root.__typename
93
+ ] ??= new Set());
94
+ retainedRecordsIds.add(retainedQuery.root.__link);
95
+
96
+ if (record) {
97
+ recordReachableIdsFromRecord(
98
+ store,
99
+ record,
100
+ mutableRetainedIds,
101
+ retainedQuery.normalizationAst,
102
+ retainedQuery.variables,
103
+ );
104
+ }
73
105
  }
74
106
 
75
107
  function recordReachableIdsFromRecord(
76
108
  store: IsographStore,
77
109
  currentRecord: StoreRecord,
78
- mutableRetainedIds: Set<DataId>,
79
- selections: NormalizationAst,
110
+ mutableRetainedIds: RetainedIds,
111
+ selections: NormalizationAstNodes,
80
112
  variables: Variables | null,
81
113
  ) {
82
114
  for (const selection of selections) {
@@ -85,25 +117,44 @@ function recordReachableIdsFromRecord(
85
117
  const linkKey = getParentRecordKey(selection, variables ?? {});
86
118
  const linkedFieldOrFields = currentRecord[linkKey];
87
119
 
88
- const ids = [];
120
+ const links: Link[] = [];
89
121
  if (Array.isArray(linkedFieldOrFields)) {
90
122
  for (const maybeLink of linkedFieldOrFields) {
91
123
  const link = assertLink(maybeLink);
92
124
  if (link != null) {
93
- ids.push(link.__link);
125
+ links.push(link);
94
126
  }
95
127
  }
96
128
  } else {
97
129
  const link = assertLink(linkedFieldOrFields);
98
130
  if (link != null) {
99
- ids.push(link.__link);
131
+ links.push(link);
100
132
  }
101
133
  }
102
134
 
103
- for (const nextRecordId of ids) {
104
- const nextRecord = store[nextRecordId];
135
+ let typeStore =
136
+ selection.concreteType !== null
137
+ ? store[selection.concreteType]
138
+ : null;
139
+
140
+ if (typeStore == null && selection.concreteType !== null) {
141
+ continue;
142
+ }
143
+
144
+ for (const nextRecordLink of links) {
145
+ let __typename = nextRecordLink.__typename;
146
+
147
+ const resolvedTypeStore = typeStore ?? store[__typename];
148
+
149
+ if (resolvedTypeStore == null) {
150
+ continue;
151
+ }
152
+
153
+ const nextRecord = resolvedTypeStore[nextRecordLink.__link];
105
154
  if (nextRecord != null) {
106
- mutableRetainedIds.add(nextRecordId);
155
+ const retainedRecordsIds = (mutableRetainedIds[__typename] ??=
156
+ new Set());
157
+ retainedRecordsIds.add(nextRecordLink.__link);
107
158
  recordReachableIdsFromRecord(
108
159
  store,
109
160
  nextRecord,