@isograph/react 0.0.0-main-d564e834 → 0.0.0-main-fbe23de7

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.
Files changed (42) hide show
  1. package/dist/core/FragmentReference.d.ts +2 -0
  2. package/dist/core/PromiseWrapper.d.ts +17 -4
  3. package/dist/core/PromiseWrapper.js +35 -10
  4. package/dist/core/cache.d.ts +2 -2
  5. package/dist/core/cache.js +31 -4
  6. package/dist/core/componentCache.d.ts +2 -1
  7. package/dist/core/componentCache.js +2 -2
  8. package/dist/core/entrypoint.d.ts +2 -0
  9. package/dist/core/makeNetworkRequest.d.ts +2 -2
  10. package/dist/core/makeNetworkRequest.js +6 -2
  11. package/dist/core/read.d.ts +5 -1
  12. package/dist/core/read.js +32 -10
  13. package/dist/index.d.ts +6 -6
  14. package/dist/index.js +6 -3
  15. package/dist/loadable-hooks/useClientSideDefer.js +0 -1
  16. package/dist/react/{FragmentReferenceReader.d.ts → FragmentReader.d.ts} +4 -1
  17. package/dist/react/{FragmentReferenceReader.js → FragmentReader.js} +5 -4
  18. package/dist/react/useImperativeReference.d.ts +1 -1
  19. package/dist/react/useImperativeReference.js +3 -2
  20. package/dist/react/useLazyReference.js +2 -3
  21. package/dist/react/useReadAndSubscribe.d.ts +2 -2
  22. package/dist/react/useReadAndSubscribe.js +5 -3
  23. package/dist/react/useRerenderOnChange.d.ts +1 -2
  24. package/dist/react/useRerenderOnChange.js +3 -1
  25. package/dist/react/useResult.d.ts +4 -1
  26. package/dist/react/useResult.js +17 -4
  27. package/package.json +3 -3
  28. package/src/core/FragmentReference.ts +2 -0
  29. package/src/core/PromiseWrapper.ts +58 -12
  30. package/src/core/cache.ts +80 -57
  31. package/src/core/componentCache.ts +6 -1
  32. package/src/core/entrypoint.ts +1 -0
  33. package/src/core/makeNetworkRequest.ts +11 -6
  34. package/src/core/read.ts +52 -7
  35. package/src/index.ts +35 -25
  36. package/src/loadable-hooks/useClientSideDefer.ts +1 -1
  37. package/src/react/{FragmentReferenceReader.tsx → FragmentReader.tsx} +8 -2
  38. package/src/react/useImperativeReference.ts +4 -3
  39. package/src/react/useLazyReference.ts +2 -3
  40. package/src/react/useReadAndSubscribe.ts +8 -5
  41. package/src/react/useRerenderOnChange.ts +2 -2
  42. package/src/react/useResult.ts +28 -1
@@ -1,20 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useResult = void 0;
3
+ exports.maybeUnwrapNetworkRequest = exports.useResult = void 0;
4
4
  const IsographEnvironmentProvider_1 = require("../react/IsographEnvironmentProvider");
5
5
  const componentCache_1 = require("../core/componentCache");
6
6
  const useReadAndSubscribe_1 = require("./useReadAndSubscribe");
7
- function useResult(fragmentReference) {
7
+ const PromiseWrapper_1 = require("../core/PromiseWrapper");
8
+ function useResult(fragmentReference, networkRequestOptions) {
8
9
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
10
+ maybeUnwrapNetworkRequest(fragmentReference.networkRequest, networkRequestOptions);
9
11
  switch (fragmentReference.readerArtifact.kind) {
10
12
  case 'ComponentReaderArtifact': {
11
13
  // @ts-expect-error
12
- return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.readerArtifact.componentName, fragmentReference);
14
+ return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.readerArtifact.componentName, fragmentReference, networkRequestOptions);
13
15
  }
14
16
  case 'EagerReaderArtifact': {
15
- const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(environment, fragmentReference);
17
+ const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(fragmentReference, networkRequestOptions);
16
18
  return fragmentReference.readerArtifact.resolver(data);
17
19
  }
18
20
  }
