@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.
@@ -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(cache),
35
- found: !!cache[index],
40
+ cache: Object.keys(environment.suspenseCache),
41
+ found: !!environment.suspenseCache[index],
36
42
  });
37
43
  }
38
- if (cache[index] == null) {
39
- cache[index] = new ParentCache(factory);
44
+ if (environment.suspenseCache[index] == null) {
45
+ environment.suspenseCache[index] = new ParentCache(factory);
40
46
  }
41
47
 
42
- return cache[index];
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
- if (network == null) {
94
- throw new Error('Network must be set before makeNetworkRequest is called');
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(callback: () => void): () => void {
208
- subscriptions.add(callback);
209
- return () => subscriptions.delete(callback);
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
- const subscriptions: Set<() => void> = new Set();
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,
@@ -3,22 +3,18 @@ import {
3
3
  RefetchQueryArtifactWrapper,
4
4
  readButDoNotEvaluate,
5
5
  } from './index';
6
- import { DataId, stableCopy } from './cache';
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,