@isograph/react 0.0.0-main-e403ba82 → 0.0.0-main-82cbc3c0
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/.turbo/turbo-compile-libs.log +1 -1
- package/dist/core/FragmentReference.d.ts +1 -1
- package/dist/core/FragmentReference.d.ts.map +1 -1
- package/dist/core/FragmentReference.js +2 -2
- package/dist/core/IsographEnvironment.d.ts +7 -5
- package/dist/core/IsographEnvironment.d.ts.map +1 -1
- package/dist/core/IsographEnvironment.js +5 -4
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.js +2 -5
- package/dist/core/cache.d.ts +5 -18
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +10 -222
- package/dist/core/check.js +5 -5
- package/dist/core/componentCache.d.ts +2 -2
- package/dist/core/componentCache.d.ts.map +1 -1
- package/dist/core/componentCache.js +1 -26
- package/dist/core/entrypoint.d.ts +2 -2
- package/dist/core/entrypoint.d.ts.map +1 -1
- package/dist/core/garbageCollection.d.ts +2 -2
- package/dist/core/garbageCollection.d.ts.map +1 -1
- package/dist/core/garbageCollection.js +3 -3
- package/dist/core/getOrCreateCacheForArtifact.d.ts +8 -0
- package/dist/core/getOrCreateCacheForArtifact.d.ts.map +1 -0
- package/dist/core/getOrCreateCacheForArtifact.js +40 -0
- package/dist/core/logging.d.ts +8 -8
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.d.ts +3 -3
- package/dist/core/makeNetworkRequest.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.js +3 -2
- package/dist/core/optimisticProxy.d.ts.map +1 -1
- package/dist/core/optimisticProxy.js +24 -22
- package/dist/core/read.d.ts +3 -3
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +4 -4
- package/dist/core/startUpdate.d.ts.map +1 -1
- package/dist/core/startUpdate.js +2 -1
- package/dist/core/subscribe.d.ts +8 -0
- package/dist/core/subscribe.d.ts.map +1 -0
- package/dist/core/subscribe.js +127 -0
- package/dist/core/util.d.ts +7 -0
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +26 -0
- package/dist/core/writeData.d.ts +7 -0
- package/dist/core/writeData.d.ts.map +1 -0
- package/dist/core/writeData.js +36 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +3 -3
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.js +3 -3
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +3 -3
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.js +3 -3
- package/dist/react/createIsographEnvironment.d.ts +4 -0
- package/dist/react/createIsographEnvironment.d.ts.map +1 -0
- package/dist/react/createIsographEnvironment.js +8 -0
- package/dist/react/maybeUnwrapNetworkRequest.d.ts +4 -0
- package/dist/react/maybeUnwrapNetworkRequest.d.ts.map +1 -0
- package/dist/react/maybeUnwrapNetworkRequest.js +14 -0
- package/dist/react/useLazyReference.d.ts.map +1 -1
- package/dist/react/useLazyReference.js +2 -2
- package/dist/react/useReadAndSubscribe.d.ts +4 -2
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
- package/dist/react/useReadAndSubscribe.js +31 -2
- package/dist/react/useRerenderOnChange.d.ts +2 -2
- package/dist/react/useRerenderOnChange.d.ts.map +1 -1
- package/dist/react/useRerenderOnChange.js +2 -2
- package/dist/react/useResult.d.ts +2 -4
- package/dist/react/useResult.d.ts.map +1 -1
- package/dist/react/useResult.js +3 -13
- package/package.json +4 -4
- package/src/core/FragmentReference.ts +2 -2
- package/src/core/IsographEnvironment.ts +26 -10
- package/src/core/areEqualWithDeepComparison.ts +2 -6
- package/src/core/cache.ts +18 -364
- package/src/core/check.ts +5 -5
- package/src/core/componentCache.ts +8 -43
- package/src/core/entrypoint.ts +2 -2
- package/src/core/garbageCollection.ts +8 -8
- package/src/core/getOrCreateCacheForArtifact.ts +86 -0
- package/src/core/logging.ts +10 -10
- package/src/core/makeNetworkRequest.ts +8 -8
- package/src/core/optimisticProxy.ts +27 -26
- package/src/core/read.ts +17 -17
- package/src/core/startUpdate.ts +1 -1
- package/src/core/subscribe.ts +195 -0
- package/src/core/util.ts +26 -0
- package/src/core/writeData.ts +79 -0
- package/src/index.ts +3 -4
- package/src/loadable-hooks/useConnectionSpecPagination.ts +6 -6
- package/src/loadable-hooks/useSkipLimitPagination.ts +6 -6
- package/src/react/createIsographEnvironment.ts +23 -0
- package/src/react/maybeUnwrapNetworkRequest.ts +17 -0
- package/src/react/useLazyReference.ts +2 -4
- package/src/react/useReadAndSubscribe.ts +53 -5
- package/src/react/useRerenderOnChange.ts +3 -3
- package/src/react/useResult.ts +6 -24
- package/src/tests/garbageCollection.test.ts +3 -6
- package/src/tests/meNameSuccessor.ts +1 -1
- package/src/tests/nodeQuery.ts +1 -1
- package/src/tests/normalizeData.test.ts +5 -3
- package/src/tests/optimisticProxy.test.ts +7 -5
- package/src/tests/startUpdate.test.ts +5 -7
- package/vitest.config.ts +5 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getParentRecordKey } from './cache';
|
|
2
|
-
import { NormalizationAstNodes,
|
|
3
|
-
import { Variables } from './FragmentReference';
|
|
2
|
+
import type { NormalizationAstNodes, NormalizationAst } from './entrypoint';
|
|
3
|
+
import type { Variables } from './FragmentReference';
|
|
4
4
|
import {
|
|
5
5
|
assertLink,
|
|
6
|
-
DataId,
|
|
7
|
-
IsographEnvironment,
|
|
8
|
-
StoreRecord,
|
|
6
|
+
type DataId,
|
|
7
|
+
type IsographEnvironment,
|
|
8
|
+
type StoreRecord,
|
|
9
9
|
type StoreLayerData,
|
|
10
10
|
type StoreLink,
|
|
11
11
|
type TypeName,
|
|
@@ -101,7 +101,7 @@ export function garbageCollectBaseStoreLayer(
|
|
|
101
101
|
const retainedTypeIds = retainedIds[typeName];
|
|
102
102
|
|
|
103
103
|
// delete all objects
|
|
104
|
-
if (retainedTypeIds
|
|
104
|
+
if (retainedTypeIds === undefined || retainedTypeIds.size === 0) {
|
|
105
105
|
delete baseStoreLayer.data[typeName];
|
|
106
106
|
continue;
|
|
107
107
|
}
|
|
@@ -175,11 +175,11 @@ function recordReachableIdsFromRecord(
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
let typeStore =
|
|
178
|
-
selection.concreteType
|
|
178
|
+
selection.concreteType != null
|
|
179
179
|
? dataLayer[selection.concreteType]
|
|
180
180
|
: null;
|
|
181
181
|
|
|
182
|
-
if (typeStore == null && selection.concreteType
|
|
182
|
+
if (typeStore == null && selection.concreteType != null) {
|
|
183
183
|
continue;
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ItemCleanupPair } from '@isograph/isograph-disposable-types/dist';
|
|
2
|
+
import type { ParentCache } from '@isograph/isograph-react-disposable-state/dist';
|
|
3
|
+
import {
|
|
4
|
+
type NetworkResponseObject,
|
|
5
|
+
getOrCreateItemInSuspenseCache,
|
|
6
|
+
} from './cache';
|
|
7
|
+
import type { FetchOptions } from './check';
|
|
8
|
+
import type {
|
|
9
|
+
IsographEntrypoint,
|
|
10
|
+
NormalizationAst,
|
|
11
|
+
NormalizationAstLoader,
|
|
12
|
+
} from './entrypoint';
|
|
13
|
+
import type {
|
|
14
|
+
ExtractParameters,
|
|
15
|
+
FragmentReference,
|
|
16
|
+
UnknownTReadFromStore,
|
|
17
|
+
} from './FragmentReference';
|
|
18
|
+
import {
|
|
19
|
+
type IsographEnvironment,
|
|
20
|
+
getOrLoadReaderWithRefetchQueries,
|
|
21
|
+
ROOT_ID,
|
|
22
|
+
} from './IsographEnvironment';
|
|
23
|
+
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
|
24
|
+
import { stableCopy } from './util';
|
|
25
|
+
|
|
26
|
+
export function getOrCreateCacheForArtifact<
|
|
27
|
+
TReadFromStore extends UnknownTReadFromStore,
|
|
28
|
+
TClientFieldValue,
|
|
29
|
+
TNormalizationAst extends NormalizationAst | NormalizationAstLoader,
|
|
30
|
+
TRawResponseType extends NetworkResponseObject,
|
|
31
|
+
>(
|
|
32
|
+
environment: IsographEnvironment,
|
|
33
|
+
entrypoint: IsographEntrypoint<
|
|
34
|
+
TReadFromStore,
|
|
35
|
+
TClientFieldValue,
|
|
36
|
+
TNormalizationAst,
|
|
37
|
+
TRawResponseType
|
|
38
|
+
>,
|
|
39
|
+
variables: ExtractParameters<TReadFromStore>,
|
|
40
|
+
fetchOptions?: FetchOptions<TClientFieldValue, TRawResponseType>,
|
|
41
|
+
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
|
42
|
+
let cacheKey = '';
|
|
43
|
+
switch (entrypoint.networkRequestInfo.operation.kind) {
|
|
44
|
+
case 'Operation':
|
|
45
|
+
cacheKey =
|
|
46
|
+
entrypoint.networkRequestInfo.operation.text +
|
|
47
|
+
JSON.stringify(stableCopy(variables));
|
|
48
|
+
break;
|
|
49
|
+
case 'PersistedOperation':
|
|
50
|
+
cacheKey =
|
|
51
|
+
entrypoint.networkRequestInfo.operation.operationId +
|
|
52
|
+
JSON.stringify(stableCopy(variables));
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const factory = () => {
|
|
56
|
+
const { fieldName, readerArtifactKind, readerWithRefetchQueries } =
|
|
57
|
+
getOrLoadReaderWithRefetchQueries(
|
|
58
|
+
environment,
|
|
59
|
+
entrypoint.readerWithRefetchQueries,
|
|
60
|
+
);
|
|
61
|
+
const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
|
|
62
|
+
environment,
|
|
63
|
+
entrypoint,
|
|
64
|
+
variables,
|
|
65
|
+
readerWithRefetchQueries,
|
|
66
|
+
fetchOptions ?? null,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const itemCleanupPair: ItemCleanupPair<
|
|
70
|
+
FragmentReference<TReadFromStore, TClientFieldValue>
|
|
71
|
+
> = [
|
|
72
|
+
{
|
|
73
|
+
kind: 'FragmentReference',
|
|
74
|
+
readerWithRefetchQueries,
|
|
75
|
+
fieldName,
|
|
76
|
+
readerArtifactKind,
|
|
77
|
+
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
78
|
+
variables,
|
|
79
|
+
networkRequest: networkRequest,
|
|
80
|
+
},
|
|
81
|
+
disposeNetworkRequest,
|
|
82
|
+
];
|
|
83
|
+
return itemCleanupPair;
|
|
84
|
+
};
|
|
85
|
+
return getOrCreateItemInSuspenseCache(environment, cacheKey, factory);
|
|
86
|
+
}
|
package/src/core/logging.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { CleanupFn } from '@isograph/disposable-types';
|
|
2
|
-
import { NetworkResponseObject,
|
|
3
|
-
import { CheckResult } from './check';
|
|
4
|
-
import {
|
|
1
|
+
import type { CleanupFn } from '@isograph/disposable-types';
|
|
2
|
+
import type { NetworkResponseObject, EncounteredIds } from './cache';
|
|
3
|
+
import type { CheckResult } from './check';
|
|
4
|
+
import type {
|
|
5
5
|
IsographEntrypoint,
|
|
6
6
|
RefetchQueryNormalizationArtifact,
|
|
7
|
-
|
|
7
|
+
NormalizationAstNodes,
|
|
8
8
|
} from './entrypoint';
|
|
9
|
-
import { FragmentReference, Variables } from './FragmentReference';
|
|
10
|
-
import {
|
|
9
|
+
import type { FragmentReference, Variables } from './FragmentReference';
|
|
10
|
+
import type {
|
|
11
11
|
IsographEnvironment,
|
|
12
12
|
StoreRecord,
|
|
13
|
-
|
|
13
|
+
StoreLink,
|
|
14
14
|
} from './IsographEnvironment';
|
|
15
|
-
import { ReadDataResult } from './read';
|
|
16
|
-
import { Arguments } from './util';
|
|
15
|
+
import type { ReadDataResult } from './read';
|
|
16
|
+
import type { Arguments } from './util';
|
|
17
17
|
import type { StoreLayer } from './optimisticProxy';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { ItemCleanupPair } from '@isograph/disposable-types';
|
|
1
|
+
import type { ItemCleanupPair } from '@isograph/disposable-types';
|
|
2
2
|
import {
|
|
3
|
-
callSubscriptions,
|
|
4
3
|
normalizeData,
|
|
5
4
|
type EncounteredIds,
|
|
6
5
|
type NetworkResponseObject,
|
|
7
6
|
} from './cache';
|
|
8
7
|
import { check, DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check';
|
|
9
8
|
import { getOrCreateCachedComponent } from './componentCache';
|
|
10
|
-
import {
|
|
9
|
+
import type {
|
|
11
10
|
IsographEntrypoint,
|
|
11
|
+
NormalizationAst,
|
|
12
|
+
NormalizationAstLoader,
|
|
12
13
|
ReaderWithRefetchQueries,
|
|
13
14
|
RefetchQueryNormalizationArtifact,
|
|
14
|
-
type NormalizationAst,
|
|
15
|
-
type NormalizationAstLoader,
|
|
16
15
|
} from './entrypoint';
|
|
17
|
-
import {
|
|
16
|
+
import type {
|
|
18
17
|
ExtractParameters,
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
FragmentReference,
|
|
19
|
+
UnknownTReadFromStore,
|
|
21
20
|
} from './FragmentReference';
|
|
22
21
|
import {
|
|
23
22
|
garbageCollectEnvironment,
|
|
@@ -42,6 +41,7 @@ import {
|
|
|
42
41
|
} from './PromiseWrapper';
|
|
43
42
|
import { readButDoNotEvaluate } from './read';
|
|
44
43
|
import { getOrCreateCachedStartUpdate } from './startUpdate';
|
|
44
|
+
import { callSubscriptions } from './subscribe';
|
|
45
45
|
|
|
46
46
|
let networkRequestId = 0;
|
|
47
47
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
insertEmptySetIfMissing,
|
|
4
|
-
type EncounteredIds,
|
|
5
|
-
} from './cache';
|
|
1
|
+
import { insertEmptySetIfMissing, type EncounteredIds } from './cache';
|
|
2
|
+
import { callSubscriptions } from './subscribe';
|
|
6
3
|
import type {
|
|
7
4
|
BaseStoreLayerData,
|
|
8
5
|
IsographEnvironment,
|
|
@@ -29,15 +26,18 @@ export function getStoreRecordProxy(
|
|
|
29
26
|
link: StoreLink,
|
|
30
27
|
): Readonly<StoreRecord> | null | undefined {
|
|
31
28
|
let startNode: StoreLayer | null = storeLayer;
|
|
32
|
-
while (startNode
|
|
29
|
+
while (startNode != null) {
|
|
33
30
|
const storeRecord = startNode.data[link.__typename]?.[link.__link];
|
|
34
|
-
if (storeRecord ===
|
|
35
|
-
|
|
31
|
+
if (storeRecord === undefined) {
|
|
32
|
+
startNode = startNode.parentStoreLayer;
|
|
33
|
+
continue;
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
|
|
36
|
+
if (storeRecord == null) {
|
|
37
|
+
return null;
|
|
39
38
|
}
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
return getMutableStoreRecordProxy(startNode, link);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
return undefined;
|
|
@@ -52,13 +52,13 @@ export function getMutableStoreRecordProxy(
|
|
|
52
52
|
{
|
|
53
53
|
get(_, propertyName) {
|
|
54
54
|
let currentStoreLayer: StoreLayer | null = childMostStoreLayer;
|
|
55
|
-
while (currentStoreLayer
|
|
55
|
+
while (currentStoreLayer != null) {
|
|
56
56
|
const storeRecord =
|
|
57
57
|
currentStoreLayer.data[link.__typename]?.[link.__link];
|
|
58
|
-
if (storeRecord
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (storeRecord !== undefined) {
|
|
59
|
+
if (storeRecord == null) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
62
|
const value = Reflect.get(storeRecord, propertyName);
|
|
63
63
|
if (value !== undefined) {
|
|
64
64
|
return value;
|
|
@@ -69,15 +69,16 @@ export function getMutableStoreRecordProxy(
|
|
|
69
69
|
},
|
|
70
70
|
has(_, propertyName) {
|
|
71
71
|
let currentStoreLayer: StoreLayer | null = childMostStoreLayer;
|
|
72
|
-
while (currentStoreLayer
|
|
72
|
+
while (currentStoreLayer != null) {
|
|
73
73
|
const storeRecord =
|
|
74
74
|
currentStoreLayer.data[link.__typename]?.[link.__link];
|
|
75
|
-
if (storeRecord
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
if (storeRecord !== undefined) {
|
|
76
|
+
if (storeRecord == null) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
79
80
|
const value = Reflect.has(storeRecord, propertyName);
|
|
80
|
-
if (value
|
|
81
|
+
if (value) {
|
|
81
82
|
return true;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -193,7 +194,7 @@ function mergeDataLayer(target: StoreLayerData, source: StoreLayerData): void {
|
|
|
193
194
|
}
|
|
194
195
|
const targetRecordById = (target[typeName] ??= {});
|
|
195
196
|
for (const [id, sourceRecord] of Object.entries(sourceById)) {
|
|
196
|
-
if (sourceRecord
|
|
197
|
+
if (sourceRecord == null) {
|
|
197
198
|
targetRecordById[id] = null;
|
|
198
199
|
continue;
|
|
199
200
|
}
|
|
@@ -355,7 +356,7 @@ function reexecuteUpdatesAndMergeData(
|
|
|
355
356
|
// reflects whatever replaced the optimistic layer
|
|
356
357
|
newMergedData: StoreLayerData,
|
|
357
358
|
): void {
|
|
358
|
-
while (storeLayer
|
|
359
|
+
while (storeLayer != null) {
|
|
359
360
|
mergeDataLayer(oldMergedData, storeLayer.data);
|
|
360
361
|
switch (storeLayer.kind) {
|
|
361
362
|
case 'OptimisticNetworkResponseStoreLayer':
|
|
@@ -394,7 +395,7 @@ function setChildOfNode<TStoreLayer extends StoreLayer>(
|
|
|
394
395
|
newChildStoreLayer: TStoreLayer['childStoreLayer'],
|
|
395
396
|
) {
|
|
396
397
|
storeLayerToModify.childStoreLayer = newChildStoreLayer;
|
|
397
|
-
if (newChildStoreLayer
|
|
398
|
+
if (newChildStoreLayer != null) {
|
|
398
399
|
newChildStoreLayer.parentStoreLayer = storeLayerToModify;
|
|
399
400
|
} else {
|
|
400
401
|
environment.store = storeLayerToModify;
|
|
@@ -427,7 +428,7 @@ export function revertOptimisticStoreLayerAndMaybeReplace(
|
|
|
427
428
|
|
|
428
429
|
let newMergedData = {};
|
|
429
430
|
let childNode = optimisticNode.childStoreLayer;
|
|
430
|
-
if (normalizeData
|
|
431
|
+
if (normalizeData != null) {
|
|
431
432
|
const networkResponseStoreLayer: NetworkResponseStoreLayer = {
|
|
432
433
|
kind: 'NetworkResponseStoreLayer',
|
|
433
434
|
data: {},
|
package/src/core/read.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CleanupFn,
|
|
1
|
+
import type { CleanupFn, ItemCleanupPair } from '@isograph/disposable-types';
|
|
2
2
|
import {
|
|
3
3
|
getParentRecordKey,
|
|
4
4
|
insertEmptySetIfMissing,
|
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
} from './cache';
|
|
8
8
|
import { FetchOptions } from './check';
|
|
9
9
|
import { getOrCreateCachedComponent } from './componentCache';
|
|
10
|
-
import {
|
|
10
|
+
import type {
|
|
11
11
|
IsographEntrypoint,
|
|
12
|
+
ReaderWithRefetchQueries,
|
|
12
13
|
RefetchQueryNormalizationArtifactWrapper,
|
|
13
|
-
type ReaderWithRefetchQueries,
|
|
14
14
|
} from './entrypoint';
|
|
15
|
-
import {
|
|
15
|
+
import type {
|
|
16
16
|
ExtractData,
|
|
17
17
|
FragmentReference,
|
|
18
|
+
UnknownTReadFromStore,
|
|
18
19
|
Variables,
|
|
19
|
-
type UnknownTReadFromStore,
|
|
20
20
|
} from './FragmentReference';
|
|
21
21
|
import {
|
|
22
22
|
assertLink,
|
|
@@ -38,17 +38,17 @@ import {
|
|
|
38
38
|
wrapPromise,
|
|
39
39
|
wrapResolvedValue,
|
|
40
40
|
} from './PromiseWrapper';
|
|
41
|
-
import {
|
|
41
|
+
import type {
|
|
42
|
+
LoadablySelectedField,
|
|
42
43
|
ReaderAst,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
type ReaderScalarField,
|
|
44
|
+
ReaderClientPointer,
|
|
45
|
+
ReaderImperativelyLoadedField,
|
|
46
|
+
ReaderLinkedField,
|
|
47
|
+
ReaderNonLoadableResolverField,
|
|
48
|
+
ReaderScalarField,
|
|
49
49
|
} from './reader';
|
|
50
50
|
import { getOrCreateCachedStartUpdate } from './startUpdate';
|
|
51
|
-
import { Arguments } from './util';
|
|
51
|
+
import type { Arguments } from './util';
|
|
52
52
|
|
|
53
53
|
export type WithEncounteredRecords<T> = {
|
|
54
54
|
readonly encounteredRecords: EncounteredIds;
|
|
@@ -164,7 +164,7 @@ function readData<TReadFromStore>(
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
if (storeRecord
|
|
167
|
+
if (storeRecord == null) {
|
|
168
168
|
return {
|
|
169
169
|
kind: 'Success',
|
|
170
170
|
data: null as any,
|
|
@@ -736,7 +736,7 @@ export function readLinkedFieldData(
|
|
|
736
736
|
JSON.stringify(item),
|
|
737
737
|
recordLink: root,
|
|
738
738
|
};
|
|
739
|
-
} else if (link
|
|
739
|
+
} else if (link == null) {
|
|
740
740
|
results.push(null);
|
|
741
741
|
continue;
|
|
742
742
|
}
|
|
@@ -827,7 +827,7 @@ export function readLinkedFieldData(
|
|
|
827
827
|
} else {
|
|
828
828
|
link = altLink;
|
|
829
829
|
}
|
|
830
|
-
} else if (link
|
|
830
|
+
} else if (link == null) {
|
|
831
831
|
return {
|
|
832
832
|
kind: 'Success',
|
|
833
833
|
data: null,
|
|
@@ -869,7 +869,7 @@ export function readLinkedFieldData(
|
|
|
869
869
|
function isClientPointer(
|
|
870
870
|
field: ReaderLinkedField,
|
|
871
871
|
): field is ReaderClientPointer {
|
|
872
|
-
return field.refetchQueryIndex
|
|
872
|
+
return field.refetchQueryIndex != null;
|
|
873
873
|
}
|
|
874
874
|
|
|
875
875
|
export function readClientPointerData(
|
package/src/core/startUpdate.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
callSubscriptions,
|
|
3
2
|
getParentRecordKey,
|
|
4
3
|
insertEmptySetIfMissing,
|
|
5
4
|
type EncounteredIds,
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
type ReadDataResultSuccess,
|
|
38
37
|
} from './read';
|
|
39
38
|
import type { ReaderAst } from './reader';
|
|
39
|
+
import { callSubscriptions } from './subscribe';
|
|
40
40
|
|
|
41
41
|
export function getOrCreateCachedStartUpdate<
|
|
42
42
|
TReadFromStore extends UnknownTReadFromStore,
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
|
|
2
|
+
import type { EncounteredIds } from './cache';
|
|
3
|
+
import type {
|
|
4
|
+
FragmentReference,
|
|
5
|
+
UnknownTReadFromStore,
|
|
6
|
+
} from './FragmentReference';
|
|
7
|
+
import type {
|
|
8
|
+
FragmentSubscription,
|
|
9
|
+
IsographEnvironment,
|
|
10
|
+
} from './IsographEnvironment';
|
|
11
|
+
import { logMessage } from './logging';
|
|
12
|
+
import { type WithEncounteredRecords, readButDoNotEvaluate } from './read';
|
|
13
|
+
import type { ReaderAst } from './reader';
|
|
14
|
+
|
|
15
|
+
export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
|
16
|
+
environment: IsographEnvironment,
|
|
17
|
+
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
|
18
|
+
fragmentReference: FragmentReference<TReadFromStore, any>,
|
|
19
|
+
callback: (
|
|
20
|
+
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
|
21
|
+
) => void,
|
|
22
|
+
readerAst: ReaderAst<TReadFromStore>,
|
|
23
|
+
): () => void {
|
|
24
|
+
const fragmentSubscription: FragmentSubscription<TReadFromStore> = {
|
|
25
|
+
kind: 'FragmentSubscription',
|
|
26
|
+
callback,
|
|
27
|
+
encounteredDataAndRecords,
|
|
28
|
+
fragmentReference,
|
|
29
|
+
readerAst,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// subscribe is called in an effect. (We should actually subscribe during the
|
|
33
|
+
// initial render.) Because it's called in an effect, we might have missed some
|
|
34
|
+
// changes since the initial render! So, at this point, we re-read and call the
|
|
35
|
+
// subscription (i.e. re-render) if the fragment data has changed.
|
|
36
|
+
callSubscriptionIfDataChanged(environment, fragmentSubscription);
|
|
37
|
+
|
|
38
|
+
environment.subscriptions.add(fragmentSubscription);
|
|
39
|
+
return () => environment.subscriptions.delete(fragmentSubscription);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Calls to readButDoNotEvaluate can suspend (i.e. throw a promise).
|
|
43
|
+
// Maybe in the future, they will be able to throw errors.
|
|
44
|
+
//
|
|
45
|
+
// That's probably okay to ignore. We don't, however, want to prevent
|
|
46
|
+
// updating other subscriptions if one subscription had missing data.
|
|
47
|
+
function logAnyError(
|
|
48
|
+
environment: IsographEnvironment,
|
|
49
|
+
context: any,
|
|
50
|
+
f: () => void,
|
|
51
|
+
) {
|
|
52
|
+
try {
|
|
53
|
+
f();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
logMessage(environment, () => ({
|
|
56
|
+
kind: 'ErrorEncounteredInWithErrorHandling',
|
|
57
|
+
error: e,
|
|
58
|
+
context,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function callSubscriptions(
|
|
64
|
+
environment: IsographEnvironment,
|
|
65
|
+
recordsEncounteredWhenNormalizing: EncounteredIds,
|
|
66
|
+
) {
|
|
67
|
+
environment.subscriptions.forEach((subscription) =>
|
|
68
|
+
logAnyError(environment, { situation: 'calling subscriptions' }, () => {
|
|
69
|
+
switch (subscription.kind) {
|
|
70
|
+
case 'FragmentSubscription': {
|
|
71
|
+
// TODO if there are multiple components subscribed to the same
|
|
72
|
+
// fragment, we will call readButNotEvaluate multiple times. We
|
|
73
|
+
// should fix that.
|
|
74
|
+
if (
|
|
75
|
+
hasOverlappingIds(
|
|
76
|
+
recordsEncounteredWhenNormalizing,
|
|
77
|
+
subscription.encounteredDataAndRecords.encounteredRecords,
|
|
78
|
+
)
|
|
79
|
+
) {
|
|
80
|
+
callSubscriptionIfDataChanged(environment, subscription);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
case 'AnyRecords': {
|
|
85
|
+
logAnyError(
|
|
86
|
+
environment,
|
|
87
|
+
{ situation: 'calling AnyRecords callback' },
|
|
88
|
+
() => subscription.callback(),
|
|
89
|
+
);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case 'AnyChangesToRecord': {
|
|
93
|
+
if (
|
|
94
|
+
recordsEncounteredWhenNormalizing
|
|
95
|
+
.get(subscription.recordLink.__typename)
|
|
96
|
+
?.has(subscription.recordLink.__link) != null
|
|
97
|
+
) {
|
|
98
|
+
logAnyError(
|
|
99
|
+
environment,
|
|
100
|
+
{ situation: 'calling AnyChangesToRecord callback' },
|
|
101
|
+
() => subscription.callback(),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
default: {
|
|
107
|
+
// Ensure we have covered all variants
|
|
108
|
+
const _: never = subscription;
|
|
109
|
+
_;
|
|
110
|
+
throw new Error('Unexpected case');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function callSubscriptionIfDataChanged<
|
|
118
|
+
TReadFromStore extends UnknownTReadFromStore,
|
|
119
|
+
>(
|
|
120
|
+
environment: IsographEnvironment,
|
|
121
|
+
subscription: FragmentSubscription<TReadFromStore>,
|
|
122
|
+
) {
|
|
123
|
+
const newEncounteredDataAndRecords = readButDoNotEvaluate(
|
|
124
|
+
environment,
|
|
125
|
+
subscription.fragmentReference,
|
|
126
|
+
// Is this wrong?
|
|
127
|
+
// Reasons to think no:
|
|
128
|
+
// - we are only updating the read-out value, and the network
|
|
129
|
+
// options only affect whether we throw.
|
|
130
|
+
// - the component will re-render, and re-throw on its own, anyway.
|
|
131
|
+
//
|
|
132
|
+
// Reasons to think not:
|
|
133
|
+
// - it seems more efficient to suspend here and not update state,
|
|
134
|
+
// if we expect that the component will just throw anyway
|
|
135
|
+
// - consistency
|
|
136
|
+
// - it's also weird, this is called from makeNetworkRequest, where
|
|
137
|
+
// we don't currently pass network request options
|
|
138
|
+
{
|
|
139
|
+
suspendIfInFlight: false,
|
|
140
|
+
throwOnNetworkError: false,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const mergedItem = mergeObjectsUsingReaderAst(
|
|
145
|
+
subscription.readerAst,
|
|
146
|
+
subscription.encounteredDataAndRecords.item,
|
|
147
|
+
newEncounteredDataAndRecords.item,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
logMessage(environment, () => ({
|
|
151
|
+
kind: 'DeepEqualityCheck',
|
|
152
|
+
fragmentReference: subscription.fragmentReference,
|
|
153
|
+
old: subscription.encounteredDataAndRecords.item,
|
|
154
|
+
new: newEncounteredDataAndRecords.item,
|
|
155
|
+
deeplyEqual: mergedItem === subscription.encounteredDataAndRecords.item,
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
if (mergedItem !== subscription.encounteredDataAndRecords.item) {
|
|
159
|
+
logAnyError(
|
|
160
|
+
environment,
|
|
161
|
+
{ situation: 'calling FragmentSubscription callback' },
|
|
162
|
+
() => {
|
|
163
|
+
subscription.callback(newEncounteredDataAndRecords);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
subscription.encounteredDataAndRecords = newEncounteredDataAndRecords;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function hasOverlappingIds(
|
|
171
|
+
ids1: EncounteredIds,
|
|
172
|
+
ids2: EncounteredIds,
|
|
173
|
+
): boolean {
|
|
174
|
+
for (const [typeName, set1] of ids1.entries()) {
|
|
175
|
+
const set2 = ids2.get(typeName);
|
|
176
|
+
if (set2 === undefined) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isNotDisjointFrom(set1, set2)) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// TODO use a polyfill library
|
|
188
|
+
function isNotDisjointFrom<T>(set1: Set<T>, set2: Set<T>): boolean {
|
|
189
|
+
for (const id of set1) {
|
|
190
|
+
if (set2.has(id)) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
package/src/core/util.ts
CHANGED
|
@@ -31,3 +31,29 @@ export type ArgumentValue =
|
|
|
31
31
|
readonly kind: 'Object';
|
|
32
32
|
readonly value: Arguments;
|
|
33
33
|
};
|
|
34
|
+
|
|
35
|
+
export function isArray(value: unknown): value is readonly unknown[] {
|
|
36
|
+
return Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a copy of the provided value, ensuring any nested objects have their
|
|
41
|
+
* keys sorted such that equivalent values would have identical JSON.stringify
|
|
42
|
+
* results.
|
|
43
|
+
*/
|
|
44
|
+
export function stableCopy<T>(value: T): T {
|
|
45
|
+
if (value == null || typeof value !== 'object') {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (isArray(value)) {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
return value.map(stableCopy);
|
|
51
|
+
}
|
|
52
|
+
const keys = Object.keys(value).sort();
|
|
53
|
+
const stable: { [index: string]: any } = {};
|
|
54
|
+
for (let i = 0; i < keys.length; i++) {
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
stable[keys[i]] = stableCopy(value[keys[i]]);
|
|
57
|
+
}
|
|
58
|
+
return stable as any;
|
|
59
|
+
}
|