@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.
- package/dist/core/FragmentReference.d.ts +25 -0
- package/dist/core/FragmentReference.d.ts.map +1 -0
- package/dist/core/FragmentReference.js +16 -0
- package/dist/core/IsographEnvironment.d.ts +89 -0
- package/dist/core/IsographEnvironment.d.ts.map +1 -0
- package/dist/core/IsographEnvironment.js +65 -0
- package/dist/core/PromiseWrapper.d.ts +28 -0
- package/dist/core/PromiseWrapper.d.ts.map +1 -0
- package/dist/core/PromiseWrapper.js +57 -0
- package/dist/core/areEqualWithDeepComparison.d.ts +5 -0
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
- package/dist/core/areEqualWithDeepComparison.js +95 -0
- package/dist/core/cache.d.ts +44 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +514 -0
- 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 +5 -0
- package/dist/core/componentCache.d.ts.map +1 -0
- package/dist/core/componentCache.js +38 -0
- package/dist/core/entrypoint.d.ts +69 -0
- package/dist/core/entrypoint.d.ts.map +1 -0
- package/dist/core/entrypoint.js +7 -0
- package/dist/core/garbageCollection.d.ts +13 -0
- package/dist/core/garbageCollection.d.ts.map +1 -0
- package/dist/core/garbageCollection.js +107 -0
- 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 +9 -0
- package/dist/core/makeNetworkRequest.d.ts.map +1 -0
- package/dist/core/makeNetworkRequest.js +118 -0
- package/dist/core/read.d.ts +27 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +478 -0
- package/dist/core/reader.d.ts +87 -0
- package/dist/core/reader.d.ts.map +1 -0
- package/dist/core/reader.js +2 -0
- package/dist/core/util.d.ts +19 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +2 -0
- package/dist/index.d.ts +26 -120
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -306
- package/dist/loadable-hooks/useClientSideDefer.d.ts +16 -0
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
- package/dist/loadable-hooks/useClientSideDefer.js +13 -0
- 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 +6 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +14 -0
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +17 -0
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +14 -0
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +27 -0
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +162 -0
- package/dist/react/FragmentReader.d.ts +16 -0
- package/dist/react/FragmentReader.d.ts.map +1 -0
- package/dist/{EntrypointReader.js → react/FragmentReader.js} +7 -5
- package/dist/react/IsographEnvironmentProvider.d.ts +11 -0
- package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
- package/dist/{IsographEnvironment.js → react/IsographEnvironmentProvider.js} +4 -22
- 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 +12 -0
- package/dist/react/useImperativeReference.d.ts.map +1 -0
- package/dist/react/useImperativeReference.js +35 -0
- package/dist/react/useLazyReference.d.ts +10 -0
- package/dist/react/useLazyReference.d.ts.map +1 -0
- package/dist/react/useLazyReference.js +21 -0
- package/dist/react/useReadAndSubscribe.d.ts +20 -0
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
- package/dist/react/useReadAndSubscribe.js +40 -0
- package/dist/react/useRerenderOnChange.d.ts +8 -0
- package/dist/react/useRerenderOnChange.d.ts.map +1 -0
- package/dist/react/useRerenderOnChange.js +22 -0
- package/dist/react/useResult.d.ts +9 -0
- package/dist/react/useResult.d.ts.map +1 -0
- package/dist/react/useResult.js +39 -0
- package/docs/how-useLazyReference-works.md +117 -0
- package/isograph.config.json +7 -0
- package/package.json +18 -9
- package/schema.graphql +17 -0
- package/src/core/FragmentReference.ts +49 -0
- package/src/core/IsographEnvironment.ts +192 -0
- package/src/core/PromiseWrapper.ts +86 -0
- package/src/core/areEqualWithDeepComparison.ts +112 -0
- package/src/core/cache.ts +835 -0
- package/src/core/check.ts +207 -0
- package/src/core/componentCache.ts +62 -0
- package/src/core/entrypoint.ts +106 -0
- package/src/core/garbageCollection.ts +173 -0
- package/src/core/logging.ts +116 -0
- package/src/core/makeNetworkRequest.ts +175 -0
- package/src/core/read.ts +722 -0
- package/src/core/reader.ts +160 -0
- package/src/core/util.ts +27 -0
- package/src/index.ts +99 -0
- package/src/loadable-hooks/useClientSideDefer.ts +58 -0
- package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +17 -0
- package/src/loadable-hooks/useImperativeLoadableField.ts +52 -0
- package/src/loadable-hooks/useSkipLimitPagination.ts +352 -0
- package/src/react/FragmentReader.tsx +43 -0
- package/src/react/IsographEnvironmentProvider.tsx +33 -0
- package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
- package/src/react/useImperativeReference.ts +68 -0
- package/src/react/useLazyReference.ts +42 -0
- package/src/react/useReadAndSubscribe.ts +81 -0
- package/src/react/useRerenderOnChange.ts +38 -0
- package/src/react/useResult.ts +73 -0
- package/src/tests/__isograph/Query/meName/entrypoint.ts +52 -0
- package/src/tests/__isograph/Query/meName/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meName/param_type.ts +9 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +33 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +90 -0
- package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +14 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +57 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +57 -0
- package/src/tests/__isograph/Query/nodeField/output_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/param_type.ts +10 -0
- package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +38 -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 +99 -0
- package/src/tests/garbageCollection.test.ts +142 -0
- package/src/tests/meNameSuccessor.ts +25 -0
- package/src/tests/nodeQuery.ts +19 -0
- package/src/tests/normalizeData.test.ts +120 -0
- package/src/tests/tsconfig.json +21 -0
- package/tsconfig.json +6 -0
- package/tsconfig.pkg.json +7 -1
- package/vitest.config.ts +20 -0
- package/dist/EntrypointReader.d.ts +0 -6
- package/dist/IsographEnvironment.d.ts +0 -56
- package/dist/PromiseWrapper.d.ts +0 -13
- package/dist/PromiseWrapper.js +0 -22
- package/dist/cache.d.ts +0 -26
- package/dist/cache.js +0 -274
- package/dist/componentCache.d.ts +0 -6
- package/dist/componentCache.js +0 -31
- package/dist/useImperativeReference.d.ts +0 -8
- package/dist/useImperativeReference.js +0 -28
- package/src/EntrypointReader.tsx +0 -20
- package/src/IsographEnvironment.tsx +0 -120
- package/src/PromiseWrapper.ts +0 -29
- package/src/cache.tsx +0 -484
- package/src/componentCache.ts +0 -41
- package/src/index.tsx +0 -617
- 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
|
+
}
|