@isograph/react 0.0.0-main-3779371d → 0.0.0-main-adc27d88

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 (35) 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 +4 -4
  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 +43 -0
  21. package/src/tests/__isograph/Query/meName/reader.ts +40 -0
  22. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +79 -0
  23. package/src/tests/__isograph/Query/meNameSuccessor/reader.ts +67 -0
  24. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +42 -0
  25. package/src/tests/__isograph/Query/nodeField/reader.ts +45 -0
  26. package/src/tests/__isograph/iso.ts +60 -0
  27. package/src/tests/garbageCollection.test.ts +130 -0
  28. package/src/tests/isograph.config.json +7 -0
  29. package/src/tests/meNameSuccessor.ts +20 -0
  30. package/src/tests/nodeQuery.ts +17 -0
  31. package/src/tests/schema.graphql +16 -0
  32. package/src/tests/tsconfig.json +21 -0
  33. package/src/useRerenderWhenEncounteredRecordChanges.ts +15 -0
  34. package/src/useResult.ts +8 -9
  35. package/tsconfig.pkg.json +2 -1
@@ -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-3779371d",
3
+ "version": "0.0.0-main-adc27d88",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -10,14 +10,14 @@
10
10
  "scripts": {
11
11
  "compile": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
12
12
  "compile-watch": "tsc -p tsconfig.pkg.json --watch",
13
- "test": "echo no tests yet",
13
+ "test": "vitest run",
14
14
  "test-watch": "vitest watch",
15
15
  "coverage": "vitest run --coverage",
16
16
  "prepack": "yarn run test && yarn run compile"
17
17
  },
18
18
  "dependencies": {
19
- "@isograph/disposable-types": "0.0.0-main-3779371d",
20
- "@isograph/react-disposable-state": "0.0.0-main-3779371d",
19
+ "@isograph/disposable-types": "0.0.0-main-adc27d88",
20
+ "@isograph/react-disposable-state": "0.0.0-main-adc27d88",
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
+ }