@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
@@ -0,0 +1,207 @@
1
+ import { getParentRecordKey } from './cache';
2
+ import { NormalizationAst } 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 = 'Yes' | 'No' | 'IfNecessary';
13
+
14
+ export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary';
15
+
16
+ export type FetchOptions = {
17
+ shouldFetch?: ShouldFetch;
18
+ onComplete?: () => void;
19
+ onError?: () => void;
20
+ };
21
+
22
+ export type CheckResult =
23
+ | {
24
+ kind: 'EnoughData';
25
+ }
26
+ | {
27
+ kind: 'MissingData';
28
+ record: Link;
29
+ };
30
+
31
+ export function check(
32
+ environment: IsographEnvironment,
33
+ normalizationAst: NormalizationAst,
34
+ variables: Variables,
35
+ root: Link,
36
+ ): CheckResult {
37
+ const recordsById = (environment.store[root.__typename] ??= {});
38
+ const newStoreRecord = (recordsById[root.__link] ??= {});
39
+
40
+ const checkResult = checkFromRecord(
41
+ environment,
42
+ normalizationAst,
43
+ variables,
44
+ newStoreRecord,
45
+ root,
46
+ );
47
+ logMessage(environment, {
48
+ kind: 'EnvironmentCheck',
49
+ result: checkResult,
50
+ });
51
+ return checkResult;
52
+ }
53
+
54
+ function checkFromRecord(
55
+ environment: IsographEnvironment,
56
+ normalizationAst: NormalizationAst,
57
+ variables: Variables,
58
+ record: StoreRecord,
59
+ recordLink: Link,
60
+ ): CheckResult {
61
+ normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
62
+ switch (normalizationAstNode.kind) {
63
+ case 'Scalar': {
64
+ const parentRecordKey = getParentRecordKey(
65
+ normalizationAstNode,
66
+ variables,
67
+ );
68
+ const scalarValue = record[parentRecordKey];
69
+
70
+ // null means the value is known to be missing, so it must
71
+ // be exactly undefined
72
+ if (scalarValue === undefined) {
73
+ return {
74
+ kind: 'MissingData',
75
+ record: recordLink,
76
+ };
77
+ }
78
+ continue normalizationAstLoop;
79
+ }
80
+ case 'Linked': {
81
+ const parentRecordKey = getParentRecordKey(
82
+ normalizationAstNode,
83
+ variables,
84
+ );
85
+
86
+ const linkedValue = record[parentRecordKey];
87
+
88
+ if (linkedValue === undefined) {
89
+ return {
90
+ kind: 'MissingData',
91
+ record: recordLink,
92
+ };
93
+ } else if (linkedValue === null) {
94
+ continue;
95
+ } else if (Array.isArray(linkedValue)) {
96
+ arrayItemsLoop: for (const item of linkedValue) {
97
+ const link = getLink(item);
98
+ if (link === null) {
99
+ throw new Error(
100
+ 'Unexpected non-link in the Isograph store. ' +
101
+ 'This is indicative of a bug in Isograph.',
102
+ );
103
+ }
104
+
105
+ const linkedRecord =
106
+ environment.store[link.__typename]?.[link.__link];
107
+
108
+ if (linkedRecord === undefined) {
109
+ return {
110
+ kind: 'MissingData',
111
+ record: link,
112
+ };
113
+ } else if (linkedRecord === null) {
114
+ continue arrayItemsLoop;
115
+ } else {
116
+ // TODO in __DEV__ assert linkedRecord is an object
117
+ const result = checkFromRecord(
118
+ environment,
119
+ normalizationAstNode.selections,
120
+ variables,
121
+ linkedRecord,
122
+ link,
123
+ );
124
+
125
+ if (result.kind === 'MissingData') {
126
+ return result;
127
+ }
128
+ }
129
+ }
130
+ } else {
131
+ const link = getLink(linkedValue);
132
+ if (link === null) {
133
+ throw new Error(
134
+ 'Unexpected non-link in the Isograph store. ' +
135
+ 'This is indicative of a bug in Isograph.',
136
+ );
137
+ }
138
+
139
+ const linkedRecord =
140
+ environment.store[link.__typename]?.[link.__link];
141
+
142
+ if (linkedRecord === undefined) {
143
+ return {
144
+ kind: 'MissingData',
145
+ record: link,
146
+ };
147
+ } else if (linkedRecord === null) {
148
+ continue normalizationAstLoop;
149
+ } else {
150
+ // TODO in __DEV__ assert linkedRecord is an object
151
+ const result = checkFromRecord(
152
+ environment,
153
+ normalizationAstNode.selections,
154
+ variables,
155
+ linkedRecord,
156
+ link,
157
+ );
158
+
159
+ if (result.kind === 'MissingData') {
160
+ return result;
161
+ }
162
+ }
163
+ }
164
+
165
+ continue normalizationAstLoop;
166
+ }
167
+ case 'InlineFragment': {
168
+ const existingRecordTypename = record['__typename'];
169
+
170
+ if (
171
+ existingRecordTypename == null ||
172
+ existingRecordTypename !== normalizationAstNode.type
173
+ ) {
174
+ return {
175
+ kind: 'MissingData',
176
+ record: recordLink,
177
+ };
178
+ }
179
+
180
+ const result = checkFromRecord(
181
+ environment,
182
+ normalizationAstNode.selections,
183
+ variables,
184
+ record,
185
+ recordLink,
186
+ );
187
+
188
+ if (result.kind === 'MissingData') {
189
+ return result;
190
+ }
191
+
192
+ continue normalizationAstLoop;
193
+ }
194
+ default: {
195
+ let _: never = normalizationAstNode;
196
+ _;
197
+ throw new Error(
198
+ 'Unexpected case. This is indicative of a bug in Isograph.',
199
+ );
200
+ }
201
+ }
202
+ }
203
+
204
+ return {
205
+ kind: 'EnoughData',
206
+ };
207
+ }
@@ -4,6 +4,7 @@ import { FragmentReference } from './FragmentReference';
4
4
  import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
