@isograph/react 0.0.0-main-47ff1729 → 0.0.0-main-5c3c47b2

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/index.d.ts CHANGED
@@ -16,4 +16,4 @@ export { useReadAndSubscribe, useSubscribeToMultiple, } from './react/useReadAnd
16
16
  export { useLazyReference } from './react/useLazyReference';
17
17
  export { useRerenderOnChange } from './react/useRerenderOnChange';
18
18
  export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
19
- export { useSuspensefulSkipLimitPagination } from './loadable-hooks/useSuspensefulSkipLimitPagination';
19
+ export { useSkipLimitPagination } from './loadable-hooks/useSkipLimitPagination';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useSuspensefulSkipLimitPagination = exports.useClientSideDefer = exports.useRerenderOnChange = exports.useLazyReference = exports.useSubscribeToMultiple = exports.useReadAndSubscribe = exports.useResult = exports.FragmentReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.stableIdForFragmentReference = exports.readButDoNotEvaluate = exports.assertIsEntrypoint = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.makeNetworkRequest = exports.normalizeData = exports.subscribe = exports.wrapPromise = exports.wrapResolvedValue = exports.getPromiseState = exports.readPromise = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
3
+ exports.useSkipLimitPagination = exports.useClientSideDefer = exports.useRerenderOnChange = exports.useLazyReference = exports.useSubscribeToMultiple = exports.useReadAndSubscribe = exports.useResult = exports.FragmentReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.stableIdForFragmentReference = exports.readButDoNotEvaluate = exports.assertIsEntrypoint = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.makeNetworkRequest = exports.normalizeData = exports.subscribe = exports.wrapPromise = exports.wrapResolvedValue = exports.getPromiseState = exports.readPromise = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
4
4
  var garbageCollection_1 = require("./core/garbageCollection");
5
5
  Object.defineProperty(exports, "retainQuery", { enumerable: true, get: function () { return garbageCollection_1.retainQuery; } });
6
6
  Object.defineProperty(exports, "unretainQuery", { enumerable: true, get: function () { return garbageCollection_1.unretainQuery; } });
@@ -44,5 +44,5 @@ var useRerenderOnChange_1 = require("./react/useRerenderOnChange");
44
44
  Object.defineProperty(exports, "useRerenderOnChange", { enumerable: true, get: function () { return useRerenderOnChange_1.useRerenderOnChange; } });
45
45
  var useClientSideDefer_1 = require("./loadable-hooks/useClientSideDefer");
46
46
  Object.defineProperty(exports, "useClientSideDefer", { enumerable: true, get: function () { return useClientSideDefer_1.useClientSideDefer; } });
47
- var useSuspensefulSkipLimitPagination_1 = require("./loadable-hooks/useSuspensefulSkipLimitPagination");
48
- Object.defineProperty(exports, "useSuspensefulSkipLimitPagination", { enumerable: true, get: function () { return useSuspensefulSkipLimitPagination_1.useSuspensefulSkipLimitPagination; } });
47
+ var useSkipLimitPagination_1 = require("./loadable-hooks/useSkipLimitPagination");
48
+ Object.defineProperty(exports, "useSkipLimitPagination", { enumerable: true, get: function () { return useSkipLimitPagination_1.useSkipLimitPagination; } });
@@ -1,9 +1,15 @@
1
1
  import { LoadableField } from '../core/reader';
2
+ import { FragmentReference } from '../core/FragmentReference';
2
3
  type SkipOrLimit = 'skip' | 'limit';
3
4
  type OmitSkipLimit<TArgs> = keyof Omit<TArgs, SkipOrLimit> extends never ? void | Record<string, never> : Omit<TArgs, SkipOrLimit>;
4
5
  type UseSkipLimitReturnValue<TArgs, TItem> = {
6
+ readonly kind: 'Complete';
5
7
  readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
6
8
  readonly results: ReadonlyArray<TItem>;
9
+ } | {
10
+ readonly kind: 'Pending';
11
+ readonly results: ReadonlyArray<TItem>;
12
+ readonly pendingFragment: FragmentReference<any, ReadonlyArray<TItem>>;
7
13
  };
