@isograph/react 0.2.0 → 0.3.0

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 (134) hide show
  1. package/dist/core/FragmentReference.d.ts +14 -4
  2. package/dist/core/FragmentReference.d.ts.map +1 -0
  3. package/dist/core/FragmentReference.js +2 -3
  4. package/dist/core/IsographEnvironment.d.ts +28 -10
  5. package/dist/core/IsographEnvironment.d.ts.map +1 -0
  6. package/dist/core/IsographEnvironment.js +15 -22
  7. package/dist/core/PromiseWrapper.d.ts +1 -0
  8. package/dist/core/PromiseWrapper.d.ts.map +1 -0
  9. package/dist/core/PromiseWrapper.js +4 -5
  10. package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
  11. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
  12. package/dist/core/areEqualWithDeepComparison.js +73 -39
  13. package/dist/core/cache.d.ts +26 -10
  14. package/dist/core/cache.d.ts.map +1 -0
  15. package/dist/core/cache.js +160 -98
  16. package/dist/core/check.d.ts +18 -0
  17. package/dist/core/check.d.ts.map +1 -0
  18. package/dist/core/check.js +127 -0
  19. package/dist/core/componentCache.d.ts +1 -1
  20. package/dist/core/componentCache.d.ts.map +1 -0
  21. package/dist/core/componentCache.js +14 -14
  22. package/dist/core/entrypoint.d.ts +27 -8
  23. package/dist/core/entrypoint.d.ts.map +1 -0
  24. package/dist/core/entrypoint.js +1 -2
  25. package/dist/core/garbageCollection.d.ts +3 -1
  26. package/dist/core/garbageCollection.d.ts.map +1 -0
  27. package/dist/core/garbageCollection.js +48 -15
  28. package/dist/core/logging.d.ts +69 -0
  29. package/dist/core/logging.d.ts.map +1 -0
  30. package/dist/core/logging.js +19 -0
  31. package/dist/core/makeNetworkRequest.d.ts +4 -1
  32. package/dist/core/makeNetworkRequest.d.ts.map +1 -0
  33. package/dist/core/makeNetworkRequest.js +71 -15
  34. package/dist/core/read.d.ts +20 -5
  35. package/dist/core/read.d.ts.map +1 -0
  36. package/dist/core/read.js +104 -41
  37. package/dist/core/reader.d.ts +34 -10
  38. package/dist/core/reader.d.ts.map +1 -0
  39. package/dist/core/util.d.ts +2 -0
  40. package/dist/core/util.d.ts.map +1 -0
  41. package/dist/index.d.ts +10 -5
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +10 -2
  44. package/dist/loadable-hooks/useClientSideDefer.d.ts +15 -3
  45. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
  46. package/dist/loadable-hooks/useClientSideDefer.js +4 -6
  47. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
  48. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
  49. package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
  50. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -0
  51. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
  52. package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
  53. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -5
  54. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
  55. package/dist/loadable-hooks/useImperativeLoadableField.js +3 -4
  56. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +18 -24
  57. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
  58. package/dist/loadable-hooks/useSkipLimitPagination.js +88 -44
  59. package/dist/react/FragmentReader.d.ts +7 -4
  60. package/dist/react/FragmentReader.d.ts.map +1 -0
  61. package/dist/react/FragmentReader.js +4 -2
  62. package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
  63. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
  64. package/dist/react/IsographEnvironmentProvider.js +3 -3
  65. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
  66. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
  67. package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
  68. package/dist/react/useImperativeReference.d.ts +8 -3
  69. package/dist/react/useImperativeReference.d.ts.map +1 -0
  70. package/dist/react/useImperativeReference.js +4 -5
  71. package/dist/react/useLazyReference.d.ts +7 -2
  72. package/dist/react/useLazyReference.d.ts.map +1 -0
  73. package/dist/react/useLazyReference.js +11 -4
  74. package/dist/react/useReadAndSubscribe.d.ts +12 -3
  75. package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
  76. package/dist/react/useReadAndSubscribe.js +6 -7
  77. package/dist/react/useRerenderOnChange.d.ts +6 -1
  78. package/dist/react/useRerenderOnChange.d.ts.map +1 -0
  79. package/dist/react/useRerenderOnChange.js +3 -4
  80. package/dist/react/useResult.d.ts +5 -1
  81. package/dist/react/useResult.d.ts.map +1 -0
  82. package/dist/react/useResult.js +8 -5
  83. package/{src/tests/isograph.config.json → isograph.config.json} +1 -1
  84. package/package.json +12 -8
  85. package/{src/tests/schema.graphql → schema.graphql} +1 -0
  86. package/src/core/FragmentReference.ts +17 -5
  87. package/src/core/IsographEnvironment.ts +38 -29
  88. package/src/core/areEqualWithDeepComparison.ts +76 -42
  89. package/src/core/cache.ts +237 -123
  90. package/src/core/check.ts +207 -0
  91. package/src/core/componentCache.ts +18 -17
  92. package/src/core/entrypoint.ts +15 -8
  93. package/src/core/garbageCollection.ts +71 -20
  94. package/src/core/logging.ts +116 -0
  95. package/src/core/makeNetworkRequest.ts +89 -13
  96. package/src/core/read.ts +162 -55
  97. package/src/core/reader.ts +40 -13
  98. package/src/core/util.ts +4 -0
  99. package/src/index.ts +14 -1
  100. package/src/loadable-hooks/useClientSideDefer.ts +45 -15
  101. package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
  102. package/src/loadable-hooks/useImperativeLoadableField.ts +36 -10
  103. package/src/loadable-hooks/useSkipLimitPagination.ts +231 -90
  104. package/src/react/FragmentReader.tsx +13 -4
  105. package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
  106. package/src/react/useImperativeReference.ts +18 -7
  107. package/src/react/useLazyReference.ts +24 -4
  108. package/src/react/useReadAndSubscribe.ts +20 -5
  109. package/src/react/useRerenderOnChange.ts +6 -1
  110. package/src/react/useResult.ts +10 -2
  111. package/src/tests/__isograph/Query/meName/entrypoint.ts +7 -2
  112. package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
  113. package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
  114. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +9 -2
  115. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
  116. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
  117. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +13 -2
  118. package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
  119. package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
  120. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
  121. package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
  122. package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
  123. package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
  124. package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
  125. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
  126. package/src/tests/__isograph/iso.ts +22 -11
  127. package/src/tests/garbageCollection.test.ts +45 -39
  128. package/src/tests/meNameSuccessor.ts +8 -3
  129. package/src/tests/nodeQuery.ts +6 -4
  130. package/src/tests/normalizeData.test.ts +120 -0
  131. package/src/tests/tsconfig.json +3 -3
  132. package/tsconfig.json +2 -2
  133. package/tsconfig.pkg.json +6 -1
  134. package/vitest.config.ts +20 -0
