@isograph/react 0.0.0-main-6b81bb01 → 0.0.0-main-d629a5be

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 (34) hide show
  1. package/dist/IsographEnvironment.d.ts +6 -1
  2. package/dist/IsographEnvironment.js +11 -1
  3. package/dist/cache.d.ts +2 -2
  4. package/dist/cache.js +66 -12
  5. package/dist/componentCache.d.ts +1 -1
  6. package/dist/componentCache.js +11 -6
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +3 -1
  9. package/dist/read.d.ts +7 -3
  10. package/dist/read.js +40 -17
  11. package/dist/useRerenderWhenEncounteredRecordChanges.d.ts +2 -0
  12. package/dist/useRerenderWhenEncounteredRecordChanges.js +14 -0
  13. package/dist/useResult.js +4 -9
  14. package/package.json +3 -3
  15. package/src/IsographEnvironment.tsx +17 -1
  16. package/src/cache.ts +78 -13
  17. package/src/componentCache.ts +24 -11
  18. package/src/index.ts +1 -0
  19. package/src/read.ts +55 -18
  20. package/src/tests/__isograph/Query/meName/entrypoint.ts +2 -1
  21. package/src/tests/__isograph/Query/meName/output_type.ts +4 -0
  22. package/src/tests/__isograph/Query/meName/param_type.ts +6 -0
  23. package/src/tests/__isograph/Query/meName/reader.ts +3 -10
  24. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +2 -1
  25. package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +4 -0
  26. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +11 -0
  27. package/src/tests/__isograph/Query/meNameSuccessor/reader.ts +3 -15
  28. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +2 -1
  29. package/src/tests/__isograph/Query/nodeField/output_type.ts +4 -0
  30. package/src/tests/__isograph/Query/nodeField/param_type.ts +6 -0
  31. package/src/tests/__isograph/Query/nodeField/reader.ts +3 -10
  32. package/src/tests/__isograph/iso.ts +6 -6
  33. package/src/useRerenderWhenEncounteredRecordChanges.ts +15 -0
  34. package/src/useResult.ts +8 -9
@@ -10,7 +10,11 @@ type ComponentCache = {
10
10
  };
11
11
  };
12
12
  };
13
- export type Subscriptions = Set<() => void>;
13
+ type CallbackAndRecords = {
14
+ callback: () => void;
15
+ records: Set<DataId> | null;
16
+ };
17
+ type Subscriptions = Set<CallbackAndRecords>;
14
18
  type SuspenseCache = {
15
19
  [index: string]: ParentCache<any>;
16
20
  };
@@ -55,4 +59,5 @@ export declare function defaultMissingFieldHandler(_storeRecord: StoreRecord, _r
55
59
  [index: string]: any;
56
60
  } | null): Link | undefined;
57
61
  export declare function assertLink(link: DataTypeValue): Link | null | undefined;
62
+ export declare function getLink(maybeLink: DataTypeValue): Link | null;
58
63
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertLink = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = void 0;
3
+ exports.getLink = exports.assertLink = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = void 0;
4
4
  exports.ROOT_ID = '__ROOT';
5
5
  const DEFAULT_GC_BUFFER_SIZE = 10;
6
6
  function createIsographEnvironment(store, networkFunction, missingFieldHandler) {
@@ -47,3 +47,13 @@ function assertLink(link) {
47
47
  throw new Error('Invalid link');
48
48
  }
49
49
  exports.assertLink = assertLink;
50
+ function getLink(maybeLink) {
51
+ if (maybeLink != null &&
52
+ typeof maybeLink === 'object' &&
53
+ // @ts-expect-error this is safe
54
+ maybeLink.__link != null) {
55
+ return maybeLink;
56
+ }
57
+ return null;
58
+ }
59
+ exports.getLink = getLink;
package/dist/cache.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ItemCleanupPair, ParentCache } from '@isograph/react-disposable-state';
2
2
  import { PromiseWrapper } from './PromiseWrapper';
3
- import { type IsographEnvironment } from './IsographEnvironment';
3
+ import { DataId, type IsographEnvironment } from './IsographEnvironment';
4
4
  import { IsographEntrypoint, NormalizationLinkedField, NormalizationScalarField } from './entrypoint';
5
5
  import { ReaderLinkedField, ReaderScalarField } from './reader';
