@isograph/react 0.0.0-main-c88a0561 → 0.0.0-main-f49c67bb
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/cache.d.ts +3 -19
- package/dist/cache.js +29 -44
- package/dist/componentCache.d.ts +3 -3
- package/dist/componentCache.js +2 -2
- package/dist/context.d.ts +34 -0
- package/dist/context.js +48 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +35 -37
- package/package.json +3 -3
- package/src/{cache.ts → cache.tsx} +72 -79
- package/src/componentCache.ts +10 -3
- package/src/context.tsx +90 -0
- package/src/index.tsx +141 -59
@@ -1,4 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
Factory,
|
3
|
+
ItemCleanupPair,
|
4
|
+
ParentCache,
|
5
|
+
} from '@isograph/react-disposable-state';
|
2
6
|
import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
3
7
|
import {
|
4
8
|
Argument,
|
@@ -11,6 +15,13 @@ import {
|
|
11
15
|
ReaderScalarField,
|
12
16
|
RefetchQueryArtifactWrapper,
|
13
17
|
} from './index';
|
18
|
+
import {
|
19
|
+
DataId,
|
20
|
+
ROOT_ID,
|
21
|
+
StoreRecord,
|
22
|
+
Link,
|
23
|
+
type IsographEnvironment,
|
24
|
+
} from './context';
|
14
25
|
|
15
26
|
declare global {
|
16
27
|
interface Window {
|
@@ -20,7 +31,10 @@ declare global {
|
|
20
31
|
|
21
32
|
const cache: { [index: string]: ParentCache<any> } = {};
|
22
33
|
|
23
|
-
function getOrCreateCache<T>(
|
34
|
+
function getOrCreateCache<T>(
|
35
|
+
index: string,
|
36
|
+
factory: Factory<T>,
|
37
|
+
): ParentCache<T> {
|
24
38
|
if (typeof window !== 'undefined' && window.__LOG) {
|
25
39
|
console.log('getting cache for', {
|
26
40
|
index,
|
@@ -60,44 +74,39 @@ export function stableCopy<T>(value: T): T {
|
|
60
74
|
type IsoResolver = IsographEntrypoint<any, any, any>;
|
61
75
|
|
62
76
|
export function getOrCreateCacheForArtifact<T>(
|
77
|
+
environment: IsographEnvironment,
|
63
78
|
artifact: IsographEntrypoint<any, any, T>,
|
64
79
|
variables: object,
|
65
80
|
): ParentCache<PromiseWrapper<T>> {
|
66
81
|
const cacheKey = artifact.queryText + JSON.stringify(stableCopy(variables));
|
67
|
-
const factory: Factory<PromiseWrapper<T>> = () =>
|
82
|
+
const factory: Factory<PromiseWrapper<T>> = () =>
|
83
|
+
makeNetworkRequest<T>(environment, artifact, variables);
|
68
84
|
return getOrCreateCache<PromiseWrapper<T>>(cacheKey, factory);
|
69
85
|
}
|
70
86
|
|
71
|
-
let network: ((queryText: string, variables: object) => Promise<any>) | null;
|
72
|
-
|
73
|
-
// This is a hack until we store this in context somehow
|
74
|
-
export function setNetwork(newNetwork: typeof network) {
|
75
|
-
network = newNetwork;
|
76
|
-
}
|
77
|
-
|
78
87
|
export function makeNetworkRequest<T>(
|
88
|
+
environment: IsographEnvironment,
|
79
89
|
artifact: IsoResolver,
|
80
90
|
variables: object,
|
81
91
|
): ItemCleanupPair<PromiseWrapper<T>> {
|
82
92
|
if (typeof window !== 'undefined' && window.__LOG) {
|
83
93
|
console.log('make network request', artifact, variables);
|
84
94
|
}
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
});
|
95
|
+
const promise = environment
|
96
|
+
.networkFunction(artifact.queryText, variables)
|
97
|
+
.then((networkResponse) => {
|
98
|
+
if (typeof window !== 'undefined' && window.__LOG) {
|
99
|
+
console.log('network response', artifact);
|
100
|
+
}
|
101
|
+
normalizeData(
|
102
|
+
environment,
|
103
|
+
artifact.normalizationAst,
|
104
|
+
networkResponse.data,
|
105
|
+
variables,
|
106
|
+
artifact.nestedRefetchQueries,
|
107
|
+
);
|
108
|
+
return networkResponse.data;
|
109
|
+
});
|
101
110
|
|
102
111
|
const wrapper = wrapPromise(promise);
|
103
112
|
|
@@ -110,48 +119,6 @@ export function makeNetworkRequest<T>(
|
|
110
119
|
return response;
|
111
120
|
}
|
112
121
|
|
113
|
-
export type Link = {
|
114
|
-
__link: DataId;
|
115
|
-
};
|
116
|
-
export type DataTypeValue =
|
117
|
-
// N.B. undefined is here to support optional id's, but
|
118
|
-
// undefined should not *actually* be present in the store.
|
119
|
-
| undefined
|
120
|
-
// Singular scalar fields:
|
121
|
-
| number
|
122
|
-
| boolean
|
123
|
-
| string
|
124
|
-
| null
|
125
|
-
// Singular linked fields:
|
126
|
-
| Link
|
127
|
-
// Plural scalar and linked fields:
|
128
|
-
| DataTypeValue[];
|
129
|
-
|
130
|
-
export type StoreRecord = {
|
131
|
-
[index: DataId | string]: DataTypeValue;
|
132
|
-
// TODO __typename?: T, which is restricted to being a concrete string
|
133
|
-
// TODO this shouldn't always be named id
|
134
|
-
id?: DataId;
|
135
|
-
};
|
136
|
-
|
137
|
-
export type DataId = string;
|
138
|
-
|
139
|
-
export const ROOT_ID: DataId & '__ROOT' = '__ROOT';
|
140
|
-
let store: {
|
141
|
-
[index: DataId]: StoreRecord | null;
|
142
|
-
__ROOT: StoreRecord;
|
143
|
-
} = {
|
144
|
-
__ROOT: {},
|
145
|
-
};
|
146
|
-
export function getStore() {
|
147
|
-
return store;
|
148
|
-
}
|
149
|
-
export function clearStore() {
|
150
|
-
store = {
|
151
|
-
__ROOT: {},
|
152
|
-
};
|
153
|
-
}
|
154
|
-
|
155
122
|
type NetworkResponseScalarValue = string | number | boolean;
|
156
123
|
type NetworkResponseValue =
|
157
124
|
| NetworkResponseScalarValue
|
@@ -167,24 +134,31 @@ type NetworkResponseObject = {
|
|
167
134
|
};
|
168
135
|
|
169
136
|
function normalizeData(
|
137
|
+
environment: IsographEnvironment,
|
170
138
|
normalizationAst: NormalizationAst,
|
171
139
|
networkResponse: NetworkResponseObject,
|
172
140
|
variables: Object,
|
173
141
|
nestedRefetchQueries: RefetchQueryArtifactWrapper[],
|
174
142
|
) {
|
175
143
|
if (typeof window !== 'undefined' && window.__LOG) {
|
176
|
-
console.log(
|
144
|
+
console.log(
|
145
|
+
'about to normalize',
|
146
|
+
normalizationAst,
|
147
|
+
networkResponse,
|
148
|
+
variables,
|
149
|
+
);
|
177
150
|
}
|
178
151
|
normalizeDataIntoRecord(
|
152
|
+
environment,
|
179
153
|
normalizationAst,
|
180
154
|
networkResponse,
|
181
|
-
store.__ROOT,
|
155
|
+
environment.store.__ROOT,
|
182
156
|
ROOT_ID,
|
183
157
|
variables as any,
|
184
158
|
nestedRefetchQueries,
|
185
159
|
);
|
186
160
|
if (typeof window !== 'undefined' && window.__LOG) {
|
187
|
-
console.log('after normalization', { store });
|
161
|
+
console.log('after normalization', { store: environment.store });
|
188
162
|
}
|
189
163
|
callSubscriptions();
|
190
164
|
}
|
@@ -213,6 +187,7 @@ function callSubscriptions() {
|
|
213
187
|
* Mutate targetParentRecord according to the normalizationAst and networkResponseParentRecord.
|
214
188
|
*/
|
215
189
|
function normalizeDataIntoRecord(
|
190
|
+
environment: IsographEnvironment,
|
216
191
|
normalizationAst: NormalizationAst,
|
217
192
|
networkResponseParentRecord: NetworkResponseObject,
|
218
193
|
targetParentRecord: StoreRecord,
|
@@ -233,6 +208,7 @@ function normalizeDataIntoRecord(
|
|
233
208
|
}
|
234
209
|
case 'Linked': {
|
235
210
|
normalizeLinkedField(
|
211
|
+
environment,
|
236
212
|
normalizationNode,
|
237
213
|
networkResponseParentRecord,
|
238
214
|
targetParentRecord,
|
@@ -256,7 +232,10 @@ function normalizeScalarField(
|
|
256
232
|
const networkResponseData = networkResponseParentRecord[networkResponseKey];
|
257
233
|
const parentRecordKey = getParentRecordKey(astNode, variables);
|
258
234
|
|
259
|
-
if (
|
235
|
+
if (
|
236
|
+
networkResponseData == null ||
|
237
|
+
isScalarOrEmptyArray(networkResponseData)
|
238
|
+
) {
|
260
239
|
targetStoreRecord[parentRecordKey] = networkResponseData;
|
261
240
|
} else {
|
262
241
|
throw new Error('Unexpected object array when normalizing scalar');
|
@@ -267,6 +246,7 @@ function normalizeScalarField(
|
|
267
246
|
* Mutate targetParentRecord with a given linked field ast node.
|
268
247
|
*/
|
269
248
|
function normalizeLinkedField(
|
249
|
+
environment: IsographEnvironment,
|
270
250
|
astNode: NormalizationLinkedField,
|
271
251
|
networkResponseParentRecord: NetworkResponseObject,
|
272
252
|
targetParentRecord: StoreRecord,
|
@@ -284,15 +264,18 @@ function normalizeLinkedField(
|
|
284
264
|
}
|
285
265
|
|
286
266
|
if (isScalarButNotEmptyArray(networkResponseData)) {
|
287
|
-
throw new Error(
|
267
|
+
throw new Error(
|
268
|
+
'Unexpected scalar network response when normalizing a linked field',
|
269
|
+
);
|
288
270
|
}
|
289
271
|
|
290
272
|
if (Array.isArray(networkResponseData)) {
|
291
273
|
// TODO check astNode.plural or the like
|
292
|
-
const dataIds = [];
|
274
|
+
const dataIds: Link[] = [];
|
293
275
|
for (let i = 0; i < networkResponseData.length; i++) {
|
294
276
|
const networkResponseObject = networkResponseData[i];
|
295
277
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
278
|
+
environment,
|
296
279
|
astNode,
|
297
280
|
networkResponseObject,
|
298
281
|
targetParentRecordId,
|
@@ -305,6 +288,7 @@ function normalizeLinkedField(
|
|
305
288
|
targetParentRecord[parentRecordKey] = dataIds;
|
306
289
|
} else {
|
307
290
|
const newStoreRecordId = normalizeNetworkResponseObject(
|
291
|
+
environment,
|
308
292
|
astNode,
|
309
293
|
networkResponseData,
|
310
294
|
targetParentRecordId,
|
@@ -319,6 +303,7 @@ function normalizeLinkedField(
|
|
319
303
|
}
|
320
304
|
|
321
305
|
function normalizeNetworkResponseObject(
|
306
|
+
environment: IsographEnvironment,
|
322
307
|
astNode: NormalizationLinkedField,
|
323
308
|
networkResponseData: NetworkResponseObject,
|
324
309
|
targetParentRecordId: string,
|
@@ -334,10 +319,11 @@ function normalizeNetworkResponseObject(
|
|
334
319
|
index,
|
335
320
|
);
|
336
321
|
|
337
|
-
const newStoreRecord = store[newStoreRecordId] ?? {};
|
338
|
-
store[newStoreRecordId] = newStoreRecord;
|
322
|
+
const newStoreRecord = environment.store[newStoreRecordId] ?? {};
|
323
|
+
environment.store[newStoreRecordId] = newStoreRecord;
|
339
324
|
|
340
325
|
normalizeDataIntoRecord(
|
326
|
+
environment,
|
341
327
|
astNode.selections,
|
342
328
|
networkResponseData,
|
343
329
|
newStoreRecord,
|
@@ -358,7 +344,9 @@ function isScalarOrEmptyArray(
|
|
358
344
|
return (data as any).every((x: any) => isScalarOrEmptyArray(x));
|
359
345
|
}
|
360
346
|
const isScalarValue =
|
361
|
-
typeof data === 'string' ||
|
347
|
+
typeof data === 'string' ||
|
348
|
+
typeof data === 'number' ||
|
349
|
+
typeof data === 'boolean';
|
362
350
|
return isScalarValue;
|
363
351
|
}
|
364
352
|
|
@@ -374,7 +362,9 @@ function isScalarButNotEmptyArray(
|
|
374
362
|
return (data as any).every((x: any) => isScalarOrEmptyArray(x));
|
375
363
|
}
|
376
364
|
const isScalarValue =
|
377
|
-
typeof data === 'string' ||
|
365
|
+
typeof data === 'string' ||
|
366
|
+
typeof data === 'number' ||
|
367
|
+
typeof data === 'boolean';
|
378
368
|
return isScalarValue;
|
379
369
|
}
|
380
370
|
|
@@ -418,7 +408,10 @@ function getStoreKeyChunkForArgumentValue(
|
|
418
408
|
}
|
419
409
|
}
|
420
410
|
|
421
|
-
function getStoreKeyChunkForArgument(
|
411
|
+
function getStoreKeyChunkForArgument(
|
412
|
+
argument: Argument,
|
413
|
+
variables: { [index: string]: string },
|
414
|
+
) {
|
422
415
|
const chunk = getStoreKeyChunkForArgumentValue(argument[1], variables);
|
423
416
|
return `${FIRST_SPLIT_KEY}${argument[0]}${SECOND_SPLIT_KEY}${chunk}`;
|
424
417
|
}
|
package/src/componentCache.ts
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
-
import {
|
2
|
-
|
1
|
+
import {
|
2
|
+
DataId,
|
3
|
+
ReaderArtifact,
|
4
|
+
RefetchQueryArtifactWrapper,
|
5
|
+
readButDoNotEvaluate,
|
6
|
+
} from './index';
|
7
|
+
import { stableCopy } from './cache';
|
8
|
+
import { IsographEnvironment } from '../dist';
|
3
9
|
|
4
10
|
type ComponentName = string;
|
5
11
|
type StringifiedArgs = string;
|
@@ -9,6 +15,7 @@ const cachedComponentsById: {
|
|
9
15
|
};
|
10
16
|
} = {};
|
11
17
|
export function getOrCreateCachedComponent(
|
18
|
+
environment: IsographEnvironment,
|
12
19
|
root: DataId,
|
13
20
|
componentName: string,
|
14
21
|
readerArtifact: ReaderArtifact<any, any, any>,
|
@@ -24,7 +31,7 @@ export function getOrCreateCachedComponent(
|
|
24
31
|
byArgs[stringifiedArgs] ??
|
25
32
|
(() => {
|
26
33
|
function Component(additionalRuntimeProps) {
|
27
|
-
const data = readButDoNotEvaluate({
|
34
|
+
const data = readButDoNotEvaluate(environment, {
|
28
35
|
kind: 'FragmentReference',
|
29
36
|
readerArtifact: readerArtifact,
|
30
37
|
root,
|
package/src/context.tsx
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
import { ReactNode, createContext, useContext } from 'react';
|
2
|
+
import * as React from 'react';
|
3
|
+
import { subscribe } from './cache';
|
4
|
+
|
5
|
+
export const IsographEnvironmentContext =
|
6
|
+
createContext<IsographEnvironment | null>(null);
|
7
|
+
|
8
|
+
export type IsographEnvironment = {
|
9
|
+
store: IsographStore;
|
10
|
+
networkFunction: IsographNetworkFunction;
|
11
|
+
missingFieldHandler: MissingFieldHandler | null;
|
12
|
+
};
|
13
|
+
|
14
|
+
export type MissingFieldHandler = (
|
15
|
+
storeRecord: StoreRecord,
|
16
|
+
root: DataId,
|
17
|
+
fieldName: string,
|
18
|
+
arguments_: { [index: string]: any } | null,
|
19
|
+
variables: { [index: string]: any } | null,
|
20
|
+
) => Link | undefined;
|
21
|
+
|
22
|
+
export type IsographNetworkFunction = (
|
23
|
+
queryText: string,
|
24
|
+
variables: object,
|
25
|
+
) => Promise<any>;
|
26
|
+
|
27
|
+
export type Link = {
|
28
|
+
__link: DataId;
|
29
|
+
};
|
30
|
+
export type DataTypeValue =
|
31
|
+
// N.B. undefined is here to support optional id's, but
|
32
|
+
// undefined should not *actually* be present in the store.
|
33
|
+
| undefined
|
34
|
+
// Singular scalar fields:
|
35
|
+
| number
|
36
|
+
| boolean
|
37
|
+
| string
|
38
|
+
| null
|
39
|
+
// Singular linked fields:
|
40
|
+
| Link
|
41
|
+
// Plural scalar and linked fields:
|
42
|
+
| DataTypeValue[];
|
43
|
+
|
44
|
+
export type StoreRecord = {
|
45
|
+
[index: DataId | string]: DataTypeValue;
|
46
|
+
// TODO __typename?: T, which is restricted to being a concrete string
|
47
|
+
// TODO this shouldn't always be named id
|
48
|
+
id?: DataId;
|
49
|
+
};
|
50
|
+
|
51
|
+
export type DataId = string;
|
52
|
+
|
53
|
+
export const ROOT_ID: DataId & '__ROOT' = '__ROOT';
|
54
|
+
|
55
|
+
export type IsographStore = {
|
56
|
+
[index: DataId]: StoreRecord | null;
|
57
|
+
__ROOT: StoreRecord;
|
58
|
+
};
|
59
|
+
|
60
|
+
export type IsographEnvironmentProviderProps = {
|
61
|
+
environment: IsographEnvironment;
|
62
|
+
children: ReactNode;
|
63
|
+
};
|
64
|
+
|
65
|
+
export function IsographEnvironmentProvider({
|
66
|
+
environment,
|
67
|
+
children,
|
68
|
+
}: IsographEnvironmentProviderProps) {
|
69
|
+
const [, setState] = React.useState<object | void>();
|
70
|
+
React.useEffect(() => {
|
71
|
+
return subscribe(() => setState({}));
|
72
|
+
}, []);
|
73
|
+
|
74
|
+
return (
|
75
|
+
<IsographEnvironmentContext.Provider value={environment}>
|
76
|
+
{children}
|
77
|
+
</IsographEnvironmentContext.Provider>
|
78
|
+
);
|
79
|
+
}
|
80
|
+
|
81
|
+
export function useIsographEnvironment(): IsographEnvironment {
|
82
|
+
const context = useContext(IsographEnvironmentContext);
|
83
|
+
if (context == null) {
|
84
|
+
throw new Error(
|
85
|
+
'Unexpected null environment context. Make sure to render ' +
|
86
|
+
'this component within an IsographEnvironmentProvider component',
|
87
|
+
);
|
88
|
+
}
|
89
|
+
return context;
|
90
|
+
}
|