@isograph/react 0.1.1 → 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 (160) hide show
  1. package/dist/core/FragmentReference.d.ts +25 -0
  2. package/dist/core/FragmentReference.d.ts.map +1 -0
  3. package/dist/core/FragmentReference.js +16 -0
  4. package/dist/core/IsographEnvironment.d.ts +89 -0
  5. package/dist/core/IsographEnvironment.d.ts.map +1 -0
  6. package/dist/core/IsographEnvironment.js +65 -0
  7. package/dist/core/PromiseWrapper.d.ts +28 -0
  8. package/dist/core/PromiseWrapper.d.ts.map +1 -0
  9. package/dist/core/PromiseWrapper.js +57 -0
  10. package/dist/core/areEqualWithDeepComparison.d.ts +5 -0
  11. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
  12. package/dist/core/areEqualWithDeepComparison.js +95 -0
  13. package/dist/core/cache.d.ts +44 -0
  14. package/dist/core/cache.d.ts.map +1 -0
  15. package/dist/core/cache.js +514 -0
  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 +5 -0
  20. package/dist/core/componentCache.d.ts.map +1 -0
  21. package/dist/core/componentCache.js +38 -0
  22. package/dist/core/entrypoint.d.ts +69 -0
  23. package/dist/core/entrypoint.d.ts.map +1 -0
  24. package/dist/core/entrypoint.js +7 -0
  25. package/dist/core/garbageCollection.d.ts +13 -0
  26. package/dist/core/garbageCollection.d.ts.map +1 -0
  27. package/dist/core/garbageCollection.js +107 -0
  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 +9 -0
  32. package/dist/core/makeNetworkRequest.d.ts.map +1 -0
  33. package/dist/core/makeNetworkRequest.js +118 -0
  34. package/dist/core/read.d.ts +27 -0
  35. package/dist/core/read.d.ts.map +1 -0
  36. package/dist/core/read.js +478 -0
  37. package/dist/core/reader.d.ts +87 -0
  38. package/dist/core/reader.d.ts.map +1 -0
  39. package/dist/core/reader.js +2 -0
  40. package/dist/core/util.d.ts +19 -0
  41. package/dist/core/util.d.ts.map +1 -0
  42. package/dist/core/util.js +2 -0
  43. package/dist/index.d.ts +26 -120
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +57 -306
  46. package/dist/loadable-hooks/useClientSideDefer.d.ts +16 -0
  47. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
  48. package/dist/loadable-hooks/useClientSideDefer.js +13 -0
  49. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
  50. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
  51. package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
  52. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +6 -0
  53. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
  54. package/dist/loadable-hooks/useImperativeExposedMutationField.js +14 -0
  55. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +17 -0
  56. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
  57. package/dist/loadable-hooks/useImperativeLoadableField.js +14 -0
  58. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +27 -0
  59. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
  60. package/dist/loadable-hooks/useSkipLimitPagination.js +162 -0
  61. package/dist/react/FragmentReader.d.ts +16 -0
  62. package/dist/react/FragmentReader.d.ts.map +1 -0
  63. package/dist/{EntrypointReader.js → react/FragmentReader.js} +7 -5
  64. package/dist/react/IsographEnvironmentProvider.d.ts +11 -0
  65. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
  66. package/dist/{IsographEnvironment.js → react/IsographEnvironmentProvider.js} +4 -22
  67. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
  68. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
  69. package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
  70. package/dist/react/useImperativeReference.d.ts +12 -0
  71. package/dist/react/useImperativeReference.d.ts.map +1 -0
  72. package/dist/react/useImperativeReference.js +35 -0
  73. package/dist/react/useLazyReference.d.ts +10 -0
  74. package/dist/react/useLazyReference.d.ts.map +1 -0
  75. package/dist/react/useLazyReference.js +21 -0
  76. package/dist/react/useReadAndSubscribe.d.ts +20 -0
  77. package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
  78. package/dist/react/useReadAndSubscribe.js +40 -0
  79. package/dist/react/useRerenderOnChange.d.ts +8 -0
  80. package/dist/react/useRerenderOnChange.d.ts.map +1 -0
  81. package/dist/react/useRerenderOnChange.js +22 -0
  82. package/dist/react/useResult.d.ts +9 -0
  83. package/dist/react/useResult.d.ts.map +1 -0
  84. package/dist/react/useResult.js +39 -0
  85. package/docs/how-useLazyReference-works.md +117 -0
  86. package/isograph.config.json +7 -0
  87. package/package.json +18 -9
  88. package/schema.graphql +17 -0
  89. package/src/core/FragmentReference.ts +49 -0
  90. package/src/core/IsographEnvironment.ts +192 -0
  91. package/src/core/PromiseWrapper.ts +86 -0
  92. package/src/core/areEqualWithDeepComparison.ts +112 -0
  93. package/src/core/cache.ts +835 -0
  94. package/src/core/check.ts +207 -0
  95. package/src/core/componentCache.ts +62 -0
  96. package/src/core/entrypoint.ts +106 -0
  97. package/src/core/garbageCollection.ts +173 -0
  98. package/src/core/logging.ts +116 -0
  99. package/src/core/makeNetworkRequest.ts +175 -0
  100. package/src/core/read.ts +722 -0
  101. package/src/core/reader.ts +160 -0
  102. package/src/core/util.ts +27 -0
  103. package/src/index.ts +99 -0
  104. package/src/loadable-hooks/useClientSideDefer.ts +58 -0
  105. package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
  106. package/src/loadable-hooks/useImperativeExposedMutationField.ts +17 -0
  107. package/src/loadable-hooks/useImperativeLoadableField.ts +52 -0
  108. package/src/loadable-hooks/useSkipLimitPagination.ts +352 -0
  109. package/src/react/FragmentReader.tsx +43 -0
  110. package/src/react/IsographEnvironmentProvider.tsx +33 -0
  111. package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
  112. package/src/react/useImperativeReference.ts +68 -0
  113. package/src/react/useLazyReference.ts +42 -0
  114. package/src/react/useReadAndSubscribe.ts +81 -0
  115. package/src/react/useRerenderOnChange.ts +38 -0
  116. package/src/react/useResult.ts +73 -0
  117. package/src/tests/__isograph/Query/meName/entrypoint.ts +52 -0
  118. package/src/tests/__isograph/Query/meName/output_type.ts +3 -0
  119. package/src/tests/__isograph/Query/meName/param_type.ts +9 -0
  120. package/src/tests/__isograph/Query/meName/resolver_reader.ts +33 -0
  121. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +90 -0
  122. package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +3 -0
  123. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +14 -0
  124. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +57 -0
  125. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +57 -0
  126. package/src/tests/__isograph/Query/nodeField/output_type.ts +3 -0
  127. package/src/tests/__isograph/Query/nodeField/param_type.ts +10 -0
  128. package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
  129. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +38 -0
  130. package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
  131. package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
  132. package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
  133. package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
  134. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
  135. package/src/tests/__isograph/iso.ts +99 -0
  136. package/src/tests/garbageCollection.test.ts +142 -0
  137. package/src/tests/meNameSuccessor.ts +25 -0
  138. package/src/tests/nodeQuery.ts +19 -0
  139. package/src/tests/normalizeData.test.ts +120 -0
  140. package/src/tests/tsconfig.json +21 -0
  141. package/tsconfig.json +6 -0
  142. package/tsconfig.pkg.json +7 -1
  143. package/vitest.config.ts +20 -0
  144. package/dist/EntrypointReader.d.ts +0 -6
  145. package/dist/IsographEnvironment.d.ts +0 -56
  146. package/dist/PromiseWrapper.d.ts +0 -13
  147. package/dist/PromiseWrapper.js +0 -22
  148. package/dist/cache.d.ts +0 -26
  149. package/dist/cache.js +0 -274
  150. package/dist/componentCache.d.ts +0 -6
  151. package/dist/componentCache.js +0 -31
  152. package/dist/useImperativeReference.d.ts +0 -8
  153. package/dist/useImperativeReference.js +0 -28
  154. package/src/EntrypointReader.tsx +0 -20
  155. package/src/IsographEnvironment.tsx +0 -120
  156. package/src/PromiseWrapper.ts +0 -29
  157. package/src/cache.tsx +0 -484
  158. package/src/componentCache.ts +0 -41
  159. package/src/index.tsx +0 -617
  160. package/src/useImperativeReference.ts +0 -58
