@isograph/react 0.0.0-main-ae8aa2fe → 0.0.0-main-d3ef6e33

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 +75 -33
  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 +108 -41
  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
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') {
@@ -299,42 +338,50 @@ function readData<TReadFromStore>(
299
338
  nestedReason: refetchReaderParams,
300
339
  };
301
340
  } else {
302
- target[field.alias] = (args: any) => [
303
- // Stable id
304
- root + '__' + field.name,
305
- // Fetcher
306
- () => {
307
- // TODO we should use the reader AST for this
308
- const includeReadOutData = (variables: any, readOutData: any) => {
309
- variables.id = readOutData.id;
310
- return variables;
311
- };
312
- const localVariables = includeReadOutData(
313
- args ?? {},
314
- refetchReaderParams.data,
315
- );
316
- writeQueryArgsToVariables(
317
- localVariables,
318
- field.queryArguments,
319
- variables,
320
- );
321
- const [_networkRequest, disposeNetworkRequest] =
322
- makeNetworkRequest(
323
- environment,
324
- field.entrypoint,
325
- localVariables,
326
- );
327
- const fragmentReference = {
328
- kind: 'FragmentReference',
329
- readerArtifact: field.entrypoint.readerArtifact,
330
- // TODO localVariables is not guaranteed to have an id field
331
- root: localVariables.id,
332
- variables: localVariables,
333
- nestedRefetchQueries: field.entrypoint.nestedRefetchQueries,
334
- } as const;
335
- return [fragmentReference, disposeNetworkRequest];
336
- },
337
- ];
341
+ target[field.alias] = (args: any) => {
342
+ // TODO we should use the reader AST for this
343
+ const includeReadOutData = (variables: any, readOutData: any) => {
344
+ variables.id = readOutData.id;
345
+ return variables;
346
+ };
347
+ const localVariables = includeReadOutData(
348
+ args ?? {},
349
+ refetchReaderParams.data,
350
+ );
351
+ writeQueryArgsToVariables(
352
+ localVariables,
353
+ field.queryArguments,
354
+ variables,
355
+ );
356
+
357
+ return [
358
+ // Stable id
359
+ root +
360
+ '/' +
361
+ field.name +
362
+ '/' +
363
+ stableStringifyArgs(localVariables),
364
+ // Fetcher
365
+ () => {
366
+ const [networkRequest, disposeNetworkRequest] =
367
+ makeNetworkRequest(
368
+ environment,
369
+ field.entrypoint,
370
+ localVariables,
371
+ );
372
+ const fragmentReference = {
373
+ kind: 'FragmentReference',
374
+ readerArtifact: field.entrypoint.readerArtifact,
375
+ // TODO localVariables is not guaranteed to have an id field
376
+ root: localVariables.id,
377
+ variables: localVariables,
378
+ nestedRefetchQueries: field.entrypoint.nestedRefetchQueries,
379
+ networkRequest,
380
+ } as FragmentReference<any, any>;
381
+ return [fragmentReference, disposeNetworkRequest];
382
+ },
383
+ ];
384
+ };
338
385
  }
339
386
  break;
340
387
  }
@@ -419,3 +466,23 @@ function writeQueryArgsToVariables(
419
466
  }
420
467
  }
421
468
  }
469
+
470
+ export type NetworkRequestReaderOptions = {
471
+ suspendIfInFlight?: boolean;
472
+ throwOnNetworkError?: boolean;
473
+ };
474
+
475
+ // TODO use a description of the params for this?
476
+ // TODO call stableStringifyArgs on the variable values, as well.
477
+ // This doesn't matter for now, since we are just using primitive values
478
+ // in the demo.
479
+ function stableStringifyArgs(args: Object) {
480
+ const keys = Object.keys(args);
481
+ keys.sort();
482
+ let s = '';
483
+ for (const key of keys) {
484
+ // @ts-expect-error
485
+ s += `${key}=${JSON.stringify(args[key])};`;
486
+ }
487
+ return s;
488
+ }
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);
@@ -2,8 +2,9 @@ import * as React from 'react';
2
2
  import { ExtractReadFromStore, IsographEntrypoint } from '../core/entrypoint';
3
3
  import { FragmentReference } from '../core/FragmentReference';
