@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.
- package/dist/FragmentReference.d.ts +4 -3
- package/dist/IsographEnvironment.d.ts +17 -7
- package/dist/areEqualWithDeepComparison.d.ts +3 -0
- package/dist/areEqualWithDeepComparison.js +61 -0
- package/dist/cache.d.ts +5 -2
- package/dist/cache.js +60 -13
- package/dist/componentCache.d.ts +3 -6
- package/dist/componentCache.js +16 -17
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -4
- package/dist/read.d.ts +0 -1
- package/dist/read.js +8 -36
- package/dist/reader.d.ts +2 -0
- package/dist/useReadAndSubscribe.d.ts +7 -0
- package/dist/useReadAndSubscribe.js +16 -0
- package/dist/useRerenderOnChange.d.ts +4 -0
- package/dist/useRerenderOnChange.js +21 -0
- package/dist/useResult.js +13 -5
- package/package.json +3 -3
- package/src/FragmentReference.ts +3 -1
- package/src/IsographEnvironment.tsx +20 -7
- package/src/areEqualWithDeepComparison.ts +78 -0
- package/src/cache.ts +81 -13
- package/src/componentCache.ts +29 -31
- package/src/index.ts +2 -2
- package/src/read.ts +7 -52
- package/src/reader.ts +3 -0
- package/src/tests/__isograph/Query/meName/reader.ts +1 -0
- package/src/tests/__isograph/Query/meNameSuccessor/reader.ts +1 -0
- package/src/tests/__isograph/Query/nodeField/reader.ts +1 -0
- package/src/useReadAndSubscribe.ts +25 -0
- package/src/useRerenderOnChange.ts +33 -0
- package/src/useResult.ts +17 -10
- package/dist/useRerenderWhenEncounteredRecordChanges.d.ts +0 -2
- package/dist/useRerenderWhenEncounteredRecordChanges.js +0 -17
- 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
|
-
|
5
|
-
|
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:
|
10
|
+
[key: ComponentOrFieldName]: {
|
9
11
|
[key: StringifiedArgs]: React.FC<any>;
|
10
12
|
};
|
11
13
|
};
|
12
14
|
};
|
13
|
-
type
|
14
|
-
|
15
|
-
|
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
|
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 {
|
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
|
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
|
112
|
-
const
|
113
|
+
function subscribeToAnyChange(environment, callback) {
|
114
|
+
const subscription = {
|
115
|
+
kind: 'AnyRecords',
|
113
116
|
callback,
|
114
|
-
records: encounteredRecords,
|
115
117
|
};
|
116
|
-
environment.subscriptions.add(
|
117
|
-
return () => environment.subscriptions.delete(
|
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 =
|
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((
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
}
|
package/dist/componentCache.d.ts
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
/// <reference types="react" />
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
|
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>;
|
package/dist/componentCache.js
CHANGED
@@ -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
|
6
|
-
|
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
|
-
|
11
|
-
|
12
|
-
const componentsByName = cachedComponentsById[
|
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
|
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: ' +
|
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: ${
|
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 {
|
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 {
|
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.
|
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
|
33
|
-
Object.defineProperty(exports, "
|
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 =
|
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,
|
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
|
6
|
-
const
|
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
|
-
|
10
|
-
|
11
|
-
|
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-
|
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-
|
20
|
-
"@isograph/react-disposable-state": "0.0.0-main-
|
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": {
|
package/src/FragmentReference.ts
CHANGED
@@ -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:
|
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
|
5
|
-
type StringifiedArgs = string;
|
6
|
+
export type ComponentOrFieldName = string;
|
7
|
+
export type StringifiedArgs = string;
|
6
8
|
type ComponentCache = {
|
7
9
|
[key: DataId]: {
|
8
|
-
[key:
|
10
|
+
[key: ComponentOrFieldName]: { [key: StringifiedArgs]: React.FC<any> };
|
9
11
|
};
|
10
12
|
};
|
11
13
|
|
12
|
-
type
|
13
|
-
|
14
|
-
|
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
|
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
|
228
|
+
export function subscribeToAnyChange(
|
226
229
|
environment: IsographEnvironment,
|
227
|
-
encounteredRecords: Set<DataId> | null,
|
228
230
|
callback: () => void,
|
229
231
|
): () => void {
|
230
|
-
const
|
232
|
+
const subscription = {
|
233
|
+
kind: 'AnyRecords',
|
231
234
|
callback,
|
232
|
-
|
233
|
-
|
234
|
-
environment.subscriptions.
|
235
|
-
|
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 =
|
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((
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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
|
}
|
package/src/componentCache.ts
CHANGED
@@ -1,51 +1,49 @@
|
|
1
1
|
import { stableCopy } from './cache';
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import {
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
18
|
-
cachedComponentsById[
|
19
|
-
|
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
|
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(
|
33
|
+
console.log(
|
34
|
+
'Component re-rendered: ' +
|
35
|
+
componentName +
|
36
|
+
' ' +
|
37
|
+
fragmentReference.root,
|
38
|
+
);
|
44
39
|
}
|
45
40
|
|
46
|
-
return readerArtifact.resolver(
|
41
|
+
return fragmentReference.readerArtifact.resolver(
|
42
|
+
data,
|
43
|
+
additionalRuntimeProps,
|
44
|
+
);
|
47
45
|
}
|
48
|
-
Component.displayName = `${componentName} (id: ${
|
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 {
|
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 {
|
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
|
-
|
351
|
-
|
352
|
-
|
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
|
};
|
@@ -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 {
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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,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
|
-
}
|