@isograph/react 0.0.0-main-0a7c1aef → 0.0.0-main-b72868e3

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 (36) hide show
  1. package/dist/FragmentReference.d.ts +4 -3
  2. package/dist/IsographEnvironment.d.ts +17 -7
  3. package/dist/areEqualWithDeepComparison.d.ts +3 -0
  4. package/dist/areEqualWithDeepComparison.js +61 -0
  5. package/dist/cache.d.ts +5 -2
  6. package/dist/cache.js +60 -13
  7. package/dist/componentCache.d.ts +3 -6
  8. package/dist/componentCache.js +16 -17
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +3 -4
  11. package/dist/read.d.ts +0 -1
  12. package/dist/read.js +8 -36
  13. package/dist/reader.d.ts +2 -0
  14. package/dist/useReadAndSubscribe.d.ts +7 -0
  15. package/dist/useReadAndSubscribe.js +16 -0
  16. package/dist/useRerenderOnChange.d.ts +4 -0
  17. package/dist/useRerenderOnChange.js +21 -0
  18. package/dist/useResult.js +13 -5
  19. package/package.json +3 -3
  20. package/src/FragmentReference.ts +3 -1
  21. package/src/IsographEnvironment.tsx +20 -7
  22. package/src/areEqualWithDeepComparison.ts +78 -0
  23. package/src/cache.ts +81 -13
  24. package/src/componentCache.ts +29 -31
  25. package/src/index.ts +2 -2
  26. package/src/read.ts +7 -52
  27. package/src/reader.ts +3 -0
  28. package/src/tests/__isograph/Query/meName/reader.ts +1 -0
  29. package/src/tests/__isograph/Query/meNameSuccessor/reader.ts +1 -0
  30. package/src/tests/__isograph/Query/nodeField/reader.ts +1 -0
  31. package/src/useReadAndSubscribe.ts +25 -0
  32. package/src/useRerenderOnChange.ts +33 -0
  33. package/src/useResult.ts +17 -10
  34. package/dist/useRerenderWhenEncounteredRecordChanges.d.ts +0 -2
  35. package/dist/useRerenderWhenEncounteredRecordChanges.js +0 -17
  36. package/src/useRerenderWhenEncounteredRecordChanges.ts +0 -18
@@ -2,12 +2,13 @@ import { DataId } from './IsographEnvironment';
2
2
  import { RefetchQueryArtifactWrapper } from './entrypoint';
3
3
  import { ReaderArtifact } from './reader';
4
4
  export type Variable = any;
5
+ export type Variables = {
6
+ readonly [index: string]: Variable;
7
+ };
5
8
  export type FragmentReference<TReadFromStore extends Object, TClientFieldValue> = {
6
9
  kind: 'FragmentReference';
7
10
  readerArtifact: ReaderArtifact<TReadFromStore, TClientFieldValue>;
8
11
  root: DataId;
9
- variables: {
10
- [index: string]: Variable;
11
- } | null;
12
+ variables: Variables | null;
12
13
  nestedRefetchQueries: RefetchQueryArtifactWrapper[];
13
14
  };
@@ -1,20 +1,30 @@
1
1
  /// <reference types="react" />
2
2
  import { ParentCache } from '@isograph/react-disposable-state';
3
3
  import { RetainedQuery } from './garbageCollection';
