@isograph/react 0.0.0-main-d564e834 → 0.0.0-main-fbe23de7
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 +2 -0
- package/dist/core/PromiseWrapper.d.ts +17 -4
- package/dist/core/PromiseWrapper.js +35 -10
- package/dist/core/cache.d.ts +2 -2
- package/dist/core/cache.js +31 -4
- package/dist/core/componentCache.d.ts +2 -1
- package/dist/core/componentCache.js +2 -2
- package/dist/core/entrypoint.d.ts +2 -0
- package/dist/core/makeNetworkRequest.d.ts +2 -2
- package/dist/core/makeNetworkRequest.js +6 -2
- package/dist/core/read.d.ts +5 -1
- package/dist/core/read.js +32 -10
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -3
- package/dist/loadable-hooks/useClientSideDefer.js +0 -1
- package/dist/react/{FragmentReferenceReader.d.ts → FragmentReader.d.ts} +4 -1
- package/dist/react/{FragmentReferenceReader.js → FragmentReader.js} +5 -4
- package/dist/react/useImperativeReference.d.ts +1 -1
- package/dist/react/useImperativeReference.js +3 -2
- package/dist/react/useLazyReference.js +2 -3
- package/dist/react/useReadAndSubscribe.d.ts +2 -2
- package/dist/react/useReadAndSubscribe.js +5 -3
- package/dist/react/useRerenderOnChange.d.ts +1 -2
- package/dist/react/useRerenderOnChange.js +3 -1
- package/dist/react/useResult.d.ts +4 -1
- package/dist/react/useResult.js +17 -4
- package/package.json +3 -3
- package/src/core/FragmentReference.ts +2 -0
- package/src/core/PromiseWrapper.ts +58 -12
- package/src/core/cache.ts +80 -57
- package/src/core/componentCache.ts +6 -1
- package/src/core/entrypoint.ts +1 -0
- package/src/core/makeNetworkRequest.ts +11 -6
- package/src/core/read.ts +52 -7
- package/src/index.ts +35 -25
- package/src/loadable-hooks/useClientSideDefer.ts +1 -1
- package/src/react/{FragmentReferenceReader.tsx → FragmentReader.tsx} +8 -2
- package/src/react/useImperativeReference.ts +4 -3
- package/src/react/useLazyReference.ts +2 -3
- package/src/react/useReadAndSubscribe.ts +8 -5
- package/src/react/useRerenderOnChange.ts +2 -2
- package/src/react/useResult.ts +28 -1
package/dist/react/useResult.js
CHANGED
@@ -1,20 +1,33 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useResult = void 0;
|
3
|
+
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
|
-
|
7
|
+
const PromiseWrapper_1 = require("../core/PromiseWrapper");
|
8
|
+
function useResult(fragmentReference, networkRequestOptions) {
|
8
9
|
const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
|
10
|
+
maybeUnwrapNetworkRequest(fragmentReference.networkRequest, networkRequestOptions);
|
9
11
|
switch (fragmentReference.readerArtifact.kind) {
|
10
12
|
case 'ComponentReaderArtifact': {
|
11
13
|
// @ts-expect-error
|
12
|
-
return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.readerArtifact.componentName, fragmentReference);
|
14
|
+
return (0, componentCache_1.getOrCreateCachedComponent)(environment, fragmentReference.readerArtifact.componentName, fragmentReference, networkRequestOptions);
|
13
15
|
}
|
14
16
|
case 'EagerReaderArtifact': {
|
15
|
-
const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(
|
17
|
+
const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(fragmentReference, networkRequestOptions);
|
16
18
|
return fragmentReference.readerArtifact.resolver(data);
|
17
19
|
}
|
18
20
|
}
|
19
21
|
}
|
20
22
|
exports.useResult = useResult;
|
23
|
+
function maybeUnwrapNetworkRequest(networkRequest, networkRequestOptions) {
|
24
|
+
const state = (0, PromiseWrapper_1.getPromiseState)(networkRequest);
|
25
|
+
if (state.kind === 'Err' && networkRequestOptions.throwOnNetworkError) {
|
26
|
+
throw state.error;
|
27
|
+
}
|
28
|
+
else if (state.kind === 'Pending' &&
|
29
|
+
networkRequestOptions.suspendIfInFlight) {
|
30
|
+
throw state.promise;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
exports.maybeUnwrapNetworkRequest = maybeUnwrapNetworkRequest;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@isograph/react",
|
3
|
-
"version": "0.0.0-main-
|
3
|
+
"version": "0.0.0-main-fbe23de7",
|
4
4
|
"description": "Use Isograph with React",
|
5
5
|
"homepage": "https://isograph.dev",
|
6
6
|
"main": "dist/index.js",
|
@@ -17,8 +17,8 @@
|
|
17
17
|
"tsc": "tsc"
|
18
18
|
},
|
19
19
|
"dependencies": {
|
20
|
-
"@isograph/disposable-types": "0.0.0-main-
|
21
|
-
"@isograph/react-disposable-state": "0.0.0-main-
|
20
|
+
"@isograph/disposable-types": "0.0.0-main-fbe23de7",
|
21
|
+
"@isograph/react-disposable-state": "0.0.0-main-fbe23de7",
|
22
22
|
"react": "^18.2.0"
|
23
23
|
},
|
24
24
|
"devDependencies": {
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { DataId } from './IsographEnvironment';
|
2
2
|
import { RefetchQueryNormalizationArtifactWrapper } from '../core/entrypoint';
|
3
3
|
import { TopLevelReaderArtifact } from './reader';
|
4
|
+
import { PromiseWrapper } from './PromiseWrapper';
|
4
5
|
|
5
6
|
// TODO type this better
|
6
7
|
export type VariableValue = string | number | boolean | null | object;
|
@@ -21,4 +22,5 @@ export type FragmentReference<
|
|
21
22
|
readonly variables: Variables | null;
|
22
23
|
// TODO: We should instead have ReaderAst<TClientFieldProps>
|
23
24
|
readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
|
25
|
+
readonly networkRequest: PromiseWrapper<void, any>;
|
24
26
|
};
|
@@ -1,29 +1,75 @@
|
|
1
|
+
export type AnyError = any;
|
2
|
+
|
1
3
|
const NOT_SET: Symbol = Symbol('NOT_SET');
|
2
4
|
type NotSet = typeof NOT_SET;
|
3
5
|
|
6
|
+
type Result<T, E> =
|
7
|
+
| {
|
8
|
+
kind: 'Ok';
|
9
|
+
value: T;
|
10
|
+
}
|
11
|
+
| {
|
12
|
+
kind: 'Err';
|
13
|
+
error: E;
|
14
|
+
};
|
15
|
+
|
4
16
|
/**
|
5
17
|
* Invariant:
|
6
18
|
* Before the promise is resolved, value becomes non-null.
|
7
19
|
*/
|
8
|
-
export type PromiseWrapper<T> = {
|
20
|
+
export type PromiseWrapper<T, E> = {
|
9
21
|
readonly promise: Promise<T>;
|
10
|
-
|
22
|
+
result: Result<Exclude<T, NotSet>, E> | NotSet;
|
11
23
|
};
|
12
24
|
|
13
|
-
export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T> {
|
25
|
+
export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T, any> {
|
14
26
|
// TODO confirm suspense works if the promise is already resolved.
|
15
|
-
const wrapper: PromiseWrapper<T> = { promise,
|
16
|
-
promise
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
const wrapper: PromiseWrapper<T, any> = { promise, result: NOT_SET };
|
28
|
+
promise
|
29
|
+
.then((v) => {
|
30
|
+
// v is assignable to Exclude<T, Symbol> | Symbol
|
31
|
+
wrapper.result = { kind: 'Ok', value: v as any };
|
32
|
+
})
|
33
|
+
.catch((e) => {
|
34
|
+
// e is assignable to Exclude<T, Symbol> | Symbol
|
35
|
+
wrapper.result = { kind: 'Err', error: e as any };
|
36
|
+
});
|
20
37
|
return wrapper;
|
21
38
|
}
|
22
39
|
|
23
|
-
export function
|
24
|
-
|
25
|
-
|
26
|
-
|
40
|
+
export function readPromise<T, E>(p: PromiseWrapper<T, E>): T {
|
41
|
+
const { result } = p;
|
42
|
+
if (result !== NOT_SET) {
|
43
|
+
// Safety: p.result is either NOT_SET or an actual value.
|
44
|
+
const resultKind = result as Result<T, any>;
|
45
|
+
if (resultKind.kind === 'Ok') {
|
46
|
+
return resultKind.value;
|
47
|
+
} else {
|
48
|
+
throw resultKind.error;
|
49
|
+
}
|
27
50
|
}
|
51
|
+
|
28
52
|
throw p.promise;
|
29
53
|
}
|
54
|
+
|
55
|
+
export type PromiseState<T, E> =
|
56
|
+
| {
|
57
|
+
kind: 'Pending';
|
58
|
+
promise: Promise<T>;
|
59
|
+
}
|
60
|
+
| Result<T, E>;
|
61
|
+
|
62
|
+
export function getPromiseState<T, E>(
|
63
|
+
p: PromiseWrapper<T, E>,
|
64
|
+
): PromiseState<T, E> {
|
65
|
+
const { result } = p;
|
66
|
+
if (result !== NOT_SET) {
|
67
|
+
// Safety: p.result is either NOT_SET or an actual value.
|
68
|
+
const resultKind = result as Result<T, any>;
|
69
|
+
return resultKind;
|
70
|
+
}
|
71
|
+
return {
|
72
|
+
kind: 'Pending',
|
73
|
+
promise: p.promise,
|
74
|
+
};
|
75
|
+
}
|
package/src/core/cache.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Factory, ParentCache } from '@isograph/react-disposable-state';
|
2
|
-
import { PromiseWrapper } from './PromiseWrapper';
|
2
|
+
import { AnyError, PromiseWrapper } from './PromiseWrapper';
|
3
3
|
import {
|
4
4
|
DataId,
|
5
5
|
ROOT_ID,
|
@@ -80,15 +80,10 @@ export function getOrCreateCacheForArtifact<
|
|
80
80
|
environment: IsographEnvironment,
|
81
81
|
artifact: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
|
82
82
|
variables: Variables,
|
83
|
-
): ParentCache<PromiseWrapper<
|
83
|
+
): ParentCache<PromiseWrapper<void, AnyError>> {
|
84
84
|
const cacheKey = artifact.queryText + JSON.stringify(stableCopy(variables));
|
85
|
-
const factory
|
86
|
-
|
87
|
-
return getOrCreateCache<PromiseWrapper<TClientFieldValue>>(
|
88
|
-
environment,
|
89
|
-
cacheKey,
|
90
|
-
factory,
|
91
|
-
);
|
85
|
+
const factory = () => makeNetworkRequest(environment, artifact, variables);
|
86
|
+
return getOrCreateCache(environment, cacheKey, factory);
|
92
87
|
}
|
93
88
|
|
94
89
|
type NetworkResponseScalarValue = string | number | boolean;
|
@@ -183,64 +178,92 @@ export function onNextChange(environment: IsographEnvironment): Promise<void> {
|
|
183
178
|
});
|
184
179
|
}
|
185
180
|
|
181
|
+
// Calls to readButDoNotEvaluate can suspend (i.e. throw a promise).
|
182
|
+
// Maybe in the future, they will be able to throw errors.
|
183
|
+
//
|
184
|
+
// That's probably okay to ignore. We don't, however, want to prevent
|
185
|
+
// updating other subscriptions if one subscription had missing data.
|
186
|
+
function withErrorHandling<T>(f: (t: T) => void): (t: T) => void {
|
187
|
+
return (t) => {
|
188
|
+
try {
|
189
|
+
return f(t);
|
190
|
+
} catch {}
|
191
|
+
};
|
192
|
+
}
|
193
|
+
|
186
194
|
function callSubscriptions(
|
187
195
|
environment: IsographEnvironment,
|
188
196
|
recordsEncounteredWhenNormalizing: Set<DataId>,
|
189
197
|
) {
|
190
|
-
environment.subscriptions.forEach(
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
hasOverlappingIds(
|
198
|
-
recordsEncounteredWhenNormalizing,
|
199
|
-
subscription.encounteredDataAndRecords.encounteredRecords,
|
200
|
-
)
|
201
|
-
) {
|
202
|
-
const newEncounteredDataAndRecords = readButDoNotEvaluate(
|
203
|
-
environment,
|
204
|
-
subscription.fragmentReference,
|
205
|
-
);
|
206
|
-
|
198
|
+
environment.subscriptions.forEach(
|
199
|
+
withErrorHandling((subscription) => {
|
200
|
+
switch (subscription.kind) {
|
201
|
+
case 'FragmentSubscription': {
|
202
|
+
// TODO if there are multiple components subscribed to the same
|
203
|
+
// fragment, we will call readButNotEvaluate multiple times. We
|
204
|
+
// should fix that.
|
207
205
|
if (
|
208
|
-
|
209
|
-
|
210
|
-
|
206
|
+
hasOverlappingIds(
|
207
|
+
recordsEncounteredWhenNormalizing,
|
208
|
+
subscription.encounteredDataAndRecords.encounteredRecords,
|
211
209
|
)
|
212
210
|
) {
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
211
|
+
const newEncounteredDataAndRecords = readButDoNotEvaluate(
|
212
|
+
environment,
|
213
|
+
subscription.fragmentReference,
|
214
|
+
// Is this wrong?
|
215
|
+
// Reasons to think no:
|
216
|
+
// - we are only updating the read-out value, and the network
|
217
|
+
// options only affect whether we throw.
|
218
|
+
// - the component will re-render, and re-throw on its own, anyway.
|
219
|
+
//
|
220
|
+
// Reasons to think not:
|
221
|
+
// - it seems more efficient to suspend here and not update state,
|
222
|
+
// if we expect that the component will just throw anyway
|
223
|
+
// - consistency
|
224
|
+
// - it's also weird, this is called from makeNetworkRequest, where
|
225
|
+
// we don't currently pass network request options
|
226
|
+
{},
|
227
|
+
);
|
228
|
+
|
229
|
+
if (
|
230
|
+
!areEqualObjectsWithDeepComparison(
|
231
|
+
subscription.encounteredDataAndRecords.item,
|
232
|
+
newEncounteredDataAndRecords.item,
|
233
|
+
)
|
234
|
+
) {
|
235
|
+
if (typeof window !== 'undefined' && window.__LOG) {
|
236
|
+
console.log('Deep equality - No', {
|
237
|
+
fragmentReference: subscription.fragmentReference,
|
238
|
+
old: subscription.encounteredDataAndRecords.item,
|
239
|
+
new: newEncounteredDataAndRecords.item,
|
240
|
+
});
|
241
|
+
}
|
242
|
+
// TODO deep compare values
|
243
|
+
subscription.callback(newEncounteredDataAndRecords);
|
244
|
+
} else {
|
245
|
+
if (typeof window !== 'undefined' && window.__LOG) {
|
246
|
+
console.log('Deep equality - Yes', {
|
247
|
+
fragmentReference: subscription.fragmentReference,
|
248
|
+
old: subscription.encounteredDataAndRecords.item,
|
249
|
+
});
|
250
|
+
}
|
228
251
|
}
|
229
252
|
}
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
case 'AnyRecords': {
|
256
|
+
return subscription.callback();
|
257
|
+
}
|
258
|
+
default: {
|
259
|
+
// Ensure we have covered all variants
|
260
|
+
const _: never = subscription;
|
261
|
+
_;
|
262
|
+
throw new Error('Unexpected case');
|
230
263
|
}
|
231
|
-
return;
|
232
|
-
}
|
233
|
-
case 'AnyRecords': {
|
234
|
-
return subscription.callback();
|
235
|
-
}
|
236
|
-
default: {
|
237
|
-
// Ensure we have covered all variants
|
238
|
-
const _: never = subscription;
|
239
|
-
_;
|
240
|
-
throw new Error('Unexpected case');
|
241
264
|
}
|
242
|
-
}
|
243
|
-
|
265
|
+
}),
|
266
|
+
);
|
244
267
|
}
|
245
268
|
|
246
269
|
function hasOverlappingIds(set1: Set<DataId>, set2: Set<DataId>): boolean {
|
@@ -563,7 +586,7 @@ function getStoreKeyChunkForArgumentValue(
|
|
563
586
|
return argumentValue.value;
|
564
587
|
}
|
565
588
|
case 'Variable': {
|
566
|
-
return variables[argumentValue.name];
|
589
|
+
return variables[argumentValue.name] ?? 'null';
|
567
590
|
}
|
568
591
|
case 'String': {
|
569
592
|
return argumentValue.value;
|
@@ -2,11 +2,13 @@ import { stableCopy } from './cache';
|
|
2
2
|
import { IsographEnvironment } from './IsographEnvironment';
|
3
3
|
import { FragmentReference } from './FragmentReference';
|
4
4
|
import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
|
5
|
+
import { NetworkRequestReaderOptions } from './read';
|
5
6
|
|
6
7
|
export function getOrCreateCachedComponent(
|
7
8
|
environment: IsographEnvironment,
|
8
9
|
componentName: string,
|
9
10
|
fragmentReference: FragmentReference<any, any>,
|
11
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
10
12
|
): React.FC<any> {
|
11
13
|
// cachedComponentsById is a three layer cache: id, then component name, then
|
12
14
|
// stringified args. These three, together, uniquely identify a read at a given
|
@@ -27,7 +29,10 @@ export function getOrCreateCachedComponent(
|
|
27
29
|
byArgs[stringifiedArgs] ??
|
28
30
|
(() => {
|
29
31
|
function Component(additionalRuntimeProps: { [key: string]: any }) {
|
30
|
-
const data = useReadAndSubscribe(
|
32
|
+
const data = useReadAndSubscribe(
|
33
|
+
fragmentReference,
|
34
|
+
networkRequestOptions,
|
35
|
+
);
|
31
36
|
|
32
37
|
if (typeof window !== 'undefined' && window.__LOG) {
|
33
38
|
console.log(
|
package/src/core/entrypoint.ts
CHANGED
@@ -74,3 +74,4 @@ export type ExtractReadFromStore<Type> =
|
|
74
74
|
Type extends IsographEntrypoint<infer X, any> ? X : never;
|
75
75
|
export type ExtractResolverResult<Type> =
|
76
76
|
Type extends IsographEntrypoint<any, infer X> ? X : never;
|
77
|
+
export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
|
@@ -11,14 +11,14 @@ import {
|
|
11
11
|
unretainQuery,
|
12
12
|
} from './garbageCollection';
|
13
13
|
import { IsographEnvironment } from './IsographEnvironment';
|
14
|
-
import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
14
|
+
import { AnyError, PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
15
15
|
import { normalizeData } from './cache';
|
16
16
|
|
17
|
-
export function makeNetworkRequest
|
17
|
+
export function makeNetworkRequest(
|
18
18
|
environment: IsographEnvironment,
|
19
19
|
artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
|
20
20
|
variables: Variables,
|
21
|
-
): ItemCleanupPair<PromiseWrapper<
|
21
|
+
): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
|
22
22
|
if (typeof window !== 'undefined' && window.__LOG) {
|
23
23
|
console.log('make network request', artifact, variables);
|
24
24
|
}
|
@@ -33,6 +33,13 @@ export function makeNetworkRequest<T>(
|
|
33
33
|
console.log('network response', artifact, networkResponse);
|
34
34
|
}
|
35
35
|
|
36
|
+
if (networkResponse.errors != null) {
|
37
|
+
// @ts-expect-error Why are we getting the wrong constructor here?
|
38
|
+
throw new Error('GraphQL network response had errors', {
|
39
|
+
cause: networkResponse,
|
40
|
+
});
|
41
|
+
}
|
42
|
+
|
36
43
|
if (status.kind === 'UndisposedIncomplete') {
|
37
44
|
normalizeData(
|
38
45
|
environment,
|
@@ -51,13 +58,11 @@ export function makeNetworkRequest<T>(
|
|
51
58
|
};
|
52
59
|
retainQuery(environment, retainedQuery);
|
53
60
|
}
|
54
|
-
// TODO return null
|
55
|
-
return networkResponse;
|
56
61
|
});
|
57
62
|
|
58
63
|
const wrapper = wrapPromise(promise);
|
59
64
|
|
60
|
-
const response: ItemCleanupPair<PromiseWrapper<
|
65
|
+
const response: ItemCleanupPair<PromiseWrapper<void, AnyError>> = [
|
61
66
|
wrapper,
|
62
67
|
() => {
|
63
68
|
if (status.kind === 'UndisposedComplete') {
|
package/src/core/read.ts
CHANGED
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
IsographEnvironment,
|
10
10
|
} from './IsographEnvironment';
|
11
11
|
import { makeNetworkRequest } from './makeNetworkRequest';
|
12
|
+
import { PromiseWrapper } from './PromiseWrapper';
|
12
13
|
import { ReaderAst } from './reader';
|
13
14
|
import { Arguments } from './util';
|
14
15
|
|
@@ -19,21 +20,43 @@ export type WithEncounteredRecords<T> = {
|
|
19
20
|
|
20
21
|
export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
21
22
|
environment: IsographEnvironment,
|
22
|
-
|
23
|
+
fragmentReference: FragmentReference<TReadFromStore, unknown>,
|
24
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
23
25
|
): WithEncounteredRecords<TReadFromStore> {
|
24
26
|
const mutableEncounteredRecords = new Set<DataId>();
|
25
27
|
const response = readData(
|
26
28
|
environment,
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
fragmentReference.readerArtifact.readerAst,
|
30
|
+
fragmentReference.root,
|
31
|
+
fragmentReference.variables ?? {},
|
32
|
+
fragmentReference.nestedRefetchQueries,
|
33
|
+
fragmentReference.networkRequest,
|
34
|
+
networkRequestOptions,
|
31
35
|
mutableEncounteredRecords,
|
32
36
|
);
|
33
37
|
if (typeof window !== 'undefined' && window.__LOG) {
|
34
38
|
console.log('done reading', { response });
|
35
39
|
}
|
36
40
|
if (response.kind === 'MissingData') {
|
41
|
+
// There are two cases here that we care about:
|
42
|
+
// 1. the network request is in flight, we haven't suspend on it, and we want
|
43
|
+
// to throw if it errors out. So, networkRequestOptions.suspendIfInFlight === false
|
44
|
+
// and networkRequestOptions.throwOnNetworkError === true.
|
45
|
+
// 2. everything else
|
46
|
+
//
|
47
|
+
// In the first case, we cannot simply throw onNextChange, because if the network
|
48
|
+
// response errors out, we will not update the store, so the onNextChange promise
|
49
|
+
// will not resolve.
|
50
|
+
if (
|
51
|
+
!networkRequestOptions.suspendIfInFlight &&
|
52
|
+
networkRequestOptions.throwOnNetworkError
|
53
|
+
) {
|
54
|
+
// TODO assert that the network request state is not Err
|
55
|
+
throw new Promise((resolve, reject) => {
|
56
|
+
onNextChange(environment).then(resolve);
|
57
|
+
fragmentReference.networkRequest.promise.catch(reject);
|
58
|
+
});
|
59
|
+
}
|
37
60
|
throw onNextChange(environment);
|
38
61
|
} else {
|
39
62
|
return {
|
@@ -61,6 +84,8 @@ function readData<TReadFromStore>(
|
|
61
84
|
root: DataId,
|
62
85
|
variables: Variables,
|
63
86
|
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
87
|
+
networkRequest: PromiseWrapper<void, any>,
|
88
|
+
networkRequestOptions: NetworkRequestReaderOptions,
|
64
89
|
mutableEncounteredRecords: Set<DataId>,
|
65
90
|
): ReadDataResult<TReadFromStore> {
|
66
91
|
mutableEncounteredRecords.add(root);
|
@@ -126,6 +151,8 @@ function readData<TReadFromStore>(
|
|
126
151
|
link.__link,
|
127
152
|
variables,
|
128
153
|
nestedRefetchQueries,
|
154
|
+
networkRequest,
|
155
|
+
networkRequestOptions,
|
129
156
|
mutableEncounteredRecords,
|
130
157
|
);
|
131
158
|
if (result.kind === 'MissingData') {
|
@@ -183,6 +210,8 @@ function readData<TReadFromStore>(
|
|
183
210
|
targetId,
|
184
211
|
variables,
|
185
212
|
nestedRefetchQueries,
|
213
|
+
networkRequest,
|
214
|
+
networkRequestOptions,
|
186
215
|
mutableEncounteredRecords,
|
187
216
|
);
|
188
217
|
if (data.kind === 'MissingData') {
|
@@ -205,6 +234,10 @@ function readData<TReadFromStore>(
|
|
205
234
|
variables,
|
206
235
|
// Refetch fields just read the id, and don't need refetch query artifacts
|
207
236
|
[],
|
237
|
+
// This is probably indicative of the fact that we are doing redundant checks
|
238
|
+
// on the status of this network request...
|
239
|
+
networkRequest,
|
240
|
+
networkRequestOptions,
|
208
241
|
mutableEncounteredRecords,
|
209
242
|
);
|
210
243
|
if (data.kind === 'MissingData') {
|
@@ -256,6 +289,8 @@ function readData<TReadFromStore>(
|
|
256
289
|
root,
|
257
290
|
variables,
|
258
291
|
resolverRefetchQueries,
|
292
|
+
networkRequest,
|
293
|
+
networkRequestOptions,
|
259
294
|
mutableEncounteredRecords,
|
260
295
|
);
|
261
296
|
if (data.kind === 'MissingData') {
|
@@ -276,8 +311,10 @@ function readData<TReadFromStore>(
|
|
276
311
|
readerArtifact: field.readerArtifact,
|
277
312
|
root,
|
278
313
|
variables: generateChildVariableMap(variables, field.arguments),
|
314
|
+
networkRequest,
|
279
315
|
nestedRefetchQueries: resolverRefetchQueries,
|
280
316
|
} as const,
|
317
|
+
networkRequestOptions,
|
281
318
|
);
|
282
319
|
}
|
283
320
|
break;
|
@@ -290,6 +327,8 @@ function readData<TReadFromStore>(
|
|
290
327
|
variables,
|
291
328
|
// Refetch fields just read the id, and don't need refetch query artifacts
|
292
329
|
[],
|
330
|
+
networkRequest,
|
331
|
+
networkRequestOptions,
|
293
332
|
mutableEncounteredRecords,
|
294
333
|
);
|
295
334
|
if (refetchReaderParams.kind === 'MissingData') {
|
@@ -318,7 +357,7 @@ function readData<TReadFromStore>(
|
|
318
357
|
field.queryArguments,
|
319
358
|
variables,
|
320
359
|
);
|
321
|
-
const [
|
360
|
+
const [networkRequest, disposeNetworkRequest] =
|
322
361
|
makeNetworkRequest(
|
323
362
|
environment,
|
324
363
|
field.entrypoint,
|
@@ -331,7 +370,8 @@ function readData<TReadFromStore>(
|
|
331
370
|
root: localVariables.id,
|
332
371
|
variables: localVariables,
|
333
372
|
nestedRefetchQueries: field.entrypoint.nestedRefetchQueries,
|
334
|
-
|
373
|
+
networkRequest,
|
374
|
+
} as FragmentReference<any, any>;
|
335
375
|
return [fragmentReference, disposeNetworkRequest];
|
336
376
|
},
|
337
377
|
];
|
@@ -419,3 +459,8 @@ function writeQueryArgsToVariables(
|
|
419
459
|
}
|
420
460
|
}
|
421
461
|
}
|
462
|
+
|
463
|
+
export type NetworkRequestReaderOptions = {
|
464
|
+
suspendIfInFlight?: boolean;
|
465
|
+
throwOnNetworkError?: boolean;
|
466
|
+
};
|
package/src/index.ts
CHANGED
@@ -4,7 +4,11 @@ export {
|
|
4
4
|
type RetainedQuery,
|
5
5
|
garbageCollectEnvironment,
|
6
6
|
} from './core/garbageCollection';
|
7
|
-
export {
|
7
|
+
export {
|
8
|
+
type PromiseWrapper,
|
9
|
+
readPromise,
|
10
|
+
getPromiseState,
|
11
|
+
} from './core/PromiseWrapper';
|
8
12
|
export { subscribe, normalizeData } from './core/cache';
|
9
13
|
export { makeNetworkRequest } from './core/makeNetworkRequest';
|
10
14
|
export {
|
@@ -21,36 +25,42 @@ export {
|
|
21
25
|
defaultMissingFieldHandler,
|
22
26
|
} from './core/IsographEnvironment';
|
23
27
|
export {
|
24
|
-
EagerReaderArtifact,
|
25
|
-
ComponentReaderArtifact,
|
26
|
-
RefetchReaderArtifact,
|
27
|
-
ReaderAst,
|
28
|
-
ReaderAstNode,
|
29
|
-
ReaderLinkedField,
|
30
|
-
ReaderNonLoadableResolverField,
|
31
|
-
ReaderScalarField,
|
32
|
-
TopLevelReaderArtifact,
|
33
|
-
LoadableField,
|
28
|
+
type EagerReaderArtifact,
|
29
|
+
type ComponentReaderArtifact,
|
30
|
+
type RefetchReaderArtifact,
|
31
|
+
type ReaderAst,
|
32
|
+
type ReaderAstNode,
|
33
|
+
type ReaderLinkedField,
|
34
|
+
type ReaderNonLoadableResolverField,
|
35
|
+
type ReaderScalarField,
|
36
|
+
type TopLevelReaderArtifact,
|
37
|
+
type LoadableField,
|
34
38
|
} from './core/reader';
|
35
39
|
export {
|
36
|
-
NormalizationAst,
|
37
|
-
NormalizationAstNode,
|
38
|
-
NormalizationLinkedField,
|
39
|
-
NormalizationScalarField,
|
40
|
-
IsographEntrypoint,
|
40
|
+
type NormalizationAst,
|
41
|
+
type NormalizationAstNode,
|
42
|
+
type NormalizationLinkedField,
|
43
|
+
type NormalizationScalarField,
|
44
|
+
type IsographEntrypoint,
|
41
45
|
assertIsEntrypoint,
|
42
|
-
RefetchQueryNormalizationArtifact,
|
43
|
-
RefetchQueryNormalizationArtifactWrapper,
|
46
|
+
type RefetchQueryNormalizationArtifact,
|
47
|
+
type RefetchQueryNormalizationArtifactWrapper,
|
48
|
+
type ExtractProps,
|
49
|
+
type ExtractReadFromStore,
|
50
|
+
type ExtractResolverResult,
|
44
51
|
} from './core/entrypoint';
|
45
52
|
export { readButDoNotEvaluate } from './core/read';
|
46
53
|
export {
|
47
|
-
ExtractSecondParam,
|
48
|
-
Argument,
|
49
|
-
ArgumentName,
|
50
|
-
ArgumentValue,
|
51
|
-
Arguments,
|
54
|
+
type ExtractSecondParam,
|
55
|
+
type Argument,
|
56
|
+
type ArgumentName,
|
57
|
+
type ArgumentValue,
|
58
|
+
type Arguments,
|
52
59
|
} from './core/util';
|
53
|
-
export {
|
60
|
+
export {
|
61
|
+
type FragmentReference,
|
62
|
+
type Variables,
|
63
|
+
} from './core/FragmentReference';
|
54
64
|
|
55
65
|
export {
|
56
66
|
IsographEnvironmentProvider,
|
@@ -58,7 +68,7 @@ export {
|
|
58
68
|
type IsographEnvironmentProviderProps,
|
59
69
|
} from './react/IsographEnvironmentProvider';
|
60
70
|
export { useImperativeReference } from './react/useImperativeReference';
|
61
|
-
export {
|
71
|
+
export { FragmentReader } from './react/FragmentReader';
|
62
72
|
export { useResult } from './react/useResult';
|
63
73
|
export { useLazyReference } from './react/useLazyReference';
|
64
74
|
export { useRerenderOnChange } from './react/useRerenderOnChange';
|
@@ -13,10 +13,10 @@ export function useClientSideDefer<TArgs, TResult>(
|
|
13
13
|
args: TArgs,
|
14
14
|
): FragmentReference<any, TResult>;
|
15
15
|
|
16
|
-
// TODO allow the user to pass props somehow
|
17
16
|
export function useClientSideDefer<TArgs, TResult>(
|
18
17
|
loadableField: LoadableField<TArgs, TResult>,
|
19
18
|
args?: TArgs,
|
19
|
+
// TODO this should return { fragmentReference, networkRequestReference }
|
20
20
|
): FragmentReference<any, TResult> {
|
21
21
|
// @ts-expect-error args is missing iff it has the type void
|
22
22
|
const [id, loader] = loadableField(args);
|