@isograph/react 0.0.0-main-c6a74674 → 0.0.0-main-edade9ce

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.
@@ -12,7 +12,7 @@ type ComponentCache = {
12
12
  };
13
13
  };
14
14
  };
15
- type FragmentSubscription<TReadFromStore extends Object> = {
15
+ export type FragmentSubscription<TReadFromStore extends Object> = {
16
16
  readonly kind: 'FragmentSubscription';
17
17
  readonly callback: (newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>) => void;
18
18
  /** The value read out from the previous call to readButDoNotEvaluate */
@@ -59,6 +59,7 @@ function normalizeData(environment, normalizationAst, networkResponse, variables
59
59
  console.log('after normalization', {
60
60
  store: environment.store,
61
61
  encounteredIds,
62
+ environment,
62
63
  });
63
64
  }
64
65
  callSubscriptions(environment, encounteredIds);
@@ -74,6 +75,7 @@ function subscribeToAnyChange(environment, callback) {
74
75
  return () => environment.subscriptions.delete(subscription);
75
76
  }
76
77
  exports.subscribeToAnyChange = subscribeToAnyChange;
78
+ // TODO we should re-read and call callback if the value has changed
77
79
  function subscribe(environment, encounteredDataAndRecords, fragmentReference, callback) {
78
80
  const fragmentSubscription = {
79
81
  kind: 'FragmentSubscription',
@@ -130,7 +132,10 @@ function callSubscriptions(environment, recordsEncounteredWhenNormalizing) {
130
132
  // - consistency
131
133
  // - it's also weird, this is called from makeNetworkRequest, where
132
134
  // we don't currently pass network request options
133
- {});
135
+ {
136
+ suspendIfInFlight: false,
137
+ throwOnNetworkError: false,
138
+ });
134
139
  if (!(0, areEqualWithDeepComparison_1.areEqualObjectsWithDeepComparison)(subscription.encounteredDataAndRecords.item, newEncounteredDataAndRecords.item)) {
135
140
  if (typeof window !== 'undefined' && window.__LOG) {
136
141
  console.log('Deep equality - No', {
@@ -6,6 +6,7 @@ export type WithEncounteredRecords<T> = {
6
6
  };
7
7
  export declare function readButDoNotEvaluate<TReadFromStore extends Object>(environment: IsographEnvironment, fragmentReference: FragmentReference<TReadFromStore, unknown>, networkRequestOptions: NetworkRequestReaderOptions): WithEncounteredRecords<TReadFromStore>;
8
8
  export type NetworkRequestReaderOptions = {
9
- suspendIfInFlight?: boolean;
10
- throwOnNetworkError?: boolean;
9
+ suspendIfInFlight: boolean;
10
+ throwOnNetworkError: boolean;
11
11
  };
12
+ export declare function getNetworkRequestOptionsWithDefaults(networkRequestOptions?: Partial<NetworkRequestReaderOptions> | void): NetworkRequestReaderOptions;
package/dist/core/read.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readButDoNotEvaluate = void 0;
3
+ exports.getNetworkRequestOptionsWithDefaults = exports.readButDoNotEvaluate = void 0;
4
4
  const cache_1 = require("./cache");
5
5
  const componentCache_1 = require("./componentCache");
6
6
  const IsographEnvironment_1 = require("./IsographEnvironment");
@@ -329,6 +329,14 @@ function writeQueryArgsToVariables(targetVariables, queryArgs, variables) {
329
329
  }
330
330
  }
331
331
  }
332
+ function getNetworkRequestOptionsWithDefaults(networkRequestOptions) {
333
+ var _a, _b;
334
+ return {
335
+ suspendIfInFlight: (_a = networkRequestOptions === null || networkRequestOptions === void 0 ? void 0 : networkRequestOptions.suspendIfInFlight) !== null && _a !== void 0 ? _a : false,
336
+ throwOnNetworkError: (_b = networkRequestOptions === null || networkRequestOptions === void 0 ? void 0 : networkRequestOptions.throwOnNetworkError) !== null && _b !== void 0 ? _b : true,
337
+ };
338
+ }
339
+ exports.getNetworkRequestOptionsWithDefaults = getNetworkRequestOptionsWithDefaults;
332
340
  // TODO use a description of the params for this?
333
341
  // TODO call stableStringifyArgs on the variable values, as well.
334
342
  // This doesn't matter for now, since we are just using primitive values
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export { IsographEnvironmentProvider, useIsographEnvironment, type IsographEnvir
12
12
  export { useImperativeReference } from './react/useImperativeReference';
13
13
  export { FragmentReader } from './react/FragmentReader';
14
14
  export { useResult } from './react/useResult';
15
+ export { useReadAndSubscribe, useSubscribeToMultiple, } from './react/useReadAndSubscribe';
15
16
  export { useLazyReference } from './react/useLazyReference';
16
17
  export { useRerenderOnChange } from './react/useRerenderOnChange';
17
18
  export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
19
+ export { useSuspensefulSkipLimitPagination } from './loadable-hooks/useSuspensefulSkipLimitPagination';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useClientSideDefer = exports.useRerenderOnChange = exports.useLazyReference = 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.getPromiseState = exports.readPromise = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
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.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; } });
@@ -33,9 +33,14 @@ var FragmentReader_1 = require("./react/FragmentReader");
33
33
  Object.defineProperty(exports, "FragmentReader", { enumerable: true, get: function () { return FragmentReader_1.FragmentReader; } });
34
34
  var useResult_1 = require("./react/useResult");
35
35
  Object.defineProperty(exports, "useResult", { enumerable: true, get: function () { return useResult_1.useResult; } });
36
+ var useReadAndSubscribe_1 = require("./react/useReadAndSubscribe");
37
+ Object.defineProperty(exports, "useReadAndSubscribe", { enumerable: true, get: function () { return useReadAndSubscribe_1.useReadAndSubscribe; } });
38
+ Object.defineProperty(exports, "useSubscribeToMultiple", { enumerable: true, get: function () { return useReadAndSubscribe_1.useSubscribeToMultiple; } });
36
39
  var useLazyReference_1 = require("./react/useLazyReference");
37
40
  Object.defineProperty(exports, "useLazyReference", { enumerable: true, get: function () { return useLazyReference_1.useLazyReference; } });
38
41
  var useRerenderOnChange_1 = require("./react/useRerenderOnChange");
39
42
  Object.defineProperty(exports, "useRerenderOnChange", { enumerable: true, get: function () { return useRerenderOnChange_1.useRerenderOnChange; } });
40
43
  var useClientSideDefer_1 = require("./loadable-hooks/useClientSideDefer");
41
44
  Object.defineProperty(exports, "useClientSideDefer", { enumerable: true, get: function () { return useClientSideDefer_1.useClientSideDefer; } });
45
+ var useSuspensefulSkipLimitPagination_1 = require("./loadable-hooks/useSuspensefulSkipLimitPagination");
46
+ Object.defineProperty(exports, "useSuspensefulSkipLimitPagination", { enumerable: true, get: function () { return useSuspensefulSkipLimitPagination_1.useSuspensefulSkipLimitPagination; } });
@@ -0,0 +1,23 @@
1
+ import { LoadableField } from '../core/reader';
2
+ type SkipOrLimit = 'skip' | 'limit';
3
+ type OmitSkipLimit<TArgs> = keyof Omit<TArgs, SkipOrLimit> extends never ? void | Record<string, never> : Omit<TArgs, SkipOrLimit>;
4
+ type UseSkipLimitReturnValue<TArgs, TItem> = {
5
+ readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
6
+ readonly results: ReadonlyArray<TItem>;
7
+ };
8
+ /**
9
+ * accepts a loadableField that accepts skip and limit arguments
10
+ * and returns:
11
+ * - a fetchMore function that, when called, triggers a network
12
+ * request for additional data, and
13
+ * - the data received so far.
14
+ *
15
+ * This hook will suspend if any network request is in flight.
16
+ *
17
+ * Calling fetchMore before the hook mounts is a no-op.
18
+ */
19
+ export declare function useSuspensefulSkipLimitPagination<TArgs extends {
20
+ skip: number | void | null;
21
+ limit: number | void | null;
22
+ }, TItem>(loadableField: LoadableField<TArgs, Array<TItem>>): UseSkipLimitReturnValue<TArgs, TItem>;
23
+ export {};
@@ -0,0 +1,78 @@
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
+ function flatten(arr) {
10
+ let outArray = [];
11
+ for (const subarr of arr) {
12
+ for (const item of subarr) {
13
+ outArray.push(item);
14
+ }
15
+ }
16
+ return outArray;
17
+ }
18
+ /**
19
+ * accepts a loadableField that accepts skip and limit arguments
20
+ * and returns:
21
+ * - a fetchMore function that, when called, triggers a network
22
+ * request for additional data, and
23
+ * - the data received so far.
24
+ *
25
+ * This hook will suspend if any network request is in flight.
26
+ *
27
+ * Calling fetchMore before the hook mounts is a no-op.
28
+ */
29
+ function useSuspensefulSkipLimitPagination(loadableField) {
30
+ const networkRequestOptions = {
31
+ suspendIfInFlight: true,
32
+ throwOnNetworkError: true,
33
+ };
34
+ const { state, setState } = (0, react_disposable_state_1.useUpdatableDisposableState)();
35
+ const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
36
+ const loadedReferences = state === react_disposable_state_1.UNASSIGNED_STATE ? [] : state;
37
+ const results = loadedReferences.map(([pointer]) => {
38
+ const fragmentReference = pointer.getItemIfNotDisposed();
39
+ if (fragmentReference == null) {
40
+ throw new Error('FragmentReference is unexpectedly disposed. \
41
+ This is indicative of a bug in Isograph.');
42
+ }
43
+ (0, useResult_1.maybeUnwrapNetworkRequest)(fragmentReference.networkRequest, networkRequestOptions);
44
+ const data = (0, read_1.readButDoNotEvaluate)(environment, fragmentReference, networkRequestOptions);
45
+ return fragmentReference.readerArtifact.resolver(data.item, undefined);
46
+ });
47
+ const items = flatten(results);
48
+ const loadedSoFar = items.length;
49
+ return {
50
+ fetchMore: (args, count) => {
51
+ // @ts-expect-error
52
+ const loadedField = loadableField(Object.assign(Object.assign({}, args), { skip: loadedSoFar, limit: count }))[1]();
53
+ const newPointer = (0, reference_counted_pointer_1.createReferenceCountedPointer)(loadedField);
54
+ const clonedPointers = [
55
+ ...loadedReferences.map(([refCountedPointer]) => {
56
+ const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
57
+ if (clonedRefCountedPointer == null) {
58
+ throw new Error('This reference counted pointer has already been disposed. \
59
+ This is indicative of a bug in useSuspensefulSkipLimitPagination.');
60
+ }
61
+ return clonedRefCountedPointer;
62
+ }),
63
+ ];
64
+ const allPointers = [...clonedPointers, newPointer];
65
+ const totalItemCleanupPair = [
66
+ allPointers,
67
+ () => {
68
+ allPointers.forEach(([, dispose]) => {
69
+ dispose();
70
+ });
71
+ },
72
+ ];
73
+ setState(totalItemCleanupPair);
74
+ },
75
+ results: items,
76
+ };
77
+ }
78
+ exports.useSuspensefulSkipLimitPagination = useSuspensefulSkipLimitPagination;
@@ -5,9 +5,9 @@ import { NetworkRequestReaderOptions } from '../core/read';
5
5
  export declare function FragmentReader<TProps extends Record<any, any>, TEntrypoint extends IsographEntrypoint<any, React.FC<TProps>>>(props: TProps extends Record<string, never> ? {
6
6
  fragmentReference: FragmentReference<ExtractReadFromStore<TEntrypoint>, React.FC<{}>>;
7
7
  additionalProps?: TProps;
8
- networkRequestOptions?: NetworkRequestReaderOptions;
8
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
9
9
  } : {
10
10
  fragmentReference: FragmentReference<ExtractReadFromStore<TEntrypoint>, React.FC<TProps>>;
11
11
  additionalProps: TProps;
12
- networkRequestOptions?: NetworkRequestReaderOptions;
12
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
13
13
  }): React.ReactNode;
@@ -27,8 +27,7 @@ exports.FragmentReader = void 0;
27
27
  const React = __importStar(require("react"));
28
28
  const useResult_1 = require("./useResult");
29
29
  function FragmentReader(props) {
30
- var _a;
31
- const Component = (0, useResult_1.useResult)(props.fragmentReference, (_a = props.networkRequestOptions) !== null && _a !== void 0 ? _a : {});
30
+ const Component = (0, useResult_1.useResult)(props.fragmentReference, props.networkRequestOptions);
32
31
  return React.createElement(Component, Object.assign({}, props.additionalProps));
33
32
  }
34
33
  exports.FragmentReader = FragmentReader;
@@ -1,5 +1,5 @@
1
1
  import { FragmentReference } from '../core/FragmentReference';
2
2
  import { NetworkRequestReaderOptions } from '../core/read';
3
3
  import { PromiseWrapper } from '../core/PromiseWrapper';
4
- export declare function useResult<TReadFromStore extends Object, TClientFieldValue>(fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>, networkRequestOptions: NetworkRequestReaderOptions): TClientFieldValue;
4
+ export declare function useResult<TReadFromStore extends Object, TClientFieldValue>(fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>, partialNetworkRequestOptions?: Partial<NetworkRequestReaderOptions> | void): TClientFieldValue;
5
5
  export declare function maybeUnwrapNetworkRequest(networkRequest: PromiseWrapper<void, any>, networkRequestOptions: NetworkRequestReaderOptions): void;
@@ -4,9 +4,11 @@ exports.maybeUnwrapNetworkRequest = exports.useResult = void 0;
4
4
  const IsographEnvironmentProvider_1 = require("../react/IsographEnvironmentProvider");
5
5
  const componentCache_1 = require("../core/componentCache");
6
6
  const useReadAndSubscribe_1 = require("./useReadAndSubscribe");
7
+ const read_1 = require("../core/read");
7
8
  const PromiseWrapper_1 = require("../core/PromiseWrapper");
8
- function useResult(fragmentReference, networkRequestOptions) {
9
+ function useResult(fragmentReference, partialNetworkRequestOptions) {
9
10
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
11
+ const networkRequestOptions = (0, read_1.getNetworkRequestOptionsWithDefaults)(partialNetworkRequestOptions);
10
12
  maybeUnwrapNetworkRequest(fragmentReference.networkRequest, networkRequestOptions);
11
13
  switch (fragmentReference.readerArtifact.kind) {
12
14
  case 'ComponentReaderArtifact': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-c6a74674",
3
+ "version": "0.0.0-main-edade9ce",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -17,8 +17,9 @@
17
17
  "tsc": "tsc"
18
18
  },
19
19
  "dependencies": {
20
- "@isograph/disposable-types": "0.0.0-main-c6a74674",
21
- "@isograph/react-disposable-state": "0.0.0-main-c6a74674",
20
+ "@isograph/disposable-types": "0.0.0-main-edade9ce",
21
+ "@isograph/react-disposable-state": "0.0.0-main-edade9ce",
22
+ "@isograph/reference-counted-pointer": "0.0.0-main-edade9ce",
22
23
  "react": "^18.2.0"
23
24
  },
24
25
  "devDependencies": {
@@ -11,7 +11,7 @@ type ComponentCache = {
11
11
  };
12
12
  };
13
13
 
14
- type FragmentSubscription<TReadFromStore extends Object> = {
14
+ export type FragmentSubscription<TReadFromStore extends Object> = {
15
15
  readonly kind: 'FragmentSubscription';
16
16
  readonly callback: (
17
17
  newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
package/src/core/cache.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  type IsographEnvironment,
9
9
  DataTypeValue,
10
10
  getLink,
11
+ FragmentSubscription,
11
12
  } from './IsographEnvironment';
12
13
  import {
13
14
  IsographEntrypoint,
@@ -131,6 +132,7 @@ export function normalizeData(
131
132
  console.log('after normalization', {
132
133
  store: environment.store,
133
134
  encounteredIds,
135
+ environment,
134
136
  });
135
137
  }
136
138
  callSubscriptions(environment, encounteredIds);
@@ -149,6 +151,7 @@ export function subscribeToAnyChange(
149
151
  return () => environment.subscriptions.delete(subscription);
150
152
  }
151
153
 
154
+ // TODO we should re-read and call callback if the value has changed
152
155
  export function subscribe<TReadFromStore extends Object>(
153
156
  environment: IsographEnvironment,
154
157
  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
@@ -157,12 +160,12 @@ export function subscribe<TReadFromStore extends Object>(
157
160
  newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
158
161
  ) => void,
159
162
  ): () => void {
160
- const fragmentSubscription = {
163
+ const fragmentSubscription: FragmentSubscription<TReadFromStore> = {
161
164
  kind: 'FragmentSubscription',
162
165
  callback,
163
166
  encounteredDataAndRecords,
164
167
  fragmentReference,
165
- } as const;
168
+ };
166
169
  // @ts-expect-error
167
170
  environment.subscriptions.add(fragmentSubscription);
168
171
  // @ts-expect-error
@@ -223,7 +226,10 @@ function callSubscriptions(
223
226
  // - consistency
224
227
  // - it's also weird, this is called from makeNetworkRequest, where
225
228
  // we don't currently pass network request options
226
- {},
229
+ {
230
+ suspendIfInFlight: false,
231
+ throwOnNetworkError: false,
232
+ },
227
233
  );
228
234
 
229
235
  if (
package/src/core/read.ts CHANGED
@@ -468,10 +468,19 @@ function writeQueryArgsToVariables(
468
468
  }
469
469
 
470
470
  export type NetworkRequestReaderOptions = {
471
- suspendIfInFlight?: boolean;
472
- throwOnNetworkError?: boolean;
471
+ suspendIfInFlight: boolean;
472
+ throwOnNetworkError: boolean;
473
473
  };
474
474
 
475
+ export function getNetworkRequestOptionsWithDefaults(
476
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions> | void,
477
+ ): NetworkRequestReaderOptions {
478
+ return {
479
+ suspendIfInFlight: networkRequestOptions?.suspendIfInFlight ?? false,
480
+ throwOnNetworkError: networkRequestOptions?.throwOnNetworkError ?? true,
481
+ };
482
+ }
483
+
475
484
  // TODO use a description of the params for this?
476
485
  // TODO call stableStringifyArgs on the variable values, as well.
477
486
  // This doesn't matter for now, since we are just using primitive values
package/src/index.ts CHANGED
@@ -71,7 +71,12 @@ export {
71
71
  export { useImperativeReference } from './react/useImperativeReference';
72
72
  export { FragmentReader } from './react/FragmentReader';
73
73
  export { useResult } from './react/useResult';
74
+ export {
75
+ useReadAndSubscribe,
76
+ useSubscribeToMultiple,
77
+ } from './react/useReadAndSubscribe';
74
78
  export { useLazyReference } from './react/useLazyReference';
75
79
  export { useRerenderOnChange } from './react/useRerenderOnChange';
76
80
 
77
81
  export { useClientSideDefer } from './loadable-hooks/useClientSideDefer';
82
+ export { useSuspensefulSkipLimitPagination } from './loadable-hooks/useSuspensefulSkipLimitPagination';
@@ -0,0 +1,147 @@
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
+
16
+ type SkipOrLimit = 'skip' | 'limit';
17
+ type OmitSkipLimit<TArgs> = keyof Omit<TArgs, SkipOrLimit> extends never
18
+ ? void | Record<string, never>
19
+ : Omit<TArgs, SkipOrLimit>;
20
+
21
+ type UseSkipLimitReturnValue<TArgs, TItem> = {
22
+ readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
23
+ readonly results: ReadonlyArray<TItem>;
24
+ };
25
+
26
+ type ArrayFragmentReference<TItem> = FragmentReference<
27
+ any,
28
+ ReadonlyArray<TItem>
29
+ >;
30
+
31
+ type LoadedFragmentReferences<TItem> = ReadonlyArray<
32
+ ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
33
+ >;
34
+
35
+ function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
36
+ let outArray: Array<T> = [];
37
+ for (const subarr of arr) {
38
+ for (const item of subarr) {
39
+ outArray.push(item);
40
+ }
41
+ }
42
+ return outArray;
43
+ }
44
+
45
+ /**
46
+ * accepts a loadableField that accepts skip and limit arguments
47
+ * and returns:
48
+ * - a fetchMore function that, when called, triggers a network
49
+ * request for additional data, and
50
+ * - the data received so far.
51
+ *
52
+ * This hook will suspend if any network request is in flight.
53
+ *
54
+ * Calling fetchMore before the hook mounts is a no-op.
55
+ */
56
+ export function useSuspensefulSkipLimitPagination<
57
+ TArgs extends {
58
+ skip: number | void | null;
59
+ limit: number | void | null;
60
+ },
61
+ TItem,
62
+ >(
63
+ loadableField: LoadableField<TArgs, Array<TItem>>,
64
+ ): UseSkipLimitReturnValue<TArgs, TItem> {
65
+ const networkRequestOptions = {
66
+ suspendIfInFlight: true,
67
+ throwOnNetworkError: true,
68
+ };
69
+ const { state, setState } =
70
+ useUpdatableDisposableState<LoadedFragmentReferences<TItem>>();
71
+
72
+ const environment = useIsographEnvironment();
73
+
74
+ const loadedReferences = state === UNASSIGNED_STATE ? [] : state;
75
+
76
+ const results = loadedReferences.map(([pointer]) => {
77
+ const fragmentReference = pointer.getItemIfNotDisposed();
78
+ if (fragmentReference == null) {
79
+ throw new Error(
80
+ 'FragmentReference is unexpectedly disposed. \
81
+ This is indicative of a bug in Isograph.',
82
+ );
83
+ }
84
+
85
+ maybeUnwrapNetworkRequest(
86
+ fragmentReference.networkRequest,
87
+ networkRequestOptions,
88
+ );
89
+ const data = readButDoNotEvaluate(
90
+ environment,
91
+ fragmentReference,
92
+ networkRequestOptions,
93
+ );
94
+ return fragmentReference.readerArtifact.resolver(
95
+ data.item,
96
+ undefined,
97
+ ) as ReadonlyArray<any>;
98
+ });
99
+
100
+ const items = flatten(results);
101
+ const loadedSoFar = items.length;
102
+
103
+ return {
104
+ fetchMore: (args, count) => {
105
+ // @ts-expect-error
106
+ const loadedField = loadableField({
107
+ ...args,
108
+ skip: loadedSoFar,
109
+ limit: count,
110
+ })[1]();
111
+ const newPointer = createReferenceCountedPointer(loadedField);
112
+ const clonedPointers = [
113
+ ...loadedReferences.map(([refCountedPointer]) => {
114
+ const clonedRefCountedPointer =
115
+ refCountedPointer.cloneIfNotDisposed();
116
+ if (clonedRefCountedPointer == null) {
117
+ throw new Error(
118
+ 'This reference counted pointer has already been disposed. \
119
+ This is indicative of a bug in useSuspensefulSkipLimitPagination.',
120
+ );
121
+ }
122
+ return clonedRefCountedPointer;
123
+ }),
124
+ ];
125
+
126
+ const allPointers = [...clonedPointers, newPointer];
127
+
128
+ const totalItemCleanupPair: ItemCleanupPair<
129
+ ReadonlyArray<
130
+ ItemCleanupPair<
131
+ ReferenceCountedPointer<ArrayFragmentReference<TItem>>
132
+ >
133
+ >
134
+ > = [
135
+ allPointers,
136
+ () => {
137
+ allPointers.forEach(([, dispose]) => {
138
+ dispose();
139
+ });
140
+ },
141
+ ];
142
+
143
+ setState(totalItemCleanupPair);
144
+ },
145
+ results: items,
146
+ };
147
+ }
@@ -15,7 +15,7 @@ export function FragmentReader<
15
15
  React.FC<{}>
16
16
  >;
17
17
  additionalProps?: TProps;
18
- networkRequestOptions?: NetworkRequestReaderOptions;
18
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
19
19
  }
20
20
  : {
21
21
  fragmentReference: FragmentReference<
@@ -23,12 +23,12 @@ export function FragmentReader<
23
23
  React.FC<TProps>
24
24
  >;
25
25
  additionalProps: TProps;
26
- networkRequestOptions?: NetworkRequestReaderOptions;
26
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
27
27
  },
28
28
  ): React.ReactNode {
29
29
  const Component = useResult(
30
30
  props.fragmentReference,
31
- props.networkRequestOptions ?? {},
31
+ props.networkRequestOptions,
32
32
  );
33
33
  return <Component {...props.additionalProps} />;
34
34
  }
@@ -2,14 +2,20 @@ import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
2
2
  import { FragmentReference } from '../core/FragmentReference';
3
3
  import { getOrCreateCachedComponent } from '../core/componentCache';
4
4
  import { useReadAndSubscribe } from './useReadAndSubscribe';
5
- import { NetworkRequestReaderOptions } from '../core/read';
5
+ import {
6
+ getNetworkRequestOptionsWithDefaults,
7
+ NetworkRequestReaderOptions,
8
+ } from '../core/read';
6
9
  import { getPromiseState, PromiseWrapper } from '../core/PromiseWrapper';
7
10
 
8
11
  export function useResult<TReadFromStore extends Object, TClientFieldValue>(
9
12
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
10
- networkRequestOptions: NetworkRequestReaderOptions,
13
+ partialNetworkRequestOptions?: Partial<NetworkRequestReaderOptions> | void,
11
14
  ): TClientFieldValue {
12
15
  const environment = useIsographEnvironment();
16
+ const networkRequestOptions = getNetworkRequestOptionsWithDefaults(
17
+ partialNetworkRequestOptions,
18
+ );
13
19
 
14
20
  maybeUnwrapNetworkRequest(
15
21
  fragmentReference.networkRequest,