@@ -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
+ }
@@ -0,0 +1,62 @@
1
+ import { stableCopy } from './cache';
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ import { FragmentReference } from './FragmentReference';
4
+ import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
5
+ import { NetworkRequestReaderOptions } from './read';
6
+ import { readPromise } from './PromiseWrapper';
7
+ import { logMessage } from './logging';
8
+
9
+ export function getOrCreateCachedComponent(
10
+ environment: IsographEnvironment,
11
+ componentName: string,
12
+ fragmentReference: FragmentReference<any, any>,
13
+ networkRequestOptions: NetworkRequestReaderOptions,
14
+ ): 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];
62
+ }
@@ -0,0 +1,106 @@
1
+ import type { TypeName } from './IsographEnvironment';
2
+ import { TopLevelReaderArtifact } from './reader';
3
+ import { Arguments } from './util';
4
+
5
+ export type ReaderWithRefetchQueries<
6
+ TReadFromStore extends { parameters: object; data: object },
7
+ TClientFieldValue,
8
+ > = {
9
+ readonly kind: 'ReaderWithRefetchQueries';
10
+ readonly readerArtifact: TopLevelReaderArtifact<
11
+ TReadFromStore,
12
+ TClientFieldValue,
13
+ // TODO don't type this as any
14
+ any
15
+ >;
16
+ readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
17
+ };
18
+
19
+ export type NetworkRequestInfo = {
20
+ readonly kind: 'NetworkRequestInfo';
21
+ readonly queryText: string;
22
+ readonly normalizationAst: NormalizationAst;
23
+ };
24
+ // This type should be treated as an opaque type.
25
+ export type IsographEntrypoint<
26
+ TReadFromStore extends { parameters: object; data: object },
27
+ TClientFieldValue,
28
+ > = {
29
+ readonly kind: 'Entrypoint';
30
+ readonly networkRequestInfo: NetworkRequestInfo;
31
+ readonly readerWithRefetchQueries: ReaderWithRefetchQueries<
32
+ TReadFromStore,
33
+ TClientFieldValue
34
+ >;
35
+ readonly concreteType: TypeName;
36
+ };
37
+
38
+ export type IsographEntrypointLoader<
39
+ TReadFromStore extends { parameters: object; data: object },
40
+ TClientFieldValue,
41
+ > = {
42
+ readonly kind: 'EntrypointLoader';
43
+ readonly typeAndField: string;
44
+ readonly loader: () => Promise<
45
+ IsographEntrypoint<TReadFromStore, TClientFieldValue>
46
+ >;
47
+ };
48
+
49
+ export type NormalizationAstNode =
50
+ | NormalizationScalarField
51
+ | NormalizationLinkedField
52
+ | NormalizationInlineFragment;
53
+ export type NormalizationAst = ReadonlyArray<NormalizationAstNode>;
54
+
55
+ export type NormalizationScalarField = {
56
+ readonly kind: 'Scalar';
57
+ readonly fieldName: string;
58
+ readonly arguments: Arguments | null;
59
+ };
60
+
61
+ export type NormalizationLinkedField = {
62
+ readonly kind: 'Linked';
63
+ readonly fieldName: string;
64
+ readonly arguments: Arguments | null;
65
+ readonly selections: NormalizationAst;
66
+ readonly concreteType: TypeName | null;
67
+ };
68
+
69
+ export type NormalizationInlineFragment = {
70
+ readonly kind: 'InlineFragment';
71
+ readonly type: string;
72
+ readonly selections: NormalizationAst;
73
+ };
74
+
75
+ // This is more like an entrypoint, but one specifically for a refetch query/mutation
76
+ export type RefetchQueryNormalizationArtifact = {
77
+ readonly kind: 'RefetchQuery';
78
+ readonly networkRequestInfo: NetworkRequestInfo;
79
+ readonly concreteType: TypeName;
80
+ };
81
+
82
+ // TODO rename
83
+ export type RefetchQueryNormalizationArtifactWrapper = {
84
+ readonly artifact: RefetchQueryNormalizationArtifact;
85
+ readonly allowedVariables: string[];
86
+ };
87
+
88
+ export function assertIsEntrypoint<
89
+ TReadFromStore extends { parameters: object; data: object },
90
+ TClientFieldValue,
91
+ >(
92
+ value:
93
+ | IsographEntrypoint<TReadFromStore, TClientFieldValue>
94
+ | ((_: any) => any)
95
+ // Temporarily, allow any here. Once we automatically provide
96
+ // types to entrypoints, we probably don't need this.
97
+ | any,
98
+ ): asserts value is IsographEntrypoint<TReadFromStore, TClientFieldValue> {
99
+ if (typeof value === 'function') throw new Error('Not a string');
100
+ }
101
+
102
+ export type ExtractReadFromStore<Type> =
103
+ Type extends IsographEntrypoint<infer X, any> ? X : never;
104
+ export type ExtractResolverResult<Type> =
105
+ Type extends IsographEntrypoint<any, infer X> ? X : never;
106
+ export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
@@ -0,0 +1,173 @@
1
+ import { Variables } from './FragmentReference';
2
+ import {
3
+ DataId,
4
+ IsographEnvironment,
5
+ IsographStore,
6
+ StoreRecord,
7
+ assertLink,
8
+ type Link,
9
+ type TypeName,
10
+ } from './IsographEnvironment';
11
+ import { getParentRecordKey } from './cache';
12
+ import { NormalizationAst } from './entrypoint';
13
+
14
+ export type RetainedQuery = {
15
+ readonly normalizationAst: NormalizationAst;
16
+ readonly variables: {};
17
+ readonly root: Link;
18
+ };
19
+
20
+ type DidUnretainSomeQuery = boolean;
21
+ export function unretainQuery(
22
+ environment: IsographEnvironment,
23
+ retainedQuery: RetainedQuery,
24
+ ): DidUnretainSomeQuery {
25
+ environment.retainedQueries.delete(retainedQuery);
26
+ environment.gcBuffer.push(retainedQuery);
27
+
28
+ if (environment.gcBuffer.length > environment.gcBufferSize) {
29
+ environment.gcBuffer.shift();
30
+ return true;
31
+ }
32
+
33
+ return false;
34
+ }
35
+
36
+ export function retainQuery(
37
+ environment: IsographEnvironment,
38
+ queryToRetain: RetainedQuery,
39
+ ) {
40
+ environment.retainedQueries.add(queryToRetain);
41
+ // TODO can we remove this query from the buffer somehow?
42
+ // We are relying on === equality, but we really should be comparing
43
+ // id + variables
44
+ }
45
+
46
+ export function garbageCollectEnvironment(environment: IsographEnvironment) {
47
+ const retainedIds: RetainedIds = {};
48
+
49
+ for (const query of environment.retainedQueries) {
50
+ recordReachableIds(environment.store, query, retainedIds);
51
+ }
52
+ for (const query of environment.gcBuffer) {
53
+ recordReachableIds(environment.store, query, retainedIds);
54
+ }
55
+
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];
75
+ }
76
+ }
77
+ }
78
+
79
+ interface RetainedIds {
80
+ [typeName: TypeName]: Set<DataId>;
81
+ }
82
+
83
+ function recordReachableIds(
84
+ store: IsographStore,
85
+ retainedQuery: RetainedQuery,
86
+ mutableRetainedIds: RetainedIds,
87
+ ) {
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
+ }
105
+ }
106
+
107
+ function recordReachableIdsFromRecord(
108
+ store: IsographStore,
109
+ currentRecord: StoreRecord,
110
+ mutableRetainedIds: RetainedIds,
111
+ selections: NormalizationAst,
112
+ variables: Variables | null,
113
+ ) {
114
+ for (const selection of selections) {
115
+ switch (selection.kind) {
116
+ case 'Linked':
117
+ const linkKey = getParentRecordKey(selection, variables ?? {});
118
+ const linkedFieldOrFields = currentRecord[linkKey];
119
+
120
+ const links: Link[] = [];
121
+ if (Array.isArray(linkedFieldOrFields)) {
122
+ for (const maybeLink of linkedFieldOrFields) {
123
+ const link = assertLink(maybeLink);
124
+ if (link != null) {
125
+ links.push(link);
126
+ }
127
+ }
128
+ } else {
129
+ const link = assertLink(linkedFieldOrFields);
130
+ if (link != null) {
131
+ links.push(link);
132
+ }
133
+ }
134
+
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];
154
+ if (nextRecord != null) {
155
+ const retainedRecordsIds = (mutableRetainedIds[__typename] ??=
156
+ new Set());
157
+ retainedRecordsIds.add(nextRecordLink.__link);
158
+ recordReachableIdsFromRecord(
159
+ store,
160
+ nextRecord,
161
+ mutableRetainedIds,
162
+ selection.selections,
163
+ variables,
164
+ );
165
+ }
166
+ }
167
+
168
+ continue;
169
+ case 'Scalar':
170
+ continue;
171
+ }
172
+ }
173
+ }
@@ -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
+ }