4
- type ComponentName = string;
5
- type StringifiedArgs = string;
4
+ import { WithEncounteredRecords } from './read';
5
+ import { FragmentReference } from './FragmentReference';
6
+ export type ComponentOrFieldName = string;
7
+ export type StringifiedArgs = string;
6
8
  type ComponentCache = {
7
9
  [key: DataId]: {
8
- [key: ComponentName]: {
10
+ [key: ComponentOrFieldName]: {
9
11
  [key: StringifiedArgs]: React.FC<any>;
10
12
  };
11
13
  };
12
14
  };
13
- type CallbackAndRecords = {
14
- callback: () => void;
15
- records: Set<DataId> | null;
15
+ type FragmentSubscription<TReadFromStore extends Object> = {
16
+ readonly kind: 'FragmentSubscription';
17
+ readonly callback: (newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>) => void;
18
+ /** The value read out from the previous call to readButDoNotEvaluate */
19
+ readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
20
+ readonly fragmentReference: FragmentReference<TReadFromStore, any>;
16
21
  };
17
- type Subscriptions = Set<CallbackAndRecords>;
22
+ type AnyRecordSubscription = {
23
+ readonly kind: 'AnyRecords';
24
+ readonly callback: () => void;
25
+ };
26
+ type Subscription = FragmentSubscription<Object> | AnyRecordSubscription;
27
+ type Subscriptions = Set<Subscription>;
18
28
  type SuspenseCache = {
19
29
  [index: string]: ParentCache<any>;
20
30
  };
@@ -0,0 +1,3 @@
1
+ export declare function areEqualWithDeepComparison(oldItem: unknown, newItem: unknown): boolean;
2
+ export declare function areEqualArraysWithDeepComparison(oldItems: ReadonlyArray<unknown>, newItems: ReadonlyArray<unknown>): boolean;
3
+ export declare function areEqualObjectsWithDeepComparison(oldItemObject: object, newItemObject: object): boolean;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.areEqualObjectsWithDeepComparison = exports.areEqualArraysWithDeepComparison = exports.areEqualWithDeepComparison = void 0;
4
+ function areEqualWithDeepComparison(oldItem, newItem) {
5
+ if (newItem === null) {
6
+ return oldItem === null;
7
+ }
8
+ if (newItem === undefined) {
9
+ return oldItem === undefined;
10
+ }
11
+ if (Array.isArray(newItem)) {
12
+ if (!Array.isArray(oldItem)) {
13
+ return false;
14
+ }
15
+ return areEqualArraysWithDeepComparison(oldItem, newItem);
16
+ }
17
+ if (typeof newItem === 'object') {
18
+ if (typeof oldItem !== 'object') {
19
+ return false;
20
+ }
21
+ if (oldItem === null) {
22
+ return false;
23
+ }
24
+ return areEqualObjectsWithDeepComparison(oldItem, newItem);
25
+ }
26
+ return newItem === oldItem;
27
+ }
28
+ exports.areEqualWithDeepComparison = areEqualWithDeepComparison;
29
+ function areEqualArraysWithDeepComparison(oldItems, newItems) {
30
+ if (newItems.length !== oldItems.length) {
31
+ return false;
32
+ }
33
+ for (let i = 0; i < newItems.length; i++) {
34
+ if (!areEqualWithDeepComparison(oldItems[i], newItems[i])) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ exports.areEqualArraysWithDeepComparison = areEqualArraysWithDeepComparison;
41
+ function areEqualObjectsWithDeepComparison(oldItemObject, newItemObject) {
42
+ const oldKeys = Object.keys(oldItemObject);
43
+ const newKeys = Object.keys(newItemObject);
44
+ if (oldKeys.length !== newKeys.length) {
45
+ return false;
46
+ }
47
+ for (const oldKey of oldKeys) {
48
+ if (!(oldKey in newItemObject)) {
49
+ return false;
50
+ }
51
+ // @ts-expect-error
52
+ const oldValue = oldItemObject[oldKey];
53
+ // @ts-expect-error
54
+ const newValue = newItemObject[oldKey];
55
+ if (!areEqualWithDeepComparison(oldValue, newValue)) {
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+ exports.areEqualObjectsWithDeepComparison = areEqualObjectsWithDeepComparison;
package/dist/cache.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { ItemCleanupPair, ParentCache } from '@isograph/react-disposable-state';
2
2
  import { PromiseWrapper } from './PromiseWrapper';
3
- import { DataId, type IsographEnvironment } from './IsographEnvironment';
3
+ import { type IsographEnvironment } from './IsographEnvironment';
4
4
  import { IsographEntrypoint, NormalizationLinkedField, NormalizationScalarField } from './entrypoint';
5
5
  import { ReaderLinkedField, ReaderScalarField } from './reader';
6
+ import { WithEncounteredRecords } from './read';
7
+ import { FragmentReference } from './FragmentReference';
6
8
  declare global {
7
9
  interface Window {
8
10
  __LOG: boolean;
@@ -17,7 +19,8 @@ export declare function stableCopy<T>(value: T): T;
17
19
  type IsoResolver = IsographEntrypoint<any, any>;
18
20
  export declare function getOrCreateCacheForArtifact<TReadFromStore extends Object, TClientFieldValue>(environment: IsographEnvironment, artifact: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: object): ParentCache<PromiseWrapper<TClientFieldValue>>;
19
21
  export declare function makeNetworkRequest<T>(environment: IsographEnvironment, artifact: IsoResolver, variables: object): ItemCleanupPair<PromiseWrapper<T>>;
20
- export declare function subscribe(environment: IsographEnvironment, encounteredRecords: Set<DataId> | null, callback: () => void): () => void;
22
+ export declare function subscribeToAnyChange(environment: IsographEnvironment, callback: () => void): () => void;
23
+ export declare function subscribe<TReadFromStore extends Object>(environment: IsographEnvironment, encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>, fragmentReference: FragmentReference<TReadFromStore, any>, callback: (newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>) => void): () => void;
21
24
  export declare function onNextChange(environment: IsographEnvironment): Promise<void>;
22
25
  export declare function getParentRecordKey(astNode: NormalizationLinkedField | NormalizationScalarField | ReaderLinkedField | ReaderScalarField, variables: {
23
26
  [index: string]: string;
package/dist/cache.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SECOND_SPLIT_KEY = exports.FIRST_SPLIT_KEY = exports.getParentRecordKey = exports.onNextChange = exports.subscribe = exports.makeNetworkRequest = exports.getOrCreateCacheForArtifact = exports.stableCopy = void 0;
3
+ exports.SECOND_SPLIT_KEY = exports.FIRST_SPLIT_KEY = exports.getParentRecordKey = exports.onNextChange = exports.subscribe = exports.subscribeToAnyChange = exports.makeNetworkRequest = exports.getOrCreateCacheForArtifact = exports.stableCopy = void 0;
4
4
  const react_disposable_state_1 = require("@isograph/react-disposable-state");
5
5
  const PromiseWrapper_1 = require("./PromiseWrapper");
6
6
  const IsographEnvironment_1 = require("./IsographEnvironment");
7
7
  const garbageCollection_1 = require("./garbageCollection");
8
+ const read_1 = require("./read");
9
+ const areEqualWithDeepComparison_1 = require("./areEqualWithDeepComparison");
8
10
  function getOrCreateCache(environment, index, factory) {
9
11
  if (typeof window !== 'undefined' && window.__LOG) {
10
12
  console.log('getting cache for', {
@@ -108,18 +110,31 @@ function normalizeData(environment, normalizationAst, networkResponse, variables
108
110
  callSubscriptions(environment, encounteredIds);
109
111
  return encounteredIds;
110
112
  }
111
- function subscribe(environment, encounteredRecords, callback) {
112
- const callbackAndRecords = {
113
+ function subscribeToAnyChange(environment, callback) {
114
+ const subscription = {
115
+ kind: 'AnyRecords',
113
116
  callback,
114
- records: encounteredRecords,
115
117
  };
116
- environment.subscriptions.add(callbackAndRecords);
117
- return () => environment.subscriptions.delete(callbackAndRecords);
118
+ environment.subscriptions.add(subscription);
119
+ return () => environment.subscriptions.delete(subscription);
120
+ }
121
+ exports.subscribeToAnyChange = subscribeToAnyChange;
122
+ function subscribe(environment, encounteredDataAndRecords, fragmentReference, callback) {
123
+ const fragmentSubscription = {
124
+ kind: 'FragmentSubscription',
125
+ callback,
126
+ encounteredDataAndRecords,
127
+ fragmentReference,
128
+ };
129
+ // @ts-expect-error
130
+ environment.subscriptions.add(fragmentSubscription);
131
+ // @ts-expect-error
132
+ return () => environment.subscriptions.delete(fragmentSubscription);
118
133
  }
119
134
  exports.subscribe = subscribe;
120
135
  function onNextChange(environment) {
121
136
  return new Promise((resolve) => {
122
- const unsubscribe = subscribe(environment, null, () => {
137
+ const unsubscribe = subscribeToAnyChange(environment, () => {
123
138
  unsubscribe();
124
139
  resolve();
125
140
  });
@@ -127,12 +142,44 @@ function onNextChange(environment) {
127
142
  }
128
143
  exports.onNextChange = onNextChange;
129
144
  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();
145
+ environment.subscriptions.forEach((subscription) => {
146
+ switch (subscription.kind) {
147
+ case 'FragmentSubscription': {
148
+ // TODO if there are multiple components subscribed to the same
149
+ // fragment, we will call readButNotEvaluate multiple times. We
150
+ // should fix that.
151
+ if (hasOverlappingIds(recordsEncounteredWhenNormalizing, subscription.encounteredDataAndRecords.encounteredRecords)) {
152
+ const newEncounteredDataAndRecords = (0, read_1.readButDoNotEvaluate)(environment, subscription.fragmentReference);
153
+ if (!(0, areEqualWithDeepComparison_1.areEqualObjectsWithDeepComparison)(subscription.encounteredDataAndRecords.item, newEncounteredDataAndRecords.item)) {
154
+ if (typeof window !== 'undefined' && window.__LOG) {
155
+ console.log('Deep equality - No', {
156
+ fragmentReference: subscription.fragmentReference,
157
+ old: subscription.encounteredDataAndRecords.item,
158
+ new: newEncounteredDataAndRecords.item,
159
+ });
160
+ }
161
+ // TODO deep compare values
162
+ subscription.callback(newEncounteredDataAndRecords);
163
+ }
164
+ else {
165
+ if (typeof window !== 'undefined' && window.__LOG) {
166
+ console.log('Deep equality - Yes', {
167
+ fragmentReference: subscription.fragmentReference,
168
+ old: subscription.encounteredDataAndRecords.item,
169
+ });
170
+ }
171
+ }
172
+ }
173
+ return;
174
+ }
175
+ case 'AnyRecords': {
176
+ return subscription.callback();
177
+ }
178
+ default: {
179
+ // @ts-expect-error(6133)
180
+ const _ = subscription;
181
+ throw new Error('Unexpected case');
182
+ }
136
183
  }
137
184
  });
138
185
  }
@@ -1,7 +1,4 @@
1
1
  /// <reference types="react" />
2
- import { RefetchQueryArtifactWrapper } from './entrypoint';
3
- import { IsographEnvironment, DataId } from './IsographEnvironment';
4
- import { ReaderArtifact } from './reader';
5
- export declare function getOrCreateCachedComponent(environment: IsographEnvironment, rootId: DataId, componentName: string, readerArtifact: ReaderArtifact<any, any>, variables: {
6
- [key: string]: string;
7
- }, resolverRefetchQueries: RefetchQueryArtifactWrapper[]): import("react").FC<any>;
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ import { FragmentReference } from './FragmentReference';
4
+ export declare function getOrCreateCachedComponent(environment: IsographEnvironment, componentName: string, fragmentReference: FragmentReference<any, any>): React.FC<any>;
@@ -2,33 +2,32 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getOrCreateCachedComponent = void 0;
4
4
  const cache_1 = require("./cache");
5
- const read_1 = require("./read");
6
- const useRerenderWhenEncounteredRecordChanges_1 = require("./useRerenderWhenEncounteredRecordChanges");
7
- function getOrCreateCachedComponent(environment, rootId, componentName, readerArtifact, variables, resolverRefetchQueries) {
5
+ const useReadAndSubscribe_1 = require("./useReadAndSubscribe");
6
+ function getOrCreateCachedComponent(environment, componentName, fragmentReference) {
8
7
  var _a, _b, _c;
8
+ // cachedComponentsById is a three layer cache: id, then component name, then
9
+ // stringified args. These three, together, uniquely identify a read at a given
10
+ // time.
9
11
  const cachedComponentsById = environment.componentCache;
10
- const stringifiedArgs = JSON.stringify((0, cache_1.stableCopy)(variables));
11
- cachedComponentsById[rootId] = (_a = cachedComponentsById[rootId]) !== null && _a !== void 0 ? _a : {};
12
- const componentsByName = cachedComponentsById[rootId];
12
+ cachedComponentsById[fragmentReference.root] =
13
+ (_a = cachedComponentsById[fragmentReference.root]) !== null && _a !== void 0 ? _a : {};
14
+ const componentsByName = cachedComponentsById[fragmentReference.root];
13
15
  componentsByName[componentName] = (_b = componentsByName[componentName]) !== null && _b !== void 0 ? _b : {};
14
16
  const byArgs = componentsByName[componentName];
17
+ const stringifiedArgs = JSON.stringify((0, cache_1.stableCopy)(fragmentReference.variables));
15
18
  byArgs[stringifiedArgs] =
16
19
  (_c = byArgs[stringifiedArgs]) !== null && _c !== void 0 ? _c : (() => {
17
20
  function Component(additionalRuntimeProps) {
18
- const { item: data, encounteredRecords } = (0, read_1.readButDoNotEvaluate)(environment, {
19
- kind: 'FragmentReference',
20
- readerArtifact: readerArtifact,
21
- root: rootId,
22
- variables,
23
- nestedRefetchQueries: resolverRefetchQueries,
24
- });
25
- (0, useRerenderWhenEncounteredRecordChanges_1.useRerenderWhenEncounteredRecordChanges)(environment, encounteredRecords);
21
+ const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(environment, fragmentReference);
26
22
  if (typeof window !== 'undefined' && window.__LOG) {
27
- console.log('Component re-rendered: ' + componentName + ' ' + rootId);
23
+ console.log('Component re-rendered: ' +
24
+ componentName +
25
+ ' ' +
26
+ fragmentReference.root);
28
27
  }
29
- return readerArtifact.resolver(data, additionalRuntimeProps);
28
+ return fragmentReference.readerArtifact.resolver(data, additionalRuntimeProps);
30
29
  }
31
- Component.displayName = `${componentName} (id: ${rootId}) @component`;
30
+ Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
32
31
  return Component;
33
32
  })();
34
33
  return byArgs[stringifiedArgs];
package/dist/index.d.ts CHANGED
@@ -7,9 +7,9 @@ export { useImperativeReference } from './useImperativeReference';
7
7
  export { EntrypointReader } from './EntrypointReader';
8
8
  export { type ReaderArtifact, ReaderAst, ReaderAstNode, ReaderLinkedField, ReaderMutationField, ReaderRefetchField, ReaderResolverField, ReaderResolverVariant, ReaderScalarField, } from './reader';
9
9
  export { NormalizationAst, NormalizationAstNode, NormalizationLinkedField, NormalizationScalarField, IsographEntrypoint, assertIsEntrypoint, RefetchQueryArtifact, RefetchQueryArtifactWrapper, } from './entrypoint';
10
- export { read, readButDoNotEvaluate } from './read';
10
+ export { readButDoNotEvaluate } from './read';
11
11
  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';
15
+ export { useRerenderOnChange } from './useRerenderOnChange';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.useRerenderOnChange = exports.useLazyReference = exports.useResult = exports.readButDoNotEvaluate = 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; } });
@@ -23,11 +23,10 @@ Object.defineProperty(exports, "EntrypointReader", { enumerable: true, get: func
23
23
  var entrypoint_1 = require("./entrypoint");
24
24
  Object.defineProperty(exports, "assertIsEntrypoint", { enumerable: true, get: function () { return entrypoint_1.assertIsEntrypoint; } });
25
25
  var read_1 = require("./read");
26
- Object.defineProperty(exports, "read", { enumerable: true, get: function () { return read_1.read; } });
27
26
  Object.defineProperty(exports, "readButDoNotEvaluate", { enumerable: true, get: function () { return read_1.readButDoNotEvaluate; } });
28
27
  var useResult_1 = require("./useResult");
29
28
  Object.defineProperty(exports, "useResult", { enumerable: true, get: function () { return useResult_1.useResult; } });
30
29
  var useLazyReference_1 = require("./useLazyReference");
31
30
  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; } });
31
+ var useRerenderOnChange_1 = require("./useRerenderOnChange");
32
+ Object.defineProperty(exports, "useRerenderOnChange", { enumerable: true, get: function () { return useRerenderOnChange_1.useRerenderOnChange; } });
package/dist/read.d.ts CHANGED
@@ -4,5 +4,4 @@ export type WithEncounteredRecords<T> = {
4
4
  encounteredRecords: Set<DataId>;
5
5
  item: T;
6
6
  };
7
- export declare function read<TReadFromStore extends Object, TClientFieldValue>(environment: IsographEnvironment, fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>): WithEncounteredRecords<TClientFieldValue>;
8
7
  export declare function readButDoNotEvaluate<TReadFromStore extends Object>(environment: IsographEnvironment, reference: FragmentReference<TReadFromStore, unknown>): WithEncounteredRecords<TReadFromStore>;
package/dist/read.js CHANGED
@@ -1,37 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readButDoNotEvaluate = exports.read = void 0;
3
+ exports.readButDoNotEvaluate = void 0;
4
4
  const cache_1 = require("./cache");
5
5
  const componentCache_1 = require("./componentCache");
6
6
  const IsographEnvironment_1 = require("./IsographEnvironment");
7
- function read(environment, fragmentReference) {
8
- var _a, _b;
9
- const variant = fragmentReference.readerArtifact.variant;
10
- if (variant.kind === 'Eager') {
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);
13
- if (data.kind === 'MissingData') {
14
- throw (0, cache_1.onNextChange)(environment);
15
- }
16
- else {
17
- return {
18
- encounteredRecords: mutableEncounteredRecords,
19
- // @ts-expect-error This not properly typed yet
20
- item: fragmentReference.readerArtifact.resolver(data.data),
21
- };
22
- }
23
- }
24
- else if (variant.kind === 'Component') {
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
- };
30
- }
31
- // Why can't Typescript realize that this is unreachable??
32
- throw new Error('This is unreachable');
33
- }
34
- exports.read = read;
35
7
  function readButDoNotEvaluate(environment, reference) {
36
8
  var _a;
37
9
  const mutableEncounteredRecords = new Set();
@@ -164,9 +136,6 @@ function readData(environment, ast, root, variables, nestedRefetchQueries, mutab
164
136
  const data = readData(environment, field.readerArtifact.readerAst, root, variables,
165
137
  // Refetch fields just read the id, and don't need refetch query artifacts
166
138
  [], mutableEncounteredRecords);
167
- if (typeof window !== 'undefined' && window.__LOG) {
168
- console.log('refetch field data', data, field);
169
- }
170
139
  if (data.kind === 'MissingData') {
171
140
  return {
172
141
  kind: 'MissingData',
@@ -192,9 +161,6 @@ function readData(environment, ast, root, variables, nestedRefetchQueries, mutab
192
161
  const data = readData(environment, field.readerArtifact.readerAst, root, variables,
193
162
  // Mutation don't need refetch query artifacts
194
163
  [], mutableEncounteredRecords);
195
- if (typeof window !== 'undefined' && window.__LOG) {
196
- console.log('refetch field data', data, field);
197
- }
198
164
  if (data.kind === 'MissingData') {
199
165
  return {
200
166
  kind: 'MissingData',
@@ -235,7 +201,13 @@ function readData(environment, ast, root, variables, nestedRefetchQueries, mutab
235
201
  }
236
202
  }
237
203
  else if (variant.kind === 'Component') {
238
- target[field.alias] = (0, componentCache_1.getOrCreateCachedComponent)(environment, root, variant.componentName, field.readerArtifact, variables, resolverRefetchQueries);
204
+ target[field.alias] = (0, componentCache_1.getOrCreateCachedComponent)(environment, variant.componentName, {
205
+ kind: 'FragmentReference',
206
+ readerArtifact: field.readerArtifact,
207
+ root,
208
+ variables,
209
+ nestedRefetchQueries: resolverRefetchQueries,
210
+ });
239
211
  }
240
212
  break;
241
213
  }
package/dist/reader.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import { ComponentOrFieldName } from './IsographEnvironment';
1
2
  import { Arguments } from './util';
2
3
  export type ReaderArtifact<TReadFromStore extends Object, TClientFieldValue> = {
3
4
  kind: 'ReaderArtifact';
5
+ fieldName: ComponentOrFieldName;
4
6
  readerAst: ReaderAst<TReadFromStore>;
5
7
  resolver: (data: TReadFromStore, runtimeProps: any) => TClientFieldValue;
6
8
  variant: ReaderResolverVariant;
@@ -0,0 +1,7 @@
1
+ import { FragmentReference } from './FragmentReference';
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ /**
4
+ * Read the data from a fragment reference and subscribe to updates.
5
+ * Does not pass the data to the fragment reference's resolver function.
6
+ */
7
+ export declare function useReadAndSubscribe<TReadFromStore extends Object>(environment: IsographEnvironment, fragmentReference: FragmentReference<TReadFromStore, any>): TReadFromStore;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useReadAndSubscribe = void 0;
4
+ const react_1 = require("react");
5
+ const read_1 = require("./read");
6
+ const useRerenderOnChange_1 = require("./useRerenderOnChange");
7
+ /**
8
+ * Read the data from a fragment reference and subscribe to updates.
9
+ * Does not pass the data to the fragment reference's resolver function.
10
+ */
11
+ function useReadAndSubscribe(environment, fragmentReference) {
12
+ const [readOutDataAndRecords, setReadOutDataAndRecords] = (0, react_1.useState)(() => (0, read_1.readButDoNotEvaluate)(environment, fragmentReference));
13
+ (0, useRerenderOnChange_1.useRerenderOnChange)(environment, readOutDataAndRecords, fragmentReference, setReadOutDataAndRecords);
14
+ return readOutDataAndRecords.item;
15
+ }
16
+ exports.useReadAndSubscribe = useReadAndSubscribe;
@@ -0,0 +1,4 @@
1
+ import { IsographEnvironment } from './IsographEnvironment';
2
+ import { WithEncounteredRecords } from './read';
3
+ import { FragmentReference } from './FragmentReference';
4
+ export declare function useRerenderOnChange<TReadFromStore extends Object>(environment: IsographEnvironment, encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>, fragmentReference: FragmentReference<any, any>, setEncounteredDataAndRecords: (data: WithEncounteredRecords<TReadFromStore>) => void): void;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRerenderOnChange = void 0;
4
+ const react_1 = require("react");
5
+ const cache_1 = require("./cache");
6
+ // TODO add unit tests for this. Add integration tests that test
7
+ // behavior when the encounteredRecords underneath a fragment change.
8
+ function useRerenderOnChange(environment, encounteredDataAndRecords, fragmentReference, setEncounteredDataAndRecords) {
9
+ (0, react_1.useEffect)(() => {
10
+ return (0, cache_1.subscribe)(environment, encounteredDataAndRecords, fragmentReference, (newEncounteredDataAndRecords) => {
11
+ setEncounteredDataAndRecords(newEncounteredDataAndRecords);
12
+ });
13
+ // Note: this is an empty array on purpose:
14
+ // - the fragment reference is stable for the life of the component
15
+ // - ownership of encounteredDataAndRecords is transferred into the
16
+ // environment
17
+ // - though maybe we need to include setEncounteredDataAndRecords in
18
+ // the dependency array
19
+ }, []);
20
+ }
21
+ exports.useRerenderOnChange = useRerenderOnChange;
package/dist/useResult.js CHANGED
@@ -2,12 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useResult = void 0;
4
4
  const IsographEnvironmentProvider_1 = require("./IsographEnvironmentProvider");
5
- const read_1 = require("./read");
6
- const useRerenderWhenEncounteredRecordChanges_1 = require("./useRerenderWhenEncounteredRecordChanges");
5
+ const componentCache_1 = require("./componentCache");
6
+ const useReadAndSubscribe_1 = require("./useReadAndSubscribe");
7
7
  function useResult(fragmentReference) {
8
8
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
9
- const { item: data, encounteredRecords } = (0, read_1.read)(environment, fragmentReference);
10
- (0, useRerenderWhenEncounteredRecordChanges_1.useRerenderWhenEncounteredRecordChanges)(environment, encounteredRecords);
11
- return data;
9
+ switch (fragmentReference.readerArtifact.variant.kind) {
10
+ case 'Component': {
11
+ // @ts-expect-error
12
+ return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.readerArtifact.variant.componentName, fragmentReference);
13
+ }
14
+ case 'Eager': {
15
+ const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(environment, fragmentReference);
16
+ // @ts-expect-error resolver is incorrectly typed in ReaderArtifact
17
+ return fragmentReference.readerArtifact.resolver(data);
18
+ }
19
+ }
12
20
  }
13
21
  exports.useResult = useResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-0a7c1aef",
3
+ "version": "0.0.0-main-b72868e3",
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-0a7c1aef",
20
- "@isograph/react-disposable-state": "0.0.0-main-0a7c1aef",
19
+ "@isograph/disposable-types": "0.0.0-main-b72868e3",
20
+ "@isograph/react-disposable-state": "0.0.0-main-b72868e3",
21
21
  "react": "^18.2.0"
22
22
  },
23
23
  "devDependencies": {
@@ -5,6 +5,8 @@ import { ReaderArtifact } from './reader';
5
5
  // TODO type this better
6
6
  export type Variable = any;
7
7
 
8
+ export type Variables = { readonly [index: string]: Variable };
9
+
8
10
  export type FragmentReference<
9
11
  TReadFromStore extends Object,
10
12
  TClientFieldValue,
@@ -12,7 +14,7 @@ export type FragmentReference<
12
14
  kind: 'FragmentReference';
13
15
  readerArtifact: ReaderArtifact<TReadFromStore, TClientFieldValue>;
14
16
  root: DataId;
15
- variables: { [index: string]: Variable } | null;
17
+ variables: Variables | null;
16
18
  // TODO: We should instead have ReaderAst<TClientFieldProps>
17
19
  nestedRefetchQueries: RefetchQueryArtifactWrapper[];
18
20
  };
@@ -1,19 +1,32 @@
1
1
  import { ParentCache } from '@isograph/react-disposable-state';
2
2
  import { RetainedQuery } from './garbageCollection';
3
+ import { WithEncounteredRecords } from './read';
4
+ import { FragmentReference } from './FragmentReference';
3
5
 
4
- type ComponentName = string;
5
- type StringifiedArgs = string;
6
+ export type ComponentOrFieldName = string;
7
+ export type StringifiedArgs = string;
6
8
  type ComponentCache = {
7
9
  [key: DataId]: {
8
- [key: ComponentName]: { [key: StringifiedArgs]: React.FC<any> };
10
+ [key: ComponentOrFieldName]: { [key: StringifiedArgs]: React.FC<any> };
9
11
  };
10
12
  };
11
13
 
12
- type CallbackAndRecords = {
13
- callback: () => void;
14
- records: Set<DataId> | null;
14
+ type FragmentSubscription<TReadFromStore extends Object> = {
15
+ readonly kind: 'FragmentSubscription';
16
+ readonly callback: (
17
+ newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
18
+ ) => void;
19
+ /** The value read out from the previous call to readButDoNotEvaluate */
20
+ readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
21
+ readonly fragmentReference: FragmentReference<TReadFromStore, any>;
15
22
  };
16
- type Subscriptions = Set<CallbackAndRecords>;
23
+ type AnyRecordSubscription = {
24
+ readonly kind: 'AnyRecords';
25
+ readonly callback: () => void;
26
+ };
27
+
28
+ type Subscription = FragmentSubscription<Object> | AnyRecordSubscription;
29
+ type Subscriptions = Set<Subscription>;
17
30
  type SuspenseCache = { [index: string]: ParentCache<any> };
18
31
 
19
32
  export type IsographEnvironment = {
@@ -0,0 +1,78 @@
1
+ export function areEqualWithDeepComparison(
2
+ oldItem: unknown,
3
+ newItem: unknown,
4
+ ): boolean {
5
+ if (newItem === null) {
6
+ return oldItem === null;
7
+ }
8
+
9
+ if (newItem === undefined) {
10
+ return oldItem === undefined;
11
+ }
12
+
13
+ if (Array.isArray(newItem)) {
14
+ if (!Array.isArray(oldItem)) {
15
+ return false;
16
+ }
17
+
18
+ return areEqualArraysWithDeepComparison(oldItem, newItem);
19
+ }
20
+
21
+ if (typeof newItem === 'object') {
22
+ if (typeof oldItem !== 'object') {
23
+ return false;
24
+ }
25
+
26
+ if (oldItem === null) {
27
+ return false;
28
+ }
29
+
30
+ return areEqualObjectsWithDeepComparison(oldItem, newItem);
31
+ }
32
+
33
+ return newItem === oldItem;
34
+ }
35
+
36
+ export function areEqualArraysWithDeepComparison(
37
+ oldItems: ReadonlyArray<unknown>,
38
+ newItems: ReadonlyArray<unknown>,
39
+ ): boolean {
40
+ if (newItems.length !== oldItems.length) {
41
+ return false;
42
+ }
43
+
44
+ for (let i = 0; i < newItems.length; i++) {
45
+ if (!areEqualWithDeepComparison(oldItems[i], newItems[i])) {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ return true;
51
+ }
52
+
53
+ export function areEqualObjectsWithDeepComparison(
54
+ oldItemObject: object,
55
+ newItemObject: object,
56
+ ): boolean {
57
+ const oldKeys = Object.keys(oldItemObject);
58
+ const newKeys = Object.keys(newItemObject);
59
+
60
+ if (oldKeys.length !== newKeys.length) {
61
+ return false;
62
+ }
63
+
64
+ for (const oldKey of oldKeys) {
65
+ if (!(oldKey in newItemObject)) {
66
+ return false;
67
+ }
68
+ // @ts-expect-error
69
+ const oldValue = oldItemObject[oldKey];
70
+ // @ts-expect-error
71
+ const newValue = newItemObject[oldKey];
72
+
73
+ if (!areEqualWithDeepComparison(oldValue, newValue)) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ }
package/src/cache.ts CHANGED
@@ -28,6 +28,9 @@ import {
28
28
  } from './entrypoint';
29
29
  import { ReaderLinkedField, ReaderScalarField } from './reader';
30
30
  import { Argument, ArgumentValue } from './util';
31
+ import { WithEncounteredRecords, readButDoNotEvaluate } from './read';
32
+ import { FragmentReference } from './FragmentReference';
33
+ import { areEqualObjectsWithDeepComparison } from './areEqualWithDeepComparison';
31
34
 
32
35
  declare global {
33
36
  interface Window {
@@ -222,22 +225,41 @@ function normalizeData(
222
225
  return encounteredIds;
223
226
  }
224
227
 
225
- export function subscribe(
228
+ export function subscribeToAnyChange(
226
229
  environment: IsographEnvironment,
227
- encounteredRecords: Set<DataId> | null,
228
230
  callback: () => void,
229
231
  ): () => void {
230
- const callbackAndRecords = {
232
+ const subscription = {
233
+ kind: 'AnyRecords',
231
234
  callback,
232
- records: encounteredRecords,
233
- };
234
- environment.subscriptions.add(callbackAndRecords);
235
- return () => environment.subscriptions.delete(callbackAndRecords);
235
+ } as const;
236
+ environment.subscriptions.add(subscription);
237
+ return () => environment.subscriptions.delete(subscription);
238
+ }
239
+
240
+ export function subscribe<TReadFromStore extends Object>(
241
+ environment: IsographEnvironment,
242
+ encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
243
+ fragmentReference: FragmentReference<TReadFromStore, any>,
244
+ callback: (
245
+ newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
246
+ ) => void,
247
+ ): () => void {
248
+ const fragmentSubscription = {
249
+ kind: 'FragmentSubscription',
250
+ callback,
251
+ encounteredDataAndRecords,
252
+ fragmentReference,
253
+ } as const;
254
+ // @ts-expect-error
255
+ environment.subscriptions.add(fragmentSubscription);
256
+ // @ts-expect-error
257
+ return () => environment.subscriptions.delete(fragmentSubscription);
236
258
  }
237
259
 
238
260
  export function onNextChange(environment: IsographEnvironment): Promise<void> {
239
261
  return new Promise((resolve) => {
240
- const unsubscribe = subscribe(environment, null, () => {
262
+ const unsubscribe = subscribeToAnyChange(environment, () => {
241
263
  unsubscribe();
242
264
  resolve();
243
265
  });
@@ -248,11 +270,57 @@ function callSubscriptions(
248
270
  environment: IsographEnvironment,
249
271
  recordsEncounteredWhenNormalizing: Set<DataId>,
250
272
  ) {
251
- environment.subscriptions.forEach(({ callback, records }) => {
252
- if (records === null) {
253
- callback();
254
- } else if (hasOverlappingIds(recordsEncounteredWhenNormalizing, records)) {
255
- callback();
273
+ environment.subscriptions.forEach((subscription) => {
274
+ switch (subscription.kind) {
275
+ case 'FragmentSubscription': {
276
+ // TODO if there are multiple components subscribed to the same
277
+ // fragment, we will call readButNotEvaluate multiple times. We
278
+ // should fix that.
279
+ if (
280
+ hasOverlappingIds(
281
+ recordsEncounteredWhenNormalizing,
282
+ subscription.encounteredDataAndRecords.encounteredRecords,
283
+ )
284
+ ) {
285
+ const newEncounteredDataAndRecords = readButDoNotEvaluate(
286
+ environment,
287
+ subscription.fragmentReference,
288
+ );
289
+
290
+ if (
291
+ !areEqualObjectsWithDeepComparison(
292
+ subscription.encounteredDataAndRecords.item,
293
+ newEncounteredDataAndRecords.item,
294
+ )
295
+ ) {
296
+ if (typeof window !== 'undefined' && window.__LOG) {
297
+ console.log('Deep equality - No', {
298
+ fragmentReference: subscription.fragmentReference,
299
+ old: subscription.encounteredDataAndRecords.item,
300
+ new: newEncounteredDataAndRecords.item,
301
+ });
302
+ }
303
+ // TODO deep compare values
304
+ subscription.callback(newEncounteredDataAndRecords);
305
+ } else {
306
+ if (typeof window !== 'undefined' && window.__LOG) {
307
+ console.log('Deep equality - Yes', {
308
+ fragmentReference: subscription.fragmentReference,
309
+ old: subscription.encounteredDataAndRecords.item,
310
+ });
311
+ }
312
+ }
313
+ }
314
+ return;
315
+ }
316
+ case 'AnyRecords': {
317
+ return subscription.callback();
318
+ }
319
+ default: {
320
+ // @ts-expect-error(6133)
321
+ const _: never = subscription;
322
+ throw new Error('Unexpected case');
323
+ }
256
324
  }
257
325
  });
258
326
  }
@@ -1,51 +1,49 @@
1
1
  import { stableCopy } from './cache';
2
- import { RefetchQueryArtifactWrapper } from './entrypoint';
3
- import { IsographEnvironment, DataId } from './IsographEnvironment';
4
- import { readButDoNotEvaluate } from './read';
5
- import { ReaderArtifact } from './reader';
6
- import { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ import { FragmentReference } from './FragmentReference';
4
+ import { useReadAndSubscribe } from './useReadAndSubscribe';
7
5
 
8
6
  export function getOrCreateCachedComponent(
9
7
  environment: IsographEnvironment,
10
- rootId: DataId,
11
8
  componentName: string,
12
- readerArtifact: ReaderArtifact<any, any>,
13
- variables: { [key: string]: string },
14
- resolverRefetchQueries: RefetchQueryArtifactWrapper[],
15
- ) {
9
+ fragmentReference: FragmentReference<any, any>,
10
+ ): React.FC<any> {
11
+ // cachedComponentsById is a three layer cache: id, then component name, then
12
+ // stringified args. These three, together, uniquely identify a read at a given
13
+ // time.
16
14
  const cachedComponentsById = environment.componentCache;
17
- const stringifiedArgs = JSON.stringify(stableCopy(variables));
18
- cachedComponentsById[rootId] = cachedComponentsById[rootId] ?? {};
19
- const componentsByName = cachedComponentsById[rootId];
15
+
16
+ cachedComponentsById[fragmentReference.root] =
17
+ cachedComponentsById[fragmentReference.root] ?? {};
18
+ const componentsByName = cachedComponentsById[fragmentReference.root];
19
+
20
20
  componentsByName[componentName] = componentsByName[componentName] ?? {};
21
21
  const byArgs = componentsByName[componentName];
22
+
23
+ const stringifiedArgs = JSON.stringify(
24
+ stableCopy(fragmentReference.variables),
25
+ );
22
26
  byArgs[stringifiedArgs] =
23
27
  byArgs[stringifiedArgs] ??
24
28
  (() => {
25
29
  function Component(additionalRuntimeProps: { [key: string]: any }) {
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
- );
30
+ const data = useReadAndSubscribe(environment, fragmentReference);
41
31
 
42
32
  if (typeof window !== 'undefined' && window.__LOG) {
43
- console.log('Component re-rendered: ' + componentName + ' ' + rootId);
33
+ console.log(
34
+ 'Component re-rendered: ' +
35
+ componentName +
36
+ ' ' +
37
+ fragmentReference.root,
38
+ );
44
39
  }
45
40
 
46
- return readerArtifact.resolver(data, additionalRuntimeProps);
41
+ return fragmentReference.readerArtifact.resolver(
42
+ data,
43
+ additionalRuntimeProps,
44
+ );
47
45
  }
48
- Component.displayName = `${componentName} (id: ${rootId}) @component`;
46
+ Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
49
47
  return Component;
50
48
  })();
51
49
  return byArgs[stringifiedArgs];
package/src/index.ts CHANGED
@@ -47,7 +47,7 @@ export {
47
47
  RefetchQueryArtifact,
48
48
  RefetchQueryArtifactWrapper,
49
49
  } from './entrypoint';
50
- export { read, readButDoNotEvaluate } from './read';
50
+ export { readButDoNotEvaluate } from './read';
51
51
  export { useResult } from './useResult';
52
52
  export { type FragmentReference } from './FragmentReference';
53
53
  export { useLazyReference } from './useLazyReference';
@@ -58,4 +58,4 @@ export {
58
58
  ArgumentValue,
59
59
  Arguments,
60
60
  } from './util';
61
- export { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
61
+ export { useRerenderOnChange } from './useRerenderOnChange';
package/src/read.ts CHANGED
@@ -15,48 +15,6 @@ export type WithEncounteredRecords<T> = {
15
15
  item: T;
16
16
  };
17
17
 
18
- export function read<TReadFromStore extends Object, TClientFieldValue>(
19
- environment: IsographEnvironment,
20
- fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
21
- ): WithEncounteredRecords<TClientFieldValue> {
22
- const variant = fragmentReference.readerArtifact.variant;
23
- if (variant.kind === 'Eager') {
24
- const mutableEncounteredRecords = new Set<DataId>();
25
- const data = readData(
26
- environment,
27
- fragmentReference.readerArtifact.readerAst,
28
- fragmentReference.root,
29
- fragmentReference.variables ?? {},
30
- fragmentReference.nestedRefetchQueries,
31
- mutableEncounteredRecords,
32
- );
33
- if (data.kind === 'MissingData') {
34
- throw onNextChange(environment);
35
- } else {
36
- return {
37
- encounteredRecords: mutableEncounteredRecords,
38
- // @ts-expect-error This not properly typed yet
39
- item: fragmentReference.readerArtifact.resolver(data.data),
40
- };
41
- }
42
- } else if (variant.kind === 'Component') {
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
- };
55
- }
56
- // Why can't Typescript realize that this is unreachable??
57
- throw new Error('This is unreachable');
58
- }
59
-
60
18
  export function readButDoNotEvaluate<TReadFromStore extends Object>(
61
19
  environment: IsographEnvironment,
62
20
  reference: FragmentReference<TReadFromStore, unknown>,
@@ -245,9 +203,6 @@ function readData<TReadFromStore>(
245
203
  [],
246
204
  mutableEncounteredRecords,
247
205
  );
248
- if (typeof window !== 'undefined' && window.__LOG) {
249
- console.log('refetch field data', data, field);
250
- }
251
206
  if (data.kind === 'MissingData') {
252
207
  return {
253
208
  kind: 'MissingData',
@@ -288,9 +243,6 @@ function readData<TReadFromStore>(
288
243
  [],
289
244
  mutableEncounteredRecords,
290
245
  );
291
- if (typeof window !== 'undefined' && window.__LOG) {
292
- console.log('refetch field data', data, field);
293
- }
294
246
  if (data.kind === 'MissingData') {
295
247
  return {
296
248
  kind: 'MissingData',
@@ -345,11 +297,14 @@ function readData<TReadFromStore>(
345
297
  } else if (variant.kind === 'Component') {
346
298
  target[field.alias] = getOrCreateCachedComponent(
347
299
  environment,
348
- root,
349
300
  variant.componentName,
350
- field.readerArtifact,
351
- variables,
352
- resolverRefetchQueries,
301
+ {
302
+ kind: 'FragmentReference',
303
+ readerArtifact: field.readerArtifact,
304
+ root,
305
+ variables,
306
+ nestedRefetchQueries: resolverRefetchQueries,
307
+ } as const,
353
308
  );
354
309
  }
355
310
  break;
package/src/reader.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import { ComponentOrFieldName } from './IsographEnvironment';
1
2
  import { Arguments } from './util';
2
3
 
3
4
  // TODO this should probably be at least three distinct types, for @component,
4
5
  // non-@component and refetch resolvers
5
6
  export type ReaderArtifact<TReadFromStore extends Object, TClientFieldValue> = {
6
7
  kind: 'ReaderArtifact';
8
+ fieldName: ComponentOrFieldName;
7
9
  readerAst: ReaderAst<TReadFromStore>;
10
+ // TODO move resolver into the variant
8
11
  resolver: (data: TReadFromStore, runtimeProps: any) => TClientFieldValue;
9
12
  variant: ReaderResolverVariant;
10
13
  };
@@ -25,6 +25,7 @@ const artifact: ReaderArtifact<
25
25
  Query__meName__outputType
26
26
  > = {
27
27
  kind: "ReaderArtifact",
28
+ fieldName: "meName",
28
29
  resolver: resolver as any,
29
30
  readerAst,
30
31
  variant: { kind: "Eager" },
@@ -47,6 +47,7 @@ const artifact: ReaderArtifact<
47
47
  Query__meNameSuccessor__outputType
48
48
  > = {
49
49
  kind: "ReaderArtifact",
50
+ fieldName: "meNameSuccessor",
50
51
  resolver: resolver as any,
51
52
  readerAst,
52
53
  variant: { kind: "Eager" },
@@ -30,6 +30,7 @@ const artifact: ReaderArtifact<
30
30
  Query__nodeField__outputType
31
31
  > = {
32
32
  kind: "ReaderArtifact",
33
+ fieldName: "nodeField",
33
34
  resolver: resolver as any,
34
35
  readerAst,
35
36
  variant: { kind: "Eager" },
@@ -0,0 +1,25 @@
1
+ import { useState } from 'react';
2
+ import { FragmentReference } from './FragmentReference';
3
+ import { IsographEnvironment } from './IsographEnvironment';
4
+ import { readButDoNotEvaluate } from './read';
5
+ import { useRerenderOnChange } from './useRerenderOnChange';
6
+
7
+ /**
8
+ * Read the data from a fragment reference and subscribe to updates.
9
+ * Does not pass the data to the fragment reference's resolver function.
10
+ */
11
+ export function useReadAndSubscribe<TReadFromStore extends Object>(
12
+ environment: IsographEnvironment,
13
+ fragmentReference: FragmentReference<TReadFromStore, any>,
14
+ ): TReadFromStore {
15
+ const [readOutDataAndRecords, setReadOutDataAndRecords] = useState(() =>
16
+ readButDoNotEvaluate(environment, fragmentReference),
17
+ );
18
+ useRerenderOnChange(
19
+ environment,
20
+ readOutDataAndRecords,
21
+ fragmentReference,
22
+ setReadOutDataAndRecords,
23
+ );
24
+ return readOutDataAndRecords.item;
25
+ }
@@ -0,0 +1,33 @@
1
+ import { useEffect } from 'react';
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ import { subscribe } from './cache';
4
+ import { WithEncounteredRecords } from './read';
5
+ import { FragmentReference } from './FragmentReference';
6
+
7
+ // TODO add unit tests for this. Add integration tests that test
8
+ // behavior when the encounteredRecords underneath a fragment change.
9
+ export function useRerenderOnChange<TReadFromStore extends Object>(
10
+ environment: IsographEnvironment,
11
+ encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
12
+ fragmentReference: FragmentReference<any, any>,
13
+ setEncounteredDataAndRecords: (
14
+ data: WithEncounteredRecords<TReadFromStore>,
15
+ ) => void,
16
+ ) {
17
+ useEffect(() => {
18
+ return subscribe(
19
+ environment,
20
+ encounteredDataAndRecords,
21
+ fragmentReference,
22
+ (newEncounteredDataAndRecords) => {
23
+ setEncounteredDataAndRecords(newEncounteredDataAndRecords);
24
+ },
25
+ );
26
+ // Note: this is an empty array on purpose:
27
+ // - the fragment reference is stable for the life of the component
28
+ // - ownership of encounteredDataAndRecords is transferred into the
29
+ // environment
30
+ // - though maybe we need to include setEncounteredDataAndRecords in
31
+ // the dependency array
32
+ }, []);
33
+ }
package/src/useResult.ts CHANGED
@@ -1,19 +1,26 @@
1
1
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
2
- import { read } from './read';
3
2
  import { FragmentReference } from './FragmentReference';
4
- import { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
3
+ import { getOrCreateCachedComponent } from './componentCache';
4
+ import { useReadAndSubscribe } from './useReadAndSubscribe';
5
5
 
6
6
  export function useResult<TReadFromStore extends Object, TClientFieldValue>(
7
7
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
8
8
  ): TClientFieldValue {
9
9
  const environment = useIsographEnvironment();
10
10
 
11
- const { item: data, encounteredRecords } = read(
12
- environment,
13
- fragmentReference,
14
- );
15
-
16
- useRerenderWhenEncounteredRecordChanges(environment, encounteredRecords);
17
-
18
- return data;
11
+ switch (fragmentReference.readerArtifact.variant.kind) {
12
+ case 'Component': {
13
+ // @ts-expect-error
14
+ return getOrCreateCachedComponent(
15
+ environment,
16
+ fragmentReference.readerArtifact.variant.componentName,
17
+ fragmentReference,
18
+ );
19
+ }
20
+ case 'Eager': {
21
+ const data = useReadAndSubscribe(environment, fragmentReference);
22
+ // @ts-expect-error resolver is incorrectly typed in ReaderArtifact
23
+ return fragmentReference.readerArtifact.resolver(data);
24
+ }
25
+ }
19
26
  }
@@ -1,2 +0,0 @@
1
- import { DataId, IsographEnvironment } from './IsographEnvironment';
2
- export declare function useRerenderWhenEncounteredRecordChanges(environment: IsographEnvironment, encounteredRecords: Set<DataId>): void;
@@ -1,17 +0,0 @@
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
- // TODO this is probably buggy — we should re-evaluate the effect when
13
- // encounteredRecords changes. However, it is not a stable object, so...
14
- // how?
15
- }, []);
16
- }
17
- exports.useRerenderWhenEncounteredRecordChanges = useRerenderWhenEncounteredRecordChanges;
@@ -1,18 +0,0 @@
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
- // TODO this is probably buggy — we should re-evaluate the effect when
15
- // encounteredRecords changes. However, it is not a stable object, so...
16
- // how?
17
- }, []);
18
- }