@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.
Files changed (105) hide show
  1. package/.turbo/turbo-compile-libs.log +1 -1
  2. package/dist/core/FragmentReference.d.ts +1 -1
  3. package/dist/core/FragmentReference.d.ts.map +1 -1
  4. package/dist/core/FragmentReference.js +2 -2
  5. package/dist/core/IsographEnvironment.d.ts +7 -5
  6. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  7. package/dist/core/IsographEnvironment.js +5 -4
  8. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
  9. package/dist/core/areEqualWithDeepComparison.js +2 -5
  10. package/dist/core/cache.d.ts +5 -18
  11. package/dist/core/cache.d.ts.map +1 -1
  12. package/dist/core/cache.js +10 -222
  13. package/dist/core/check.js +5 -5
  14. package/dist/core/componentCache.d.ts +2 -2
  15. package/dist/core/componentCache.d.ts.map +1 -1
  16. package/dist/core/componentCache.js +1 -26
  17. package/dist/core/entrypoint.d.ts +2 -2
  18. package/dist/core/entrypoint.d.ts.map +1 -1
  19. package/dist/core/garbageCollection.d.ts +2 -2
  20. package/dist/core/garbageCollection.d.ts.map +1 -1
  21. package/dist/core/garbageCollection.js +3 -3
  22. package/dist/core/getOrCreateCacheForArtifact.d.ts +8 -0
  23. package/dist/core/getOrCreateCacheForArtifact.d.ts.map +1 -0
  24. package/dist/core/getOrCreateCacheForArtifact.js +40 -0
  25. package/dist/core/logging.d.ts +8 -8
  26. package/dist/core/logging.d.ts.map +1 -1
  27. package/dist/core/makeNetworkRequest.d.ts +3 -3
  28. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  29. package/dist/core/makeNetworkRequest.js +3 -2
  30. package/dist/core/optimisticProxy.d.ts.map +1 -1
  31. package/dist/core/optimisticProxy.js +24 -22
  32. package/dist/core/read.d.ts +3 -3
  33. package/dist/core/read.d.ts.map +1 -1
  34. package/dist/core/read.js +4 -4
  35. package/dist/core/startUpdate.d.ts.map +1 -1
  36. package/dist/core/startUpdate.js +2 -1
  37. package/dist/core/subscribe.d.ts +8 -0
  38. package/dist/core/subscribe.d.ts.map +1 -0
  39. package/dist/core/subscribe.js +127 -0
  40. package/dist/core/util.d.ts +7 -0
  41. package/dist/core/util.d.ts.map +1 -1
  42. package/dist/core/util.js +26 -0
  43. package/dist/core/writeData.d.ts +7 -0
  44. package/dist/core/writeData.d.ts.map +1 -0
  45. package/dist/core/writeData.js +36 -0
  46. package/dist/index.d.ts +5 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +8 -5
  49. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +3 -3
  50. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
  51. package/dist/loadable-hooks/useConnectionSpecPagination.js +3 -3
  52. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +3 -3
  53. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
  54. package/dist/loadable-hooks/useSkipLimitPagination.js +3 -3
  55. package/dist/react/createIsographEnvironment.d.ts +4 -0
  56. package/dist/react/createIsographEnvironment.d.ts.map +1 -0
  57. package/dist/react/createIsographEnvironment.js +8 -0
  58. package/dist/react/maybeUnwrapNetworkRequest.d.ts +4 -0
  59. package/dist/react/maybeUnwrapNetworkRequest.d.ts.map +1 -0
  60. package/dist/react/maybeUnwrapNetworkRequest.js +14 -0
  61. package/dist/react/useLazyReference.d.ts.map +1 -1
  62. package/dist/react/useLazyReference.js +2 -2
  63. package/dist/react/useReadAndSubscribe.d.ts +4 -2
  64. package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
  65. package/dist/react/useReadAndSubscribe.js +31 -2
  66. package/dist/react/useRerenderOnChange.d.ts +2 -2
  67. package/dist/react/useRerenderOnChange.d.ts.map +1 -1
  68. package/dist/react/useRerenderOnChange.js +2 -2
  69. package/dist/react/useResult.d.ts +2 -4
  70. package/dist/react/useResult.d.ts.map +1 -1
  71. package/dist/react/useResult.js +3 -13
  72. package/package.json +4 -4
  73. package/src/core/FragmentReference.ts +2 -2
  74. package/src/core/IsographEnvironment.ts +26 -10
  75. package/src/core/areEqualWithDeepComparison.ts +2 -6
  76. package/src/core/cache.ts +18 -364
  77. package/src/core/check.ts +5 -5
  78. package/src/core/componentCache.ts +8 -43
  79. package/src/core/entrypoint.ts +2 -2
  80. package/src/core/garbageCollection.ts +8 -8
  81. package/src/core/getOrCreateCacheForArtifact.ts +86 -0
  82. package/src/core/logging.ts +10 -10
  83. package/src/core/makeNetworkRequest.ts +8 -8
  84. package/src/core/optimisticProxy.ts +27 -26
  85. package/src/core/read.ts +17 -17
  86. package/src/core/startUpdate.ts +1 -1
  87. package/src/core/subscribe.ts +195 -0
  88. package/src/core/util.ts +26 -0
  89. package/src/core/writeData.ts +79 -0
  90. package/src/index.ts +3 -4
  91. package/src/loadable-hooks/useConnectionSpecPagination.ts +6 -6
  92. package/src/loadable-hooks/useSkipLimitPagination.ts +6 -6
  93. package/src/react/createIsographEnvironment.ts +23 -0
  94. package/src/react/maybeUnwrapNetworkRequest.ts +17 -0
  95. package/src/react/useLazyReference.ts +2 -4
  96. package/src/react/useReadAndSubscribe.ts +53 -5
  97. package/src/react/useRerenderOnChange.ts +3 -3
  98. package/src/react/useResult.ts +6 -24
  99. package/src/tests/garbageCollection.test.ts +3 -6
  100. package/src/tests/meNameSuccessor.ts +1 -1
  101. package/src/tests/nodeQuery.ts +1 -1
  102. package/src/tests/normalizeData.test.ts +5 -3
  103. package/src/tests/optimisticProxy.test.ts +7 -5
  104. package/src/tests/startUpdate.test.ts +5 -7
  105. package/vitest.config.ts +5 -0
