@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.
@@ -1,4 +1,8 @@
1
- import { Factory, ItemCleanupPair, ParentCache } from '@isograph/react-disposable-state';
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>(index: string, factory: Factory<T>): ParentCache<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>> = () => makeNetworkRequest<T>(artifact, variables);
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
- if (network == null) {
86
- throw new Error('Network must be set before makeNetworkRequest is called');
87
- }
88
-
89
- const promise = network(artifact.queryText, variables).then((networkResponse) => {
90
- if (typeof window !== 'undefined' && window.__LOG) {
91
- console.log('network response', artifact);
92
- }
93
- normalizeData(
94
- artifact.normalizationAst,
95
- networkResponse.data,
96
- variables,
97
- artifact.nestedRefetchQueries,
98
- );
99
- return networkResponse.data;
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('about to normalize', normalizationAst, networkResponse, variables);
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 (networkResponseData == null || isScalarOrEmptyArray(networkResponseData)) {
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('Unexpected scalar network response when normalizing a linked field');
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' || typeof data === 'number' || typeof data === 'boolean';
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' || typeof data === 'number' || typeof data === 'boolean';
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(argument: Argument, variables: { [index: string]: string }) {
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
  }
@@ -1,5 +1,11 @@
1
- import { ReaderArtifact, RefetchQueryArtifactWrapper, readButDoNotEvaluate } from './index';
2
- import { DataId, stableCopy } from './cache';
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,
@@ -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
+ }