@isograph/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/core/FragmentReference.d.ts +14 -4
  2. package/dist/core/FragmentReference.d.ts.map +1 -0
  3. package/dist/core/FragmentReference.js +2 -3
  4. package/dist/core/IsographEnvironment.d.ts +28 -10
  5. package/dist/core/IsographEnvironment.d.ts.map +1 -0
  6. package/dist/core/IsographEnvironment.js +15 -22
  7. package/dist/core/PromiseWrapper.d.ts +1 -0
  8. package/dist/core/PromiseWrapper.d.ts.map +1 -0
  9. package/dist/core/PromiseWrapper.js +4 -5
  10. package/dist/core/areEqualWithDeepComparison.d.ts +5 -3
  11. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -0
  12. package/dist/core/areEqualWithDeepComparison.js +73 -39
  13. package/dist/core/cache.d.ts +26 -10
  14. package/dist/core/cache.d.ts.map +1 -0
  15. package/dist/core/cache.js +160 -98
  16. package/dist/core/check.d.ts +18 -0
  17. package/dist/core/check.d.ts.map +1 -0
  18. package/dist/core/check.js +127 -0
  19. package/dist/core/componentCache.d.ts +1 -1
  20. package/dist/core/componentCache.d.ts.map +1 -0
  21. package/dist/core/componentCache.js +14 -14
  22. package/dist/core/entrypoint.d.ts +27 -8
  23. package/dist/core/entrypoint.d.ts.map +1 -0
  24. package/dist/core/entrypoint.js +1 -2
  25. package/dist/core/garbageCollection.d.ts +3 -1
  26. package/dist/core/garbageCollection.d.ts.map +1 -0
  27. package/dist/core/garbageCollection.js +48 -15
  28. package/dist/core/logging.d.ts +69 -0
  29. package/dist/core/logging.d.ts.map +1 -0
  30. package/dist/core/logging.js +19 -0
  31. package/dist/core/makeNetworkRequest.d.ts +4 -1
  32. package/dist/core/makeNetworkRequest.d.ts.map +1 -0
  33. package/dist/core/makeNetworkRequest.js +71 -15
  34. package/dist/core/read.d.ts +20 -5
  35. package/dist/core/read.d.ts.map +1 -0
  36. package/dist/core/read.js +104 -41
  37. package/dist/core/reader.d.ts +34 -10
  38. package/dist/core/reader.d.ts.map +1 -0
  39. package/dist/core/util.d.ts +2 -0
  40. package/dist/core/util.d.ts.map +1 -0
  41. package/dist/index.d.ts +10 -5
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +10 -2
  44. package/dist/loadable-hooks/useClientSideDefer.d.ts +15 -3
  45. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -0
  46. package/dist/loadable-hooks/useClientSideDefer.js +4 -6
  47. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +34 -0
  48. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -0
  49. package/dist/loadable-hooks/useConnectionSpecPagination.js +160 -0
  50. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -0
  51. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -0
  52. package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -2
  53. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +13 -5
  54. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -0
  55. package/dist/loadable-hooks/useImperativeLoadableField.js +3 -4
  56. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +18 -24
  57. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -0
  58. package/dist/loadable-hooks/useSkipLimitPagination.js +88 -44
  59. package/dist/react/FragmentReader.d.ts +7 -4
  60. package/dist/react/FragmentReader.d.ts.map +1 -0
  61. package/dist/react/FragmentReader.js +4 -2
  62. package/dist/react/IsographEnvironmentProvider.d.ts +1 -0
  63. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -0
  64. package/dist/react/IsographEnvironmentProvider.js +3 -3
  65. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts +10 -0
  66. package/dist/react/RenderAfterCommit__DO_NOT_USE.d.ts.map +1 -0
  67. package/dist/react/RenderAfterCommit__DO_NOT_USE.js +15 -0
  68. package/dist/react/useImperativeReference.d.ts +8 -3
  69. package/dist/react/useImperativeReference.d.ts.map +1 -0
  70. package/dist/react/useImperativeReference.js +4 -5
  71. package/dist/react/useLazyReference.d.ts +7 -2
  72. package/dist/react/useLazyReference.d.ts.map +1 -0
  73. package/dist/react/useLazyReference.js +11 -4
  74. package/dist/react/useReadAndSubscribe.d.ts +12 -3
  75. package/dist/react/useReadAndSubscribe.d.ts.map +1 -0
  76. package/dist/react/useReadAndSubscribe.js +6 -7
  77. package/dist/react/useRerenderOnChange.d.ts +6 -1
  78. package/dist/react/useRerenderOnChange.d.ts.map +1 -0
  79. package/dist/react/useRerenderOnChange.js +3 -4
  80. package/dist/react/useResult.d.ts +5 -1
  81. package/dist/react/useResult.d.ts.map +1 -0
  82. package/dist/react/useResult.js +8 -5
  83. package/{src/tests/isograph.config.json → isograph.config.json} +1 -1
  84. package/package.json +12 -8
  85. package/{src/tests/schema.graphql → schema.graphql} +1 -0
  86. package/src/core/FragmentReference.ts +17 -5
  87. package/src/core/IsographEnvironment.ts +38 -29
  88. package/src/core/areEqualWithDeepComparison.ts +76 -42
  89. package/src/core/cache.ts +237 -123
  90. package/src/core/check.ts +207 -0
  91. package/src/core/componentCache.ts +18 -17
  92. package/src/core/entrypoint.ts +15 -8
  93. package/src/core/garbageCollection.ts +71 -20
  94. package/src/core/logging.ts +116 -0
  95. package/src/core/makeNetworkRequest.ts +89 -13
  96. package/src/core/read.ts +162 -55
  97. package/src/core/reader.ts +40 -13
  98. package/src/core/util.ts +4 -0
  99. package/src/index.ts +14 -1
  100. package/src/loadable-hooks/useClientSideDefer.ts +45 -15
  101. package/src/loadable-hooks/useConnectionSpecPagination.ts +331 -0
  102. package/src/loadable-hooks/useImperativeLoadableField.ts +36 -10
  103. package/src/loadable-hooks/useSkipLimitPagination.ts +231 -90
  104. package/src/react/FragmentReader.tsx +13 -4
  105. package/src/react/RenderAfterCommit__DO_NOT_USE.tsx +17 -0
  106. package/src/react/useImperativeReference.ts +18 -7
  107. package/src/react/useLazyReference.ts +24 -4
  108. package/src/react/useReadAndSubscribe.ts +20 -5
  109. package/src/react/useRerenderOnChange.ts +6 -1
  110. package/src/react/useResult.ts +10 -2
  111. package/src/tests/__isograph/Query/meName/entrypoint.ts +7 -2
  112. package/src/tests/__isograph/Query/meName/param_type.ts +5 -2
  113. package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
  114. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +9 -2
  115. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +9 -6
  116. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
  117. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +13 -2
  118. package/src/tests/__isograph/Query/nodeField/param_type.ts +7 -3
  119. package/src/tests/__isograph/Query/nodeField/parameters_type.ts +3 -0
  120. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
  121. package/src/tests/__isograph/Query/subquery/entrypoint.ts +67 -0
  122. package/src/tests/__isograph/Query/subquery/output_type.ts +3 -0
  123. package/src/tests/__isograph/Query/subquery/param_type.ts +12 -0
  124. package/src/tests/__isograph/Query/subquery/parameters_type.ts +3 -0
  125. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +47 -0
  126. package/src/tests/__isograph/iso.ts +22 -11
  127. package/src/tests/garbageCollection.test.ts +45 -39
  128. package/src/tests/meNameSuccessor.ts +8 -3
  129. package/src/tests/nodeQuery.ts +6 -4
  130. package/src/tests/normalizeData.test.ts +120 -0
  131. package/src/tests/tsconfig.json +3 -3
  132. package/tsconfig.json +2 -2
  133. package/tsconfig.pkg.json +6 -1
  134. package/vitest.config.ts +20 -0