6
6
  declare global {
@@ -17,7 +17,7 @@ export declare function stableCopy<T>(value: T): T;
17
17
  type IsoResolver = IsographEntrypoint<any, any>;
18
18
  export declare function getOrCreateCacheForArtifact<TReadFromStore extends Object, TClientFieldValue>(environment: IsographEnvironment, artifact: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: object): ParentCache<PromiseWrapper<TClientFieldValue>>;
19
19
  export declare function makeNetworkRequest<T>(environment: IsographEnvironment, artifact: IsoResolver, variables: object): ItemCleanupPair<PromiseWrapper<T>>;
20
- export declare function subscribe(environment: IsographEnvironment, callback: () => void): () => void;
20
+ export declare function subscribe(environment: IsographEnvironment, encounteredRecords: Set<DataId> | null, callback: () => void): () => void;
21
21
  export declare function onNextChange(environment: IsographEnvironment): Promise<void>;
22
22
  export declare function getParentRecordKey(astNode: NormalizationLinkedField | NormalizationScalarField | ReaderLinkedField | ReaderScalarField, variables: {
23
23
  [index: string]: string;
package/dist/cache.js CHANGED
@@ -100,45 +100,74 @@ function normalizeData(environment, normalizationAst, networkResponse, variables
100
100
  }
101
101
  normalizeDataIntoRecord(environment, normalizationAst, networkResponse, environment.store.__ROOT, IsographEnvironment_1.ROOT_ID, variables, nestedRefetchQueries, encounteredIds);
102
102
  if (typeof window !== 'undefined' && window.__LOG) {
103
- console.log('after normalization', { store: environment.store });
103
+ console.log('after normalization', {
104
+ store: environment.store,
105
+ encounteredIds,
106
+ });
104
107
  }
105
- callSubscriptions(environment);
108
+ callSubscriptions(environment, encounteredIds);
106
109
  return encounteredIds;
107
110
  }
108
- function subscribe(environment, callback) {
109
- environment.subscriptions.add(callback);
110
- return () => environment.subscriptions.delete(callback);
111
+ function subscribe(environment, encounteredRecords, callback) {
112
+ const callbackAndRecords = {
113
+ callback,
114
+ records: encounteredRecords,
115
+ };
116
+ environment.subscriptions.add(callbackAndRecords);
117
+ return () => environment.subscriptions.delete(callbackAndRecords);
111
118
  }
112
119
  exports.subscribe = subscribe;
113
120
  function onNextChange(environment) {
114
121
  return new Promise((resolve) => {
115
- const unsubscribe = subscribe(environment, () => {
122
+ const unsubscribe = subscribe(environment, null, () => {
116
123
  unsubscribe();
117
124
  resolve();
118
125
  });
119
126
  });
120
127
  }
121
128
  exports.onNextChange = onNextChange;
122
- function callSubscriptions(environment) {
123
- environment.subscriptions.forEach((callback) => callback());
129
+ function callSubscriptions(environment, recordsEncounteredWhenNormalizing) {
130
+ environment.subscriptions.forEach(({ callback, records }) => {
131
+ if (records === null) {
132
+ callback();
133
+ }
134
+ else if (hasOverlappingIds(recordsEncounteredWhenNormalizing, records)) {
135
+ callback();
136
+ }
137
+ });
138
+ }
139
+ function hasOverlappingIds(set1, set2) {
140
+ for (const id of set1) {
141
+ if (set2.has(id)) {
142
+ return true;
143
+ }
144
+ }
145
+ return false;
124
146
  }
125
147
  /**
126
148
  * Mutate targetParentRecord according to the normalizationAst and networkResponseParentRecord.
127
149
  */
128
150
  function normalizeDataIntoRecord(environment, normalizationAst, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds) {
129
- mutableEncounteredIds.add(targetParentRecordId);
151
+ let recordHasBeenUpdated = false;
130
152
  for (const normalizationNode of normalizationAst) {
131
153
  switch (normalizationNode.kind) {
132
154
  case 'Scalar': {
133
- normalizeScalarField(normalizationNode, networkResponseParentRecord, targetParentRecord, variables);
155
+ const scalarFieldResultedInChange = normalizeScalarField(normalizationNode, networkResponseParentRecord, targetParentRecord, variables);
156
+ recordHasBeenUpdated =
157
+ recordHasBeenUpdated || scalarFieldResultedInChange;
134
158
  break;
135
159
  }
136
160
  case 'Linked': {
137
- normalizeLinkedField(environment, normalizationNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds);
161
+ const linkedFieldResultedInChange = normalizeLinkedField(environment, normalizationNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds);
162
+ recordHasBeenUpdated =
163
+ recordHasBeenUpdated || linkedFieldResultedInChange;
138
164
  break;
139
165
  }
140
166
  }
141
167
  }
168
+ if (recordHasBeenUpdated) {
169
+ mutableEncounteredIds.add(targetParentRecordId);
170
+ }
142
171
  }
143
172
  function normalizeScalarField(astNode, networkResponseParentRecord, targetStoreRecord, variables) {
144
173
  const networkResponseKey = getNetworkResponseKey(astNode);
@@ -146,7 +175,9 @@ function normalizeScalarField(astNode, networkResponseParentRecord, targetStoreR
146
175
  const parentRecordKey = getParentRecordKey(astNode, variables);
147
176
  if (networkResponseData == null ||
148
177
  isScalarOrEmptyArray(networkResponseData)) {
178
+ const existingValue = targetStoreRecord[parentRecordKey];
149
179
  targetStoreRecord[parentRecordKey] = networkResponseData;
180
+ return existingValue !== networkResponseData;
150
181
  }
151
182
  else {
152
183
  throw new Error('Unexpected object array when normalizing scalar');
@@ -159,9 +190,10 @@ function normalizeLinkedField(environment, astNode, networkResponseParentRecord,
159
190
  const networkResponseKey = getNetworkResponseKey(astNode);
160
191
  const networkResponseData = networkResponseParentRecord[networkResponseKey];
161
192
  const parentRecordKey = getParentRecordKey(astNode, variables);
193
+ const existingValue = targetParentRecord[parentRecordKey];
162
194
  if (networkResponseData == null) {
163
195
  targetParentRecord[parentRecordKey] = null;
164
- return;
196
+ return existingValue !== null;
165
197
  }
166
198
  if (isScalarButNotEmptyArray(networkResponseData)) {
167
199
  throw new Error('Unexpected scalar network response when normalizing a linked field');
@@ -175,12 +207,34 @@ function normalizeLinkedField(environment, astNode, networkResponseParentRecord,
175
207
  dataIds.push({ __link: newStoreRecordId });
176
208
  }
177
209
  targetParentRecord[parentRecordKey] = dataIds;
210
+ return !dataIdsAreTheSame(existingValue, dataIds);
178
211
  }
179
212
  else {
180
213
  const newStoreRecordId = normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, null, nestedRefetchQueries, mutableEncounteredIds);
181
214
  targetParentRecord[parentRecordKey] = {
182
215
  __link: newStoreRecordId,
183
216
  };
217
+ const link = (0, IsographEnvironment_1.getLink)(existingValue);
218
+ return (link === null || link === void 0 ? void 0 : link.__link) !== newStoreRecordId;
219
+ }
220
+ }
221
+ function dataIdsAreTheSame(existingValue, newDataIds) {
222
+ if (Array.isArray(existingValue)) {
223
+ if (newDataIds.length !== existingValue.length) {
224
+ return false;
225
+ }
226
+ for (let i = 0; i < newDataIds.length; i++) {
227
+ const maybeLink = (0, IsographEnvironment_1.getLink)(existingValue[i]);
228
+ if (maybeLink !== null) {
229
+ if (newDataIds[i].__link !== maybeLink.__link) {
230
+ return false;
231
+ }
232
+ }
233
+ }
234
+ return true;
235
+ }
236
+ else {
237
+ return false;
184
238
  }
185
239
  }
186
240
  function normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, index, nestedRefetchQueries, mutableEncounteredIds) {
@@ -2,6 +2,6 @@
2
2
  import { RefetchQueryArtifactWrapper } from './entrypoint';
3
3
  import { IsographEnvironment, DataId } from './IsographEnvironment';
4
4
  import { ReaderArtifact } from './reader';
5
- export declare function getOrCreateCachedComponent(environment: IsographEnvironment, root: DataId, componentName: string, readerArtifact: ReaderArtifact<any, any>, variables: {
5
+ export declare function getOrCreateCachedComponent(environment: IsographEnvironment, rootId: DataId, componentName: string, readerArtifact: ReaderArtifact<any, any>, variables: {
6
6
  [key: string]: string;
7
7
  }, resolverRefetchQueries: RefetchQueryArtifactWrapper[]): import("react").FC<any>;
@@ -3,27 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getOrCreateCachedComponent = void 0;
4
4
  const cache_1 = require("./cache");
5
5
  const read_1 = require("./read");
6
- function getOrCreateCachedComponent(environment, root, componentName, readerArtifact, variables, resolverRefetchQueries) {
6
+ const useRerenderWhenEncounteredRecordChanges_1 = require("./useRerenderWhenEncounteredRecordChanges");
7
+ function getOrCreateCachedComponent(environment, rootId, componentName, readerArtifact, variables, resolverRefetchQueries) {
7
8
  var _a, _b, _c;
8
9
  const cachedComponentsById = environment.componentCache;
9
10
  const stringifiedArgs = JSON.stringify((0, cache_1.stableCopy)(variables));
10
- cachedComponentsById[root] = (_a = cachedComponentsById[root]) !== null && _a !== void 0 ? _a : {};
11
- const componentsByName = cachedComponentsById[root];
11
+ cachedComponentsById[rootId] = (_a = cachedComponentsById[rootId]) !== null && _a !== void 0 ? _a : {};
12
+ const componentsByName = cachedComponentsById[rootId];
12
13
  componentsByName[componentName] = (_b = componentsByName[componentName]) !== null && _b !== void 0 ? _b : {};
13
14
  const byArgs = componentsByName[componentName];
14
15
  byArgs[stringifiedArgs] =
15
16
  (_c = byArgs[stringifiedArgs]) !== null && _c !== void 0 ? _c : (() => {
16
17
  function Component(additionalRuntimeProps) {
17
- const data = (0, read_1.readButDoNotEvaluate)(environment, {
18
+ const { item: data, encounteredRecords } = (0, read_1.readButDoNotEvaluate)(environment, {
18
19
  kind: 'FragmentReference',
19
20
  readerArtifact: readerArtifact,
20
- root,
21
+ root: rootId,
21
22
  variables,
22
23
  nestedRefetchQueries: resolverRefetchQueries,
23
24
  });
25
+ (0, useRerenderWhenEncounteredRecordChanges_1.useRerenderWhenEncounteredRecordChanges)(environment, encounteredRecords);
26
+ if (typeof window !== 'undefined' && window.__LOG) {
27
+ console.log('Component re-rendered: ' + componentName + ' ' + rootId);
28
+ }
24
29
  return readerArtifact.resolver(data, additionalRuntimeProps);
25
30
  }
26
- Component.displayName = `${componentName} (id: ${root}) @component`;
31
+ Component.displayName = `${componentName} (id: ${rootId}) @component`;
27
32
  return Component;
28
33
  })();
29
34
  return byArgs[stringifiedArgs];
package/dist/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export { useResult } from './useResult';
12
12
  export { type FragmentReference } from './FragmentReference';
13
13
  export { useLazyReference } from './useLazyReference';
14
14
  export { ExtractSecondParam, Argument, ArgumentName, ArgumentValue, Arguments, } from './util';
15
+ export { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useLazyReference = exports.useResult = exports.readButDoNotEvaluate = exports.read = exports.assertIsEntrypoint = exports.EntrypointReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.subscribe = exports.makeNetworkRequest = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
3
+ exports.useRerenderWhenEncounteredRecordChanges = exports.useLazyReference = exports.useResult = exports.readButDoNotEvaluate = exports.read = exports.assertIsEntrypoint = exports.EntrypointReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.subscribe = exports.makeNetworkRequest = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
4
4
  var garbageCollection_1 = require("./garbageCollection");
5
5
  Object.defineProperty(exports, "retainQuery", { enumerable: true, get: function () { return garbageCollection_1.retainQuery; } });
6
6
  Object.defineProperty(exports, "unretainQuery", { enumerable: true, get: function () { return garbageCollection_1.unretainQuery; } });
@@ -29,3 +29,5 @@ var useResult_1 = require("./useResult");
29
29
  Object.defineProperty(exports, "useResult", { enumerable: true, get: function () { return useResult_1.useResult; } });
30
30
  var useLazyReference_1 = require("./useLazyReference");
31
31
  Object.defineProperty(exports, "useLazyReference", { enumerable: true, get: function () { return useLazyReference_1.useLazyReference; } });
32
+ var useRerenderWhenEncounteredRecordChanges_1 = require("./useRerenderWhenEncounteredRecordChanges");
33
+ Object.defineProperty(exports, "useRerenderWhenEncounteredRecordChanges", { enumerable: true, get: function () { return useRerenderWhenEncounteredRecordChanges_1.useRerenderWhenEncounteredRecordChanges; } });
package/dist/read.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { FragmentReference } from './FragmentReference';
2
- import { IsographEnvironment } from './IsographEnvironment';
3
- export declare function read<TReadFromStore extends Object, TClientFieldValue>(environment: IsographEnvironment, fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>): TClientFieldValue;
4
- export declare function readButDoNotEvaluate<TReadFromStore extends Object>(environment: IsographEnvironment, reference: FragmentReference<TReadFromStore, unknown>): TReadFromStore;
2
+ import { DataId, IsographEnvironment } from './IsographEnvironment';
3
+ export type WithEncounteredRecords<T> = {
4
+ encounteredRecords: Set<DataId>;
5
+ item: T;
6
+ };
7
+ export declare function read<TReadFromStore extends Object, TClientFieldValue>(environment: IsographEnvironment, fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>): WithEncounteredRecords<TClientFieldValue>;
8
+ export declare function readButDoNotEvaluate<TReadFromStore extends Object>(environment: IsographEnvironment, reference: FragmentReference<TReadFromStore, unknown>): WithEncounteredRecords<TReadFromStore>;
package/dist/read.js CHANGED
@@ -8,18 +8,25 @@ function read(environment, fragmentReference) {
8
8
  var _a, _b;
9
9
  const variant = fragmentReference.readerArtifact.variant;
10
10
  if (variant.kind === 'Eager') {
11
- const data = readData(environment, fragmentReference.readerArtifact.readerAst, fragmentReference.root, (_a = fragmentReference.variables) !== null && _a !== void 0 ? _a : {}, fragmentReference.nestedRefetchQueries);
11
+ const mutableEncounteredRecords = new Set();
12
+ const data = readData(environment, fragmentReference.readerArtifact.readerAst, fragmentReference.root, (_a = fragmentReference.variables) !== null && _a !== void 0 ? _a : {}, fragmentReference.nestedRefetchQueries, mutableEncounteredRecords);
12
13
  if (data.kind === 'MissingData') {
13
14
  throw (0, cache_1.onNextChange)(environment);
14
15
  }
15
16
  else {
16
- // @ts-expect-error This not properly typed yet
17
- return fragmentReference.readerArtifact.resolver(data.data);
17
+ return {
18
+ encounteredRecords: mutableEncounteredRecords,
19
+ // @ts-expect-error This not properly typed yet
20
+ item: fragmentReference.readerArtifact.resolver(data.data),
21
+ };
18
22
  }
19
23
  }
20
24
  else if (variant.kind === 'Component') {
21
- // @ts-ignore
22
- return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.root, variant.componentName, fragmentReference.readerArtifact, (_b = fragmentReference.variables) !== null && _b !== void 0 ? _b : {}, fragmentReference.nestedRefetchQueries);
25
+ return {
26
+ // @ts-ignore
27
+ item: (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.root, variant.componentName, fragmentReference.readerArtifact, (_b = fragmentReference.variables) !== null && _b !== void 0 ? _b : {}, fragmentReference.nestedRefetchQueries),
28
+ encounteredRecords: new Set(),
29
+ };
23
30
  }
24
31
  // Why can't Typescript realize that this is unreachable??
25
32
  throw new Error('This is unreachable');
@@ -27,7 +34,8 @@ function read(environment, fragmentReference) {
27
34
  exports.read = read;
28
35
  function readButDoNotEvaluate(environment, reference) {
29
36
  var _a;
30
- const response = readData(environment, reference.readerArtifact.readerAst, reference.root, (_a = reference.variables) !== null && _a !== void 0 ? _a : {}, reference.nestedRefetchQueries);
37
+ const mutableEncounteredRecords = new Set();
38
+ const response = readData(environment, reference.readerArtifact.readerAst, reference.root, (_a = reference.variables) !== null && _a !== void 0 ? _a : {}, reference.nestedRefetchQueries, mutableEncounteredRecords);
31
39
  if (typeof window !== 'undefined' && window.__LOG) {
32
40
  console.log('done reading', { response });
33
41
  }
@@ -35,18 +43,29 @@ function readButDoNotEvaluate(environment, reference) {
35
43
  throw (0, cache_1.onNextChange)(environment);
36
44
  }
37
45
  else {
38
- return response.data;
46
+ return {
47
+ encounteredRecords: mutableEncounteredRecords,
48
+ item: response.data,
49
+ };
39
50
  }
40
51
  }
41
52
  exports.readButDoNotEvaluate = readButDoNotEvaluate;
42
- function readData(environment, ast, root, variables, nestedRefetchQueries) {
53
+ function readData(environment, ast, root, variables, nestedRefetchQueries, mutableEncounteredRecords) {
43
54
  var _a, _b, _c, _d, _e;
55
+ mutableEncounteredRecords.add(root);
44
56
  let storeRecord = environment.store[root];
45
57
  if (storeRecord === undefined) {
46
- return { kind: 'MissingData', reason: 'No record for root ' + root };
58
+ return {
59
+ kind: 'MissingData',
60
+ reason: 'No record for root ' + root,
61
+ };
47
62
  }
48
63
  if (storeRecord === null) {
49
- return { kind: 'Success', data: null };
64
+ return {
65
+ kind: 'Success',
66
+ data: null,
67
+ encounteredRecords: mutableEncounteredRecords,
68
+ };
50
69
  }
51
70
  let target = {};
52
71
  for (const field of ast) {
@@ -87,7 +106,7 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
87
106
  results.push(null);
88
107
  continue;
89
108
  }
90
- const result = readData(environment, field.selections, link.__link, variables, nestedRefetchQueries);
109
+ const result = readData(environment, field.selections, link.__link, variables, nestedRefetchQueries, mutableEncounteredRecords);
91
110
  if (result.kind === 'MissingData') {
92
111
  return {
93
112
  kind: 'MissingData',
@@ -130,7 +149,7 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
130
149
  break;
131
150
  }
132
151
  const targetId = link.__link;
133
- const data = readData(environment, field.selections, targetId, variables, nestedRefetchQueries);
152
+ const data = readData(environment, field.selections, targetId, variables, nestedRefetchQueries, mutableEncounteredRecords);
134
153
  if (data.kind === 'MissingData') {
135
154
  return {
136
155
  kind: 'MissingData',
@@ -144,7 +163,7 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
144
163
  case 'RefetchField': {
145
164
  const data = readData(environment, field.readerArtifact.readerAst, root, variables,
146
165
  // Refetch fields just read the id, and don't need refetch query artifacts
147
- []);
166
+ [], mutableEncounteredRecords);
148
167
  if (typeof window !== 'undefined' && window.__LOG) {
149
168
  console.log('refetch field data', data, field);
150
169
  }
@@ -171,8 +190,8 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
171
190
  }
172
191
  case 'MutationField': {
173
192
  const data = readData(environment, field.readerArtifact.readerAst, root, variables,
174
- // Refetch fields just read the id, and don't need refetch query artifacts
175
- []);
193
+ // Mutation don't need refetch query artifacts
194
+ [], mutableEncounteredRecords);
176
195
  if (typeof window !== 'undefined' && window.__LOG) {
177
196
  console.log('refetch field data', data, field);
178
197
  }
@@ -202,7 +221,7 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
202
221
  const resolverRefetchQueries = usedRefetchQueries.map((index) => nestedRefetchQueries[index]);
203
222
  const variant = field.readerArtifact.variant;
204
223
  if (variant.kind === 'Eager') {
205
- const data = readData(environment, field.readerArtifact.readerAst, root, variables, resolverRefetchQueries);
224
+ const data = readData(environment, field.readerArtifact.readerAst, root, variables, resolverRefetchQueries, mutableEncounteredRecords);
206
225
  if (data.kind === 'MissingData') {
207
226
  return {
208
227
  kind: 'MissingData',
@@ -222,7 +241,11 @@ function readData(environment, ast, root, variables, nestedRefetchQueries) {
222
241
  }
223
242
  }
224
243
  }
225
- return { kind: 'Success', data: target };
244
+ return {
245
+ kind: 'Success',
246
+ data: target,
247
+ encounteredRecords: mutableEncounteredRecords,
248
+ };
226
249
  }
227
250
  function filterVariables(variables, allowedVariables) {
228
251
  const result = {};
@@ -0,0 +1,2 @@
1
+ import { DataId, IsographEnvironment } from './IsographEnvironment';
2
+ export declare function useRerenderWhenEncounteredRecordChanges(environment: IsographEnvironment, encounteredRecords: Set<DataId>): void;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRerenderWhenEncounteredRecordChanges = void 0;
4
+ const react_1 = require("react");
5
+ const cache_1 = require("./cache");
6
+ function useRerenderWhenEncounteredRecordChanges(environment, encounteredRecords) {
7
+ const [, setState] = (0, react_1.useState)();
8
+ (0, react_1.useEffect)(() => {
9
+ return (0, cache_1.subscribe)(environment, encounteredRecords, () => {
10
+ return setState({});
11
+ });
12
+ }, []);
13
+ }
14
+ exports.useRerenderWhenEncounteredRecordChanges = useRerenderWhenEncounteredRecordChanges;
package/dist/useResult.js CHANGED
@@ -1,18 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useResult = void 0;
4
- const react_1 = require("react");
5
4
  const IsographEnvironmentProvider_1 = require("./IsographEnvironmentProvider");
6
- const cache_1 = require("./cache");
7
5
  const read_1 = require("./read");
6
+ const useRerenderWhenEncounteredRecordChanges_1 = require("./useRerenderWhenEncounteredRecordChanges");
8
7
  function useResult(fragmentReference) {
9
8
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
10
- const [, setState] = (0, react_1.useState)();
11
- (0, react_1.useEffect)(() => {
12
- return (0, cache_1.subscribe)(environment, () => {
13
- return setState({});
14
- });
15
- }, []);
16
- return (0, read_1.read)(environment, fragmentReference);
9
+ const { item: data, encounteredRecords } = (0, read_1.read)(environment, fragmentReference);
10
+ (0, useRerenderWhenEncounteredRecordChanges_1.useRerenderWhenEncounteredRecordChanges)(environment, encounteredRecords);
11
+ return data;
17
12
  }
18
13
  exports.useResult = useResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-6b81bb01",
3
+ "version": "0.0.0-main-d629a5be",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -16,8 +16,8 @@
16
16
  "prepack": "yarn run test && yarn run compile"
17
17
  },
18
18
  "dependencies": {
19
- "@isograph/disposable-types": "0.0.0-main-6b81bb01",
20
- "@isograph/react-disposable-state": "0.0.0-main-6b81bb01",
19
+ "@isograph/disposable-types": "0.0.0-main-d629a5be",
20
+ "@isograph/react-disposable-state": "0.0.0-main-d629a5be",
21
21
  "react": "^18.2.0"
22
22
  },
23
23
  "devDependencies": {
@@ -9,7 +9,11 @@ type ComponentCache = {
9
9
  };
10
10
  };
11
11
 
12
- export type Subscriptions = Set<() => void>;
12
+ type CallbackAndRecords = {
13
+ callback: () => void;
14
+ records: Set<DataId> | null;
15
+ };
16
+ type Subscriptions = Set<CallbackAndRecords>;
13
17
  type SuspenseCache = { [index: string]: ParentCache<any> };
14
18
 
15
19
  export type IsographEnvironment = {
@@ -126,3 +130,15 @@ export function assertLink(link: DataTypeValue): Link | null | undefined {
126
130
  }
127
131
  throw new Error('Invalid link');
128
132
  }
133
+
134
+ export function getLink(maybeLink: DataTypeValue): Link | null {
135
+ if (
136
+ maybeLink != null &&
137
+ typeof maybeLink === 'object' &&
138
+ // @ts-expect-error this is safe
139
+ maybeLink.__link != null
140
+ ) {
141
+ return maybeLink as any;
142
+ }
143
+ return null;
144
+ }
package/src/cache.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  StoreRecord,
11
11
  Link,
12
12
  type IsographEnvironment,
13
+ DataTypeValue,
14
+ getLink,
13
15
  } from './IsographEnvironment';
14
16
  import {
15
17
  RetainedQuery,
@@ -211,31 +213,57 @@ function normalizeData(
211
213
  encounteredIds,
212
214
  );
213
215
  if (typeof window !== 'undefined' && window.__LOG) {
214
- console.log('after normalization', { store: environment.store });
216
+ console.log('after normalization', {
217
+ store: environment.store,
218
+ encounteredIds,
219
+ });
215
220
  }
216
- callSubscriptions(environment);
221
+ callSubscriptions(environment, encounteredIds);
217
222
  return encounteredIds;
218
223
  }
219
224
 
220
225
  export function subscribe(
221
226
  environment: IsographEnvironment,
227
+ encounteredRecords: Set<DataId> | null,
222
228
  callback: () => void,
223
229
  ): () => void {
224
- environment.subscriptions.add(callback);
225
- return () => environment.subscriptions.delete(callback);
230
+ const callbackAndRecords = {
231
+ callback,
232
+ records: encounteredRecords,
233
+ };
234
+ environment.subscriptions.add(callbackAndRecords);
235
+ return () => environment.subscriptions.delete(callbackAndRecords);
226
236
  }
227
237
 
228
238
  export function onNextChange(environment: IsographEnvironment): Promise<void> {
229
239
  return new Promise((resolve) => {
230
- const unsubscribe = subscribe(environment, () => {
240
+ const unsubscribe = subscribe(environment, null, () => {
231
241
  unsubscribe();
232
242
  resolve();
233
243
  });
234
244
  });
235
245
  }
236
246
 
237
- function callSubscriptions(environment: IsographEnvironment) {
238
- environment.subscriptions.forEach((callback) => callback());
247
+ function callSubscriptions(
248
+ environment: IsographEnvironment,
249
+ recordsEncounteredWhenNormalizing: Set<DataId>,
250
+ ) {
251
+ environment.subscriptions.forEach(({ callback, records }) => {
252
+ if (records === null) {
253
+ callback();
254
+ } else if (hasOverlappingIds(recordsEncounteredWhenNormalizing, records)) {
255
+ callback();
256
+ }
257
+ });
258
+ }
259
+
260
+ function hasOverlappingIds(set1: Set<DataId>, set2: Set<DataId>): boolean {
261
+ for (const id of set1) {
262
+ if (set2.has(id)) {
263
+ return true;
264
+ }
265
+ }
266
+ return false;
239
267
  }
240
268
 
241
269
  /**
@@ -251,20 +279,22 @@ function normalizeDataIntoRecord(
251
279
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
252
280
  mutableEncounteredIds: Set<DataId>,
253
281
  ) {
254
- mutableEncounteredIds.add(targetParentRecordId);
282
+ let recordHasBeenUpdated = false;
255
283
  for (const normalizationNode of normalizationAst) {
256
284
  switch (normalizationNode.kind) {
257
285
  case 'Scalar': {
258
- normalizeScalarField(
286
+ const scalarFieldResultedInChange = normalizeScalarField(
259
287
  normalizationNode,
260
288
  networkResponseParentRecord,
261
289
  targetParentRecord,
262
290
  variables,
263
291
  );
292
+ recordHasBeenUpdated =
293
+ recordHasBeenUpdated || scalarFieldResultedInChange;
264
294
  break;
265
295
  }
266
296
  case 'Linked': {
267
- normalizeLinkedField(
297
+ const linkedFieldResultedInChange = normalizeLinkedField(
268
298
  environment,
269
299
  normalizationNode,
270
300
  networkResponseParentRecord,
@@ -274,18 +304,24 @@ function normalizeDataIntoRecord(
274
304
  nestedRefetchQueries,
275
305
  mutableEncounteredIds,
276
306
  );
307
+ recordHasBeenUpdated =
308
+ recordHasBeenUpdated || linkedFieldResultedInChange;
277
309
  break;
278
310
  }
279
311
  }
280
312
  }
313
+ if (recordHasBeenUpdated) {
314
+ mutableEncounteredIds.add(targetParentRecordId);
315
+ }
281
316
  }
282
317
 
318
+ type RecordHasBeenUpdated = boolean;
283
319
  function normalizeScalarField(
284
320
  astNode: NormalizationScalarField,
285
321
  networkResponseParentRecord: NetworkResponseObject,
286
322
  targetStoreRecord: StoreRecord,
287
323
  variables: { [index: string]: string },
288
- ) {
324
+ ): RecordHasBeenUpdated {
289
325
  const networkResponseKey = getNetworkResponseKey(astNode);
290
326
  const networkResponseData = networkResponseParentRecord[networkResponseKey];
291
327
  const parentRecordKey = getParentRecordKey(astNode, variables);
@@ -294,7 +330,9 @@ function normalizeScalarField(
294
330
  networkResponseData == null ||
295
331
  isScalarOrEmptyArray(networkResponseData)
296
332
  ) {
333
+ const existingValue = targetStoreRecord[parentRecordKey];
297
334
  targetStoreRecord[parentRecordKey] = networkResponseData;
335
+ return existingValue !== networkResponseData;
298
336
  } else {
299
337
  throw new Error('Unexpected object array when normalizing scalar');
300
338
  }
@@ -312,14 +350,15 @@ function normalizeLinkedField(
312
350
  variables: { [index: string]: string },
313
351
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
314
352
  mutableEncounteredIds: Set<DataId>,
315
- ) {
353
+ ): RecordHasBeenUpdated {
316
354
  const networkResponseKey = getNetworkResponseKey(astNode);
317
355
  const networkResponseData = networkResponseParentRecord[networkResponseKey];
318
356
  const parentRecordKey = getParentRecordKey(astNode, variables);
357
+ const existingValue = targetParentRecord[parentRecordKey];
319
358
 
320
359
  if (networkResponseData == null) {
321
360
  targetParentRecord[parentRecordKey] = null;
322
- return;
361
+ return existingValue !== null;
323
362
  }
324
363
 
325
364
  if (isScalarButNotEmptyArray(networkResponseData)) {
@@ -347,6 +386,7 @@ function normalizeLinkedField(
347
386
  dataIds.push({ __link: newStoreRecordId });
348
387
  }
349
388
  targetParentRecord[parentRecordKey] = dataIds;
389
+ return !dataIdsAreTheSame(existingValue, dataIds);
350
390
  } else {
351
391
  const newStoreRecordId = normalizeNetworkResponseObject(
352
392
  environment,
@@ -358,9 +398,34 @@ function normalizeLinkedField(
358
398
  nestedRefetchQueries,
359
399
  mutableEncounteredIds,
360
400
  );
401
+
361
402
  targetParentRecord[parentRecordKey] = {
362
403
  __link: newStoreRecordId,
363
404
  };
405
+ const link = getLink(existingValue);
406
+ return link?.__link !== newStoreRecordId;
407
+ }
408
+ }
409
+
410
+ function dataIdsAreTheSame(
411
+ existingValue: DataTypeValue,
412
+ newDataIds: Link[],
413
+ ): boolean {
414
+ if (Array.isArray(existingValue)) {
415
+ if (newDataIds.length !== existingValue.length) {
416
+ return false;
417
+ }
418
+ for (let i = 0; i < newDataIds.length; i++) {
419
+ const maybeLink = getLink(existingValue[i]);
420
+ if (maybeLink !== null) {
421
+ if (newDataIds[i].__link !== maybeLink.__link) {
422
+ return false;
423
+ }
424
+ }
425
+ }
426
+ return true;
427
+ } else {
428
+ return false;
364
429
  }
365
430
  }
366
431
 
@@ -3,10 +3,11 @@ import { RefetchQueryArtifactWrapper } from './entrypoint';
3
3
  import { IsographEnvironment, DataId } from './IsographEnvironment';
4
4
  import { readButDoNotEvaluate } from './read';
5
5
  import { ReaderArtifact } from './reader';
6
+ import { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
6
7
 
7
8
  export function getOrCreateCachedComponent(
8
9
  environment: IsographEnvironment,
9
- root: DataId,
10
+ rootId: DataId,
10
11
  componentName: string,
11
12
  readerArtifact: ReaderArtifact<any, any>,
12
13
  variables: { [key: string]: string },
@@ -14,25 +15,37 @@ export function getOrCreateCachedComponent(
14
15
  ) {
15
16
  const cachedComponentsById = environment.componentCache;
16
17
  const stringifiedArgs = JSON.stringify(stableCopy(variables));
17
- cachedComponentsById[root] = cachedComponentsById[root] ?? {};
18
- const componentsByName = cachedComponentsById[root];
18
+ cachedComponentsById[rootId] = cachedComponentsById[rootId] ?? {};
19
+ const componentsByName = cachedComponentsById[rootId];
19
20
  componentsByName[componentName] = componentsByName[componentName] ?? {};
20
21
  const byArgs = componentsByName[componentName];
21
22
  byArgs[stringifiedArgs] =
22
23
  byArgs[stringifiedArgs] ??
23
24
  (() => {
24
25
  function Component(additionalRuntimeProps: { [key: string]: any }) {
25
- const data = readButDoNotEvaluate(environment, {
26
- kind: 'FragmentReference',
27
- readerArtifact: readerArtifact,
28
- root,
29
- variables,
30
- nestedRefetchQueries: resolverRefetchQueries,
31
- });
26
+ const { item: data, encounteredRecords } = readButDoNotEvaluate(
27
+ environment,
28
+ {
29
+ kind: 'FragmentReference',
30
+ readerArtifact: readerArtifact,
31
+ root: rootId,
32
+ variables,
33
+ nestedRefetchQueries: resolverRefetchQueries,
34
+ },
35
+ );
36
+
37
+ useRerenderWhenEncounteredRecordChanges(
38
+ environment,
39
+ encounteredRecords,
40
+ );
41
+
42
+ if (typeof window !== 'undefined' && window.__LOG) {
43
+ console.log('Component re-rendered: ' + componentName + ' ' + rootId);
44
+ }
32
45
 
33
46
  return readerArtifact.resolver(data, additionalRuntimeProps);
34
47
  }
35
- Component.displayName = `${componentName} (id: ${root}) @component`;
48
+ Component.displayName = `${componentName} (id: ${rootId}) @component`;
36
49
  return Component;
37
50
  })();
38
51
  return byArgs[stringifiedArgs];
package/src/index.ts CHANGED
@@ -58,3 +58,4 @@ export {
58
58
  ArgumentValue,
59
59
  Arguments,
60
60
  } from './util';
61
+ export { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
package/src/read.ts CHANGED
@@ -10,35 +10,48 @@ import {
10
10
  } from './IsographEnvironment';
11
11
  import { ReaderAst } from './reader';
12
12
 
13
+ export type WithEncounteredRecords<T> = {
14
+ encounteredRecords: Set<DataId>;
15
+ item: T;
16
+ };
17
+
13
18
  export function read<TReadFromStore extends Object, TClientFieldValue>(
14
19
  environment: IsographEnvironment,
15
20
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
16
- ): TClientFieldValue {
21
+ ): WithEncounteredRecords<TClientFieldValue> {
17
22
  const variant = fragmentReference.readerArtifact.variant;
18
23
  if (variant.kind === 'Eager') {
24
+ const mutableEncounteredRecords = new Set<DataId>();
19
25
  const data = readData(
20
26
  environment,
21
27
  fragmentReference.readerArtifact.readerAst,
22
28
  fragmentReference.root,
23
29
  fragmentReference.variables ?? {},
24
30
  fragmentReference.nestedRefetchQueries,
31
+ mutableEncounteredRecords,
25
32
  );
26
33
  if (data.kind === 'MissingData') {
27
34
  throw onNextChange(environment);
28
35
  } else {
29
- // @ts-expect-error This not properly typed yet
30
- return fragmentReference.readerArtifact.resolver(data.data);
36
+ return {
37
+ encounteredRecords: mutableEncounteredRecords,
38
+ // @ts-expect-error This not properly typed yet
39
+ item: fragmentReference.readerArtifact.resolver(data.data),
40
+ };
31
41
  }
32
42
  } else if (variant.kind === 'Component') {
33
- // @ts-ignore
34
- return getOrCreateCachedComponent(
35
- environment,
36
- fragmentReference.root,
37
- variant.componentName,
38
- fragmentReference.readerArtifact,
39
- fragmentReference.variables ?? {},
40
- fragmentReference.nestedRefetchQueries,
41
- );
43
+ return {
44
+ // @ts-ignore
45
+ item: getOrCreateCachedComponent(
46
+ environment,
47
+ fragmentReference.root,
48
+ variant.componentName,
49
+ fragmentReference.readerArtifact,
50
+ fragmentReference.variables ?? {},
51
+ fragmentReference.nestedRefetchQueries,
52
+ ),
53
+ encounteredRecords: new Set(),
54
+ };
42
55
  }
43
56
  // Why can't Typescript realize that this is unreachable??
44
57
  throw new Error('This is unreachable');
@@ -47,13 +60,15 @@ export function read<TReadFromStore extends Object, TClientFieldValue>(
47
60
  export function readButDoNotEvaluate<TReadFromStore extends Object>(
48
61
  environment: IsographEnvironment,
49
62
  reference: FragmentReference<TReadFromStore, unknown>,
50
- ): TReadFromStore {
63
+ ): WithEncounteredRecords<TReadFromStore> {
64
+ const mutableEncounteredRecords = new Set<DataId>();
51
65
  const response = readData(
52
66
  environment,
53
67
  reference.readerArtifact.readerAst,
54
68
  reference.root,
55
69
  reference.variables ?? {},
56
70
  reference.nestedRefetchQueries,
71
+ mutableEncounteredRecords,
57
72
  );
58
73
  if (typeof window !== 'undefined' && window.__LOG) {
59
74
  console.log('done reading', { response });
@@ -61,7 +76,10 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
61
76
  if (response.kind === 'MissingData') {
62
77
  throw onNextChange(environment);
63
78
  } else {
64
- return response.data;
79
+ return {
80
+ encounteredRecords: mutableEncounteredRecords,
81
+ item: response.data,
82
+ };
65
83
  }
66
84
  }
67
85
 
@@ -69,6 +87,7 @@ type ReadDataResult<TReadFromStore> =
69
87
  | {
70
88
  kind: 'Success';
71
89
  data: TReadFromStore;
90
+ encounteredRecords: Set<DataId>;
72
91
  }
73
92
  | {
74
93
  kind: 'MissingData';
@@ -82,14 +101,23 @@ function readData<TReadFromStore>(
82
101
  root: DataId,
83
102
  variables: { [index: string]: string },
84
103
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
104
+ mutableEncounteredRecords: Set<DataId>,
85
105
  ): ReadDataResult<TReadFromStore> {
106
+ mutableEncounteredRecords.add(root);
86
107
  let storeRecord = environment.store[root];
87
108
  if (storeRecord === undefined) {
88
- return { kind: 'MissingData', reason: 'No record for root ' + root };
109
+ return {
110
+ kind: 'MissingData',
111
+ reason: 'No record for root ' + root,
112
+ };
89
113
  }
90
114
 
91
115
  if (storeRecord === null) {
92
- return { kind: 'Success', data: null as any };
116
+ return {
117
+ kind: 'Success',
118
+ data: null as any,
119
+ encounteredRecords: mutableEncounteredRecords,
120
+ };
93
121
  }
94
122
 
95
123
  let target: { [index: string]: any } = {};
@@ -138,6 +166,7 @@ function readData<TReadFromStore>(
138
166
  link.__link,
139
167
  variables,
140
168
  nestedRefetchQueries,
169
+ mutableEncounteredRecords,
141
170
  );
142
171
  if (result.kind === 'MissingData') {
143
172
  return {
@@ -194,6 +223,7 @@ function readData<TReadFromStore>(
194
223
  targetId,
195
224
  variables,
196
225
  nestedRefetchQueries,
226
+ mutableEncounteredRecords,
197
227
  );
198
228
  if (data.kind === 'MissingData') {
199
229
  return {
@@ -213,6 +243,7 @@ function readData<TReadFromStore>(
213
243
  variables,
214
244
  // Refetch fields just read the id, and don't need refetch query artifacts
215
245
  [],
246
+ mutableEncounteredRecords,
216
247
  );
217
248
  if (typeof window !== 'undefined' && window.__LOG) {
218
249
  console.log('refetch field data', data, field);
@@ -253,8 +284,9 @@ function readData<TReadFromStore>(
253
284
  field.readerArtifact.readerAst,
254
285
  root,
255
286
  variables,
256
- // Refetch fields just read the id, and don't need refetch query artifacts
287
+ // Mutation don't need refetch query artifacts
257
288
  [],
289
+ mutableEncounteredRecords,
258
290
  );
259
291
  if (typeof window !== 'undefined' && window.__LOG) {
260
292
  console.log('refetch field data', data, field);
@@ -298,6 +330,7 @@ function readData<TReadFromStore>(
298
330
  root,
299
331
  variables,
300
332
  resolverRefetchQueries,
333
+ mutableEncounteredRecords,
301
334
  );
302
335
  if (data.kind === 'MissingData') {
303
336
  return {
@@ -323,7 +356,11 @@ function readData<TReadFromStore>(
323
356
  }
324
357
  }
325
358
  }
326
- return { kind: 'Success', data: target as any };
359
+ return {
360
+ kind: 'Success',
361
+ data: target as any,
362
+ encounteredRecords: mutableEncounteredRecords,
363
+ };
327
364
  }
328
365
 
329
366
  function filterVariables(
@@ -1,5 +1,6 @@
1
1
  import type {IsographEntrypoint, NormalizationAst, RefetchQueryArtifactWrapper} from '@isograph/react';
2
- import type {Query__meName__param, Query__meName__outputType} from './reader';
2
+ import {Query__meName__param} from './param_type';
3
+ import {Query__meName__outputType} from './output_type';
3
4
  import readerResolver from './reader';
4
5
  const nestedRefetchQueries: RefetchQueryArtifactWrapper[] = [];
5
6
 
@@ -0,0 +1,4 @@
1
+ import type {ExtractSecondParam} from '@isograph/react';
2
+ import { meNameField as resolver } from '../../../garbageCollection.test.ts';
3
+ // the type, when read out (either via useLazyReference or via graph)
4
+ export type Query__meName__outputType = ReturnType<typeof resolver>;
@@ -0,0 +1,6 @@
1
+
2
+ export type Query__meName__param = {
3
+ me: {
4
+ name: string,
5
+ },
6
+ };
@@ -1,9 +1,8 @@
1
- import type {ReaderArtifact, ReaderAst, ExtractSecondParam} from '@isograph/react';
1
+ import type {ReaderArtifact, ReaderAst} from '@isograph/react';
2
+ import { Query__meName__param } from './param_type.ts';
3
+ import { Query__meName__outputType } from './output_type.ts';
2
4
  import { meNameField as resolver } from '../../../garbageCollection.test.ts';
3
5
 
4
- // the type, when read out (either via useLazyReference or via graph)
5
- export type Query__meName__outputType = ReturnType<typeof resolver>;
6
-
7
6
  const readerAst: ReaderAst<Query__meName__param> = [
8
7
  {
9
8
  kind: "Linked",
@@ -21,12 +20,6 @@ const readerAst: ReaderAst<Query__meName__param> = [
21
20
  },
22
21
  ];
23
22
 
24
- export type Query__meName__param = {
25
- me: {
26
- name: string,
27
- },
28
- };
29
-
30
23
  const artifact: ReaderArtifact<
31
24
  Query__meName__param,
32
25
  Query__meName__outputType
@@ -1,5 +1,6 @@
1
1
  import type {IsographEntrypoint, NormalizationAst, RefetchQueryArtifactWrapper} from '@isograph/react';
2
- import type {Query__meNameSuccessor__param, Query__meNameSuccessor__outputType} from './reader';
2
+ import {Query__meNameSuccessor__param} from './param_type';
3
+ import {Query__meNameSuccessor__outputType} from './output_type';
3
4
  import readerResolver from './reader';
4
5
  const nestedRefetchQueries: RefetchQueryArtifactWrapper[] = [];
5
6
 
@@ -0,0 +1,4 @@
1
+ import type {ExtractSecondParam} from '@isograph/react';
2
+ import { meNameField as resolver } from '../../../meNameSuccessor.ts';
3
+ // the type, when read out (either via useLazyReference or via graph)
4
+ export type Query__meNameSuccessor__outputType = ReturnType<typeof resolver>;
@@ -0,0 +1,11 @@
1
+
2
+ export type Query__meNameSuccessor__param = {
3
+ me: {
4
+ name: string,
5
+ successor: ({
6
+ successor: ({
7
+ name: string,
8
+ } | null),
9
+ } | null),
10
+ },
11
+ };
@@ -1,9 +1,8 @@
1
- import type {ReaderArtifact, ReaderAst, ExtractSecondParam} from '@isograph/react';
1
+ import type {ReaderArtifact, ReaderAst} from '@isograph/react';
2
+ import { Query__meNameSuccessor__param } from './param_type.ts';
3
+ import { Query__meNameSuccessor__outputType } from './output_type.ts';
2
4
  import { meNameField as resolver } from '../../../meNameSuccessor.ts';
3
5
 
4
- // the type, when read out (either via useLazyReference or via graph)
5
- export type Query__meNameSuccessor__outputType = ReturnType<typeof resolver>;
6
-
7
6
  const readerAst: ReaderAst<Query__meNameSuccessor__param> = [
8
7
  {
9
8
  kind: "Linked",
@@ -43,17 +42,6 @@ const readerAst: ReaderAst<Query__meNameSuccessor__param> = [
43
42
  },
44
43
  ];
45
44
 
46
- export type Query__meNameSuccessor__param = {
47
- me: {
48
- name: string,
49
- successor: ({
50
- successor: ({
51
- name: string,
52
- } | null),
53
- } | null),
54
- },
55
- };
56
-
57
45
  const artifact: ReaderArtifact<
58
46
  Query__meNameSuccessor__param,
59
47
  Query__meNameSuccessor__outputType
@@ -1,5 +1,6 @@
1
1
  import type {IsographEntrypoint, NormalizationAst, RefetchQueryArtifactWrapper} from '@isograph/react';
2
- import type {Query__nodeField__param, Query__nodeField__outputType} from './reader';
2
+ import {Query__nodeField__param} from './param_type';
3
+ import {Query__nodeField__outputType} from './output_type';
3
4
  import readerResolver from './reader';
4
5
  const nestedRefetchQueries: RefetchQueryArtifactWrapper[] = [];
5
6
 
@@ -0,0 +1,4 @@
1
+ import type {ExtractSecondParam} from '@isograph/react';
2
+ import { nodeField as resolver } from '../../../nodeQuery.ts';
3
+ // the type, when read out (either via useLazyReference or via graph)
4
+ export type Query__nodeField__outputType = ReturnType<typeof resolver>;
@@ -0,0 +1,6 @@
1
+
2
+ export type Query__nodeField__param = {
3
+ node: ({
4
+ id: string,
5
+ } | null),
6
+ };
@@ -1,9 +1,8 @@
1
- import type {ReaderArtifact, ReaderAst, ExtractSecondParam} from '@isograph/react';
1
+ import type {ReaderArtifact, ReaderAst} from '@isograph/react';
2
+ import { Query__nodeField__param } from './param_type.ts';
3
+ import { Query__nodeField__outputType } from './output_type.ts';
2
4
  import { nodeField as resolver } from '../../../nodeQuery.ts';
3
5
 
4
- // the type, when read out (either via useLazyReference or via graph)
5
- export type Query__nodeField__outputType = ReturnType<typeof resolver>;
6
-
7
6
  const readerAst: ReaderAst<Query__nodeField__param> = [
8
7
  {
9
8
  kind: "Linked",
@@ -26,12 +25,6 @@ const readerAst: ReaderAst<Query__nodeField__param> = [
26
25
  },
27
26
  ];
28
27
 
29
- export type Query__nodeField__param = {
30
- node: ({
31
- id: string,
32
- } | null),
33
- };
34
-
35
28
  const artifact: ReaderArtifact<
36
29
  Query__nodeField__param,
37
30
  Query__nodeField__outputType
@@ -1,10 +1,10 @@
1
1
  import type {IsographEntrypoint} from '@isograph/react';
2
- import { Query__meNameSuccessor__param } from './Query/meNameSuccessor/reader'
3
- import { Query__meName__param } from './Query/meName/reader'
4
- import { Query__nodeField__param } from './Query/nodeField/reader'
5
- import entrypoint_Query__meNameSuccessor from '../__isograph/Query/meNameSuccessor/entrypoint'
6
- import entrypoint_Query__meName from '../__isograph/Query/meName/entrypoint'
7
- import entrypoint_Query__nodeField from '../__isograph/Query/nodeField/entrypoint'
2
+ import { Query__meNameSuccessor__param } from './Query/meNameSuccessor/param_type';
3
+ import { Query__meName__param } from './Query/meName/param_type';
4
+ import { Query__nodeField__param } from './Query/nodeField/param_type';
5
+ import entrypoint_Query__meNameSuccessor from '../__isograph/Query/meNameSuccessor/entrypoint';
6
+ import entrypoint_Query__meName from '../__isograph/Query/meName/entrypoint';
7
+ import entrypoint_Query__nodeField from '../__isograph/Query/nodeField/entrypoint';
8
8
 
9
9
  type IdentityWithParam<TParam> = <TResolverReturn>(
10
10
  x: (param: TParam) => TResolverReturn
@@ -0,0 +1,15 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { DataId, IsographEnvironment } from './IsographEnvironment';
3
+ import { subscribe } from './cache';
4
+
5
+ export function useRerenderWhenEncounteredRecordChanges(
6
+ environment: IsographEnvironment,
7
+ encounteredRecords: Set<DataId>,
8
+ ) {
9
+ const [, setState] = useState<object | void>();
10
+ useEffect(() => {
11
+ return subscribe(environment, encounteredRecords, () => {
12
+ return setState({});
13
+ });
14
+ }, []);
15
+ }
package/src/useResult.ts CHANGED
@@ -1,20 +1,19 @@
1
- import { useEffect, useState } from 'react';
2
1
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
3
- import { subscribe } from './cache';
4
2
  import { read } from './read';
5
3
  import { FragmentReference } from './FragmentReference';
4
+ import { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
6
5
 
7
6
  export function useResult<TReadFromStore extends Object, TClientFieldValue>(
8
7
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
9
8
  ): TClientFieldValue {
10
9
  const environment = useIsographEnvironment();
11
10
 
12
- const [, setState] = useState<object | void>();
13
- useEffect(() => {
14
- return subscribe(environment, () => {
15
- return setState({});
16
- });
17
- }, []);
11
+ const { item: data, encounteredRecords } = read(
12
+ environment,
13
+ fragmentReference,
14
+ );
18
15
 
19
- return read(environment, fragmentReference);
16
+ useRerenderWhenEncounteredRecordChanges(environment, encounteredRecords);
17
+
18
+ return data;
20
19
  }