19
21
  }
20
22
  exports.useResult = useResult;
23
+ function maybeUnwrapNetworkRequest(networkRequest, networkRequestOptions) {
24
+ const state = (0, PromiseWrapper_1.getPromiseState)(networkRequest);
25
+ if (state.kind === 'Err' && networkRequestOptions.throwOnNetworkError) {
26
+ throw state.error;
27
+ }
28
+ else if (state.kind === 'Pending' &&
29
+ networkRequestOptions.suspendIfInFlight) {
30
+ throw state.promise;
31
+ }
32
+ }
33
+ exports.maybeUnwrapNetworkRequest = maybeUnwrapNetworkRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-d564e834",
3
+ "version": "0.0.0-main-fbe23de7",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -17,8 +17,8 @@
17
17
  "tsc": "tsc"
18
18
  },
19
19
  "dependencies": {
20
- "@isograph/disposable-types": "0.0.0-main-d564e834",
21
- "@isograph/react-disposable-state": "0.0.0-main-d564e834",
20
+ "@isograph/disposable-types": "0.0.0-main-fbe23de7",
21
+ "@isograph/react-disposable-state": "0.0.0-main-fbe23de7",
22
22
  "react": "^18.2.0"
23
23
  },
24
24
  "devDependencies": {
@@ -1,6 +1,7 @@
1
1
  import { DataId } from './IsographEnvironment';
2
2
  import { RefetchQueryNormalizationArtifactWrapper } from '../core/entrypoint';
3
3
  import { TopLevelReaderArtifact } from './reader';
4
+ import { PromiseWrapper } from './PromiseWrapper';
4
5
 
5
6
  // TODO type this better
6
7
  export type VariableValue = string | number | boolean | null | object;
@@ -21,4 +22,5 @@ export type FragmentReference<
21
22
  readonly variables: Variables | null;
22
23
  // TODO: We should instead have ReaderAst<TClientFieldProps>
23
24
  readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
25
+ readonly networkRequest: PromiseWrapper<void, any>;
24
26
  };
@@ -1,29 +1,75 @@
1
+ export type AnyError = any;
2
+
1
3
  const NOT_SET: Symbol = Symbol('NOT_SET');
2
4
  type NotSet = typeof NOT_SET;
3
5
 
6
+ type Result<T, E> =
7
+ | {
8
+ kind: 'Ok';
9
+ value: T;
10
+ }
11
+ | {
12
+ kind: 'Err';
13
+ error: E;
14
+ };
15
+
4
16
  /**
5
17
  * Invariant:
6
18
  * Before the promise is resolved, value becomes non-null.
7
19
  */