8
14
  /**
9
15
  * accepts a loadableField that accepts skip and limit arguments
@@ -16,7 +22,7 @@ type UseSkipLimitReturnValue<TArgs, TItem> = {
16
22
  *
17
23
  * Calling fetchMore before the hook mounts is a no-op.
18
24
  */
19
- export declare function useSuspensefulSkipLimitPagination<TArgs extends {
25
+ export declare function useSkipLimitPagination<TArgs extends {
20
26
  skip: number | void | null;
21
27
  limit: number | void | null;
22
28
  }, TItem>(loadableField: LoadableField<TArgs, Array<TItem>>): UseSkipLimitReturnValue<TArgs, TItem>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSkipLimitPagination = void 0;
4
+ const IsographEnvironmentProvider_1 = require("../react/IsographEnvironmentProvider");
5
+ const useResult_1 = require("../react/useResult");
6
+ const read_1 = require("../core/read");
7
+ const react_disposable_state_1 = require("@isograph/react-disposable-state");
8
+ const reference_counted_pointer_1 = require("@isograph/reference-counted-pointer");
9
+ const PromiseWrapper_1 = require("../core/PromiseWrapper");
10
+ function flatten(arr) {
11
+ let outArray = [];
12
+ for (const subarr of arr) {
13
+ for (const item of subarr) {
14
+ outArray.push(item);
15
+ }
16
+ }
17
+ return outArray;
18
+ }
19
+ /**
20
+ * accepts a loadableField that accepts skip and limit arguments
21
+ * and returns:
22
+ * - a fetchMore function that, when called, triggers a network
23
+ * request for additional data, and
24
+ * - the data received so far.
25
+ *
26
+ * This hook will suspend if any network request is in flight.
27
+ *
28
+ * Calling fetchMore before the hook mounts is a no-op.
29
+ */
30
+ function useSkipLimitPagination(loadableField) {
31
+ const networkRequestOptions = {
32
+ suspendIfInFlight: true,
33
+ throwOnNetworkError: true,
34
+ };
35
+ const { state, setState } = (0, react_disposable_state_1.useUpdatableDisposableState)();
36
+ const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
37
+ function readCompletedFragmentReferences(completedReferences) {
38
+ // In general, this will not suspend. But it could, if there is missing data.
39
+ // A better version of this hook would not do any reading here.
40
+ const results = completedReferences.map(([pointer]) => {
41
+ const fragmentReference = pointer.getItemIfNotDisposed();
42
+ if (fragmentReference == null) {
43
+ throw new Error('FragmentReference is unexpectedly disposed. \
44
+ This is indicative of a bug in Isograph.');
45
+ }
46
+ (0, useResult_1.maybeUnwrapNetworkRequest)(fragmentReference.networkRequest, networkRequestOptions);
47
+ const data = (0, read_1.readButDoNotEvaluate)(environment, fragmentReference, networkRequestOptions);
48
+ const readerWithRefetchQueries = (0, PromiseWrapper_1.readPromise)(fragmentReference.readerWithRefetchQueries);
49
+ return readerWithRefetchQueries.readerArtifact.resolver(data.item, undefined);
50
+ });
51
+ const items = flatten(results);
52
+ return items;
53
+ }
54
+ const getFetchMore = (loadedSoFar) => (args, count) => {
55
+ // @ts-expect-error
56
+ const loadedField = loadableField(Object.assign(Object.assign({}, args), { skip: loadedSoFar, limit: count }))[1]();
57
+ const newPointer = (0, reference_counted_pointer_1.createReferenceCountedPointer)(loadedField);
58
+ const clonedPointers = loadedReferences.map(([refCountedPointer]) => {
59
+ const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
60
+ if (clonedRefCountedPointer == null) {
61
+ throw new Error('This reference counted pointer has already been disposed. \
62
+ This is indicative of a bug in useSkipLimitPagination.');
63
+ }
64
+ return clonedRefCountedPointer;
65
+ });
66
+ clonedPointers.push(newPointer);
67
+ const totalItemCleanupPair = [
68
+ clonedPointers,
69
+ () => {
70
+ clonedPointers.forEach(([, dispose]) => {
71
+ dispose();
72
+ });
73
+ },
74
+ ];
75
+ setState(totalItemCleanupPair);
76
+ };
77
+ const loadedReferences = state === react_disposable_state_1.UNASSIGNED_STATE ? [] : state;
78
+ if (loadedReferences.length === 0) {
79
+ return {
80
+ kind: 'Complete',
81
+ fetchMore: getFetchMore(0),
82
+ results: [],
83
+ };
84
+ }
85
+ const mostRecentItem = loadedReferences[loadedReferences.length - 1];
86
+ const mostRecentFragmentReference = mostRecentItem[0].getItemIfNotDisposed();
87
+ if (mostRecentFragmentReference === null) {
88
+ throw new Error('FragmentReference is unexpectedly disposed. \
89
+ This is indicative of a bug in Isograph.');
90
+ }
91
+ const networkRequestStatus = (0, PromiseWrapper_1.getPromiseState)(mostRecentFragmentReference.networkRequest);
92
+ switch (networkRequestStatus.kind) {
93
+ case 'Pending': {
94
+ const completedFragmentReferences = loadedReferences.slice(0, loadedReferences.length - 1);
95
+ return {
96
+ kind: 'Pending',
97
+ pendingFragment: mostRecentFragmentReference,
98
+ results: readCompletedFragmentReferences(completedFragmentReferences),
99
+ };
100
+ }
101
+ case 'Err': {
102
+ throw networkRequestStatus.error;
103
+ }
104
+ case 'Ok': {
105
+ const results = readCompletedFragmentReferences(loadedReferences);
106
+ return {
107
+ kind: 'Complete',
108
+ results,
109
+ fetchMore: getFetchMore(results.length),
110
+ };
111
+ }
112
+ }
113
+ }
114
+ exports.useSkipLimitPagination = useSkipLimitPagination;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-47ff1729",
3
+ "version": "0.0.0-main-5c3c47b2",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -17,9 +17,9 @@
17
17
  "tsc": "tsc"
18
18
  },
19
19
  "dependencies": {
20
- "@isograph/disposable-types": "0.0.0-main-47ff1729",
21
- "@isograph/react-disposable-state": "0.0.0-main-47ff1729",
22
- "@isograph/reference-counted-pointer": "0.0.0-main-47ff1729"
20
+ "@isograph/disposable-types": "0.0.0-main-5c3c47b2",
21
+ "@isograph/react-disposable-state": "0.0.0-main-5c3c47b2",
22
+ "@isograph/reference-counted-pointer": "0.0.0-main-5c3c47b2"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": "18.2.0"
package/src/index.ts CHANGED
@@ -81,4 +81,4 @@ export { useLazyReference } from './react/useLazyReference';
81
81
  export { useRerenderOnChange } from './react/useRerenderOnChange';
82
82
 
83
83
  export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
84
- export { useSuspensefulSkipLimitPagination } from './loadable-hooks/useSuspensefulSkipLimitPagination';
84
+ export { useSkipLimitPagination } from './loadable-hooks/useSkipLimitPagination';
@@ -0,0 +1,207 @@
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
+ export function useSkipLimitPagination<
65
+ TArgs extends {
66
+ skip: number | void | null;
67
+ limit: number | void | null;
68
+ },
69
+ TItem,
70
+ >(
71
+ loadableField: LoadableField<TArgs, Array<TItem>>,
72
+ ): UseSkipLimitReturnValue<TArgs, TItem> {
73
+ const networkRequestOptions = {
74
+ suspendIfInFlight: true,
75
+ throwOnNetworkError: true,
76
+ };
77
+ const { state, setState } =
78
+ useUpdatableDisposableState<LoadedFragmentReferences<TItem>>();
79
+
80
+ const environment = useIsographEnvironment();
81
+
82
+ function readCompletedFragmentReferences(
83
+ completedReferences: ReadonlyArray<
84
+ ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
85
+ >,
86
+ ) {
87
+ // In general, this will not suspend. But it could, if there is missing data.
88
+ // A better version of this hook would not do any reading here.
89
+ const results = completedReferences.map(([pointer]) => {
90
+ const fragmentReference = pointer.getItemIfNotDisposed();
91
+ if (fragmentReference == null) {
92
+ throw new Error(
93
+ 'FragmentReference is unexpectedly disposed. \
94
+ This is indicative of a bug in Isograph.',
95
+ );
96
+ }
97
+
98
+ maybeUnwrapNetworkRequest(
99
+ fragmentReference.networkRequest,
100
+ networkRequestOptions,
101
+ );
102
+ const data = readButDoNotEvaluate(
103
+ environment,
104
+ fragmentReference,
105
+ networkRequestOptions,
106
+ );
107
+
108
+ const readerWithRefetchQueries = readPromise(
109
+ fragmentReference.readerWithRefetchQueries,
110
+ );
111
+
112
+ return readerWithRefetchQueries.readerArtifact.resolver(
113
+ data.item,
114
+ undefined,
115
+ ) as ReadonlyArray<any>;
116
+ });
117
+
118
+ const items = flatten(results);
119
+ return items;
120
+ }
121
+
122
+ const getFetchMore =
123
+ (loadedSoFar: number) =>
124
+ (args: OmitSkipLimit<TArgs>, count: number): void => {
125
+ // @ts-expect-error
126
+ const loadedField = loadableField({
127
+ ...args,
128
+ skip: loadedSoFar,
129
+ limit: count,
130
+ })[1]();
131
+ const newPointer = createReferenceCountedPointer(loadedField);
132
+ const clonedPointers = loadedReferences.map(([refCountedPointer]) => {
133
+ const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
134
+ if (clonedRefCountedPointer == null) {
135
+ throw new Error(
136
+ 'This reference counted pointer has already been disposed. \
137
+ This is indicative of a bug in useSkipLimitPagination.',
138
+ );
139
+ }
140
+ return clonedRefCountedPointer;
141
+ });
142
+ clonedPointers.push(newPointer);
143
+
144
+ const totalItemCleanupPair: ItemCleanupPair<
145
+ ReadonlyArray<
146
+ ItemCleanupPair<
147
+ ReferenceCountedPointer<ArrayFragmentReference<TItem>>
148
+ >
149
+ >
150
+ > = [
151
+ clonedPointers,
152
+ () => {
153
+ clonedPointers.forEach(([, dispose]) => {
154
+ dispose();
155
+ });
156
+ },
157
+ ];
158
+
159
+ setState(totalItemCleanupPair);
160
+ };
161
+
162
+ const loadedReferences = state === UNASSIGNED_STATE ? [] : state;
163
+ if (loadedReferences.length === 0) {
164
+ return {
165
+ kind: 'Complete',
166
+ fetchMore: getFetchMore(0),
167
+ results: [],
168
+ };
169
+ }
170
+
171
+ const mostRecentItem = loadedReferences[loadedReferences.length - 1];
172
+ const mostRecentFragmentReference = mostRecentItem[0].getItemIfNotDisposed();
173
+ if (mostRecentFragmentReference === null) {
174
+ throw new Error(
175
+ 'FragmentReference is unexpectedly disposed. \
176
+ This is indicative of a bug in Isograph.',
177
+ );
178
+ }
179
+
180
+ const networkRequestStatus = getPromiseState(
181
+ mostRecentFragmentReference.networkRequest,
182
+ );
183
+ switch (networkRequestStatus.kind) {
184
+ case 'Pending': {
185
+ const completedFragmentReferences = loadedReferences.slice(
186
+ 0,
187
+ loadedReferences.length - 1,
188
+ );
189
+ return {
190
+ kind: 'Pending',
191
+ pendingFragment: mostRecentFragmentReference,
192
+ results: readCompletedFragmentReferences(completedFragmentReferences),
193
+ };
194
+ }
195
+ case 'Err': {
196
+ throw networkRequestStatus.error;
197
+ }
198
+ case 'Ok': {
199
+ const results = readCompletedFragmentReferences(loadedReferences);
200
+ return {
201
+ kind: 'Complete',
202
+ results,
203
+ fetchMore: getFetchMore(results.length),
204
+ };
205
+ }
206
+ }
207
+ }
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useSuspensefulSkipLimitPagination = void 0;
4
- const IsographEnvironmentProvider_1 = require("../react/IsographEnvironmentProvider");
5
- const useResult_1 = require("../react/useResult");
6
- const read_1 = require("../core/read");
7
- const react_disposable_state_1 = require("@isograph/react-disposable-state");
8
- const reference_counted_pointer_1 = require("@isograph/reference-counted-pointer");
9
- const PromiseWrapper_1 = require("../core/PromiseWrapper");
10
- function flatten(arr) {
11
- let outArray = [];
12
- for (const subarr of arr) {
13
- for (const item of subarr) {
14
- outArray.push(item);
15
- }
16
- }
17
- return outArray;
18
- }
19
- /**
20
- * accepts a loadableField that accepts skip and limit arguments
21
- * and returns:
22
- * - a fetchMore function that, when called, triggers a network
23
- * request for additional data, and
24
- * - the data received so far.
25
- *
26
- * This hook will suspend if any network request is in flight.
27
- *
28
- * Calling fetchMore before the hook mounts is a no-op.
29
- */
30
- function useSuspensefulSkipLimitPagination(loadableField) {
31
- const networkRequestOptions = {
32
- suspendIfInFlight: true,
33
- throwOnNetworkError: true,
34
- };
35
- const { state, setState } = (0, react_disposable_state_1.useUpdatableDisposableState)();
36
- const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
37
- const loadedReferences = state === react_disposable_state_1.UNASSIGNED_STATE ? [] : state;
38
- const results = loadedReferences.map(([pointer]) => {
39
- const fragmentReference = pointer.getItemIfNotDisposed();
40
- if (fragmentReference == null) {
41
- throw new Error('FragmentReference is unexpectedly disposed. \
42
- This is indicative of a bug in Isograph.');
43
- }
44
- (0, useResult_1.maybeUnwrapNetworkRequest)(fragmentReference.networkRequest, networkRequestOptions);
45
- const data = (0, read_1.readButDoNotEvaluate)(environment, fragmentReference, networkRequestOptions);
46
- const readerWithRefetchQueries = (0, PromiseWrapper_1.readPromise)(fragmentReference.readerWithRefetchQueries);
47
- return readerWithRefetchQueries.readerArtifact.resolver(data.item, undefined);
48
- });
49
- const items = flatten(results);
50
- const loadedSoFar = items.length;
51
- return {
52
- fetchMore: (args, count) => {
53
- // @ts-expect-error
54
- const loadedField = loadableField(Object.assign(Object.assign({}, args), { skip: loadedSoFar, limit: count }))[1]();
55
- const newPointer = (0, reference_counted_pointer_1.createReferenceCountedPointer)(loadedField);
56
- const clonedPointers = [
57
- ...loadedReferences.map(([refCountedPointer]) => {
58
- const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
59
- if (clonedRefCountedPointer == null) {
60
- throw new Error('This reference counted pointer has already been disposed. \
61
- This is indicative of a bug in useSuspensefulSkipLimitPagination.');
62
- }
63
- return clonedRefCountedPointer;
64
- }),
65
- ];
66
- const allPointers = [...clonedPointers, newPointer];
67
- const totalItemCleanupPair = [
68
- allPointers,
69
- () => {
70
- allPointers.forEach(([, dispose]) => {
71
- dispose();
72
- });
73
- },
74
- ];
75
- setState(totalItemCleanupPair);
76
- },
77
- results: items,
78
- };
79
- }
80
- exports.useSuspensefulSkipLimitPagination = useSuspensefulSkipLimitPagination;
@@ -1,153 +0,0 @@
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 { 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
- readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
24
- readonly results: ReadonlyArray<TItem>;
25
- };
26
-
27
- type ArrayFragmentReference<TItem> = FragmentReference<
28
- any,
29
- ReadonlyArray<TItem>
30
- >;
31
-
32
- type LoadedFragmentReferences<TItem> = ReadonlyArray<
33
- ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
34
- >;
35
-
36
- function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
37
- let outArray: Array<T> = [];
38
- for (const subarr of arr) {
39
- for (const item of subarr) {
40
- outArray.push(item);
41
- }
42
- }
43
- return outArray;
44
- }
45
-
46
- /**
47
- * accepts a loadableField that accepts skip and limit arguments
48
- * and returns:
49
- * - a fetchMore function that, when called, triggers a network
50
- * request for additional data, and
51
- * - the data received so far.
52
- *
53
- * This hook will suspend if any network request is in flight.
54
- *
55
- * Calling fetchMore before the hook mounts is a no-op.
56
- */
57
- export function useSuspensefulSkipLimitPagination<
58
- TArgs extends {
59
- skip: number | void | null;
60
- limit: number | void | null;
61
- },
62
- TItem,
63
- >(
64
- loadableField: LoadableField<TArgs, Array<TItem>>,
65
- ): UseSkipLimitReturnValue<TArgs, TItem> {
66
- const networkRequestOptions = {
67
- suspendIfInFlight: true,
68
- throwOnNetworkError: true,
69
- };
70
- const { state, setState } =
71
- useUpdatableDisposableState<LoadedFragmentReferences<TItem>>();
72
-
73
- const environment = useIsographEnvironment();
74
-
75
- const loadedReferences = state === UNASSIGNED_STATE ? [] : state;
76
-
77
- const results = loadedReferences.map(([pointer]) => {
78
- const fragmentReference = pointer.getItemIfNotDisposed();
79
- if (fragmentReference == null) {
80
- throw new Error(
81
- 'FragmentReference is unexpectedly disposed. \
82
- This is indicative of a bug in Isograph.',
83
- );
84
- }
85
-
86
- maybeUnwrapNetworkRequest(
87
- fragmentReference.networkRequest,
88
- networkRequestOptions,
89
- );
90
- const data = readButDoNotEvaluate(
91
- environment,
92
- fragmentReference,
93
- networkRequestOptions,
94
- );
95
-
96
- const readerWithRefetchQueries = readPromise(
97
- fragmentReference.readerWithRefetchQueries,
98
- );
99
-
100
- return readerWithRefetchQueries.readerArtifact.resolver(
101
- data.item,
102
- undefined,
103
- ) as ReadonlyArray<any>;
104
- });
105
-
106
- const items = flatten(results);
107
- const loadedSoFar = items.length;
108
-
109
- return {
110
- fetchMore: (args, count) => {
111
- // @ts-expect-error
112
- const loadedField = loadableField({
113
- ...args,
114
- skip: loadedSoFar,
115
- limit: count,
116
- })[1]();
117
- const newPointer = createReferenceCountedPointer(loadedField);
118
- const clonedPointers = [
119
- ...loadedReferences.map(([refCountedPointer]) => {
120
- const clonedRefCountedPointer =
121
- refCountedPointer.cloneIfNotDisposed();
122
- if (clonedRefCountedPointer == null) {
123
- throw new Error(
124
- 'This reference counted pointer has already been disposed. \
125
- This is indicative of a bug in useSuspensefulSkipLimitPagination.',
126
- );
127
- }
128
- return clonedRefCountedPointer;
129
- }),
130
- ];
131
-
132
- const allPointers = [...clonedPointers, newPointer];
133
-
134
- const totalItemCleanupPair: ItemCleanupPair<
135
- ReadonlyArray<
136
- ItemCleanupPair<
137
- ReferenceCountedPointer<ArrayFragmentReference<TItem>>
138
- >
139
- >
140
- > = [
141
- allPointers,
142
- () => {
143
- allPointers.forEach(([, dispose]) => {
144
- dispose();
145
- });
146
- },
147
- ];
148
-
149
- setState(totalItemCleanupPair);
150
- },
151
- results: items,
152
- };
153
- }