@@ -1,11 +1,11 @@
1
1
  import { getParentRecordKey } from './cache';
2
- import { NormalizationAstNodes, type NormalizationAst } from './entrypoint';
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 == undefined || retainedTypeIds.size == 0) {
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 !== null
178
+ selection.concreteType != null
179
179
  ? dataLayer[selection.concreteType]
180
180
  : null;
181
181
 
182
- if (typeStore == null && selection.concreteType !== null) {
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
+ }
@@ -1,19 +1,19 @@
1
- import { CleanupFn } from '@isograph/disposable-types';
2
- import { NetworkResponseObject, type EncounteredIds } from './cache';
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
- type NormalizationAstNodes,
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
- type StoreLink,
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
- type FragmentReference,
20
- type UnknownTReadFromStore,
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
- callSubscriptions,
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 !== null) {
29
+ while (startNode != null) {
33
30
  const storeRecord = startNode.data[link.__typename]?.[link.__link];
34
- if (storeRecord === null) {
35
- return null;
31
+ if (storeRecord === undefined) {
32
+ startNode = startNode.parentStoreLayer;
33
+ continue;
36
34
  }
37
- if (storeRecord != null) {
38
- return getMutableStoreRecordProxy(startNode, link);
35
+
36
+ if (storeRecord == null) {
37
+ return null;
39
38
  }
40
- startNode = startNode.parentStoreLayer;
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 !== null) {
55
+ while (currentStoreLayer != null) {
56
56
  const storeRecord =
57
57
  currentStoreLayer.data[link.__typename]?.[link.__link];
58
- if (storeRecord === null) {
59
- return undefined;
60
- }
61
- if (storeRecord != null) {
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 !== null) {
72
+ while (currentStoreLayer != null) {
73
73
  const storeRecord =
74
74
  currentStoreLayer.data[link.__typename]?.[link.__link];
75
- if (storeRecord === null) {
76
- return false;
77
- }
78
- if (storeRecord != null) {
75
+ if (storeRecord !== undefined) {
76
+ if (storeRecord == null) {
77
+ return false;
78
+ }
79
+
79
80
  const value = Reflect.has(storeRecord, propertyName);
80
- if (value !== undefined) {
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 === null) {
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 !== null) {
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 !== null) {
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 !== null) {
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, type ItemCleanupPair } from '@isograph/disposable-types';
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
- type LoadablySelectedField,
44
- type ReaderClientPointer,
45
- type ReaderImperativelyLoadedField,
46
- type ReaderLinkedField,
47
- type ReaderNonLoadableResolverField,
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 === null) {
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 === null) {
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 === null) {
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 !== null;
872
+ return field.refetchQueryIndex != null;
873
873
  }
874
874
 
875
875
  export function readClientPointerData(
@@ -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
+ }