4
4
  import { useResult } from './useResult';
5
+ import { NetworkRequestReaderOptions } from '../core/read';
5
6
 
6
- export function FragmentReferenceReader<
7
+ export function FragmentReader<
7
8
  TProps extends Record<any, any>,
8
9
  TEntrypoint extends IsographEntrypoint<any, React.FC<TProps>>,
9
10
  >(
@@ -14,6 +15,7 @@ export function FragmentReferenceReader<
14
15
  React.FC<{}>
15
16
  >;
16
17
  additionalProps?: TProps;
18
+ networkRequestOptions?: NetworkRequestReaderOptions;
17
19
  }
18
20
  : {
19
21
  fragmentReference: FragmentReference<
@@ -21,8 +23,12 @@ export function FragmentReferenceReader<
21
23
  React.FC<TProps>
22
24
  >;
23
25
  additionalProps: TProps;
26
+ networkRequestOptions?: NetworkRequestReaderOptions;
24
27
  },
25
28
  ): React.ReactNode {
26
- const Component = useResult(props.fragmentReference);
29
+ const Component = useResult(
30
+ props.fragmentReference,
31
+ props.networkRequestOptions ?? {},
32
+ );
27
33
  return <Component {...props.additionalProps} />;
28
34
  }
@@ -17,7 +17,7 @@ export function useImperativeReference<
17
17
  fragmentReference:
18
18
  | FragmentReference<TReadFromStore, TClientFieldValue>
19
19
  | UnassignedState;
