@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.
- package/dist/core/FragmentReference.d.ts +14 -4
- package/dist/core/FragmentReference.d.ts.map +1 -0
- package/dist/core/FragmentReference.js +2 -3
- package/dist/core/IsographEnvironment.d.ts +28 -10
- package/dist/core/IsographEnvironment.d.ts.map +1 -0
- package/dist/core/IsographEnvironment.js +15 -22
- package/dist/core/PromiseWrapper.d.ts +1 -0
- package/dist/core/PromiseWrapper.d.ts.map +1 -0
- package/dist/core/PromiseWrapper.js +4 -5
- package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
- package/dist/core/areEqualWithDeepComparison.js +73 -39
- package/dist/core/cache.d.ts +26 -10
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +160 -98
- package/dist/core/check.d.ts +18 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +127 -0
- package/dist/core/componentCache.d.ts +1 -1
- package/dist/core/componentCache.d.ts.map +1 -0
- package/dist/core/componentCache.js +14 -14
- package/dist/core/entrypoint.d.ts +27 -8
- package/dist/core/entrypoint.d.ts.map +1 -0
- package/dist/core/entrypoint.js +1 -2
- package/dist/core/garbageCollection.d.ts +3 -1
- package/dist/core/garbageCollection.d.ts.map +1 -0
- package/dist/core/garbageCollection.js +48 -15
- package/dist/core/logging.d.ts +69 -0
- package/dist/core/logging.d.ts.map +1 -0
- package/dist/core/logging.js +19 -0
- package/dist/core/makeNetworkRequest.d.ts +4 -1
- package/dist/core/makeNetworkRequest.d.ts.map +1 -0
- package/dist/core/makeNetworkRequest.js +71 -15
- package/dist/core/read.d.ts +20 -5
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +104 -41
- package/dist/core/reader.d.ts +34 -10
- package/dist/core/reader.d.ts.map +1 -0
- package/dist/core/util.d.ts +2 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -2
- package/dist/loadable-hooks/useClientSideDefer.d.ts +15 -3
- package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
- package/dist/loadable-hooks/useClientSideDefer.js +4 -6
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -5
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +3 -4
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +18 -24
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +88 -44
- package/dist/react/FragmentReader.d.ts +7 -4
- package/dist/react/FragmentReader.d.ts.map +1 -0
- package/dist/react/FragmentReader.js +4 -2
- package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
- package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
- package/dist/react/IsographEnvironmentProvider.js +3 -3
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
- package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
- package/dist/react/useImperativeReference.d.ts +8 -3
- package/dist/react/useImperativeReference.d.ts.map +1 -0
- package/dist/react/useImperativeReference.js +4 -5
- package/dist/react/useLazyReference.d.ts +7 -2
- package/dist/react/useLazyReference.d.ts.map +1 -0
- package/dist/react/useLazyReference.js +11 -4
- package/dist/react/useReadAndSubscribe.d.ts +12 -3
- package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
- package/dist/react/useReadAndSubscribe.js +6 -7
- package/dist/react/useRerenderOnChange.d.ts +6 -1
- package/dist/react/useRerenderOnChange.d.ts.map +1 -0
- package/dist/react/useRerenderOnChange.js +3 -4
- package/dist/react/useResult.d.ts +5 -1
- package/dist/react/useResult.d.ts.map +1 -0
- package/dist/react/useResult.js +8 -5
- package/{src/tests/isograph.config.json → isograph.config.json} +1 -1
- package/package.json +12 -8
- package/{src/tests/schema.graphql → schema.graphql} +1 -0
- package/src/core/FragmentReference.ts +17 -5
- package/src/core/IsographEnvironment.ts +38 -29
- package/src/core/areEqualWithDeepComparison.ts +76 -42
- package/src/core/cache.ts +237 -123
- package/src/core/check.ts +207 -0
- package/src/core/componentCache.ts +18 -17
- package/src/core/entrypoint.ts +15 -8
- package/src/core/garbageCollection.ts +71 -20
- package/src/core/logging.ts +116 -0
- package/src/core/makeNetworkRequest.ts +89 -13
- package/src/core/read.ts +162 -55
- package/src/core/reader.ts +40 -13
- package/src/core/util.ts +4 -0
- package/src/index.ts +14 -1
- package/src/loadable-hooks/useClientSideDefer.ts +45 -15
- package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
- package/src/loadable-hooks/useImperativeLoadableField.ts +36 -10
- package/src/loadable-hooks/useSkipLimitPagination.ts +231 -90
- package/src/react/FragmentReader.tsx +13 -4
- package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
- package/src/react/useImperativeReference.ts +18 -7
- package/src/react/useLazyReference.ts +24 -4
- package/src/react/useReadAndSubscribe.ts +20 -5
- package/src/react/useRerenderOnChange.ts +6 -1
- package/src/react/useResult.ts +10 -2
- package/src/tests/__isograph/Query/meName/entrypoint.ts +7 -2
- package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +9 -2
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +13 -2
- package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
- package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
- package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
- package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
- package/src/tests/__isograph/iso.ts +22 -11
- package/src/tests/garbageCollection.test.ts +45 -39
- package/src/tests/meNameSuccessor.ts +8 -3
- package/src/tests/nodeQuery.ts +6 -4
- package/src/tests/normalizeData.test.ts +120 -0
- package/src/tests/tsconfig.json +3 -3
- package/tsconfig.json +2 -2
- package/tsconfig.pkg.json +6 -1
- package/vitest.config.ts +20 -0
package/src/core/reader.ts
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
import { Factory } from '@isograph/disposable-types';
|
2
|
-
import {
|
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
|
22
|
+
TReadFromStore extends { parameters: object; data: object },
|
18
23
|
TClientFieldValue,
|
19
|
-
TComponentProps extends Record<
|
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
|
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: (
|
35
|
+
readonly resolver: (
|
36
|
+
data: ResolverFirstParameter<TReadFromStore>,
|
37
|
+
) => TClientFieldValue;
|
31
38
|
};
|
32
39
|
|
33
40
|
export type ComponentReaderArtifact<
|
34
|
-
TReadFromStore extends
|
35
|
-
TComponentProps extends Record<string, unknown> = Record<
|
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
|
-
|
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<
|
132
|
-
|
133
|
-
|
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 {
|
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<
|
8
|
-
|
9
|
-
|
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<
|
12
|
-
|
13
|
-
|
14
|
-
|
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<
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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 {
|
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<
|
9
|
-
|
10
|
-
|
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<
|
14
|
-
|
15
|
-
|
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<
|
40
|
+
useUpdatableDisposableState<FragmentReference<TReadFromStore, TResult>>();
|
18
41
|
|
19
42
|
return {
|
20
|
-
loadField: (
|
21
|
-
|
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,
|