@isograph/react 0.0.0-main-f524690b → 0.0.0-main-38d73d29
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/IsographEnvironment.d.ts +56 -0
- package/dist/IsographEnvironment.js +65 -0
- package/dist/cache.d.ts +5 -21
- package/dist/cache.js +36 -58
- package/dist/componentCache.d.ts +2 -2
- package/dist/componentCache.js +3 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +39 -39
- package/package.json +3 -3
- package/src/IsographEnvironment.tsx +126 -0
- package/src/{cache.ts → cache.tsx} +47 -81
- package/src/componentCache.ts +5 -9
- package/src/index.tsx +66 -53
@@ -0,0 +1,126 @@
|
|
1
|
+
import { ReactNode, createContext, useContext } from 'react';
|
2
|
+
import * as React from 'react';
|
3
|
+
import { subscribe } from './cache';
|
4
|
+
import { ParentCache } from '@isograph/isograph-react-disposable-state';
|
5
|
+
|
6
|
+
export const IsographEnvironmentContext =
|
7
|
+
createContext<IsographEnvironment | null>(null);
|
8
|
+
|
9
|
+
type ComponentName = string;
|
10
|
+
type StringifiedArgs = string;
|
11
|
+
type ComponentCache = {
|
12
|
+
[key: DataId]: {
|
13
|
+
[key: ComponentName]: { [key: StringifiedArgs]: React.FC<any> };
|
14
|
+
};
|
15
|
+
};
|
16
|
+
|
17
|
+
export type Subscriptions = Set<() => void>;
|
18
|
+
type SuspenseCache = { [index: string]: ParentCache<any> };
|
19
|
+
|
20
|
+
export type IsographEnvironment = {
|
21
|
+
store: IsographStore;
|
22
|
+
networkFunction: IsographNetworkFunction;
|
23
|
+
missingFieldHandler: MissingFieldHandler | null;
|
24
|
+
componentCache: ComponentCache;
|
25
|
+
subscriptions: Subscriptions;
|
26
|
+
suspenseCache: SuspenseCache;
|
27
|
+
};
|
28
|
+
|
29
|
+
export type MissingFieldHandler = (
|
30
|
+
storeRecord: StoreRecord,
|
31
|
+
root: DataId,
|
32
|
+
fieldName: string,
|
33
|
+
arguments_: { [index: string]: any } | null,
|
34
|
+
variables: { [index: string]: any } | null,
|
35
|
+
) => Link | undefined;
|
36
|
+
|
37
|
+
export type IsographNetworkFunction = (
|
38
|
+
queryText: string,
|
39
|
+
variables: object,
|
40
|
+
) => Promise<any>;
|
41
|
+
|
42
|
+
export type Link = {
|
43
|
+
__link: DataId;
|
44
|
+
};
|
45
|
+
export type DataTypeValue =
|
46
|
+
// N.B. undefined is here to support optional id's, but
|
47
|
+
// undefined should not *actually* be present in the store.
|
48
|
+
| undefined
|
49
|
+
// Singular scalar fields:
|
50
|
+
| number
|
51
|
+
| boolean
|
52
|
+
| string
|
53
|
+
| null
|
54
|
+
// Singular linked fields:
|
55
|
+
| Link
|
56
|
+
// Plural scalar and linked fields:
|
57
|
+
| DataTypeValue[];
|
58
|
+
|
59
|
+
export type StoreRecord = {
|
60
|
+
[index: DataId | string]: DataTypeValue;
|
61
|
+
// TODO __typename?: T, which is restricted to being a concrete string
|
62
|
+
// TODO this shouldn't always be named id
|
63
|
+
id?: DataId;
|
64
|
+
};
|
65
|
+
|
66
|
+
export type DataId = string;
|
67
|
+
|
68
|
+
export const ROOT_ID: DataId & '__ROOT' = '__ROOT';
|
69
|
+
|
70
|
+
export type IsographStore = {
|
71
|
+
[index: DataId]: StoreRecord | null;
|
72
|
+
__ROOT: StoreRecord;
|
73
|
+
};
|
74
|
+
|
75
|
+
export type IsographEnvironmentProviderProps = {
|
76
|
+
environment: IsographEnvironment;
|
77
|
+
children: ReactNode;
|
78
|
+
};
|
79
|
+
|
80
|
+
export function IsographEnvironmentProvider({
|
81
|
+
environment,
|
82
|
+
children,
|
83
|
+
}: IsographEnvironmentProviderProps) {
|
84
|
+
const [, setState] = React.useState<object | void>();
|
85
|
+
React.useEffect(() => {
|
86
|
+
return subscribe(environment, () => setState({}));
|
87
|
+
}, []);
|
88
|
+
|
89
|
+
return (
|
90
|
+
<IsographEnvironmentContext.Provider value={environment}>
|
91
|
+
{children}
|
92
|
+
</IsographEnvironmentContext.Provider>
|
93
|
+
);
|
94
|
+
}
|
95
|
+
|
96
|
+
export function useIsographEnvironment(): IsographEnvironment {
|
97
|
+
const context = useContext(IsographEnvironmentContext);
|
98
|
+
if (context == null) {
|
99
|
+
throw new Error(
|
100
|
+
'Unexpected null environment context. Make sure to render ' +
|
101
|
+
'this component within an IsographEnvironmentProvider component',
|
102
|
+
);
|
103
|
+
}
|
104
|
+
return context;
|
105
|
+
}
|
106
|
+
|
107
|
+
export function createIsographEnvironment(
|
108
|
+
store: IsographStore,
|
109
|
+
networkFunction: IsographNetworkFunction,
|
110
|
+
missingFieldHandler?: MissingFieldHandler,
|
111
|
+
): IsographEnvironment {
|
112
|
+
return {
|
113
|
+
store,
|
114
|
+
networkFunction,
|
115
|
+
missingFieldHandler: missingFieldHandler ?? null,
|
116
|
+
componentCache: {},
|
117
|
+
subscriptions: new Set(),
|
118
|
+
suspenseCache: {},
|
119
|
+
};
|
120
|
+
}
|
121
|
+
|
122
|
+
export function createIsographStore() {
|
123
|
+
return {
|
124
|
+
[ROOT_ID]: {},
|
125
|
+
};
|
126
|
+
}
|
@@ -15,6 +15,13 @@ import {
|
|
15
15
|
ReaderScalarField,
|
16
16
|
RefetchQueryArtifactWrapper,
|
17
17
|
} from './index';
|
18
|
+
import {
|
19
|
+
DataId,
|
20
|
+
ROOT_ID,
|
21
|
+
StoreRecord,
|
22
|
+
Link,
|
23
|
+
type IsographEnvironment,
|
24
|
+
} from './IsographEnvironment';
|
18
25
|
|
19
26
|
declare global {
|
20
27
|
interface Window {
|
@@ -22,24 +29,23 @@ declare global {
|
|
22
29
|
}
|
23
30
|
}
|
24
31
|
|
25
|
-
const cache: { [index: string]: ParentCache<any> } = {};
|
26
|
-
|
27
32
|
function getOrCreateCache<T>(
|
33
|
+
environment: IsographEnvironment,
|
28
34
|
index: string,
|
29
35
|
factory: Factory<T>,
|
30
36
|
): ParentCache<T> {
|
31
37
|
if (typeof window !== 'undefined' && window.__LOG) {
|
32
38
|
console.log('getting cache for', {
|
33
39
|
index,
|
34
|
-
cache: Object.keys(
|
35
|
-
found: !!
|
40
|
+
cache: Object.keys(environment.suspenseCache),
|
41
|
+
found: !!environment.suspenseCache[index],
|
36
42
|
});
|
37
43
|
}
|
38
|
-
if (
|
39
|
-
|
44
|
+
if (environment.suspenseCache[index] == null) {
|
45
|
+
environment.suspenseCache[index] = new ParentCache(factory);
|
40
46
|
}
|
41
47
|
|
42
|
-
return
|
48
|
+
return environment.suspenseCache[index];
|
43
49
|
}
|
44
50
|
|
45
51
|
/**
|
@@ -67,47 +73,39 @@ export function stableCopy<T>(value: T): T {
|
|
67
73
|
type IsoResolver = IsographEntrypoint<any, any, any>;
|
68
74
|
|
69
75
|
export function getOrCreateCacheForArtifact<T>(
|
76
|
+
environment: IsographEnvironment,
|
70
77
|
artifact: IsographEntrypoint<any, any, T>,
|
71
78
|
variables: object,
|
72
79
|
): ParentCache<PromiseWrapper<T>> {
|
73
80
|
const cacheKey = artifact.queryText + JSON.stringify(stableCopy(variables));
|
74
81
|
const factory: Factory<PromiseWrapper<T>> = () =>
|
75
|
-
makeNetworkRequest<T>(artifact, variables);
|
76
|
-
return getOrCreateCache<PromiseWrapper<T>>(cacheKey, factory);
|
77
|
-
}
|
78
|
-
|
79
|
-
let network: ((queryText: string, variables: object) => Promise<any>) | null;
|
80
|
-
|
81
|
-
// This is a hack until we store this in context somehow
|
82
|
-
export function setNetwork(newNetwork: typeof network) {
|
83
|
-
network = newNetwork;
|
82
|
+
makeNetworkRequest<T>(environment, artifact, variables);
|
83
|
+
return getOrCreateCache<PromiseWrapper<T>>(environment, cacheKey, factory);
|
84
84
|
}
|
85
85
|
|
86
86
|
export function makeNetworkRequest<T>(
|
87
|
+
environment: IsographEnvironment,
|
87
88
|
artifact: IsoResolver,
|
88
89
|
variables: object,
|
89
90
|
): ItemCleanupPair<PromiseWrapper<T>> {
|
90
91
|
if (typeof window !== 'undefined' && window.__LOG) {
|
91
92
|
console.log('make network request', artifact, variables);
|
92
93
|
}
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
const promise = network(artifact.queryText, variables).then(
|
98
|
-
(networkResponse) => {
|
94
|
+
const promise = environment
|
95
|
+
.networkFunction(artifact.queryText, variables)
|
96
|
+
.then((networkResponse) => {
|
99
97
|
if (typeof window !== 'undefined' && window.__LOG) {
|
100
98
|
console.log('network response', artifact);
|
101
99
|
}
|
102
100
|
normalizeData(
|
101
|
+
environment,
|
103
102
|
artifact.normalizationAst,
|
104
103
|
networkResponse.data,
|
105
104
|
variables,
|
106
105
|
artifact.nestedRefetchQueries,
|
107
106
|
);
|
108
107
|
return networkResponse.data;
|
109
|
-
}
|
110
|
-
);
|
108
|
+
});
|
111
109
|
|
112
110
|
const wrapper = wrapPromise(promise);
|
113
111
|
|
@@ -120,48 +118,6 @@ export function makeNetworkRequest<T>(
|
|
120
118
|
return response;
|
121
119
|
}
|
122
120
|
|
123
|
-
export type Link = {
|
124
|
-
__link: DataId;
|
125
|
-
};
|
126
|
-
export type DataTypeValue =
|
127
|
-
// N.B. undefined is here to support optional id's, but
|
128
|
-
// undefined should not *actually* be present in the store.
|
129
|
-
| undefined
|
130
|
-
// Singular scalar fields:
|
131
|
-
| number
|
132
|
-
| boolean
|
133
|
-
| string
|
134
|
-
| null
|
135
|
-
// Singular linked fields:
|
136
|
-
| Link
|
137
|
-
// Plural scalar and linked fields:
|
138
|
-
| DataTypeValue[];
|
139
|
-
|
140
|
-
export type StoreRecord = {
|
141
|
-
[index: DataId | string]: DataTypeValue;
|
142
|
-
// TODO __typename?: T, which is restricted to being a concrete string
|
143
|
-
// TODO this shouldn't always be named id
|
144
|
-
id?: DataId;
|
145
|
-
};
|
146
|
-
|
147
|
-
export type DataId = string;
|
148
|
-
|
149
|
-
export const ROOT_ID: DataId & '__ROOT' = '__ROOT';
|
150
|
-
let store: {
|
151
|
-
[index: DataId]: StoreRecord | null;
|
152
|
-
__ROOT: StoreRecord;
|
153
|
-
} = {
|
154
|
-
__ROOT: {},
|
155
|
-
};
|
156
|
-
export function getStore() {
|
157
|
-
return store;
|
158
|
-
}
|
159
|
-
export function clearStore() {
|
160
|
-
store = {
|
161
|
-
__ROOT: {},
|
162
|
-
};
|
163
|
-
}
|
164
|
-
|
165
121
|
type NetworkResponseScalarValue = string | number | boolean;
|
166
122
|
type NetworkResponseValue =
|
167
123
|
| NetworkResponseScalarValue
|
@@ -177,6 +133,7 @@ type NetworkResponseObject = {
|
|
177
133
|
};
|
178
134
|
|
179
135
|
function normalizeData(
|
136
|
+
environment: IsographEnvironment,
|
180
137
|
normalizationAst: NormalizationAst,
|
181
138
|
networkResponse: NetworkResponseObject,
|
182
139
|
variables: Object,
|
@@ -191,43 +148,46 @@ function normalizeData(
|
|
191
148
|
);
|
192
149
|
}
|
193
150
|
normalizeDataIntoRecord(
|
151
|
+
environment,
|
194
152
|
normalizationAst,
|
195
153
|
networkResponse,
|
196
|
-
store.__ROOT,
|
154
|
+
environment.store.__ROOT,
|
197
155
|
ROOT_ID,
|
198
156
|
variables as any,
|
199
157
|
nestedRefetchQueries,
|
200
158
|
);
|
201
159
|
if (typeof window !== 'undefined' && window.__LOG) {
|
202
|
-
console.log('after normalization', { store });
|
160
|
+
console.log('after normalization', { store: environment.store });
|
203
161
|
}
|
204
|
-
callSubscriptions();
|
162
|
+
callSubscriptions(environment);
|
205
163
|
}
|
206
164
|
|
207
|
-
export function subscribe(
|
208
|
-
|
209
|
-
|
165
|
+
export function subscribe(
|
166
|
+
environment: IsographEnvironment,
|
167
|
+
callback: () => void,
|
168
|
+
): () => void {
|
169
|
+
environment.subscriptions.add(callback);
|
170
|
+
return () => environment.subscriptions.delete(callback);
|
210
171
|
}
|
211
172
|
|
212
|
-
export function onNextChange(): Promise<void> {
|
173
|
+
export function onNextChange(environment: IsographEnvironment): Promise<void> {
|
213
174
|
return new Promise((resolve) => {
|
214
|
-
const unsubscribe = subscribe(() => {
|
175
|
+
const unsubscribe = subscribe(environment, () => {
|
215
176
|
unsubscribe();
|
216
177
|
resolve();
|
217
178
|
});
|
218
179
|
});
|
219
180
|
}
|
220
181
|
|
221
|
-
|
222
|
-
|
223
|
-
function callSubscriptions() {
|
224
|
-
subscriptions.forEach((callback) => callback());
|
182
|
+
function callSubscriptions(environment: IsographEnvironment) {
|
183
|
+
environment.subscriptions.forEach((callback) => callback());
|
225
184
|
}
|
226
185
|
|
227
186
|
/**
|
228
187
|
* Mutate targetParentRecord according to the normalizationAst and networkResponseParentRecord.
|
229
188
|
*/
|
230
189
|
function normalizeDataIntoRecord(
|
190
|
+
environment: IsographEnvironment,
|
231
191
|
normalizationAst: NormalizationAst,
|
232
192
|
networkResponseParentRecord: NetworkResponseObject,
|
233
193
|
targetParentRecord: StoreRecord,
|
@@ -248,6 +208,7 @@ function normalizeDataIntoRecord(
|
|
248
208
|
}
|
249
209
|
case 'Linked': {
|
250
210
|
normalizeLinkedField(
|
211
|
+
environment,
|
251
212
|
normalizationNode,
|
252
213
|
networkResponseParentRecord,
|
253
214
|
targetParentRecord,
|
@@ -285,6 +246,7 @@ function normalizeScalarField(
|
|
285
246
|
* Mutate targetParentRecord with a given linked field ast node.
|
286
247
|
*/
|
287
248
|
function normalizeLinkedField(
|
249
|
+
environment: IsographEnvironment,
|
288
250
|
astNode: NormalizationLinkedField,
|
289
251
|
networkResponseParentRecord: NetworkResponseObject,
|
290
252
|
targetParentRecord: StoreRecord,
|
@@ -309,10 +271,11 @@ function normalizeLinkedField(
|
|
309
271
|
|
310
272
|
if (Array.isArray(networkResponseData)) {
|
311
273
|
// TODO check astNode.plural or the like
|
312
|
-
const dataIds = [];
|
274
|
+
const dataIds: Link[] = [];
|
313
275
|
for (let i = 0; i < networkResponseData.length; i++) {
|
314
276
|
const networkResponseObject = networkResponseData[i];
|
315
277
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
278
|
+
environment,
|
316
279
|
astNode,
|
317
280
|
networkResponseObject,
|
318
281
|
targetParentRecordId,
|
@@ -325,6 +288,7 @@ function normalizeLinkedField(
|
|
325
288
|
targetParentRecord[parentRecordKey] = dataIds;
|
326
289
|
} else {
|
327
290
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
291
|
+
environment,
|
328
292
|
astNode,
|
329
293
|
networkResponseData,
|
330
294
|
targetParentRecordId,
|
@@ -339,6 +303,7 @@ function normalizeLinkedField(
|
|
339
303
|
}
|
340
304
|
|
341
305
|
function normalizeNetworkResponseObject(
|
306
|
+
environment: IsographEnvironment,
|
342
307
|
astNode: NormalizationLinkedField,
|
343
308
|
networkResponseData: NetworkResponseObject,
|
344
309
|
targetParentRecordId: string,
|
@@ -354,10 +319,11 @@ function normalizeNetworkResponseObject(
|
|
354
319
|
index,
|
355
320
|
);
|
356
321
|
|
357
|
-
const newStoreRecord = store[newStoreRecordId] ?? {};
|
358
|
-
store[newStoreRecordId] = newStoreRecord;
|
322
|
+
const newStoreRecord = environment.store[newStoreRecordId] ?? {};
|
323
|
+
environment.store[newStoreRecordId] = newStoreRecord;
|
359
324
|
|
360
325
|
normalizeDataIntoRecord(
|
326
|
+
environment,
|
361
327
|
astNode.selections,
|
362
328
|
networkResponseData,
|
363
329
|
newStoreRecord,
|
package/src/componentCache.ts
CHANGED
@@ -3,22 +3,18 @@ import {
|
|
3
3
|
RefetchQueryArtifactWrapper,
|
4
4
|
readButDoNotEvaluate,
|
5
5
|
} from './index';
|
6
|
-
import {
|
6
|
+
import { stableCopy } from './cache';
|
7
|
+
import { IsographEnvironment, DataId } from './IsographEnvironment';
|
7
8
|
|
8
|
-
type ComponentName = string;
|
9
|
-
type StringifiedArgs = string;
|
10
|
-
const cachedComponentsById: {
|
11
|
-
[key: DataId]: {
|
12
|
-
[key: ComponentName]: { [key: StringifiedArgs]: React.FC<any> };
|
13
|
-
};
|
14
|
-
} = {};
|
15
9
|
export function getOrCreateCachedComponent(
|
10
|
+
environment: IsographEnvironment,
|
16
11
|
root: DataId,
|
17
12
|
componentName: string,
|
18
13
|
readerArtifact: ReaderArtifact<any, any, any>,
|
19
14
|
variables: { [key: string]: string },
|
20
15
|
resolverRefetchQueries: RefetchQueryArtifactWrapper[],
|
21
16
|
) {
|
17
|
+
const cachedComponentsById = environment.componentCache;
|
22
18
|
const stringifiedArgs = JSON.stringify(stableCopy(variables));
|
23
19
|
cachedComponentsById[root] = cachedComponentsById[root] ?? {};
|
24
20
|
const componentsByName = cachedComponentsById[root];
|
@@ -28,7 +24,7 @@ export function getOrCreateCachedComponent(
|
|
28
24
|
byArgs[stringifiedArgs] ??
|
29
25
|
(() => {
|
30
26
|
function Component(additionalRuntimeProps) {
|
31
|
-
const data = readButDoNotEvaluate({
|
27
|
+
const data = readButDoNotEvaluate(environment, {
|
32
28
|
kind: 'FragmentReference',
|
33
29
|
readerArtifact: readerArtifact,
|
34
30
|
root,
|