5
5
  import { NetworkRequestReaderOptions } from './read';
6
6
  import { readPromise } from './PromiseWrapper';
7
+ import { logMessage } from './logging';
7
8
 
8
9
  export function getOrCreateCachedComponent(
9
10
  environment: IsographEnvironment,
@@ -16,9 +17,9 @@ export function getOrCreateCachedComponent(
16
17
  // time.
17
18
  const cachedComponentsById = environment.componentCache;
18
19
 
19
- cachedComponentsById[fragmentReference.root] =
20
- cachedComponentsById[fragmentReference.root] ?? {};
21
- const componentsByName = cachedComponentsById[fragmentReference.root];
20
+ const recordLink = fragmentReference.root.__link;
21
+
22
+ const componentsByName = (cachedComponentsById[recordLink] ??= {});
22
23
 
23
24
  componentsByName[componentName] = componentsByName[componentName] ?? {};
24
25
  const byArgs = componentsByName[componentName];
@@ -30,27 +31,27 @@ export function getOrCreateCachedComponent(
30
31
  byArgs[stringifiedArgs] ??
31
32
  (() => {
32
33
  function Component(additionalRuntimeProps: { [key: string]: any }) {
34
+ const readerWithRefetchQueries = readPromise(
35
+ fragmentReference.readerWithRefetchQueries,
36
+ );
37
+
33
38
  const data = useReadAndSubscribe(
34
39
  fragmentReference,
35
40
  networkRequestOptions,
41
+ readerWithRefetchQueries.readerArtifact.readerAst,
36
42
  );
37
43
 
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
- }
47
-
48
- const readerWithRefetchQueries = readPromise(
49
- fragmentReference.readerWithRefetchQueries,
50
- );
44
+ logMessage(environment, {
45
+ kind: 'ComponentRerendered',
46
+ componentName,
47
+ rootLink: fragmentReference.root,
48
+ });
51
49
 
52
50
  return readerWithRefetchQueries.readerArtifact.resolver(
53
- data,
51
+ {
52
+ data,
53
+ parameters: fragmentReference.variables,
54
+ },
54
55
  additionalRuntimeProps,
55
56
  );
56
57
  }
@@ -1,8 +1,9 @@
1
+ import type { TypeName } from './IsographEnvironment';
1
2
  import { TopLevelReaderArtifact } from './reader';
2
3
  import { Arguments } from './util';
3
4
 
4
5
  export type ReaderWithRefetchQueries<
5
- TReadFromStore extends Object,
6
+ TReadFromStore extends { parameters: object; data: object },
6
7
  TClientFieldValue,
7
8
  > = {
8
9
  readonly kind: 'ReaderWithRefetchQueries';
@@ -15,22 +16,27 @@ export type ReaderWithRefetchQueries<
15
16
  readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
16
17
  };
17
18
 
19
+ export type NetworkRequestInfo = {
20
+ readonly kind: 'NetworkRequestInfo';
21
+ readonly queryText: string;
22
+ readonly normalizationAst: NormalizationAst;
23
+ };
18
24
  // This type should be treated as an opaque type.
19
25
  export type IsographEntrypoint<
20
- TReadFromStore extends Object,
26
+ TReadFromStore extends { parameters: object; data: object },
21
27
  TClientFieldValue,
22
28
  > = {
23
29
  readonly kind: 'Entrypoint';
24
- readonly queryText: string;
25
- readonly normalizationAst: NormalizationAst;
30
+ readonly networkRequestInfo: NetworkRequestInfo;
26
31
  readonly readerWithRefetchQueries: ReaderWithRefetchQueries<
27
32
  TReadFromStore,
28
33
  TClientFieldValue
29
34
  >;
35
+ readonly concreteType: TypeName;
30
36
  };
31
37
 
32
38
  export type IsographEntrypointLoader<
33
- TReadFromStore extends Object,
39
+ TReadFromStore extends { parameters: object; data: object },
34
40
  TClientFieldValue,
35
41
  > = {
36
42
  readonly kind: 'EntrypointLoader';
@@ -57,6 +63,7 @@ export type NormalizationLinkedField = {
57
63
  readonly fieldName: string;
58
64
  readonly arguments: Arguments | null;
59
65
  readonly selections: NormalizationAst;
66
+ readonly concreteType: TypeName | null;
60
67
  };
61
68
 
62
69
  export type NormalizationInlineFragment = {
@@ -68,8 +75,8 @@ export type NormalizationInlineFragment = {
68
75
  // This is more like an entrypoint, but one specifically for a refetch query/mutation
69
76
  export type RefetchQueryNormalizationArtifact = {
70
77
  readonly kind: 'RefetchQuery';
71
- readonly queryText: string;
72
- readonly normalizationAst: NormalizationAst;
78
+ readonly networkRequestInfo: NetworkRequestInfo;
79
+ readonly concreteType: TypeName;
73
80
  };
74
81
 
75
82
  // TODO rename
@@ -79,7 +86,7 @@ export type RefetchQueryNormalizationArtifactWrapper = {
79
86
  };
80
87
 
81
88
  export function assertIsEntrypoint<
82
- TReadFromStore extends Object,
89
+ TReadFromStore extends { parameters: object; data: object },
83
90
  TClientFieldValue,
84
91
  >(
85
92
  value:
@@ -3,9 +3,10 @@ import {
3
3
  DataId,
4
4
  IsographEnvironment,
5
5
  IsographStore,
6
- ROOT_ID,
7
6
  StoreRecord,
8
7
  assertLink,
8
+ type Link,
9
+ type TypeName,
9
10
  } from './IsographEnvironment';
10
11
  import { getParentRecordKey } from './cache';
11
12
  import { NormalizationAst } from './entrypoint';
@@ -13,6 +14,7 @@ import { NormalizationAst } from './entrypoint';
13
14
  export type RetainedQuery = {
14
15
  readonly normalizationAst: NormalizationAst;
15
16
  readonly variables: {};
17
+ readonly root: Link;
16
18
  };
17
19
 
18
20
  type DidUnretainSomeQuery = boolean;
@@ -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,31 +53,61 @@ 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>,
110
+ mutableRetainedIds: RetainedIds,
79
111
  selections: NormalizationAst,
80
112
  variables: Variables | null,
81
113
  ) {
@@ -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,
@@ -0,0 +1,116 @@
1
+ import { CleanupFn } from '@isograph/disposable-types';
2
+ import {
3
+ IsographEnvironment,
4
+ IsographStore,
5
+ StoreRecord,
6
+ type Link,
7
+ } from './IsographEnvironment';
8
+ import {
9
+ IsographEntrypoint,
10
+ NormalizationAst,
11
+ RefetchQueryNormalizationArtifact,
12
+ } from './entrypoint';
13
+ import { FragmentReference, Variables } from './FragmentReference';
14
+ import { NetworkResponseObject, type EncounteredIds } from './cache';
15
+ import { Arguments } from './util';
16
+ import { ReadDataResult } from './read';
17
+ import { CheckResult } from './check';
18
+
19
+ export type LogMessage =
20
+ | {
21
+ kind: 'GettingSuspenseCacheItem';
22
+ index: string;
23
+ availableCacheItems: ReadonlyArray<string>;
24
+ found: boolean;
25
+ }
26
+ | {
27
+ kind: 'AboutToNormalize';
28
+ normalizationAst: NormalizationAst;
29
+ networkResponse: NetworkResponseObject;
30
+ variables: Variables;
31
+ }
32
+ | {
33
+ kind: 'AfterNormalization';
34
+ store: IsographStore;
35
+ encounteredIds: EncounteredIds;
36
+ }
37
+ | {
38
+ kind: 'DeepEqualityCheck';
39
+ fragmentReference: FragmentReference<any, any>;
40
+ old: object;
41
+ new: object;
42
+ deeplyEqual: boolean;
43
+ }
44
+ | {
45
+ kind: 'ComponentRerendered';
46
+ componentName: string;
47
+ rootLink: Link;
48
+ }
49
+ | {
50
+ kind: 'MakeNetworkRequest';
51
+ artifact:
52
+ | RefetchQueryNormalizationArtifact
53
+ | IsographEntrypoint<any, any>;
54
+ variables: Variables;
55
+ networkRequestId: string;
56
+ }
57
+ | {
58
+ kind: 'ReceivedNetworkResponse';
59
+ // TODO should be object
60
+ networkResponse: any;
61
+ networkRequestId: string;
62
+ }
63
+ | {
64
+ kind: 'ReceivedNetworkError';
65
+ error: any;
66
+ networkRequestId: string;
67
+ }
68
+ | {
69
+ kind: 'MissingFieldHandlerCalled';
70
+ root: Link;
71
+ storeRecord: StoreRecord;
72
+ fieldName: string;
73
+ arguments: Arguments | null;
74
+ variables: Variables;
75
+ }
76
+ | {
77
+ kind: 'DoneReading';
78
+ response: ReadDataResult<any>;
79
+ }
80
+ | {
81
+ kind: 'NonEntrypointReceived';
82
+ entrypoint: any;
83
+ }
84
+ | {
85
+ kind: 'EnvironmentCheck';
86
+ result: CheckResult;
87
+ };
88
+
89
+ export type LogFunction = (logMessage: LogMessage) => void;
90
+
91
+ // wrapped so that items in the loggers set are unique.
92
+ export type WrappedLogFunction = {
93
+ log: LogFunction;
94
+ };
95
+
96
+ export function logMessage(
97
+ environment: IsographEnvironment,
98
+ message: LogMessage,
99
+ ) {
100
+ for (const logger of environment.loggers) {
101
+ try {
102
+ logger.log(message);
103
+ } catch {}
104
+ }
105
+ }
106
+
107
+ export function registerLogger(
108
+ environment: IsographEnvironment,
109
+ log: LogFunction,
110
+ ): CleanupFn {
111
+ const wrapped = { log };
112
+ environment.loggers.add(wrapped);
113
+ return () => {
114
+ environment.loggers.delete(wrapped);
115
+ };
116
+ }