@@ -1,9 +1,13 @@
1
1
  import { Factory } from '@isograph/disposable-types';
2
- import { FragmentReference } from './FragmentReference';
2
+ import {
3
+ FragmentReference,
4
+ ExtractParameters,
5
+ ExtractData,
6
+ } from './FragmentReference';
3
7
  import {
4
8
  ComponentOrFieldName,
5
- DataId,
6
9
  IsographEnvironment,
10
+ type Link,
7
11
  } from './IsographEnvironment';
8
12
  import {
9
13
  IsographEntrypoint,
@@ -12,37 +16,47 @@ import {
12
16
  RefetchQueryNormalizationArtifactWrapper,
13
17
  } from './entrypoint';
14
18
  import { Arguments } from './util';
19
+ import { FetchOptions } from './check';
15
20
 
16
21
  export type TopLevelReaderArtifact<
17
- TReadFromStore extends Object,
22
+ TReadFromStore extends { parameters: object; data: object },
18
23
  TClientFieldValue,
19
- TComponentProps extends Record<string, never>,
24
+ TComponentProps extends Record<PropertyKey, never>,
20
25
  > =
21
26
  | EagerReaderArtifact<TReadFromStore, TClientFieldValue>
22
27
  | ComponentReaderArtifact<TReadFromStore, TComponentProps>;
23
28
 
24
29
  export type EagerReaderArtifact<
25
- TReadFromStore extends Object,
30
+ TReadFromStore extends { parameters: object; data: object },
26
31
  TClientFieldValue,
27
32
  > = {
28
33
  readonly kind: 'EagerReaderArtifact';
29
34
  readonly readerAst: ReaderAst<TReadFromStore>;
30
- readonly resolver: (data: TReadFromStore) => TClientFieldValue;
35
+ readonly resolver: (
36
+ data: ResolverFirstParameter<TReadFromStore>,
37
+ ) => TClientFieldValue;
31
38
  };
32
39
 
33
40
  export type ComponentReaderArtifact<
34
- TReadFromStore extends Object,
35
- TComponentProps extends Record<string, unknown> = Record<string, never>,
41
+ TReadFromStore extends { parameters: object; data: object },
42
+ TComponentProps extends Record<string, unknown> = Record<PropertyKey, never>,
36
43
  > = {
37
44
  readonly kind: 'ComponentReaderArtifact';
38
45
  readonly componentName: ComponentOrFieldName;
39
46
  readonly readerAst: ReaderAst<TReadFromStore>;
40
47
  readonly resolver: (
41
- data: TReadFromStore,
48
+ data: ResolverFirstParameter<TReadFromStore>,
42
49
  runtimeProps: TComponentProps,
43
50
  ) => React.ReactNode;
44
51
  };
45
52
 
53
+ export type ResolverFirstParameter<
54
+ TReadFromStore extends { data: object; parameters: object },
55
+ > = {
56
+ data: ExtractData<TReadFromStore>;
57
+ parameters: ExtractParameters<TReadFromStore>;
58
+ };
59
+
46
60
  export type RefetchReaderArtifact = {
47
61
  readonly kind: 'RefetchReaderArtifact';
48
62
  readonly readerAst: ReaderAst<unknown>;
@@ -53,7 +67,7 @@ export type RefetchReaderArtifact = {
53
67
  variables: any,
54
68
  // TODO type this better
55
69
  filteredVariables: any,
56
- rootId: DataId,
70
+ rootLink: Link,
57
71
  readerArtifact: TopLevelReaderArtifact<any, any, any> | null,
58
72
  // TODO type this better
59
73
  nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
@@ -82,6 +96,10 @@ export type ReaderLinkedField = {
82
96
  readonly alias: string | null;
83
97
  readonly selections: ReaderAst<unknown>;
84
98
  readonly arguments: Arguments | null;
99
+ readonly condition: EagerReaderArtifact<
100
+ { data: object; parameters: object },
101
+ boolean | Link | null
102
+ > | null;
85
103
  };
86
104
 
87
105
  export type ReaderNonLoadableResolverField = {
@@ -128,6 +146,15 @@ type StableId = string;
128
146
  /// Passing TArgs to the LoadableField should be cheap and do no "actual" work,
129
147
  /// except to stringify the args or whatnot. Calling the factory can be
130
148
  /// expensive. For example, doing so will probably trigger a network request.
131
- export type LoadableField<TArgs, TResult> = (
132
- args: TArgs,
133
- ) => [StableId, Factory<FragmentReference<any, TResult>>];
149
+ export type LoadableField<
150
+ TReadFromStore extends { data: object; parameters: object },
151
+ TResult,
152
+ TArgs = ExtractParameters<TReadFromStore>,
153
+ > = (
154
+ args: TArgs | void,
155
+ // Note: fetchOptions is not nullable here because a LoadableField is not a
156
+ // user-facing API. Users should only interact with LoadableFields via APIs
157
+ // like useClientSideDefer. These APIs should have a nullable fetchOptions
158
+ // parameter, and provide a default value ({}) to the LoadableField.
159
+ fetchOptions: FetchOptions,
160
+ ) => [StableId, Factory<FragmentReference<TReadFromStore, TResult>>];
package/src/core/util.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export type ExtractSecondParam<T extends (arg1: any, arg2: any) => any> =
2
2
  T extends (arg1: any, arg2: infer P) => any ? P : never;
3
+ export type CombineWithIntrinsicAttributes<T> =
4
+ T extends Record<PropertyKey, never>
5
+ ? JSX.IntrinsicAttributes
6
+ : T & JSX.IntrinsicAttributes;
3
7
 
4
8
  export type Arguments = Argument[];
5
9
  export type Argument = [ArgumentName, ArgumentValue];
package/src/index.ts CHANGED
@@ -24,7 +24,6 @@ export {
24
24
  type StoreRecord,
25
25
  createIsographEnvironment,
26
26
  createIsographStore,
27
- defaultMissingFieldHandler,
28
27
  } from './core/IsographEnvironment';
29
28
  export {
30
29
  type EagerReaderArtifact,
@@ -37,6 +36,7 @@ export {
37
36
  type ReaderScalarField,
38
37
  type TopLevelReaderArtifact,
39
38
  type LoadableField,
39
+ type ResolverFirstParameter,
40
40
  } from './core/reader';
41
41
  export {
42
42
  type NormalizationAst,
@@ -50,10 +50,12 @@ export {
50
50
  type ExtractProps,
51
51
  type ExtractReadFromStore,
52
52
  type ExtractResolverResult,
53
+ type NetworkRequestInfo,
53
54
  } from './core/entrypoint';
54
55
  export { readButDoNotEvaluate } from './core/read';
55
56
  export {
56
57
  type ExtractSecondParam,
58
+ type CombineWithIntrinsicAttributes,
57
59
  type Argument,
58
60
  type ArgumentName,
59
61
  type ArgumentValue,
@@ -62,8 +64,17 @@ export {
62
64
  export {
63
65
  type FragmentReference,
64
66
  type Variables,
67
+ type ExtractParameters,
68
+ type ExtractData,
65
69
  stableIdForFragmentReference,
66
70
  } from './core/FragmentReference';
71
+ export {
72
+ type LogMessage,
73
+ type LogFunction,
74
+ logMessage,
75
+ registerLogger,
76
+ } from './core/logging';
77
+ export { check, CheckResult, FetchOptions, ShouldFetch } from './core/check';
67
78
 
68
79
  export {
69
80
  IsographEnvironmentProvider,
@@ -79,8 +90,10 @@ export {
79
90
  } from './react/useReadAndSubscribe';
80
91
  export { useLazyReference } from './react/useLazyReference';
81
92
  export { useRerenderOnChange } from './react/useRerenderOnChange';
93
+ export { RenderAfterCommit__DO_NOT_USE } from './react/RenderAfterCommit__DO_NOT_USE';
82
94
 
83
95
  export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
84
96
  export { useImperativeExposedMutationField } from './loadable-hooks/useImperativeExposedMutationField';
85
97
  export { useSkipLimitPagination } from './loadable-hooks/useSkipLimitPagination';
98
+ export { useConnectionSpecPagination } from './loadable-hooks/useConnectionSpecPagination';
86
99
  export { useImperativeLoadableField } from './loadable-hooks/useImperativeLoadableField';
@@ -1,28 +1,58 @@
1
- import { FragmentReference } from '../core/FragmentReference';
1
+ import {
2
+ ExtractParameters,
3
+ FragmentReference,
4
+ } from '../core/FragmentReference';
2
5
  import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
3
6
  import { getOrCreateItemInSuspenseCache } from '../core/cache';
4
7
  import { useLazyDisposableState } from '@isograph/react-disposable-state';
5
8
  import { LoadableField } from '../core/reader';
9
+ import { FetchOptions } from '../core/check';
6
10
 
7
- export function useClientSideDefer<TResult>(
8
- loadableField: LoadableField<void, TResult>,
9
- ): FragmentReference<Record<string, never>, TResult>;
11
+ export function useClientSideDefer<
12
+ TReadFromStore extends { data: object; parameters: object },
13
+ TResult,
14
+ >(
15
+ loadableField: LoadableField<
16
+ TReadFromStore,
17
+ TResult,
18
+ ExtractParameters<TReadFromStore>
19
+ >,
20
+ args?: Record<PropertyKey, never>,
21
+ fetchOptions?: FetchOptions,
22
+ ): { fragmentReference: FragmentReference<TReadFromStore, TResult> };
10
23
 
11
- export function useClientSideDefer<TArgs extends Object, TResult>(
12
- loadableField: LoadableField<TArgs, TResult>,
13
- args: TArgs,
14
- ): FragmentReference<TArgs, TResult>;
24
+ export function useClientSideDefer<
25
+ TReadFromStore extends { data: object; parameters: object },
26
+ TResult,
27
+ TProvidedArgs extends object,
28
+ >(
29
+ loadableField: LoadableField<
30
+ TReadFromStore,
31
+ TResult,
32
+ Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>
33
+ >,
34
+ args: Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>,
35
+ fetchOptions?: FetchOptions,
36
+ ): { fragmentReference: FragmentReference<TReadFromStore, TResult> };
15
37
 
16
- export function useClientSideDefer<TArgs extends Object, TResult>(
17
- loadableField: LoadableField<TArgs, TResult>,
18
- args?: TArgs,
19
- ): FragmentReference<TArgs, TResult> {
20
- // @ts-expect-error args is missing iff it has the type void
21
- const [id, loader] = loadableField(args);
38
+ export function useClientSideDefer<
39
+ TReadFromStore extends { data: object; parameters: object },
40
+ TResult,
41
+ TProvidedArgs extends object,
42
+ >(
43
+ loadableField: LoadableField<
44
+ TReadFromStore,
45
+ TResult,
46
+ Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>
47
+ >,
48
+ args?: Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>,
49
+ fetchOptions?: FetchOptions,
50
+ ): { fragmentReference: FragmentReference<TReadFromStore, TResult> } {
51
+ const [id, loader] = loadableField(args, fetchOptions ?? {});
22
52
  const environment = useIsographEnvironment();
23
53
  const cache = getOrCreateItemInSuspenseCache(environment, id, loader);
24
54
 
25
55
  const fragmentReference = useLazyDisposableState(cache).state;
26
56
 
27
- return fragmentReference;
57
+ return { fragmentReference };
28
58
  }
@@ -0,0 +1,331 @@
1
+ import { ItemCleanupPair } from '@isograph/disposable-types';
2
+ import {
3
+ UNASSIGNED_STATE,
4
+ useUpdatableDisposableState,
5
+ } from '@isograph/react-disposable-state';
6
+ import {
7
+ createReferenceCountedPointer,
8
+ ReferenceCountedPointer,
9
+ } from '@isograph/reference-counted-pointer';
10
+ import { useState } from 'react';
11
+ import { subscribeToAnyChange } from '../core/cache';
12
+ import { FragmentReference } from '../core/FragmentReference';
13
+ import { getPromiseState, readPromise } from '../core/PromiseWrapper';
14
+ import {
15
+ readButDoNotEvaluate,
16
+ type WithEncounteredRecords,
17
+ } from '../core/read';
18
+ import { LoadableField, type ReaderAst } from '../core/reader';
19
+ import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
20
+ import { useSubscribeToMultiple } from '../react/useReadAndSubscribe';
21
+ import { maybeUnwrapNetworkRequest } from '../react/useResult';
22
+ import { FetchOptions } from '../core/check';
23
+
24
+ type UsePaginationReturnValue<
25
+ TReadFromStore extends { parameters: object; data: object },
26
+ TItem,
27
+ > =
28
+ | {
29
+ kind: 'Pending';
30
+ pendingFragment: FragmentReference<TReadFromStore, Connection<TItem>>;
31
+ results: ReadonlyArray<TItem>;
32
+ }
33
+ | {
34
+ kind: 'Complete';
35
+ fetchMore: (count: number, fetchOptions?: FetchOptions) => void;
36
+ results: ReadonlyArray<TItem>;
37
+ hasNextPage: boolean;
38
+ };
39
+
40
+ type LoadedFragmentReferences<
41
+ TReadFromStore extends { parameters: object; data: object },
42
+ TItem,
43
+ > = ReadonlyArray<LoadedFragmentReference<TReadFromStore, TItem>>;
44
+
45
+ type LoadedFragmentReference<
46
+ TReadFromStore extends { parameters: object; data: object },
47
+ TItem,
48
+ > = ItemCleanupPair<
49
+ ReferenceCountedPointer<FragmentReference<TReadFromStore, TItem>>
50
+ >;
51
+
52
+ function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
53
+ let outArray: Array<T> = [];
54
+ for (const subarr of arr) {
55
+ for (const item of subarr) {
56
+ outArray.push(item);
57
+ }
58
+ }
59
+ return outArray;
60
+ }
61
+
62
+ type PageInfo = {
63
+ readonly hasNextPage: boolean;
64
+ readonly endCursor: string | null;
65
+ };
66
+
67
+ type Connection<T> = {
68
+ readonly edges: ReadonlyArray<T> | null;
69
+ readonly pageInfo: PageInfo;
70
+ };
71
+
72
+ type NonNullConnection<T> = {
73
+ readonly edges: ReadonlyArray<T>;
74
+ readonly pageInfo: PageInfo;
75
+ };
76
+
77
+ type UseConnectionSpecPaginationArgs = {
78
+ first: number;
79
+ after: string | null;
80
+ };
81
+
82
+ export function useConnectionSpecPagination<
83
+ TReadFromStore extends {
84
+ parameters: object;
85
+ data: object;
86
+ },
87
+ TItem,
88
+ >(
89
+ loadableField: LoadableField<
90
+ TReadFromStore,
91
+ Connection<TItem>,
92
+ UseConnectionSpecPaginationArgs
93
+ >,
94
+ initialState?: PageInfo,
95
+ ): UsePaginationReturnValue<TReadFromStore, TItem> {
96
+ const networkRequestOptions = {
97
+ suspendIfInFlight: true,
98
+ throwOnNetworkError: true,
99
+ };
100
+ const { state, setState } =
101
+ useUpdatableDisposableState<
102
+ LoadedFragmentReferences<TReadFromStore, Connection<TItem>>
103
+ >();
104
+
105
+ const environment = useIsographEnvironment();
106
+
107
+ // TODO move this out of useSkipLimitPagination, and pass environment and networkRequestOptions
108
+ // as parameters (or recreate networkRequestOptions)
109
+ function readCompletedFragmentReferences(
110
+ completedReferences: FragmentReference<TReadFromStore, Connection<TItem>>[],
111
+ ): NonNullConnection<TItem> {
112
+ const results = completedReferences.map((fragmentReference, i) => {
113
+ const readerWithRefetchQueries = readPromise(
114
+ fragmentReference.readerWithRefetchQueries,
115
+ );
116
+
117
+ // invariant: readOutDataAndRecords.length === completedReferences.length
118
+ const data = readOutDataAndRecords[i]?.item;
119
+ if (data == null) {
120
+ throw new Error(
121
+ 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.',
122
+ );
123
+ }
124
+
125
+ const firstParameter = {
126
+ data,
127
+ parameters: fragmentReference.variables,
128
+ };
129
+
130
+ if (
131
+ readerWithRefetchQueries.readerArtifact.kind !== 'EagerReaderArtifact'
132
+ ) {
133
+ throw new Error(
134
+ `@loadable field of kind "${readerWithRefetchQueries.readerArtifact.kind}" is not supported by useSkipLimitPagination`,
135
+ );
136
+ }
137
+
138
+ return readerWithRefetchQueries.readerArtifact.resolver(firstParameter);
139
+ });
140
+
141
+ const items = flatten(results.map((result) => result.edges ?? []));
142
+
143
+ return {
144
+ edges: items,
145
+ pageInfo: results[results.length - 1]?.pageInfo ?? {
146
+ endCursor: null,
147
+ hasNextPage: true,
148
+ },
149
+ };
150
+ }
151
+
152
+ function subscribeCompletedFragmentReferences(
153
+ completedReferences: FragmentReference<TReadFromStore, Connection<TItem>>[],
154
+ ) {
155
+ return completedReferences.map(
156
+ (
157
+ fragmentReference,
158
+ i,
159
+ ): {
160
+ records: WithEncounteredRecords<TReadFromStore>;
161
+ callback: (
162
+ updatedRecords: WithEncounteredRecords<TReadFromStore>,
163
+ ) => void;
164
+ fragmentReference: FragmentReference<TReadFromStore, Connection<TItem>>;
165
+ readerAst: ReaderAst<Connection<TItem>>;
166
+ } => {
167
+ maybeUnwrapNetworkRequest(
168
+ fragmentReference.networkRequest,
169
+ networkRequestOptions,
170
+ );
171
+
172
+ const readerWithRefetchQueries = readPromise(
173
+ fragmentReference.readerWithRefetchQueries,
174
+ );
175
+
176
+ const records = readOutDataAndRecords[i];
177
+ if (records == null) {
178
+ throw new Error(
179
+ 'subscribeCompletedFragmentReferences records is unexpectedly null',
180
+ );
181
+ }
182
+
183
+ return {
184
+ fragmentReference,
185
+ readerAst: readerWithRefetchQueries.readerArtifact.readerAst,
186
+ records,
187
+ callback(_data) {
188
+ rerender({});
189
+ },
190
+ };
191
+ },
192
+ );
193
+ }
194
+
195
+ const getFetchMore =
196
+ (after: string | null) =>
197
+ (count: number, fetchOptions?: FetchOptions): void => {
198
+ const loadedField = loadableField(
199
+ {
200
+ after: after,
201
+ first: count,
202
+ },
203
+ fetchOptions ?? {},
204
+ )[1]();
205
+ const newPointer = createReferenceCountedPointer(loadedField);
206
+ const clonedPointers = loadedReferences.map(([refCountedPointer]) => {
207
+ const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
208
+ if (clonedRefCountedPointer == null) {
209
+ throw new Error(
210
+ 'This reference counted pointer has already been disposed. \
211
+ This is indicative of a bug in useSkipLimitPagination.',
212
+ );
213
+ }
214
+ return clonedRefCountedPointer;
215
+ });
216
+ clonedPointers.push(newPointer);
217
+
218
+ const totalItemCleanupPair: ItemCleanupPair<
219
+ ReadonlyArray<
220
+ ItemCleanupPair<
221
+ ReferenceCountedPointer<
222
+ FragmentReference<TReadFromStore, Connection<TItem>>
223
+ >
224
+ >
225
+ >
226
+ > = [
227
+ clonedPointers,
228
+ () => {
229
+ clonedPointers.forEach(([, dispose]) => {
230
+ dispose();
231
+ });
232
+ },
233
+ ];
234
+
235
+ setState(totalItemCleanupPair);
236
+ };
237
+
238
+ const [, rerender] = useState({});
239
+
240
+ const loadedReferences = state === UNASSIGNED_STATE ? [] : state;
241
+
242
+ const mostRecentItem:
243
+ | LoadedFragmentReference<TReadFromStore, Connection<TItem>>
244
+ | undefined = loadedReferences[loadedReferences.length - 1];
245
+ const mostRecentFragmentReference =
246
+ mostRecentItem?.[0].getItemIfNotDisposed();
247
+
248
+ if (mostRecentItem && mostRecentFragmentReference === null) {
249
+ throw new Error(
250
+ 'FragmentReference is unexpectedly disposed. \
251
+ This is indicative of a bug in Isograph.',
252
+ );
253
+ }
254
+
255
+ const networkRequestStatus =
256
+ mostRecentFragmentReference &&
257
+ getPromiseState(mostRecentFragmentReference.networkRequest);
258
+
259
+ const slicedFragmentReferences =
260
+ networkRequestStatus?.kind === 'Ok'
261
+ ? loadedReferences
262
+ : loadedReferences.slice(0, loadedReferences.length - 1);
263
+
264
+ const completedFragmentReferences = slicedFragmentReferences.map(
265
+ ([pointer]) => {
266
+ const fragmentReference = pointer.getItemIfNotDisposed();
267
+ if (fragmentReference == null) {
268
+ throw new Error(
269
+ 'FragmentReference is unexpectedly disposed. \
270
+ This is indicative of a bug in Isograph.',
271
+ );
272
+ }
273
+ return fragmentReference;
274
+ },
275
+ );
276
+
277
+ const readOutDataAndRecords = completedFragmentReferences.map(
278
+ (fragmentReference) =>
279
+ readButDoNotEvaluate(
280
+ environment,
281
+ fragmentReference,
282
+ networkRequestOptions,
283
+ ),
284
+ );
285
+
286
+ useSubscribeToMultiple<TReadFromStore>(
287
+ subscribeCompletedFragmentReferences(completedFragmentReferences),
288
+ );
289
+
290
+ if (!networkRequestStatus) {
291
+ return {
292
+ kind: 'Complete',
293
+ fetchMore: getFetchMore(initialState?.endCursor ?? null),
294
+ results: [],
295
+ hasNextPage: initialState?.hasNextPage ?? true,
296
+ };
297
+ }
298
+
299
+ switch (networkRequestStatus.kind) {
300
+ case 'Pending': {
301
+ const unsubscribe = subscribeToAnyChange(environment, () => {
302
+ unsubscribe();
303
+ rerender({});
304
+ });
305
+
306
+ const results = readCompletedFragmentReferences(
307
+ completedFragmentReferences,
308
+ );
309
+ return {
310
+ results: results.edges,
311
+ kind: 'Pending',
312
+ pendingFragment: mostRecentFragmentReference,
313
+ };
314
+ }
315
+ case 'Err': {
316
+ throw networkRequestStatus.error;
317
+ }
318
+ case 'Ok': {
319
+ const results = readCompletedFragmentReferences(
320
+ completedFragmentReferences,
321
+ );
322
+
323
+ return {
324
+ results: results.edges,
325
+ hasNextPage: results.pageInfo.hasNextPage,
326
+ kind: 'Complete',
327
+ fetchMore: getFetchMore(results.pageInfo.endCursor),
328
+ };
329
+ }
330
+ }
331
+ }
@@ -1,24 +1,50 @@
1
- import { FragmentReference } from '../core/FragmentReference';
1
+ import {
2
+ ExtractParameters,
3
+ FragmentReference,
4
+ } from '../core/FragmentReference';
2
5
  import {
3
6
  UnassignedState,
4
7
  useUpdatableDisposableState,
5
8
  } from '@isograph/react-disposable-state';
6
9
  import { LoadableField } from '../core/reader';
10
+ import { FetchOptions } from '../core/check';
7
11
 
8
- type UseImperativeLoadableFieldReturn<TArgs, TResult> = {
9
- fragmentReference: FragmentReference<any, TResult> | UnassignedState;
10
- loadField: (args: TArgs) => void;
12
+ type UseImperativeLoadableFieldReturn<
13
+ TReadFromStore extends { data: object; parameters: object },
14
+ TResult,
15
+ TProvidedArgs extends object,
16
+ > = {
17
+ fragmentReference:
18
+ | FragmentReference<TReadFromStore, TResult>
19
+ | UnassignedState;
20
+ loadField: (
21
+ // TODO this should be void iff all args are provided by the query, like in
22
+ // useClientSideDefer.
23
+ args: Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs> | void,
24
+ fetchOptions?: FetchOptions,
25
+ ) => void;
11
26
  };
12
27
 
13
- export function useImperativeLoadableField<TArgs, TResult>(
14
- loadableField: LoadableField<TArgs, TResult>,
15
- ): UseImperativeLoadableFieldReturn<TArgs, TResult> {
28
+ export function useImperativeLoadableField<
29
+ TReadFromStore extends { data: object; parameters: object },
30
+ TResult,
31
+ TProvidedArgs extends object,
32
+ >(
33
+ loadableField: LoadableField<
34
+ TReadFromStore,
35
+ TResult,
36
+ Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>
37
+ >,
38
+ ): UseImperativeLoadableFieldReturn<TReadFromStore, TResult, TProvidedArgs> {
16
39
  const { state, setState } =
17
- useUpdatableDisposableState<FragmentReference<any, TResult>>();
40
+ useUpdatableDisposableState<FragmentReference<TReadFromStore, TResult>>();
18
41
 
19
42
  return {
20
- loadField: (args: TArgs) => {
21
- const [_id, loader] = loadableField(args);
43
+ loadField: (
44
+ args: Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs> | void,
45
+ fetchOptions?: FetchOptions,
46
+ ) => {
47
+ const [_id, loader] = loadableField(args, fetchOptions ?? {});
22
48
  setState(loader());
23
49
  },
24
50
  fragmentReference: state,