8
- export type PromiseWrapper<T> = {
20
+ export type PromiseWrapper<T, E> = {
9
21
  readonly promise: Promise<T>;
10
- value: Exclude<T, NotSet> | NotSet;
22
+ result: Result<Exclude<T, NotSet>, E> | NotSet;
11
23
  };
12
24
 
13
- export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T> {
25
+ export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T, any> {
14
26
  // TODO confirm suspense works if the promise is already resolved.
15
- const wrapper: PromiseWrapper<T> = { promise, value: NOT_SET };
16
- promise.then((v) => {
17
- // T is assignable to Exclude<T, Symbol> | Symbol
18
- wrapper.value = v as any;
19
- });
27
+ const wrapper: PromiseWrapper<T, any> = { promise, result: NOT_SET };
28
+ promise
29
+ .then((v) => {
30
+ // v is assignable to Exclude<T, Symbol> | Symbol
31
+ wrapper.result = { kind: 'Ok', value: v as any };
32
+ })
33
+ .catch((e) => {
34
+ // e is assignable to Exclude<T, Symbol> | Symbol
35
+ wrapper.result = { kind: 'Err', error: e as any };
36
+ });
20
37
  return wrapper;
21
38
  }
22
39
 
23
- export function useReadPromise<T>(p: PromiseWrapper<T>): T {
24
- if (p.value !== NOT_SET) {
25
- // Safety: p.value is either NOT_SET or an actual value.
26
- return p.value as any;
40
+ export function readPromise<T, E>(p: PromiseWrapper<T, E>): T {
41
+ const { result } = p;
42
+ if (result !== NOT_SET) {
43
+ // Safety: p.result is either NOT_SET or an actual value.
44
+ const resultKind = result as Result<T, any>;
45
+ if (resultKind.kind === 'Ok') {
46
+ return resultKind.value;
47
+ } else {
48
+ throw resultKind.error;
49
+ }
27
50
  }
51
+
28
52
  throw p.promise;
29
53
  }
54
+
55
+ export type PromiseState<T, E> =
56
+ | {
57
+ kind: 'Pending';
58
+ promise: Promise<T>;
59
+ }
60
+ | Result<T, E>;
61
+
62
+ export function getPromiseState<T, E>(
63
+ p: PromiseWrapper<T, E>,
64
+ ): PromiseState<T, E> {
65
+ const { result } = p;
66
+ if (result !== NOT_SET) {
67
+ // Safety: p.result is either NOT_SET or an actual value.
68
+ const resultKind = result as Result<T, any>;
69
+ return resultKind;
70
+ }
71
+ return {
72
+ kind: 'Pending',
73
+ promise: p.promise,
74
+ };
75
+ }
package/src/core/cache.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Factory, ParentCache } from '@isograph/react-disposable-state';
2
- import { PromiseWrapper } from './PromiseWrapper';
2
+ import { AnyError, PromiseWrapper } from './PromiseWrapper';
3
3
  import {
4
4
  DataId,
5
5
  ROOT_ID,
@@ -80,15 +80,10 @@ export function getOrCreateCacheForArtifact<
80
80
  environment: IsographEnvironment,
81
81
  artifact: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
82
82
  variables: Variables,
83
- ): ParentCache<PromiseWrapper<TClientFieldValue>> {
83
+ ): ParentCache<PromiseWrapper<void, AnyError>> {
84
84
  const cacheKey = artifact.queryText + JSON.stringify(stableCopy(variables));
85
- const factory: Factory<PromiseWrapper<TClientFieldValue>> = () =>
86
- makeNetworkRequest<TClientFieldValue>(environment, artifact, variables);
87
- return getOrCreateCache<PromiseWrapper<TClientFieldValue>>(
88
- environment,
89
- cacheKey,
90
- factory,
91
- );
85
+ const factory = () => makeNetworkRequest(environment, artifact, variables);
86
+ return getOrCreateCache(environment, cacheKey, factory);
92
87
  }
93
88
 
94
89
  type NetworkResponseScalarValue = string | number | boolean;
@@ -183,64 +178,92 @@ export function onNextChange(environment: IsographEnvironment): Promise<void> {
183
178
  });
184
179
  }
185
180
 
181
+ // Calls to readButDoNotEvaluate can suspend (i.e. throw a promise).
182
+ // Maybe in the future, they will be able to throw errors.
183
+ //
184
+ // That's probably okay to ignore. We don't, however, want to prevent
185
+ // updating other subscriptions if one subscription had missing data.
186
+ function withErrorHandling<T>(f: (t: T) => void): (t: T) => void {
187
+ return (t) => {
188
+ try {
189
+ return f(t);
190
+ } catch {}
191
+ };
192
+ }
193
+
186
194
  function callSubscriptions(
187
195
  environment: IsographEnvironment,
188
196
  recordsEncounteredWhenNormalizing: Set<DataId>,
189
197
  ) {
190
- environment.subscriptions.forEach((subscription) => {
191
- switch (subscription.kind) {
192
- case 'FragmentSubscription': {
193
- // TODO if there are multiple components subscribed to the same
194
- // fragment, we will call readButNotEvaluate multiple times. We
195
- // should fix that.
196
- if (
197
- hasOverlappingIds(
198
- recordsEncounteredWhenNormalizing,
199
- subscription.encounteredDataAndRecords.encounteredRecords,
200
- )
201
- ) {
202
- const newEncounteredDataAndRecords = readButDoNotEvaluate(
203
- environment,
204
- subscription.fragmentReference,
205
- );
206
-
198
+ environment.subscriptions.forEach(
199
+ withErrorHandling((subscription) => {
200
+ switch (subscription.kind) {
201
+ case 'FragmentSubscription': {
202
+ // TODO if there are multiple components subscribed to the same
203
+ // fragment, we will call readButNotEvaluate multiple times. We
204
+ // should fix that.
207
205
  if (
208
- !areEqualObjectsWithDeepComparison(
209
- subscription.encounteredDataAndRecords.item,
210
- newEncounteredDataAndRecords.item,
206
+ hasOverlappingIds(
207
+ recordsEncounteredWhenNormalizing,
208
+ subscription.encounteredDataAndRecords.encounteredRecords,
211
209
  )
212
210
  ) {
213
- if (typeof window !== 'undefined' && window.__LOG) {
214
- console.log('Deep equality - No', {
215
- fragmentReference: subscription.fragmentReference,
216
- old: subscription.encounteredDataAndRecords.item,
217
- new: newEncounteredDataAndRecords.item,
218
- });
219
- }
220
- // TODO deep compare values
221
- subscription.callback(newEncounteredDataAndRecords);
222
- } else {
223
- if (typeof window !== 'undefined' && window.__LOG) {
224
- console.log('Deep equality - Yes', {
225
- fragmentReference: subscription.fragmentReference,
226
- old: subscription.encounteredDataAndRecords.item,
227
- });
211
+ const newEncounteredDataAndRecords = readButDoNotEvaluate(
212
+ environment,
213
+ subscription.fragmentReference,
214
+ // Is this wrong?
215
+ // Reasons to think no:
216
+ // - we are only updating the read-out value, and the network
217
+ // options only affect whether we throw.
218
+ // - the component will re-render, and re-throw on its own, anyway.
219
+ //
220
+ // Reasons to think not:
221
+ // - it seems more efficient to suspend here and not update state,
222
+ // if we expect that the component will just throw anyway
223
+ // - consistency
224
+ // - it's also weird, this is called from makeNetworkRequest, where
225
+ // we don't currently pass network request options
226
+ {},
227
+ );
228
+
229
+ if (
230
+ !areEqualObjectsWithDeepComparison(
231
+ subscription.encounteredDataAndRecords.item,
232
+ newEncounteredDataAndRecords.item,
233
+ )
234
+ ) {
235
+ if (typeof window !== 'undefined' && window.__LOG) {
236
+ console.log('Deep equality - No', {
237
+ fragmentReference: subscription.fragmentReference,
238
+ old: subscription.encounteredDataAndRecords.item,
239
+ new: newEncounteredDataAndRecords.item,
240
+ });
241
+ }
242
+ // TODO deep compare values
243
+ subscription.callback(newEncounteredDataAndRecords);
244
+ } else {
245
+ if (typeof window !== 'undefined' && window.__LOG) {
246
+ console.log('Deep equality - Yes', {
247
+ fragmentReference: subscription.fragmentReference,
248
+ old: subscription.encounteredDataAndRecords.item,
249
+ });
250
+ }
228
251
  }
229
252
  }
253
+ return;
254
+ }
255
+ case 'AnyRecords': {
256
+ return subscription.callback();
257
+ }
258
+ default: {
259
+ // Ensure we have covered all variants
260
+ const _: never = subscription;
261
+ _;
262
+ throw new Error('Unexpected case');
230
263
  }
231
- return;
232
- }
233
- case 'AnyRecords': {
234
- return subscription.callback();
235
- }
236
- default: {
237
- // Ensure we have covered all variants
238
- const _: never = subscription;
239
- _;
240
- throw new Error('Unexpected case');
241
264
  }
242
- }
243
- });
265
+ }),
266
+ );
244
267
  }
245
268
 
246
269
  function hasOverlappingIds(set1: Set<DataId>, set2: Set<DataId>): boolean {
@@ -563,7 +586,7 @@ function getStoreKeyChunkForArgumentValue(
563
586
  return argumentValue.value;
564
587
  }
565
588
  case 'Variable': {
566
- return variables[argumentValue.name];
589
+ return variables[argumentValue.name] ?? 'null';
567
590
  }
568
591
  case 'String': {
569
592
  return argumentValue.value;
@@ -2,11 +2,13 @@ import { stableCopy } from './cache';
2
2
  import { IsographEnvironment } from './IsographEnvironment';
3
3
  import { FragmentReference } from './FragmentReference';
4
4
  import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
5
+ import { NetworkRequestReaderOptions } from './read';
5
6
 
6
7
  export function getOrCreateCachedComponent(
7
8
  environment: IsographEnvironment,
8
9
  componentName: string,
9
10
  fragmentReference: FragmentReference<any, any>,
11
+ networkRequestOptions: NetworkRequestReaderOptions,
10
12
  ): React.FC<any> {
11
13
  // cachedComponentsById is a three layer cache: id, then component name, then
12
14
  // stringified args. These three, together, uniquely identify a read at a given
@@ -27,7 +29,10 @@ export function getOrCreateCachedComponent(
27
29
  byArgs[stringifiedArgs] ??
28
30
  (() => {
29
31
  function Component(additionalRuntimeProps: { [key: string]: any }) {
30
- const data = useReadAndSubscribe(environment, fragmentReference);
32
+ const data = useReadAndSubscribe(
33
+ fragmentReference,
34
+ networkRequestOptions,
35
+ );
31
36
 
32
37
  if (typeof window !== 'undefined' && window.__LOG) {
33
38
  console.log(
@@ -74,3 +74,4 @@ export type ExtractReadFromStore<Type> =
74
74
  Type extends IsographEntrypoint<infer X, any> ? X : never;
75
75
  export type ExtractResolverResult<Type> =
76
76
  Type extends IsographEntrypoint<any, infer X> ? X : never;
77
+ export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
@@ -11,14 +11,14 @@ import {
11
11
  unretainQuery,
12
12
  } from './garbageCollection';
13
13
  import { IsographEnvironment } from './IsographEnvironment';
14
- import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
14
+ import { AnyError, PromiseWrapper, wrapPromise } from './PromiseWrapper';
15
15
  import { normalizeData } from './cache';
16
16
 
17
- export function makeNetworkRequest<T>(
17
+ export function makeNetworkRequest(
18
18
  environment: IsographEnvironment,
19
19
  artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
20
20
  variables: Variables,
21
- ): ItemCleanupPair<PromiseWrapper<T>> {
21
+ ): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
22
22
  if (typeof window !== 'undefined' && window.__LOG) {
23
23
  console.log('make network request', artifact, variables);
24
24
  }
@@ -33,6 +33,13 @@ export function makeNetworkRequest<T>(
33
33
  console.log('network response', artifact, networkResponse);
34
34
  }
35
35
 
36
+ if (networkResponse.errors != null) {
37
+ // @ts-expect-error Why are we getting the wrong constructor here?
38
+ throw new Error('GraphQL network response had errors', {
39
+ cause: networkResponse,
40
+ });
41
+ }
42
+
36
43
  if (status.kind === 'UndisposedIncomplete') {
37
44
  normalizeData(
38
45
  environment,
@@ -51,13 +58,11 @@ export function makeNetworkRequest<T>(
51
58
  };
52
59
  retainQuery(environment, retainedQuery);
53
60
  }
54
- // TODO return null
55
- return networkResponse;
56
61
  });
57
62
 
58
63
  const wrapper = wrapPromise(promise);
59
64
 
60
- const response: ItemCleanupPair<PromiseWrapper<T>> = [
65
+ const response: ItemCleanupPair<PromiseWrapper<void, AnyError>> = [
61
66
  wrapper,
62
67
  () => {
63
68
  if (status.kind === 'UndisposedComplete') {
package/src/core/read.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  IsographEnvironment,
10
10
  } from './IsographEnvironment';
11
11
  import { makeNetworkRequest } from './makeNetworkRequest';
12
+ import { PromiseWrapper } from './PromiseWrapper';
12
13
  import { ReaderAst } from './reader';
13
14
  import { Arguments } from './util';
14
15
 
@@ -19,21 +20,43 @@ export type WithEncounteredRecords<T> = {
19
20
 
20
21
  export function readButDoNotEvaluate<TReadFromStore extends Object>(
21
22
  environment: IsographEnvironment,
22
- reference: FragmentReference<TReadFromStore, unknown>,
23
+ fragmentReference: FragmentReference<TReadFromStore, unknown>,
24
+ networkRequestOptions: NetworkRequestReaderOptions,
23
25
  ): WithEncounteredRecords<TReadFromStore> {
24
26
  const mutableEncounteredRecords = new Set<DataId>();
25
27
  const response = readData(
26
28
  environment,
27
- reference.readerArtifact.readerAst,
28
- reference.root,
29
- reference.variables ?? {},
30
- reference.nestedRefetchQueries,
29
+ fragmentReference.readerArtifact.readerAst,
30
+ fragmentReference.root,
31
+ fragmentReference.variables ?? {},
32
+ fragmentReference.nestedRefetchQueries,
33
+ fragmentReference.networkRequest,
34
+ networkRequestOptions,
31
35
  mutableEncounteredRecords,
32
36
  );
33
37
  if (typeof window !== 'undefined' && window.__LOG) {
34
38
  console.log('done reading', { response });
35
39
  }
36
40
  if (response.kind === 'MissingData') {
41
+ // There are two cases here that we care about:
42
+ // 1. the network request is in flight, we haven't suspend on it, and we want
43
+ // to throw if it errors out. So, networkRequestOptions.suspendIfInFlight === false
44
+ // and networkRequestOptions.throwOnNetworkError === true.
45
+ // 2. everything else
46
+ //
47
+ // In the first case, we cannot simply throw onNextChange, because if the network
48
+ // response errors out, we will not update the store, so the onNextChange promise
49
+ // will not resolve.
50
+ if (
51
+ !networkRequestOptions.suspendIfInFlight &&
52
+ networkRequestOptions.throwOnNetworkError
53
+ ) {
54
+ // TODO assert that the network request state is not Err
55
+ throw new Promise((resolve, reject) => {
56
+ onNextChange(environment).then(resolve);
57
+ fragmentReference.networkRequest.promise.catch(reject);
58
+ });
59
+ }
37
60
  throw onNextChange(environment);
38
61
  } else {
39
62
  return {
@@ -61,6 +84,8 @@ function readData<TReadFromStore>(
61
84
  root: DataId,
62
85
  variables: Variables,
63
86
  nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
87
+ networkRequest: PromiseWrapper<void, any>,
88
+ networkRequestOptions: NetworkRequestReaderOptions,
64
89
  mutableEncounteredRecords: Set<DataId>,
65
90
  ): ReadDataResult<TReadFromStore> {
66
91
  mutableEncounteredRecords.add(root);
@@ -126,6 +151,8 @@ function readData<TReadFromStore>(
126
151
  link.__link,
127
152
  variables,
128
153
  nestedRefetchQueries,
154
+ networkRequest,
155
+ networkRequestOptions,
129
156
  mutableEncounteredRecords,
130
157
  );
131
158
  if (result.kind === 'MissingData') {
@@ -183,6 +210,8 @@ function readData<TReadFromStore>(
183
210
  targetId,
184
211
  variables,
185
212
  nestedRefetchQueries,
213
+ networkRequest,
214
+ networkRequestOptions,
186
215
  mutableEncounteredRecords,
187
216
  );
188
217
  if (data.kind === 'MissingData') {
@@ -205,6 +234,10 @@ function readData<TReadFromStore>(
205
234
  variables,
206
235
  // Refetch fields just read the id, and don't need refetch query artifacts
207
236
  [],
237
+ // This is probably indicative of the fact that we are doing redundant checks
238
+ // on the status of this network request...
239
+ networkRequest,
240
+ networkRequestOptions,
208
241
  mutableEncounteredRecords,
209
242
  );
210
243
  if (data.kind === 'MissingData') {
@@ -256,6 +289,8 @@ function readData<TReadFromStore>(
256
289
  root,
257
290
  variables,
258
291
  resolverRefetchQueries,
292
+ networkRequest,
293
+ networkRequestOptions,
259
294
  mutableEncounteredRecords,
260
295
  );
261
296
  if (data.kind === 'MissingData') {
@@ -276,8 +311,10 @@ function readData<TReadFromStore>(
276
311
  readerArtifact: field.readerArtifact,
277
312
  root,
278
313
  variables: generateChildVariableMap(variables, field.arguments),
314
+ networkRequest,
279
315
  nestedRefetchQueries: resolverRefetchQueries,
280
316
  } as const,
317
+ networkRequestOptions,
281
318
  );
282
319
  }
283
320
  break;
@@ -290,6 +327,8 @@ function readData<TReadFromStore>(
290
327
  variables,
291
328
  // Refetch fields just read the id, and don't need refetch query artifacts
292
329
  [],
330
+ networkRequest,
331
+ networkRequestOptions,
293
332
  mutableEncounteredRecords,
294
333
  );
295
334
  if (refetchReaderParams.kind === 'MissingData') {
@@ -318,7 +357,7 @@ function readData<TReadFromStore>(
318
357
  field.queryArguments,
319
358
  variables,
320
359
  );
321
- const [_networkRequest, disposeNetworkRequest] =
360
+ const [networkRequest, disposeNetworkRequest] =
322
361
  makeNetworkRequest(
323
362
  environment,
324
363
  field.entrypoint,
@@ -331,7 +370,8 @@ function readData<TReadFromStore>(
331
370
  root: localVariables.id,
332
371
  variables: localVariables,
333
372
  nestedRefetchQueries: field.entrypoint.nestedRefetchQueries,
334
- } as const;
373
+ networkRequest,
374
+ } as FragmentReference<any, any>;
335
375
  return [fragmentReference, disposeNetworkRequest];
336
376
  },
337
377
  ];
@@ -419,3 +459,8 @@ function writeQueryArgsToVariables(
419
459
  }
420
460
  }
421
461
  }
462
+
463
+ export type NetworkRequestReaderOptions = {
464
+ suspendIfInFlight?: boolean;
465
+ throwOnNetworkError?: boolean;
466
+ };
package/src/index.ts CHANGED
@@ -4,7 +4,11 @@ export {
4
4
  type RetainedQuery,
5
5
  garbageCollectEnvironment,
6
6
  } from './core/garbageCollection';
7
- export { type PromiseWrapper } from './core/PromiseWrapper';
7
+ export {
8
+ type PromiseWrapper,
9
+ readPromise,
10
+ getPromiseState,
11
+ } from './core/PromiseWrapper';
8
12
  export { subscribe, normalizeData } from './core/cache';
9
13
  export { makeNetworkRequest } from './core/makeNetworkRequest';
10
14
  export {
@@ -21,36 +25,42 @@ export {
21
25
  defaultMissingFieldHandler,
22
26
  } from './core/IsographEnvironment';
23
27
  export {
24
- EagerReaderArtifact,
25
- ComponentReaderArtifact,
26
- RefetchReaderArtifact,
27
- ReaderAst,
28
- ReaderAstNode,
29
- ReaderLinkedField,
30
- ReaderNonLoadableResolverField,
31
- ReaderScalarField,
32
- TopLevelReaderArtifact,
33
- LoadableField,
28
+ type EagerReaderArtifact,
29
+ type ComponentReaderArtifact,
30
+ type RefetchReaderArtifact,
31
+ type ReaderAst,
32
+ type ReaderAstNode,
33
+ type ReaderLinkedField,
34
+ type ReaderNonLoadableResolverField,
35
+ type ReaderScalarField,
36
+ type TopLevelReaderArtifact,
37
+ type LoadableField,
34
38
  } from './core/reader';
35
39
  export {
36
- NormalizationAst,
37
- NormalizationAstNode,
38
- NormalizationLinkedField,
39
- NormalizationScalarField,
40
- IsographEntrypoint,
40
+ type NormalizationAst,
41
+ type NormalizationAstNode,
42
+ type NormalizationLinkedField,
43
+ type NormalizationScalarField,
44
+ type IsographEntrypoint,
41
45
  assertIsEntrypoint,
42
- RefetchQueryNormalizationArtifact,
43
- RefetchQueryNormalizationArtifactWrapper,
46
+ type RefetchQueryNormalizationArtifact,
47
+ type RefetchQueryNormalizationArtifactWrapper,
48
+ type ExtractProps,
49
+ type ExtractReadFromStore,
50
+ type ExtractResolverResult,
44
51
  } from './core/entrypoint';
45
52
  export { readButDoNotEvaluate } from './core/read';
46
53
  export {
47
- ExtractSecondParam,
48
- Argument,
49
- ArgumentName,
50
- ArgumentValue,
51
- Arguments,
54
+ type ExtractSecondParam,
55
+ type Argument,
56
+ type ArgumentName,
57
+ type ArgumentValue,
58
+ type Arguments,
52
59
  } from './core/util';
53
- export { type FragmentReference } from './core/FragmentReference';
60
+ export {
61
+ type FragmentReference,
62
+ type Variables,
63
+ } from './core/FragmentReference';
54
64
 
55
65
  export {
56
66
  IsographEnvironmentProvider,
@@ -58,7 +68,7 @@ export {
58
68
  type IsographEnvironmentProviderProps,
59
69
  } from './react/IsographEnvironmentProvider';
60
70
  export { useImperativeReference } from './react/useImperativeReference';
61
- export { FragmentReferenceReader } from './react/FragmentReferenceReader';
71
+ export { FragmentReader } from './react/FragmentReader';
62
72
  export { useResult } from './react/useResult';
63
73
  export { useLazyReference } from './react/useLazyReference';
64
74
  export { useRerenderOnChange } from './react/useRerenderOnChange';
@@ -13,10 +13,10 @@ export function useClientSideDefer<TArgs, TResult>(
13
13
  args: TArgs,
14
14
  ): FragmentReference<any, TResult>;
15
15
 
16
- // TODO allow the user to pass props somehow
17
16
  export function useClientSideDefer<TArgs, TResult>(
18
17
  loadableField: LoadableField<TArgs, TResult>,
19
18
  args?: TArgs,
19
+ // TODO this should return { fragmentReference, networkRequestReference }
20
20
  ): FragmentReference<any, TResult> {
21
21
  // @ts-expect-error args is missing iff it has the type void
22
22
  const [id, loader] = loadableField(args);