@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.
- package/dist/core/FragmentReference.d.ts +14 -4
- package/dist/core/FragmentReference.d.ts.map +1 -0
- package/dist/core/FragmentReference.js +2 -3
- package/dist/core/IsographEnvironment.d.ts +28 -10
- package/dist/core/IsographEnvironment.d.ts.map +1 -0
- package/dist/core/IsographEnvironment.js +15 -22
- package/dist/core/PromiseWrapper.d.ts +1 -0
- package/dist/core/PromiseWrapper.d.ts.map +1 -0
- package/dist/core/PromiseWrapper.js +4 -5
- package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
- package/dist/core/areEqualWithDeepComparison.js +73 -39
- package/dist/core/cache.d.ts +26 -10
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +160 -98
- package/dist/core/check.d.ts +18 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +127 -0
- package/dist/core/componentCache.d.ts +1 -1
- package/dist/core/componentCache.d.ts.map +1 -0
- package/dist/core/componentCache.js +14 -14
- package/dist/core/entrypoint.d.ts +27 -8
- package/dist/core/entrypoint.d.ts.map +1 -0
- package/dist/core/entrypoint.js +1 -2
- package/dist/core/garbageCollection.d.ts +3 -1
- package/dist/core/garbageCollection.d.ts.map +1 -0
- package/dist/core/garbageCollection.js +48 -15
- package/dist/core/logging.d.ts +69 -0
- package/dist/core/logging.d.ts.map +1 -0
- package/dist/core/logging.js +19 -0
- package/dist/core/makeNetworkRequest.d.ts +4 -1
- package/dist/core/makeNetworkRequest.d.ts.map +1 -0
- package/dist/core/makeNetworkRequest.js +71 -15
- package/dist/core/read.d.ts +20 -5
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +104 -41
- package/dist/core/reader.d.ts +34 -10
- package/dist/core/reader.d.ts.map +1 -0
- package/dist/core/util.d.ts +2 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -2
- package/dist/loadable-hooks/useClientSideDefer.d.ts +15 -3
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
- package/dist/loadable-hooks/useClientSideDefer.js +4 -6
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -5
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +3 -4
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +18 -24
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +88 -44
- package/dist/react/FragmentReader.d.ts +7 -4
- package/dist/react/FragmentReader.d.ts.map +1 -0
- package/dist/react/FragmentReader.js +4 -2
- package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
- package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
- package/dist/react/IsographEnvironmentProvider.js +3 -3
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
- package/dist/react/useImperativeReference.d.ts +8 -3
- package/dist/react/useImperativeReference.d.ts.map +1 -0
- package/dist/react/useImperativeReference.js +4 -5
- package/dist/react/useLazyReference.d.ts +7 -2
- package/dist/react/useLazyReference.d.ts.map +1 -0
- package/dist/react/useLazyReference.js +11 -4
- package/dist/react/useReadAndSubscribe.d.ts +12 -3
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
- package/dist/react/useReadAndSubscribe.js +6 -7
- package/dist/react/useRerenderOnChange.d.ts +6 -1
- package/dist/react/useRerenderOnChange.d.ts.map +1 -0
- package/dist/react/useRerenderOnChange.js +3 -4
- package/dist/react/useResult.d.ts +5 -1
- package/dist/react/useResult.d.ts.map +1 -0
- package/dist/react/useResult.js +8 -5
- package/{src/tests/isograph.config.json → isograph.config.json} +1 -1
- package/package.json +12 -8
- package/{src/tests/schema.graphql → schema.graphql} +1 -0
- package/src/core/FragmentReference.ts +17 -5
- package/src/core/IsographEnvironment.ts +38 -29
- package/src/core/areEqualWithDeepComparison.ts +76 -42
- package/src/core/cache.ts +237 -123
- package/src/core/check.ts +207 -0
- package/src/core/componentCache.ts +18 -17
- package/src/core/entrypoint.ts +15 -8
- package/src/core/garbageCollection.ts +71 -20
- package/src/core/logging.ts +116 -0
- package/src/core/makeNetworkRequest.ts +89 -13
- package/src/core/read.ts +162 -55
- package/src/core/reader.ts +40 -13
- package/src/core/util.ts +4 -0
- package/src/index.ts +14 -1
- package/src/loadable-hooks/useClientSideDefer.ts +45 -15
- package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
- package/src/loadable-hooks/useImperativeLoadableField.ts +36 -10
- package/src/loadable-hooks/useSkipLimitPagination.ts +231 -90
- package/src/react/FragmentReader.tsx +13 -4
- package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
- package/src/react/useImperativeReference.ts +18 -7
- package/src/react/useLazyReference.ts +24 -4
- package/src/react/useReadAndSubscribe.ts +20 -5
- package/src/react/useRerenderOnChange.ts +6 -1
- package/src/react/useResult.ts +10 -2
- package/src/tests/__isograph/Query/meName/entrypoint.ts +7 -2
- package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +9 -2
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +13 -2
- package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
- package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
- package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
- package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
- package/src/tests/__isograph/iso.ts +22 -11
- package/src/tests/garbageCollection.test.ts +45 -39
- package/src/tests/meNameSuccessor.ts +8 -3
- package/src/tests/nodeQuery.ts +6 -4
- package/src/tests/normalizeData.test.ts +120 -0
- package/src/tests/tsconfig.json +3 -3
- package/tsconfig.json +2 -2
- package/tsconfig.pkg.json +6 -1
- 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
|
-
|
20
|
-
|
21
|
-
const componentsByName = cachedComponentsById[
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
51
|
+
{
|
52
|
+
data,
|
53
|
+
parameters: fragmentReference.variables,
|
54
|
+
},
|
54
55
|
additionalRuntimeProps,
|
55
56
|
);
|
56
57
|
}
|
package/src/core/entrypoint.ts
CHANGED
@@ -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
|
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
|
26
|
+
TReadFromStore extends { parameters: object; data: object },
|
21
27
|
TClientFieldValue,
|
22
28
|
> = {
|
23
29
|
readonly kind: 'Entrypoint';
|
24
|
-
readonly
|
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
|
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
|
72
|
-
readonly
|
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
|
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 =
|
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
|
55
|
-
|
56
|
-
|
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:
|
86
|
+
mutableRetainedIds: RetainedIds,
|
65
87
|
) {
|
66
|
-
|
67
|
-
store
|
68
|
-
|
69
|
-
|
70
|
-
retainedQuery.
|
71
|
-
|
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:
|
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
|
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
|
-
|
125
|
+
links.push(link);
|
94
126
|
}
|
95
127
|
}
|
96
128
|
} else {
|
97
129
|
const link = assertLink(linkedFieldOrFields);
|
98
130
|
if (link != null) {
|
99
|
-
|
131
|
+
links.push(link);
|
100
132
|
}
|
101
133
|
}
|
102
134
|
|
103
|
-
|
104
|
-
|
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
|
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
|
+
}
|