@@ -1,9 +1,11 @@
1
- import { LoadableField } from '../core/reader';
1
+ import { LoadableField, type ReaderAst } from '../core/reader';
2
2
  import { useIsographEnvironment } from '../react/IsographEnvironmentProvider';
3
3
  import { ItemCleanupPair } from '@isograph/disposable-types';
4
4
  import { FragmentReference } from '../core/FragmentReference';
5
5
  import { maybeUnwrapNetworkRequest } from '../react/useResult';
6
6
  import { readButDoNotEvaluate } from '../core/read';
7
+ import { subscribeToAnyChange } from '../core/cache';
8
+ import { useState } from 'react';
7
9
  import {
8
10
  UNASSIGNED_STATE,
9
11
  useUpdatableDisposableState,
@@ -13,31 +15,43 @@ import {
13
15
  ReferenceCountedPointer,
14
16
  } from '@isograph/reference-counted-pointer';
15
17
  import { getPromiseState, readPromise } from '../core/PromiseWrapper';
18
+ import { type WithEncounteredRecords } from '../core/read';
19
+ import { useSubscribeToMultiple } from '../react/useReadAndSubscribe';
20
+ import { FetchOptions } from '../core/check';
16
21
 
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> =
22
+ type UseSkipLimitReturnValue<
23
+ TReadFromStore extends { data: object; parameters: object },
24
+ TItem,
25
+ > =
23
26
  | {
24
27
  readonly kind: 'Complete';
25
- readonly fetchMore: (args: OmitSkipLimit<TArgs>, count: number) => void;
28
+ readonly fetchMore: (count: number, fetchOptions?: FetchOptions) => void;
26
29
  readonly results: ReadonlyArray<TItem>;
27
30
  }
28
31
  | {
29
32
  readonly kind: 'Pending';
30
33
  readonly results: ReadonlyArray<TItem>;
31
- readonly pendingFragment: FragmentReference<any, ReadonlyArray<TItem>>;
34
+ readonly pendingFragment: FragmentReference<
35
+ TReadFromStore,
36
+ ReadonlyArray<TItem>
37
+ >;
32
38
  };
33
39
 
34
- type ArrayFragmentReference<TItem> = FragmentReference<
35
- any,
36
- ReadonlyArray<TItem>
37
- >;
40
+ type ArrayFragmentReference<
41
+ TReadFromStore extends { parameters: object; data: object },
42
+ TItem,
43
+ > = FragmentReference<TReadFromStore, ReadonlyArray<TItem>>;
44
+
45
+ type LoadedFragmentReferences<
46
+ TReadFromStore extends { parameters: object; data: object },
47
+ TItem,
48
+ > = ReadonlyArray<LoadedFragmentReference<TReadFromStore, TItem>>;
38
49
 
39
- type LoadedFragmentReferences<TItem> = ReadonlyArray<
40
- ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
50
+ type LoadedFragmentReference<
51
+ TReadFromStore extends { parameters: object; data: object },
52
+ TItem,
53
+ > = ItemCleanupPair<
54
+ ReferenceCountedPointer<ArrayFragmentReference<TReadFromStore, TItem>>
41
55
  >;
42
56
 
43
57
  function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
@@ -50,95 +64,136 @@ function flatten<T>(arr: ReadonlyArray<ReadonlyArray<T>>): ReadonlyArray<T> {
50
64
  return outArray;
51
65
  }
52
66
 
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
- */
67
+ type UseSkipLimitPaginationArgs = {
68
+ skip: number;
69
+ limit: number;
70
+ };
71
+
68
72
  export function useSkipLimitPagination<
69
- TArgs extends {
70
- skip: number | void | null;
71
- limit: number | void | null;
72
- },
73
73
  TItem,
74
+ TReadFromStore extends {
75
+ parameters: object;
76
+ data: object;
77
+ },
74
78
  >(
75
- loadableField: LoadableField<TArgs, Array<TItem>>,
76
- ): UseSkipLimitReturnValue<TArgs, TItem> {
79
+ loadableField: LoadableField<
80
+ TReadFromStore,
81
+ ReadonlyArray<TItem>,
82
+ UseSkipLimitPaginationArgs
83
+ >,
84
+ initialState?: {
85
+ skip?: number | void | null;
86
+ },
87
+ ): UseSkipLimitReturnValue<TReadFromStore, TItem> {
77
88
  const networkRequestOptions = {
78
89
  suspendIfInFlight: true,
79
90
  throwOnNetworkError: true,
80
91
  };
81
92
  const { state, setState } =
82
- useUpdatableDisposableState<LoadedFragmentReferences<TItem>>();
93
+ useUpdatableDisposableState<
94
+ LoadedFragmentReferences<TReadFromStore, TItem>
95
+ >();
83
96
 
84
97
  const environment = useIsographEnvironment();
85
98
 
99
+ // TODO move this out of useSkipLimitPagination, and pass environment and networkRequestOptions
100
+ // as parameters (or recreate networkRequestOptions)
86
101
  function readCompletedFragmentReferences(
87
- completedReferences: ReadonlyArray<
88
- ItemCleanupPair<ReferenceCountedPointer<ArrayFragmentReference<TItem>>>
89
- >,
102
+ completedReferences: ArrayFragmentReference<TReadFromStore, TItem>[],
90
103
  ) {
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) {
104
+ const results = completedReferences.map((fragmentReference, i) => {
105
+ const readerWithRefetchQueries = readPromise(
106
+ fragmentReference.readerWithRefetchQueries,
107
+ );
108
+
109
+ // invariant: readOutDataAndRecords.length === completedReferences.length
110
+ const data = readOutDataAndRecords[i]?.item;
111
+ if (data == null) {
96
112
  throw new Error(
97
- 'FragmentReference is unexpectedly disposed. \
98
- This is indicative of a bug in Isograph.',
113
+ 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.',
99
114
  );
100
115
  }
101
116
 
102
- maybeUnwrapNetworkRequest(
103
- fragmentReference.networkRequest,
104
- networkRequestOptions,
105
- );
106
- const data = readButDoNotEvaluate(
107
- environment,
108
- fragmentReference,
109
- networkRequestOptions,
110
- );
117
+ const firstParameter = {
118
+ data,
119
+ parameters: fragmentReference.variables,
120
+ };
111
121
 
112
- const readerWithRefetchQueries = readPromise(
113
- fragmentReference.readerWithRefetchQueries,
114
- );
122
+ if (
123
+ readerWithRefetchQueries.readerArtifact.kind !== 'EagerReaderArtifact'
124
+ ) {
125
+ throw new Error(
126
+ `@loadable field of kind "${readerWithRefetchQueries.readerArtifact.kind}" is not supported by useSkipLimitPagination`,
127
+ );
128
+ }
115
129
 
116
- return readerWithRefetchQueries.readerArtifact.resolver(
117
- data.item,
118
- undefined,
119
- ) as ReadonlyArray<any>;
130
+ return readerWithRefetchQueries.readerArtifact.resolver(firstParameter);
120
131
  });
121
132
 
122
133
  const items = flatten(results);
123
134
  return items;
124
135
  }
125
136
 
137
+ function subscribeCompletedFragmentReferences(
138
+ completedReferences: ArrayFragmentReference<TReadFromStore, TItem>[],
139
+ ) {
140
+ return completedReferences.map(
141
+ (
142
+ fragmentReference,
143
+ i,
144
+ ): {
145
+ records: WithEncounteredRecords<TReadFromStore>;
146
+ callback: (
147
+ updatedRecords: WithEncounteredRecords<TReadFromStore>,
148
+ ) => void;
149
+ fragmentReference: ArrayFragmentReference<TReadFromStore, TItem>;
150
+ readerAst: ReaderAst<TItem>;
151
+ } => {
152
+ maybeUnwrapNetworkRequest(
153
+ fragmentReference.networkRequest,
154
+ networkRequestOptions,
155
+ );
156
+
157
+ const readerWithRefetchQueries = readPromise(
158
+ fragmentReference.readerWithRefetchQueries,
159
+ );
160
+
161
+ const records = readOutDataAndRecords[i];
162
+ if (records == null) {
163
+ throw new Error(
164
+ 'subscribeCompletedFragmentReferences records is unexpectedly null',
165
+ );
166
+ }
167
+
168
+ return {
169
+ fragmentReference,
170
+ readerAst: readerWithRefetchQueries.readerArtifact.readerAst,
171
+ records,
172
+ callback(_data) {
173
+ rerender({});
174
+ },
175
+ };
176
+ },
177
+ );
178
+ }
179
+
126
180
  const getFetchMore =
127
181
  (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]();
182
+ (count: number, fetchOptions?: FetchOptions): void => {
183
+ const loadedField = loadableField(
184
+ {
185
+ skip: loadedSoFar,
186
+ limit: count,
187
+ },
188
+ fetchOptions ?? {},
189
+ )[1]();
135
190
  const newPointer = createReferenceCountedPointer(loadedField);
136
191
  const clonedPointers = loadedReferences.map(([refCountedPointer]) => {
137
192
  const clonedRefCountedPointer = refCountedPointer.cloneIfNotDisposed();
138
193
  if (clonedRefCountedPointer == null) {
139
194
  throw new Error(
140
195
  'This reference counted pointer has already been disposed. \
141
- This is indicative of a bug in useSkipLimitPagination.',
196
+ This is indicative of a bug in useSkipLimitPagination.',
142
197
  );
143
198
  }
144
199
  return clonedRefCountedPointer;
@@ -148,7 +203,9 @@ export function useSkipLimitPagination<
148
203
  const totalItemCleanupPair: ItemCleanupPair<
149
204
  ReadonlyArray<
150
205
  ItemCleanupPair<
151
- ReferenceCountedPointer<ArrayFragmentReference<TItem>>
206
+ ReferenceCountedPointer<
207
+ ArrayFragmentReference<TReadFromStore, TItem>
208
+ >
152
209
  >
153
210
  >
154
211
  > = [
@@ -163,33 +220,73 @@ export function useSkipLimitPagination<
163
220
  setState(totalItemCleanupPair);
164
221
  };
165
222
 
223
+ const [, rerender] = useState({});
224
+
166
225
  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
226
 
175
- const mostRecentItem = loadedReferences[loadedReferences.length - 1];
176
- const mostRecentFragmentReference = mostRecentItem[0].getItemIfNotDisposed();
177
- if (mostRecentFragmentReference === null) {
227
+ const mostRecentItem:
228
+ | LoadedFragmentReference<TReadFromStore, TItem>
229
+ | undefined = loadedReferences[loadedReferences.length - 1];
230
+ const mostRecentFragmentReference =
231
+ mostRecentItem?.[0].getItemIfNotDisposed();
232
+
233
+ if (mostRecentItem && mostRecentFragmentReference === null) {
178
234
  throw new Error(
179
235
  'FragmentReference is unexpectedly disposed. \
180
- This is indicative of a bug in Isograph.',
236
+ This is indicative of a bug in Isograph.',
181
237
  );
182
238
  }
183
239
 
184
- const networkRequestStatus = getPromiseState(
185
- mostRecentFragmentReference.networkRequest,
240
+ const networkRequestStatus =
241
+ mostRecentFragmentReference &&
242
+ getPromiseState(mostRecentFragmentReference.networkRequest);
243
+
244
+ const slicedFragmentReferences =
245
+ networkRequestStatus?.kind === 'Ok'
246
+ ? loadedReferences
247
+ : loadedReferences.slice(0, loadedReferences.length - 1);
248
+
249
+ const completedFragmentReferences = slicedFragmentReferences.map(
250
+ ([pointer]) => {
251
+ const fragmentReference = pointer.getItemIfNotDisposed();
252
+ if (fragmentReference == null) {
253
+ throw new Error(
254
+ 'FragmentReference is unexpectedly disposed. \
255
+ This is indicative of a bug in Isograph.',
256
+ );
257
+ }
258
+ return fragmentReference;
259
+ },
260
+ );
261
+
262
+ const readOutDataAndRecords = completedFragmentReferences.map(
263
+ (fragmentReference) =>
264
+ readButDoNotEvaluate(
265
+ environment,
266
+ fragmentReference,
267
+ networkRequestOptions,
268
+ ),
186
269
  );
270
+
271
+ useSubscribeToMultiple<TReadFromStore>(
272
+ subscribeCompletedFragmentReferences(completedFragmentReferences),
273
+ );
274
+
275
+ if (!networkRequestStatus) {
276
+ return {
277
+ kind: 'Complete',
278
+ fetchMore: getFetchMore(initialState?.skip ?? 0),
279
+ results: [],
280
+ };
281
+ }
282
+
187
283
  switch (networkRequestStatus.kind) {
188
284
  case 'Pending': {
189
- const completedFragmentReferences = loadedReferences.slice(
190
- 0,
191
- loadedReferences.length - 1,
192
- );
285
+ const unsubscribe = subscribeToAnyChange(environment, () => {
286
+ unsubscribe();
287
+ rerender({});
288
+ });
289
+
193
290
  return {
194
291
  kind: 'Pending',
195
292
  pendingFragment: mostRecentFragmentReference,
@@ -200,7 +297,9 @@ export function useSkipLimitPagination<
200
297
  throw networkRequestStatus.error;
201
298
  }
202
299
  case 'Ok': {
203
- const results = readCompletedFragmentReferences(loadedReferences);
300
+ const results = readCompletedFragmentReferences(
301
+ completedFragmentReferences,
302
+ );
204
303
  return {
205
304
  kind: 'Complete',
206
305
  results,
@@ -209,3 +308,45 @@ export function useSkipLimitPagination<
209
308
  }
210
309
  }
211
310
  }
311
+
312
+ // @ts-ignore
313
+ function tsTests() {
314
+ type Parameters = {
315
+ readonly search: string;
316
+ readonly skip: number;
317
+ readonly limit: number;
318
+ };
319
+
320
+ let basicLoadable!: LoadableField<
321
+ {
322
+ readonly data: object;
323
+ readonly parameters: Omit<Parameters, 'search'>;
324
+ },
325
+ object[]
326
+ >;
327
+
328
+ useSkipLimitPagination(basicLoadable);
329
+ useSkipLimitPagination(basicLoadable, {});
330
+ useSkipLimitPagination(basicLoadable, { skip: 10 });
331
+
332
+ let unprovidedSearchLoadable!: LoadableField<
333
+ {
334
+ readonly data: object;
335
+ readonly parameters: Parameters;
336
+ },
337
+ object[]
338
+ >;
339
+ // @ts-expect-error
340
+ useSkipLimitPagination(unprovidedSearchLoadable);
341
+
342
+ let providedSearchLoadable!: LoadableField<
343
+ {
344
+ readonly data: object;
345
+ readonly parameters: Parameters;
346
+ },
347
+ object[],
348
+ Omit<Parameters, 'search'>
349
+ >;
350
+
351
+ useSkipLimitPagination(providedSearchLoadable);
352
+ }
@@ -4,17 +4,23 @@ import { FragmentReference } from '../core/FragmentReference';
4
4
  import { useResult } from './useResult';
5
5
  import { NetworkRequestReaderOptions } from '../core/read';
6
6
 
7
+ type IsExactlyIntrinsicAttributes<T> = T extends JSX.IntrinsicAttributes
8
+ ? JSX.IntrinsicAttributes extends T
9
+ ? true
10
+ : false
11
+ : false;
12
+
7
13
  export function FragmentReader<
8
14
  TProps extends Record<any, any>,
9
15
  TEntrypoint extends IsographEntrypoint<any, React.FC<TProps>>,
10
16
  >(
11
- props: TProps extends Record<string, never>
17
+ props: IsExactlyIntrinsicAttributes<TProps> extends true
12
18
  ? {
13
19
  fragmentReference: FragmentReference<
14
20
  ExtractReadFromStore<TEntrypoint>,
15
- React.FC<{}>
21
+ React.FC<TProps>
16
22
  >;
17
- additionalProps?: TProps;
23
+ additionalProps?: Record<PropertyKey, never>;
18
24
  networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
19
25
  }
20
26
  : {
@@ -22,7 +28,7 @@ export function FragmentReader<
22
28
  ExtractReadFromStore<TEntrypoint>,
23
29
  React.FC<TProps>
24
30
  >;
25
- additionalProps: TProps;
31
+ additionalProps: Omit<TProps, keyof JSX.IntrinsicAttributes>;
26
32
  networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
27
33
  },
28
34
  ): React.ReactNode {
@@ -30,5 +36,8 @@ export function FragmentReader<
30
36
  props.fragmentReference,
31
37
  props.networkRequestOptions,
32
38
  );
39
+ // TypeScript is not understanding that if additionalProps is Record<PropertyKey, never>,
40
+ // it means that TProps === JSX.IntrinsicAttributes.
41
+ // @ts-expect-error
33
42
  return <Component {...props.additionalProps} />;
34
43
  }
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * This is a function that will render a component only after it commits.
5
+ * It should not be used in production. It's useful as a way to debug issues
6
+ * with NextJS, where an indefinite suspense causes the server to hang
7
+ * forever and never complete the original request.
8
+ */
9
+ export function RenderAfterCommit__DO_NOT_USE({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ const [show, setShow] = useState(false);
15
+ useEffect(() => setShow(true), []);
16
+ return show ? children : null;
17
+ }
@@ -3,16 +3,20 @@ import {
3
3
  useUpdatableDisposableState,
4
4
  } from '@isograph/react-disposable-state';
5
5
  import { IsographEntrypoint } from '../core/entrypoint';
6
- import { FragmentReference, Variables } from '../core/FragmentReference';
6
+ import {
7
+ FragmentReference,
8
+ ExtractParameters,
9
+ } from '../core/FragmentReference';
7
10
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
8
11
  import { ROOT_ID } from '../core/IsographEnvironment';
9
- import { makeNetworkRequest } from '../core/makeNetworkRequest';
12
+ import { maybeMakeNetworkRequest } from '../core/makeNetworkRequest';
10
13
  import { wrapResolvedValue } from '../core/PromiseWrapper';
14
+ import { FetchOptions } from '../core/check';
11
15
 
12
16
  // TODO rename this to useImperativelyLoadedEntrypoint
13
17
 
14
18
  export function useImperativeReference<
15
- TReadFromStore extends Object,
19
+ TReadFromStore extends { parameters: object; data: object },
16
20
  TClientFieldValue,
17
21
  >(
18
22
  entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
@@ -20,7 +24,10 @@ export function useImperativeReference<
20
24
  fragmentReference:
21
25
  | FragmentReference<TReadFromStore, TClientFieldValue>
22
26
  | UnassignedState;
23
- loadFragmentReference: (variables: Variables) => void;
27
+ loadFragmentReference: (
28
+ variables: ExtractParameters<TReadFromStore>,
29
+ fetchOptions?: FetchOptions,
30
+ ) => void;
24
31
  } {
25
32
  const { state, setState } =
26
33
  useUpdatableDisposableState<
@@ -29,11 +36,15 @@ export function useImperativeReference<
29
36
  const environment = useIsographEnvironment();
30
37
  return {
31
38
  fragmentReference: state,
32
- loadFragmentReference: (variables: Variables) => {
33
- const [networkRequest, disposeNetworkRequest] = makeNetworkRequest(
39
+ loadFragmentReference: (
40
+ variables: ExtractParameters<TReadFromStore>,
41
+ fetchOptions?: FetchOptions,
42
+ ) => {
43
+ const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
34
44
  environment,
35
45
  entrypoint,
36
46
  variables,
47
+ fetchOptions,
37
48
  );
38
49
  setState([
39
50
  {
@@ -44,7 +55,7 @@ export function useImperativeReference<
44
55
  nestedRefetchQueries:
45
56
  entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
46
57
  }),
47
- root: ROOT_ID,
58
+ root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
48
59
  variables,
49
60
  networkRequest,
50
61
  },
@@ -1,20 +1,40 @@
1
- import { FragmentReference, Variables } from '../core/FragmentReference';
1
+ import {
2
+ FragmentReference,
3
+ ExtractParameters,
4
+ } from '../core/FragmentReference';
2
5
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
3
6
  import { IsographEntrypoint } from '../core/entrypoint';
4
7
  import { getOrCreateCacheForArtifact } from '../core/cache';
5
8
  import { useLazyDisposableState } from '@isograph/react-disposable-state';
9
+ import { logMessage } from '../core/logging';
10
+ import { FetchOptions } from '../core/check';
6
11
 
7
12
  export function useLazyReference<
8
- TReadFromStore extends Object,
13
+ TReadFromStore extends { parameters: object; data: object },
9
14
  TClientFieldValue,
10
15
  >(
11
16
  entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
12
- variables: Variables,
17
+ variables: ExtractParameters<TReadFromStore>,
18
+ fetchOptions?: FetchOptions,
13
19
  ): {
14
20
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>;
15
21
  } {
16
22
  const environment = useIsographEnvironment();
17
- const cache = getOrCreateCacheForArtifact(environment, entrypoint, variables);
23
+
24
+ if (entrypoint?.kind !== 'Entrypoint') {
25
+ // TODO have a separate error logger
26
+ logMessage(environment, {
27
+ kind: 'NonEntrypointReceived',
28
+ entrypoint,
29
+ });
30
+ }
31
+
32
+ const cache = getOrCreateCacheForArtifact(
33
+ environment,
34
+ entrypoint,
35
+ variables,
36
+ fetchOptions,
37
+ );
18
38
 
19
39
  return {
20
40
  fragmentReference: useLazyDisposableState(cache).state,
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
2
2
  import {
3
3
  FragmentReference,
4
4
  stableIdForFragmentReference,
5
+ ExtractData,
5
6
  } from '../core/FragmentReference';
6
7
  import {
7
8
  NetworkRequestReaderOptions,
@@ -11,14 +12,18 @@ import {
11
12
  import { useRerenderOnChange } from './useRerenderOnChange';
12
13
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
13
14
  import { subscribe } from '../core/cache';
15
+ import type { ReaderAst } from '../core/reader';
14
16
 
15
17
  /**
16
18
  * Read the data from a fragment reference and subscribe to updates.
17
19
  */
18
- export function useReadAndSubscribe<TReadFromStore extends Object>(
20
+ export function useReadAndSubscribe<
21
+ TReadFromStore extends { parameters: object; data: object },
22
+ >(
19
23
  fragmentReference: FragmentReference<TReadFromStore, any>,
20
24
  networkRequestOptions: NetworkRequestReaderOptions,
21
- ): TReadFromStore {
25
+ readerAst: ReaderAst<TReadFromStore>,
26
+ ): ExtractData<TReadFromStore> {
22
27
  const environment = useIsographEnvironment();
23
28
  const [readOutDataAndRecords, setReadOutDataAndRecords] = useState(() =>
24
29
  readButDoNotEvaluate(environment, fragmentReference, networkRequestOptions),
@@ -27,23 +32,33 @@ export function useReadAndSubscribe<TReadFromStore extends Object>(
27
32
  readOutDataAndRecords,
28
33
  fragmentReference,
29
34
  setReadOutDataAndRecords,
35
+ readerAst,
30
36
  );
31
37
  return readOutDataAndRecords.item;
32
38
  }
33
39
 
34
- export function useSubscribeToMultiple<TReadFromStore extends Object>(
40
+ export function useSubscribeToMultiple<
41
+ TReadFromStore extends { parameters: object; data: object },
42
+ >(
35
43
  items: ReadonlyArray<{
36
44
  records: WithEncounteredRecords<TReadFromStore>;
37
45
  callback: (updatedRecords: WithEncounteredRecords<TReadFromStore>) => void;
38
46
  fragmentReference: FragmentReference<TReadFromStore, any>;
47
+ readerAst: ReaderAst<TReadFromStore>;
39
48
  }>,
40
49
  ) {
41
50
  const environment = useIsographEnvironment();
42
51
  useEffect(
43
52
  () => {
44
53
  const cleanupFns = items.map(
45
- ({ records, callback, fragmentReference }) => {
46
- return subscribe(environment, records, fragmentReference, callback);
54
+ ({ records, callback, fragmentReference, readerAst }) => {
55
+ return subscribe(
56
+ environment,
57
+ records,
58
+ fragmentReference,
59
+ callback,
60
+ readerAst,
61
+ );
47
62
  },
48
63
  );
49
64
  return () => {
@@ -3,15 +3,19 @@ import { subscribe } from '../core/cache';
3
3
  import { WithEncounteredRecords } from '../core/read';
4
4
  import { FragmentReference } from '../core/FragmentReference';
5
5
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
6
+ import type { ReaderAst } from '../core/reader';
6
7
 
7
8
  // TODO add unit tests for this. Add integration tests that test
8
9
  // behavior when the encounteredRecords underneath a fragment change.
9
- export function useRerenderOnChange<TReadFromStore extends Object>(
10
+ export function useRerenderOnChange<
11
+ TReadFromStore extends { parameters: object; data: object },
12
+ >(
10
13
  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
11
14
  fragmentReference: FragmentReference<any, any>,
12
15
  setEncounteredDataAndRecords: (
13
16
  data: WithEncounteredRecords<TReadFromStore>,
14
17
  ) => void,
18
+ readerAst: ReaderAst<TReadFromStore>,
15
19
  ) {
16
20
  const environment = useIsographEnvironment();
17
21
  useEffect(() => {
@@ -22,6 +26,7 @@ export function useRerenderOnChange<TReadFromStore extends Object>(
22
26
  (newEncounteredDataAndRecords) => {
23
27
  setEncounteredDataAndRecords(newEncounteredDataAndRecords);
24
28
  },
29
+ readerAst,
25
30
  );
26
31
  // Note: this is an empty array on purpose:
27
32
  // - the fragment reference is stable for the life of the component