20
- loadfragmentReference: (variables: Variables) => void;
20
+ loadFragmentReference: (variables: Variables) => void;
21
21
  } {
22
22
  const { state, setState } =
23
23
  useUpdatableDisposableState<
@@ -26,8 +26,8 @@ export function useImperativeReference<
26
26
  const environment = useIsographEnvironment();
27
27
  return {
28
28
  fragmentReference: state,
29
- loadfragmentReference: (variables: Variables) => {
30
- const [_networkRequest, disposeNetworkRequest] = makeNetworkRequest(
29
+ loadFragmentReference: (variables: Variables) => {
30
+ const [networkRequest, disposeNetworkRequest] = makeNetworkRequest(
31
31
  environment,
32
32
  entrypoint,
33
33
  variables,
@@ -39,6 +39,7 @@ export function useImperativeReference<
39
39
  root: ROOT_ID,
40
40
  variables,
41
41
  nestedRefetchQueries: entrypoint.nestedRefetchQueries,
42
+ networkRequest,
42
43
  },
43
44
  () => {
44
45
  disposeNetworkRequest();
@@ -17,9 +17,7 @@ export function useLazyReference<
17
17
  const environment = useIsographEnvironment();
18
18
  const cache = getOrCreateCacheForArtifact(environment, entrypoint, variables);
19
19
 
20
- // TODO add comment explaining why we never use this value
21
- // @ts-ignore(6133)
22
- const _data = useLazyDisposableState(cache).state;
20
+ const networkRequest = useLazyDisposableState(cache).state;
23
21
 
24
22
  return {
25
23
  fragmentReference: {
@@ -28,6 +26,7 @@ export function useLazyReference<
28
26
  root: ROOT_ID,
29
27
  variables,
30
28
  nestedRefetchQueries: entrypoint.nestedRefetchQueries,
29
+ networkRequest,
31
30
  },
32
31
  };
33
32
  }
@@ -1,22 +1,25 @@
1
1
  import { useState } from 'react';
2
2
  import { FragmentReference } from '../core/FragmentReference';
3
- import { IsographEnvironment } from '../core/IsographEnvironment';
4
- import { readButDoNotEvaluate } from '../core/read';
3
+ import {
4
+ NetworkRequestReaderOptions,
5
+ readButDoNotEvaluate,
6
+ } from '../core/read';
5
7
  import { useRerenderOnChange } from './useRerenderOnChange';
8
+ import { useIsographEnvironment } from './IsographEnvironmentProvider';
6
9
 
7
10
  /**
8
11
  * Read the data from a fragment reference and subscribe to updates.
9
12
  * Does not pass the data to the fragment reference's resolver function.
10
13
  */
11
14
  export function useReadAndSubscribe<TReadFromStore extends Object>(
12
- environment: IsographEnvironment,
13
15
  fragmentReference: FragmentReference<TReadFromStore, any>,
16
+ networkRequestOptions: NetworkRequestReaderOptions,
14
17
  ): TReadFromStore {
18
+ const environment = useIsographEnvironment();
15
19
  const [readOutDataAndRecords, setReadOutDataAndRecords] = useState(() =>
16
- readButDoNotEvaluate(environment, fragmentReference),
20
+ readButDoNotEvaluate(environment, fragmentReference, networkRequestOptions),
17
21
  );
18
22
  useRerenderOnChange(
19
- environment,
20
23
  readOutDataAndRecords,
21
24
  fragmentReference,
22
25
  setReadOutDataAndRecords,
@@ -1,19 +1,19 @@
1
1
  import { useEffect } from 'react';
2
- import { IsographEnvironment } from '../core/IsographEnvironment';
3
2
  import { subscribe } from '../core/cache';
4
3
  import { WithEncounteredRecords } from '../core/read';
5
4
  import { FragmentReference } from '../core/FragmentReference';
5
+ import { useIsographEnvironment } from './IsographEnvironmentProvider';
6
6
 
7
7
  // TODO add unit tests for this. Add integration tests that test
8
8
  // behavior when the encounteredRecords underneath a fragment change.
9
9
  export function useRerenderOnChange<TReadFromStore extends Object>(
10
- environment: IsographEnvironment,
11
10
  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
12
11
  fragmentReference: FragmentReference<any, any>,
13
12
  setEncounteredDataAndRecords: (
14
13
  data: WithEncounteredRecords<TReadFromStore>,
15
14
  ) => void,
16
15
  ) {
16
+ const environment = useIsographEnvironment();
17
17
  useEffect(() => {
18
18
  return subscribe(
19
19
  environment,
@@ -2,12 +2,20 @@ import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
2
2
  import { FragmentReference } from '../core/FragmentReference';
3
3
  import { getOrCreateCachedComponent } from '../core/componentCache';
4
4
  import { useReadAndSubscribe } from './useReadAndSubscribe';
5
+ import { NetworkRequestReaderOptions } from '../core/read';
6
+ import { getPromiseState, PromiseWrapper } from '../core/PromiseWrapper';
5
7
 
6
8
  export function useResult<TReadFromStore extends Object, TClientFieldValue>(
7
9
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
10
+ networkRequestOptions: NetworkRequestReaderOptions,
8
11
  ): TClientFieldValue {
9
12
  const environment = useIsographEnvironment();
10
13
 
14
+ maybeUnwrapNetworkRequest(
15
+ fragmentReference.networkRequest,
16
+ networkRequestOptions,
17
+ );
18
+
11
19
  switch (fragmentReference.readerArtifact.kind) {
12
20
  case 'ComponentReaderArtifact': {
13
21
  // @ts-expect-error
@@ -15,11 +23,30 @@ export function useResult<TReadFromStore extends Object, TClientFieldValue>(
15
23
  environment,
16
24
  fragmentReference.readerArtifact.componentName,
17
25
  fragmentReference,
26
+ networkRequestOptions,
18
27
  );
19
28
  }
20
29
  case 'EagerReaderArtifact': {
21
- const data = useReadAndSubscribe(environment, fragmentReference);
30
+ const data = useReadAndSubscribe(
31
+ fragmentReference,
32
+ networkRequestOptions,
33
+ );
22
34
  return fragmentReference.readerArtifact.resolver(data);
23
35
  }
24
36
  }
25
37
  }
38
+
39
+ export function maybeUnwrapNetworkRequest(
40
+ networkRequest: PromiseWrapper<void, any>,
41
+ networkRequestOptions: NetworkRequestReaderOptions,
42
+ ) {
43
+ const state = getPromiseState(networkRequest);
44
+ if (state.kind === 'Err' && networkRequestOptions.throwOnNetworkError) {
45
+ throw state.error;
46
+ } else if (
47
+ state.kind === 'Pending' &&
48
+ networkRequestOptions.suspendIfInFlight
49
+ ) {
50
+ throw state.promise;
51
+ }
52
+ }