@isograph/react 0.1.1 → 0.2.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 +15 -0
- package/dist/core/FragmentReference.js +17 -0
- package/dist/core/IsographEnvironment.d.ts +71 -0
- package/dist/core/IsographEnvironment.js +72 -0
- package/dist/core/PromiseWrapper.d.ts +27 -0
- package/dist/core/PromiseWrapper.js +58 -0
- package/dist/core/areEqualWithDeepComparison.d.ts +3 -0
- package/dist/core/areEqualWithDeepComparison.js +61 -0
- package/dist/core/cache.d.ts +28 -0
- package/dist/core/cache.js +452 -0
- package/dist/core/componentCache.d.ts +5 -0
- package/dist/core/componentCache.js +38 -0
- package/dist/core/entrypoint.d.ts +50 -0
- package/dist/core/entrypoint.js +8 -0
- package/dist/core/garbageCollection.d.ts +11 -0
- package/dist/core/garbageCollection.js +74 -0
- package/dist/core/makeNetworkRequest.d.ts +6 -0
- package/dist/core/makeNetworkRequest.js +62 -0
- package/dist/core/read.d.ts +12 -0
- package/dist/core/read.js +415 -0
- package/dist/core/reader.d.ts +63 -0
- package/dist/core/reader.js +2 -0
- package/dist/core/util.d.ts +17 -0
- package/dist/core/util.js +2 -0
- package/dist/index.d.ts +21 -120
- package/dist/index.js +49 -306
- package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -0
- package/dist/loadable-hooks/useClientSideDefer.js +15 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +5 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +15 -0
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +9 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +15 -0
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +33 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +118 -0
- package/dist/react/FragmentReader.d.ts +13 -0
- package/dist/{EntrypointReader.js → react/FragmentReader.js} +5 -5
- package/dist/react/IsographEnvironmentProvider.d.ts +10 -0
- package/dist/{IsographEnvironment.js → react/IsographEnvironmentProvider.js} +2 -20
- package/dist/react/useImperativeReference.d.ts +7 -0
- package/dist/react/useImperativeReference.js +36 -0
- package/dist/react/useLazyReference.d.ts +5 -0
- package/dist/react/useLazyReference.js +14 -0
- package/dist/react/useReadAndSubscribe.d.ts +11 -0
- package/dist/react/useReadAndSubscribe.js +41 -0
- package/dist/react/useRerenderOnChange.d.ts +3 -0
- package/dist/react/useRerenderOnChange.js +23 -0
- package/dist/react/useResult.d.ts +5 -0
- package/dist/react/useResult.js +36 -0
- package/docs/how-useLazyReference-works.md +117 -0
- package/package.json +11 -6
- package/src/core/FragmentReference.ts +37 -0
- package/src/core/IsographEnvironment.ts +183 -0
- package/src/core/PromiseWrapper.ts +86 -0
- package/src/core/areEqualWithDeepComparison.ts +78 -0
- package/src/core/cache.ts +721 -0
- package/src/core/componentCache.ts +61 -0
- package/src/core/entrypoint.ts +99 -0
- package/src/core/garbageCollection.ts +122 -0
- package/src/core/makeNetworkRequest.ts +99 -0
- package/src/core/read.ts +615 -0
- package/src/core/reader.ts +133 -0
- package/src/core/util.ts +23 -0
- package/src/index.ts +86 -0
- package/src/loadable-hooks/useClientSideDefer.ts +28 -0
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +17 -0
- package/src/loadable-hooks/useImperativeLoadableField.ts +26 -0
- package/src/loadable-hooks/useSkipLimitPagination.ts +211 -0
- package/src/react/FragmentReader.tsx +34 -0
- package/src/react/IsographEnvironmentProvider.tsx +33 -0
- package/src/react/useImperativeReference.ts +57 -0
- package/src/react/useLazyReference.ts +22 -0
- package/src/react/useReadAndSubscribe.ts +66 -0
- package/src/react/useRerenderOnChange.ts +33 -0
- package/src/react/useResult.ts +65 -0
- package/src/tests/__isograph/Query/meName/entrypoint.ts +47 -0
- package/src/tests/__isograph/Query/meName/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meName/param_type.ts +6 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +32 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +83 -0
- package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +11 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +54 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +46 -0
- package/src/tests/__isograph/Query/nodeField/output_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/param_type.ts +6 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +37 -0
- package/src/tests/__isograph/iso.ts +88 -0
- package/src/tests/garbageCollection.test.ts +136 -0
- package/src/tests/isograph.config.json +7 -0
- package/src/tests/meNameSuccessor.ts +20 -0
- package/src/tests/nodeQuery.ts +17 -0
- package/src/tests/schema.graphql +16 -0
- package/src/tests/tsconfig.json +21 -0
- package/tsconfig.json +6 -0
- package/tsconfig.pkg.json +2 -1
- package/dist/EntrypointReader.d.ts +0 -6
- package/dist/IsographEnvironment.d.ts +0 -56
- package/dist/PromiseWrapper.d.ts +0 -13
- package/dist/PromiseWrapper.js +0 -22
- package/dist/cache.d.ts +0 -26
- package/dist/cache.js +0 -274
- package/dist/componentCache.d.ts +0 -6
- package/dist/componentCache.js +0 -31
- package/dist/useImperativeReference.d.ts +0 -8
- package/dist/useImperativeReference.js +0 -28
- package/src/EntrypointReader.tsx +0 -20
- package/src/IsographEnvironment.tsx +0 -120
- package/src/PromiseWrapper.ts +0 -29
- package/src/cache.tsx +0 -484
- package/src/componentCache.ts +0 -41
- package/src/index.tsx +0 -617
- package/src/useImperativeReference.ts +0 -58
@@ -0,0 +1,133 @@
|
|
1
|
+
import { Factory } from '@isograph/disposable-types';
|
2
|
+
import { FragmentReference } from './FragmentReference';
|
3
|
+
import {
|
4
|
+
ComponentOrFieldName,
|
5
|
+
DataId,
|
6
|
+
IsographEnvironment,
|
7
|
+
} from './IsographEnvironment';
|
8
|
+
import {
|
9
|
+
IsographEntrypoint,
|
10
|
+
IsographEntrypointLoader,
|
11
|
+
RefetchQueryNormalizationArtifact,
|
12
|
+
RefetchQueryNormalizationArtifactWrapper,
|
13
|
+
} from './entrypoint';
|
14
|
+
import { Arguments } from './util';
|
15
|
+
|
16
|
+
export type TopLevelReaderArtifact<
|
17
|
+
TReadFromStore extends Object,
|
18
|
+
TClientFieldValue,
|
19
|
+
TComponentProps extends Record<string, never>,
|
20
|
+
> =
|
21
|
+
| EagerReaderArtifact<TReadFromStore, TClientFieldValue>
|
22
|
+
| ComponentReaderArtifact<TReadFromStore, TComponentProps>;
|
23
|
+
|
24
|
+
export type EagerReaderArtifact<
|
25
|
+
TReadFromStore extends Object,
|
26
|
+
TClientFieldValue,
|
27
|
+
> = {
|
28
|
+
readonly kind: 'EagerReaderArtifact';
|
29
|
+
readonly readerAst: ReaderAst<TReadFromStore>;
|
30
|
+
readonly resolver: (data: TReadFromStore) => TClientFieldValue;
|
31
|
+
};
|
32
|
+
|
33
|
+
export type ComponentReaderArtifact<
|
34
|
+
TReadFromStore extends Object,
|
35
|
+
TComponentProps extends Record<string, unknown> = Record<string, never>,
|
36
|
+
> = {
|
37
|
+
readonly kind: 'ComponentReaderArtifact';
|
38
|
+
readonly componentName: ComponentOrFieldName;
|
39
|
+
readonly readerAst: ReaderAst<TReadFromStore>;
|
40
|
+
readonly resolver: (
|
41
|
+
data: TReadFromStore,
|
42
|
+
runtimeProps: TComponentProps,
|
43
|
+
) => React.ReactNode;
|
44
|
+
};
|
45
|
+
|
46
|
+
export type RefetchReaderArtifact = {
|
47
|
+
readonly kind: 'RefetchReaderArtifact';
|
48
|
+
readonly readerAst: ReaderAst<unknown>;
|
49
|
+
readonly resolver: (
|
50
|
+
environment: IsographEnvironment,
|
51
|
+
artifact: RefetchQueryNormalizationArtifact,
|
52
|
+
// TODO type this better
|
53
|
+
variables: any,
|
54
|
+
// TODO type this better
|
55
|
+
filteredVariables: any,
|
56
|
+
rootId: DataId,
|
57
|
+
readerArtifact: TopLevelReaderArtifact<any, any, any> | null,
|
58
|
+
// TODO type this better
|
59
|
+
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
60
|
+
) => () => void;
|
61
|
+
};
|
62
|
+
|
63
|
+
export type ReaderAstNode =
|
64
|
+
| ReaderScalarField
|
65
|
+
| ReaderLinkedField
|
66
|
+
| ReaderNonLoadableResolverField
|
67
|
+
| ReaderImperativelyLoadedField
|
68
|
+
| ReaderLoadableField;
|
69
|
+
|
70
|
+
// @ts-ignore
|
71
|
+
export type ReaderAst<TReadFromStore> = ReadonlyArray<ReaderAstNode>;
|
72
|
+
|
73
|
+
export type ReaderScalarField = {
|
74
|
+
readonly kind: 'Scalar';
|
75
|
+
readonly fieldName: string;
|
76
|
+
readonly alias: string | null;
|
77
|
+
readonly arguments: Arguments | null;
|
78
|
+
};
|
79
|
+
export type ReaderLinkedField = {
|
80
|
+
readonly kind: 'Linked';
|
81
|
+
readonly fieldName: string;
|
82
|
+
readonly alias: string | null;
|
83
|
+
readonly selections: ReaderAst<unknown>;
|
84
|
+
readonly arguments: Arguments | null;
|
85
|
+
};
|
86
|
+
|
87
|
+
export type ReaderNonLoadableResolverField = {
|
88
|
+
readonly kind: 'Resolver';
|
89
|
+
readonly alias: string;
|
90
|
+
// TODO don't type this as any
|
91
|
+
readonly readerArtifact: TopLevelReaderArtifact<any, any, any>;
|
92
|
+
readonly arguments: Arguments | null;
|
93
|
+
readonly usedRefetchQueries: number[];
|
94
|
+
};
|
95
|
+
|
96
|
+
export type ReaderImperativelyLoadedField = {
|
97
|
+
readonly kind: 'ImperativelyLoadedField';
|
98
|
+
readonly alias: string;
|
99
|
+
readonly refetchReaderArtifact: RefetchReaderArtifact;
|
100
|
+
readonly refetchQuery: number;
|
101
|
+
readonly name: string;
|
102
|
+
};
|
103
|
+
|
104
|
+
export type ReaderLoadableField = {
|
105
|
+
readonly kind: 'LoadablySelectedField';
|
106
|
+
readonly alias: string;
|
107
|
+
|
108
|
+
// To generate a stable id, we need the parent id + the name + the args that
|
109
|
+
// we pass to the field, which come from: queryArgs, refetchReaderAst
|
110
|
+
// (technically, but in practice that is always "id") and the user-provided args.
|
111
|
+
readonly name: string;
|
112
|
+
readonly queryArguments: Arguments | null;
|
113
|
+
readonly refetchReaderAst: ReaderAst<any>;
|
114
|
+
|
115
|
+
// TODO we should not type these as any
|
116
|
+
readonly entrypoint:
|
117
|
+
| IsographEntrypoint<any, any>
|
118
|
+
| IsographEntrypointLoader<any, any>;
|
119
|
+
};
|
120
|
+
|
121
|
+
type StableId = string;
|
122
|
+
/// Why is LoadableField the way it is? Let's work backwards.
|
123
|
+
///
|
124
|
+
/// We ultimately need a stable id (for deduplication) and a way to produce a
|
125
|
+
/// FragmentReference (i.e. a Factory). However, this stable id depends on the
|
126
|
+
/// arguments that we pass in, hence we get the current form of LoadableField.
|
127
|
+
///
|
128
|
+
/// Passing TArgs to the LoadableField should be cheap and do no "actual" work,
|
129
|
+
/// except to stringify the args or whatnot. Calling the factory can be
|
130
|
+
/// 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>>];
|
package/src/core/util.ts
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
export type ExtractSecondParam<T extends (arg1: any, arg2: any) => any> =
|
2
|
+
T extends (arg1: any, arg2: infer P) => any ? P : never;
|
3
|
+
|
4
|
+
export type Arguments = Argument[];
|
5
|
+
export type Argument = [ArgumentName, ArgumentValue];
|
6
|
+
export type ArgumentName = string;
|
7
|
+
export type ArgumentValue =
|
8
|
+
| {
|
9
|
+
readonly kind: 'Variable';
|
10
|
+
readonly name: string;
|
11
|
+
}
|
12
|
+
| {
|
13
|
+
readonly kind: 'Literal';
|
14
|
+
readonly value: any;
|
15
|
+
}
|
16
|
+
| {
|
17
|
+
readonly kind: 'String';
|
18
|
+
readonly value: string;
|
19
|
+
}
|
20
|
+
| {
|
21
|
+
readonly kind: 'Enum';
|
22
|
+
readonly value: string;
|
23
|
+
};
|
package/src/index.ts
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
export {
|
2
|
+
retainQuery,
|
3
|
+
unretainQuery,
|
4
|
+
type RetainedQuery,
|
5
|
+
garbageCollectEnvironment,
|
6
|
+
} from './core/garbageCollection';
|
7
|
+
export {
|
8
|
+
type PromiseWrapper,
|
9
|
+
readPromise,
|
10
|
+
getPromiseState,
|
11
|
+
wrapResolvedValue,
|
12
|
+
wrapPromise,
|
13
|
+
} from './core/PromiseWrapper';
|
14
|
+
export { subscribe, normalizeData } from './core/cache';
|
15
|
+
export { makeNetworkRequest } from './core/makeNetworkRequest';
|
16
|
+
export {
|
17
|
+
ROOT_ID,
|
18
|
+
type DataId,
|
19
|
+
type DataTypeValue,
|
20
|
+
type IsographEnvironment,
|
21
|
+
type IsographNetworkFunction,
|
22
|
+
type IsographStore,
|
23
|
+
type Link,
|
24
|
+
type StoreRecord,
|
25
|
+
createIsographEnvironment,
|
26
|
+
createIsographStore,
|
27
|
+
defaultMissingFieldHandler,
|
28
|
+
} from './core/IsographEnvironment';
|
29
|
+
export {
|
30
|
+
type EagerReaderArtifact,
|
31
|
+
type ComponentReaderArtifact,
|
32
|
+
type RefetchReaderArtifact,
|
33
|
+
type ReaderAst,
|
34
|
+
type ReaderAstNode,
|
35
|
+
type ReaderLinkedField,
|
36
|
+
type ReaderNonLoadableResolverField,
|
37
|
+
type ReaderScalarField,
|
38
|
+
type TopLevelReaderArtifact,
|
39
|
+
type LoadableField,
|
40
|
+
} from './core/reader';
|
41
|
+
export {
|
42
|
+
type NormalizationAst,
|
43
|
+
type NormalizationAstNode,
|
44
|
+
type NormalizationLinkedField,
|
45
|
+
type NormalizationScalarField,
|
46
|
+
type IsographEntrypoint,
|
47
|
+
assertIsEntrypoint,
|
48
|
+
type RefetchQueryNormalizationArtifact,
|
49
|
+
type RefetchQueryNormalizationArtifactWrapper,
|
50
|
+
type ExtractProps,
|
51
|
+
type ExtractReadFromStore,
|
52
|
+
type ExtractResolverResult,
|
53
|
+
} from './core/entrypoint';
|
54
|
+
export { readButDoNotEvaluate } from './core/read';
|
55
|
+
export {
|
56
|
+
type ExtractSecondParam,
|
57
|
+
type Argument,
|
58
|
+
type ArgumentName,
|
59
|
+
type ArgumentValue,
|
60
|
+
type Arguments,
|
61
|
+
} from './core/util';
|
62
|
+
export {
|
63
|
+
type FragmentReference,
|
64
|
+
type Variables,
|
65
|
+
stableIdForFragmentReference,
|
66
|
+
} from './core/FragmentReference';
|
67
|
+
|
68
|
+
export {
|
69
|
+
IsographEnvironmentProvider,
|
70
|
+
useIsographEnvironment,
|
71
|
+
type IsographEnvironmentProviderProps,
|
72
|
+
} from './react/IsographEnvironmentProvider';
|
73
|
+
export { useImperativeReference } from './react/useImperativeReference';
|
74
|
+
export { FragmentReader } from './react/FragmentReader';
|
75
|
+
export { useResult } from './react/useResult';
|
76
|
+
export {
|
77
|
+
useReadAndSubscribe,
|
78
|
+
useSubscribeToMultiple,
|
79
|
+
} from './react/useReadAndSubscribe';
|
80
|
+
export { useLazyReference } from './react/useLazyReference';
|
81
|
+
export { useRerenderOnChange } from './react/useRerenderOnChange';
|
82
|
+
|
83
|
+
export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
|
84
|
+
export { useImperativeExposedMutationField } from './loadable-hooks/useImperativeExposedMutationField';
|
85
|
+
export { useSkipLimitPagination } from './loadable-hooks/useSkipLimitPagination';
|
86
|
+
export { useImperativeLoadableField } from './loadable-hooks/useImperativeLoadableField';
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { FragmentReference } from '../core/FragmentReference';
|
2
|
+
import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
|
3
|
+
import { getOrCreateItemInSuspenseCache } from '../core/cache';
|
4
|
+
import { useLazyDisposableState } from '@isograph/react-disposable-state';
|
5
|
+
import { LoadableField } from '../core/reader';
|
6
|
+
|
7
|
+
export function useClientSideDefer<TResult>(
|
8
|
+
loadableField: LoadableField<void, TResult>,
|
9
|
+
): FragmentReference<Record<string, never>, TResult>;
|
10
|
+
|
11
|
+
export function useClientSideDefer<TArgs extends Object, TResult>(
|
12
|
+
loadableField: LoadableField<TArgs, TResult>,
|
13
|
+
args: TArgs,
|
14
|
+
): FragmentReference<TArgs, TResult>;
|
15
|
+
|
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);
|
22
|
+
const environment = useIsographEnvironment();
|
23
|
+
const cache = getOrCreateItemInSuspenseCache(environment, id, loader);
|
24
|
+
|
25
|
+
const fragmentReference = useLazyDisposableState(cache).state;
|
26
|
+
|
27
|
+
return fragmentReference;
|
28
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
type UseImperativeLoadableFieldReturn<TArgs> = {
|
2
|
+
loadField: (args: TArgs) => void;
|
3
|
+
};
|
4
|
+
|
5
|
+
// Note: this function doesn't seem to work if there are additional arguments,
|
6
|
+
// e.g. with set_pet_tagline. Why? This seems to straightforwardly call
|
7
|
+
// exposedField(args)[1](); Odd.
|
8
|
+
export function useImperativeExposedMutationField<TArgs>(
|
9
|
+
exposedField: (args: TArgs) => [string, () => void],
|
10
|
+
): UseImperativeLoadableFieldReturn<TArgs> {
|
11
|
+
return {
|
12
|
+
loadField: (args: TArgs) => {
|
13
|
+
const [_id, loader] = exposedField(args);
|
14
|
+
loader();
|
15
|
+
},
|
16
|
+
};
|
17
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { FragmentReference } from '../core/FragmentReference';
|
2
|
+
import {
|
3
|
+
UnassignedState,
|
4
|
+
useUpdatableDisposableState,
|
5
|
+
} from '@isograph/react-disposable-state';
|
6
|
+
import { LoadableField } from '../core/reader';
|
7
|
+
|
8
|
+
type UseImperativeLoadableFieldReturn<TArgs, TResult> = {
|
9
|
+
fragmentReference: FragmentReference<any, TResult> | UnassignedState;
|
10
|
+
loadField: (args: TArgs) => void;
|
11
|
+
};
|
12
|
+
|
13
|
+
export function useImperativeLoadableField<TArgs, TResult>(
|
14
|
+
loadableField: LoadableField<TArgs, TResult>,
|
15
|
+
): UseImperativeLoadableFieldReturn<TArgs, TResult> {
|
16
|
+
const { state, setState } =
|
17
|
+
useUpdatableDisposableState<FragmentReference<any, TResult>>();
|
18
|
+
|
19
|
+
return {
|
20
|
+
loadField: (args: TArgs) => {
|
21
|
+
const [_id, loader] = loadableField(args);
|
22
|
+
setState(loader());
|
23
|
+
},
|
24
|
+
fragmentReference: state,
|
25
|
+
};
|
26
|
+
}
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import { LoadableField } from '../core/reader';
|
2
|
+
import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
|
3
|
+
import { ItemCleanupPair } from '@isograph/disposable-types';
|
4
|
+
import { FragmentReference } from '../core/FragmentReference';
|
5
|
+
import { maybeUnwrapNetworkRequest } from '../react/useResult';
|
6
|
+
import { readButDoNotEvaluate } from '../core/read';
|
7
|
+
import {
|
8
|
+
UNASSIGNED_STATE,
|
9
|
+
useUpdatableDisposableState,
|
10
|
+
} from '@isograph/react-disposable-state';
|
11
|
+
import {
|
12
|
+
createReferenceCountedPointer,
|
13
|
+
ReferenceCountedPointer,
|
14
|
+
} from '@isograph/reference-counted-pointer';
|
15
|
+
import { getPromiseState, readPromise } from '../core/PromiseWrapper';
|
16
|
+
|
17
|
+
type SkipOrLimit = 'skip' | 'limit';
|
18
|
+
type OmitSkipLimit<TArgs> = keyof Omit<TArgs, SkipOrLimit> extends never
|
19
|
+
? void | Record<string, never>
|
20
|
+
: Omit<TArgs, SkipOrLimit>;
|
21
|
+
|
22
|
+
type UseSkipLimitReturnValue<TArgs, TItem> =
|
23
|
+
| {
|
24
|
+
readonly kind: 'Complete';
|
25
|
+
readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
|
26
|
+
readonly results: ReadonlyArray<TItem>;
|
27
|
+
}
|
28
|
+
| {
|
29
|
+
readonly kind: 'Pending';
|
30
|
+
readonly results: ReadonlyArray<TItem>;
|
31
|
+
readonly pendingFragment: FragmentReference<any, ReadonlyArray<TItem>>;
|
32
|
+
};
|
33
|
+
|
34
|
+
type ArrayFragmentReference<TItem> = FragmentReference<
|
35
|
+
any,
|
36
|
+
ReadonlyArray<TItem>
|
37
|
+
>;
|
38
|
+
|
39
|
+
type LoadedFragmentReferences<TItem> = ReadonlyArray<
|
40
|
+
ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
|
41
|
+
>;
|
42
|
+
|
43
|
+
function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
|
44
|
+
let outArray: Array<T> = [];
|
45
|
+
for (const subarr of arr) {
|
46
|
+
for (const item of subarr) {
|
47
|
+
outArray.push(item);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
return outArray;
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* accepts a loadableField that accepts skip and limit arguments
|
55
|
+
* and returns:
|
56
|
+
* - a fetchMore function that, when called, triggers a network
|
57
|
+
* request for additional data, and
|
58
|
+
* - the data received so far.
|
59
|
+
*
|
60
|
+
* This hook will suspend if any network request is in flight.
|
61
|
+
*
|
62
|
+
* Calling fetchMore before the hook mounts is a no-op.
|
63
|
+
*
|
64
|
+
* NOTE: this hook does not subscribe to changes. This is a known
|
65
|
+
* issue. If you are running into this issue, reach out on GitHub/
|
66
|
+
* Twitter, and we'll fix the issue.
|
67
|
+
*/
|
68
|
+
export function useSkipLimitPagination<
|
69
|
+
TArgs extends {
|
70
|
+
skip: number | void | null;
|
71
|
+
limit: number | void | null;
|
72
|
+
},
|
73
|
+
TItem,
|
74
|
+
>(
|
75
|
+
loadableField: LoadableField<TArgs, Array<TItem>>,
|
76
|
+
): UseSkipLimitReturnValue<TArgs, TItem> {
|
77
|
+
const networkRequestOptions = {
|
78
|
+
suspendIfInFlight: true,
|
79
|
+
throwOnNetworkError: true,
|
80
|
+
};
|
81
|
+
const { state, setState } =
|
82
|
+
useUpdatableDisposableState<LoadedFragmentReferences<TItem>>();
|
83
|
+
|
84
|
+
const environment = useIsographEnvironment();
|
85
|
+
|
86
|
+
function readCompletedFragmentReferences(
|
87
|
+
completedReferences: ReadonlyArray<
|
88
|
+
ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
|
89
|
+
>,
|
90
|
+
) {
|
91
|
+
// In general, this will not suspend. But it could, if there is missing data.
|
92
|
+
// A better version of this hook would not do any reading here.
|
93
|
+
const results = completedReferences.map(([pointer]) => {
|
94
|
+
const fragmentReference = pointer.getItemIfNotDisposed();
|
95
|
+
if (fragmentReference == null) {
|
96
|
+
throw new Error(
|
97
|
+
'FragmentReference is unexpectedly disposed. \
|
98
|
+
This is indicative of a bug in Isograph.',
|
99
|
+
);
|
100
|
+
}
|
101
|
+
|
102
|
+
maybeUnwrapNetworkRequest(
|
103
|
+
fragmentReference.networkRequest,
|
104
|
+
networkRequestOptions,
|
105
|
+
);
|
106
|
+
const data = readButDoNotEvaluate(
|
107
|
+
environment,
|
108
|
+
fragmentReference,
|
109
|
+
networkRequestOptions,
|
110
|
+
);
|
111
|
+
|
112
|
+
const readerWithRefetchQueries = readPromise(
|
113
|
+
fragmentReference.readerWithRefetchQueries,
|
114
|
+
);
|
115
|
+
|
116
|
+
return readerWithRefetchQueries.readerArtifact.resolver(
|
117
|
+
data.item,
|
118
|
+
undefined,
|
119
|
+
) as ReadonlyArray<any>;
|
120
|
+
});
|
121
|
+
|
122
|
+
const items = flatten(results);
|
123
|
+
return items;
|
124
|
+
}
|
125
|
+
|
126
|
+
const getFetchMore =
|
127
|
+
(loadedSoFar: number) =>
|
128
|
+
(args: OmitSkipLimit<TArgs>, count: number): void => {
|
129
|
+
// @ts-expect-error
|
130
|
+
const loadedField = loadableField({
|
131
|
+
...args,
|
132
|
+
skip: loadedSoFar,
|
133
|
+
limit: count,
|
134
|
+
})[1]();
|
135
|
+
const newPointer = createReferenceCountedPointer(loadedField);
|
136
|
+
const clonedPointers = loadedReferences.map(([refCountedPointer]) => {
|
137
|
+
const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
|
138
|
+
if (clonedRefCountedPointer == null) {
|
139
|
+
throw new Error(
|
140
|
+
'This reference counted pointer has already been disposed. \
|
141
|
+
This is indicative of a bug in useSkipLimitPagination.',
|
142
|
+
);
|
143
|
+
}
|
144
|
+
return clonedRefCountedPointer;
|
145
|
+
});
|
146
|
+
clonedPointers.push(newPointer);
|
147
|
+
|
148
|
+
const totalItemCleanupPair: ItemCleanupPair<
|
149
|
+
ReadonlyArray<
|
150
|
+
ItemCleanupPair<
|
151
|
+
ReferenceCountedPointer<ArrayFragmentReference<TItem>>
|
152
|
+
>
|
153
|
+
>
|
154
|
+
> = [
|
155
|
+
clonedPointers,
|
156
|
+
() => {
|
157
|
+
clonedPointers.forEach(([, dispose]) => {
|
158
|
+
dispose();
|
159
|
+
});
|
160
|
+
},
|
161
|
+
];
|
162
|
+
|
163
|
+
setState(totalItemCleanupPair);
|
164
|
+
};
|
165
|
+
|
166
|
+
const loadedReferences = state === UNASSIGNED_STATE ? [] : state;
|
167
|
+
if (loadedReferences.length === 0) {
|
168
|
+
return {
|
169
|
+
kind: 'Complete',
|
170
|
+
fetchMore: getFetchMore(0),
|
171
|
+
results: [],
|
172
|
+
};
|
173
|
+
}
|
174
|
+
|
175
|
+
const mostRecentItem = loadedReferences[loadedReferences.length - 1];
|
176
|
+
const mostRecentFragmentReference = mostRecentItem[0].getItemIfNotDisposed();
|
177
|
+
if (mostRecentFragmentReference === null) {
|
178
|
+
throw new Error(
|
179
|
+
'FragmentReference is unexpectedly disposed. \
|
180
|
+
This is indicative of a bug in Isograph.',
|
181
|
+
);
|
182
|
+
}
|
183
|
+
|
184
|
+
const networkRequestStatus = getPromiseState(
|
185
|
+
mostRecentFragmentReference.networkRequest,
|
186
|
+
);
|
187
|
+
switch (networkRequestStatus.kind) {
|
188
|
+
case 'Pending': {
|
189
|
+
const completedFragmentReferences = loadedReferences.slice(
|
190
|
+
0,
|
191
|
+
loadedReferences.length - 1,
|
192
|
+
);
|
193
|
+
return {
|
194
|
+
kind: 'Pending',
|
195
|
+
pendingFragment: mostRecentFragmentReference,
|
196
|
+
results: readCompletedFragmentReferences(completedFragmentReferences),
|
197
|
+
};
|
198
|
+
}
|
199
|
+
case 'Err': {
|
200
|
+
throw networkRequestStatus.error;
|
201
|
+
}
|
202
|
+
case 'Ok': {
|
203
|
+
const results = readCompletedFragmentReferences(loadedReferences);
|
204
|
+
return {
|
205
|
+
kind: 'Complete',
|
206
|
+
results,
|
207
|
+
fetchMore: getFetchMore(results.length),
|
208
|
+
};
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import { ExtractReadFromStore, IsographEntrypoint } from '../core/entrypoint';
|
3
|
+
import { FragmentReference } from '../core/FragmentReference';
|
4
|
+
import { useResult } from './useResult';
|
5
|
+
import { NetworkRequestReaderOptions } from '../core/read';
|
6
|
+
|
7
|
+
export function FragmentReader<
|
8
|
+
TProps extends Record<any, any>,
|
9
|
+
TEntrypoint extends IsographEntrypoint<any, React.FC<TProps>>,
|
10
|
+
>(
|
11
|
+
props: TProps extends Record<string, never>
|
12
|
+
? {
|
13
|
+
fragmentReference: FragmentReference<
|
14
|
+
ExtractReadFromStore<TEntrypoint>,
|
15
|
+
React.FC<{}>
|
16
|
+
>;
|
17
|
+
additionalProps?: TProps;
|
18
|
+
networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
|
19
|
+
}
|
20
|
+
: {
|
21
|
+
fragmentReference: FragmentReference<
|
22
|
+
ExtractReadFromStore<TEntrypoint>,
|
23
|
+
React.FC<TProps>
|
24
|
+
>;
|
25
|
+
additionalProps: TProps;
|
26
|
+
networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
|
27
|
+
},
|
28
|
+
): React.ReactNode {
|
29
|
+
const Component = useResult(
|
30
|
+
props.fragmentReference,
|
31
|
+
props.networkRequestOptions,
|
32
|
+
);
|
33
|
+
return <Component {...props.additionalProps} />;
|
34
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import { ReactNode, createContext, useContext } from 'react';
|
3
|
+
import { type IsographEnvironment } from '../core/IsographEnvironment';
|
4
|
+
|
5
|
+
export const IsographEnvironmentContext =
|
6
|
+
createContext<IsographEnvironment | null>(null);
|
7
|
+
|
8
|
+
export type IsographEnvironmentProviderProps = {
|
9
|
+
readonly environment: IsographEnvironment;
|
10
|
+
readonly children: ReactNode;
|
11
|
+
};
|
12
|
+
|
13
|
+
export function IsographEnvironmentProvider({
|
14
|
+
environment,
|
15
|
+
children,
|
16
|
+
}: IsographEnvironmentProviderProps): React.ReactElement {
|
17
|
+
return (
|
18
|
+
<IsographEnvironmentContext.Provider value={environment}>
|
19
|
+
{children}
|
20
|
+
</IsographEnvironmentContext.Provider>
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
export function useIsographEnvironment(): IsographEnvironment {
|
25
|
+
const context = useContext(IsographEnvironmentContext);
|
26
|
+
if (context == null) {
|
27
|
+
throw new Error(
|
28
|
+
'Unexpected null environment context. Make sure to render ' +
|
29
|
+
'this component within an IsographEnvironmentProvider component',
|
30
|
+
);
|
31
|
+
}
|
32
|
+
return context;
|
33
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import {
|
2
|
+
UnassignedState,
|
3
|
+
useUpdatableDisposableState,
|
4
|
+
} from '@isograph/react-disposable-state';
|
5
|
+
import { IsographEntrypoint } from '../core/entrypoint';
|
6
|
+
import { FragmentReference, Variables } from '../core/FragmentReference';
|
7
|
+
import { useIsographEnvironment } from './IsographEnvironmentProvider';
|
8
|
+
import { ROOT_ID } from '../core/IsographEnvironment';
|
9
|
+
import { makeNetworkRequest } from '../core/makeNetworkRequest';
|
10
|
+
import { wrapResolvedValue } from '../core/PromiseWrapper';
|
11
|
+
|
12
|
+
// TODO rename this to useImperativelyLoadedEntrypoint
|
13
|
+
|
14
|
+
export function useImperativeReference<
|
15
|
+
TReadFromStore extends Object,
|
16
|
+
TClientFieldValue,
|
17
|
+
>(
|
18
|
+
entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
|
19
|
+
): {
|
20
|
+
fragmentReference:
|
21
|
+
| FragmentReference<TReadFromStore, TClientFieldValue>
|
22
|
+
| UnassignedState;
|
23
|
+
loadFragmentReference: (variables: Variables) => void;
|
24
|
+
} {
|
25
|
+
const { state, setState } =
|
26
|
+
useUpdatableDisposableState<
|
27
|
+
FragmentReference<TReadFromStore, TClientFieldValue>
|
28
|
+
>();
|
29
|
+
const environment = useIsographEnvironment();
|
30
|
+
return {
|
31
|
+
fragmentReference: state,
|
32
|
+
loadFragmentReference: (variables: Variables) => {
|
33
|
+
const [networkRequest, disposeNetworkRequest] = makeNetworkRequest(
|
34
|
+
environment,
|
35
|
+
entrypoint,
|
36
|
+
variables,
|
37
|
+
);
|
38
|
+
setState([
|
39
|
+
{
|
40
|
+
kind: 'FragmentReference',
|
41
|
+
readerWithRefetchQueries: wrapResolvedValue({
|
42
|
+
kind: 'ReaderWithRefetchQueries',
|
43
|
+
readerArtifact: entrypoint.readerWithRefetchQueries.readerArtifact,
|
44
|
+
nestedRefetchQueries:
|
45
|
+
entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
46
|
+
}),
|
47
|
+
root: ROOT_ID,
|
48
|
+
variables,
|
49
|
+
networkRequest,
|
50
|
+
},
|
51
|
+
() => {
|
52
|
+
disposeNetworkRequest();
|
53
|
+
},
|
54
|
+
]);
|
55
|
+
},
|
56
|
+
};
|
57
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { FragmentReference, Variables } from '../core/FragmentReference';
|
2
|
+
import { useIsographEnvironment } from './IsographEnvironmentProvider';
|
3
|
+
import { IsographEntrypoint } from '../core/entrypoint';
|
4
|
+
import { getOrCreateCacheForArtifact } from '../core/cache';
|
5
|
+
import { useLazyDisposableState } from '@isograph/react-disposable-state';
|
6
|
+
|
7
|
+
export function useLazyReference<
|
8
|
+
TReadFromStore extends Object,
|
9
|
+
TClientFieldValue,
|
10
|
+
>(
|
11
|
+
entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
|
12
|
+
variables: Variables,
|
13
|
+
): {
|
14
|
+
fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>;
|
15
|
+
} {
|
16
|
+
const environment = useIsographEnvironment();
|
17
|
+
const cache = getOrCreateCacheForArtifact(environment, entrypoint, variables);
|
18
|
+
|
19
|
+
return {
|
20
|
+
fragmentReference: useLazyDisposableState(cache).state,
|
21
|
+
};
|
22
|
+
}
|