@isograph/react 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-compile-libs.log +5 -0
- package/dist/core/FragmentReference.d.ts +5 -5
- package/dist/core/FragmentReference.d.ts.map +1 -1
- package/dist/core/IsographEnvironment.d.ts +14 -9
- package/dist/core/IsographEnvironment.d.ts.map +1 -1
- package/dist/core/PromiseWrapper.d.ts +4 -4
- package/dist/core/PromiseWrapper.d.ts.map +1 -1
- package/dist/core/PromiseWrapper.js +2 -9
- package/dist/core/areEqualWithDeepComparison.d.ts +1 -3
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.js +0 -2
- package/dist/core/brand.d.ts +2 -0
- package/dist/core/brand.d.ts.map +1 -0
- package/dist/core/brand.js +2 -0
- package/dist/core/cache.d.ts +7 -6
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +46 -28
- package/dist/core/check.d.ts +3 -3
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/componentCache.d.ts.map +1 -1
- package/dist/core/componentCache.js +1 -1
- package/dist/core/entrypoint.d.ts +20 -2
- package/dist/core/entrypoint.d.ts.map +1 -1
- package/dist/core/garbageCollection.d.ts +2 -2
- package/dist/core/garbageCollection.d.ts.map +1 -1
- package/dist/core/logging.d.ts +13 -4
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.d.ts +3 -3
- package/dist/core/makeNetworkRequest.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.js +15 -15
- package/dist/core/read.d.ts +8 -8
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +98 -29
- package/dist/core/reader.d.ts +12 -8
- package/dist/core/reader.d.ts.map +1 -1
- package/dist/core/startUpdate.d.ts +7 -4
- package/dist/core/startUpdate.d.ts.map +1 -1
- package/dist/core/startUpdate.js +153 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.js +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.js +1 -1
- package/dist/react/FragmentReader.d.ts +7 -13
- package/dist/react/FragmentReader.d.ts.map +1 -1
- package/dist/react/FragmentReader.js +3 -30
- package/dist/react/FragmentRenderer.d.ts +15 -0
- package/dist/react/FragmentRenderer.d.ts.map +1 -0
- package/dist/react/FragmentRenderer.js +35 -0
- package/dist/react/LoadableFieldReader.d.ts +12 -0
- package/dist/react/LoadableFieldReader.d.ts.map +1 -0
- package/dist/react/LoadableFieldReader.js +10 -0
- package/dist/react/LoadableFieldRenderer.d.ts +13 -0
- package/dist/react/LoadableFieldRenderer.d.ts.map +1 -0
- package/dist/react/LoadableFieldRenderer.js +37 -0
- package/dist/react/useImperativeReference.d.ts.map +1 -1
- package/dist/react/useImperativeReference.js +6 -6
- package/dist/react/useResult.d.ts.map +1 -1
- package/dist/react/useResult.js +1 -1
- package/package.json +4 -5
- package/src/core/FragmentReference.ts +16 -7
- package/src/core/IsographEnvironment.ts +19 -9
- package/src/core/PromiseWrapper.ts +13 -16
- package/src/core/areEqualWithDeepComparison.ts +5 -5
- package/src/core/brand.ts +18 -0
- package/src/core/cache.ts +50 -43
- package/src/core/check.ts +4 -4
- package/src/core/componentCache.ts +5 -1
- package/src/core/entrypoint.ts +32 -5
- package/src/core/garbageCollection.ts +3 -3
- package/src/core/logging.ts +16 -4
- package/src/core/makeNetworkRequest.ts +48 -23
- package/src/core/read.ts +153 -48
- package/src/core/reader.ts +11 -7
- package/src/core/startUpdate.ts +313 -7
- package/src/index.ts +11 -3
- package/src/loadable-hooks/useConnectionSpecPagination.ts +1 -0
- package/src/loadable-hooks/useSkipLimitPagination.ts +1 -0
- package/src/react/FragmentReader.tsx +23 -39
- package/src/react/FragmentRenderer.tsx +46 -0
- package/src/react/LoadableFieldReader.tsx +40 -0
- package/src/react/LoadableFieldRenderer.tsx +41 -0
- package/src/react/useImperativeReference.ts +9 -8
- package/src/react/useResult.ts +1 -0
- package/src/tests/__isograph/Economist/link/output_type.ts +2 -0
- package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +28 -0
- package/src/tests/__isograph/Node/link/output_type.ts +3 -0
- package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +31 -0
- package/src/tests/__isograph/Query/linkedUpdate/normalization_ast.ts +95 -0
- package/src/tests/__isograph/Query/linkedUpdate/output_type.ts +3 -0
- package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +51 -0
- package/src/tests/__isograph/Query/linkedUpdate/query_text.ts +20 -0
- package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +93 -0
- package/src/tests/__isograph/Query/meName/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/meName/query_text.ts +1 -1
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +1 -1
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/nodeField/query_text.ts +1 -1
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +31 -0
- package/src/tests/__isograph/Query/startUpdate/normalization_ast.ts +51 -0
- package/src/tests/__isograph/Query/startUpdate/output_type.ts +3 -0
- package/src/tests/__isograph/Query/startUpdate/param_type.ts +26 -0
- package/src/tests/__isograph/Query/startUpdate/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/startUpdate/query_text.ts +11 -0
- package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +55 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/subquery/query_text.ts +1 -1
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +2 -0
- package/src/tests/__isograph/iso.ts +21 -1
- package/src/tests/__isograph/tsconfig.json +8 -0
- package/src/tests/normalizeData.test.ts +0 -1
- package/src/tests/startUpdate.test.ts +205 -0
- package/.turbo/turbo-compile-typescript.log +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReaderWithRefetchQueries } from '../core/entrypoint';
|
|
2
2
|
import { stableCopy } from './cache';
|
|
3
|
-
import { type
|
|
3
|
+
import { type StoreLink } from './IsographEnvironment';
|
|
4
4
|
import { PromiseWrapper } from './PromiseWrapper';
|
|
5
5
|
import type { StartUpdate } from './reader';
|
|
6
6
|
|
|
@@ -35,11 +35,14 @@ export type ExtractParameters<T> = T extends {
|
|
|
35
35
|
? P
|
|
36
36
|
: Variables;
|
|
37
37
|
|
|
38
|
-
export type ExtractStartUpdate<
|
|
39
|
-
T
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
export type ExtractStartUpdate<T extends UnknownTReadFromStore> =
|
|
39
|
+
T['startUpdate'];
|
|
40
|
+
|
|
41
|
+
export type ExtractUpdatableData<T extends UnknownTReadFromStore> =
|
|
42
|
+
ExtractUpdatableDataFromStartUpdate<ExtractStartUpdate<T>>;
|
|
43
|
+
|
|
44
|
+
export type ExtractUpdatableDataFromStartUpdate<T> =
|
|
45
|
+
T extends StartUpdate<infer D> ? D : never;
|
|
43
46
|
|
|
44
47
|
export type FragmentReference<
|
|
45
48
|
TReadFromStore extends UnknownTReadFromStore,
|
|
@@ -49,7 +52,13 @@ export type FragmentReference<
|
|
|
49
52
|
readonly readerWithRefetchQueries: PromiseWrapper<
|
|
50
53
|
ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
|
|
51
54
|
>;
|
|
52
|
-
readonly root:
|
|
55
|
+
readonly root: StoreLink;
|
|
56
|
+
// TODO we potentially stably copy and stringify variables a lot!
|
|
57
|
+
// So, we should employ interior mutability: pretend that fragent reference
|
|
58
|
+
// is immutable, but actually store something like
|
|
59
|
+
// `Map<Variable, StablyCopiedStringifiedOutput>`
|
|
60
|
+
// and read or update that map when we would otherwise stably copy and
|
|
61
|
+
// stringify.
|
|
53
62
|
readonly variables: ExtractParameters<TReadFromStore>;
|
|
54
63
|
readonly networkRequest: PromiseWrapper<void, any>;
|
|
55
64
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ParentCache } from '@isograph/react-disposable-state';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IsographEntrypoint,
|
|
4
|
+
IsographOperation,
|
|
5
|
+
IsographPersistedOperation,
|
|
6
|
+
} from './entrypoint';
|
|
3
7
|
import {
|
|
4
8
|
FragmentReference,
|
|
5
9
|
Variables,
|
|
@@ -11,6 +15,7 @@ import { LogFunction, WrappedLogFunction } from './logging';
|
|
|
11
15
|
import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
|
12
16
|
import { WithEncounteredRecords } from './read';
|
|
13
17
|
import type { ReaderAst, StartUpdate } from './reader';
|
|
18
|
+
import type { Brand } from './brand';
|
|
14
19
|
|
|
15
20
|
export type ComponentOrFieldName = string;
|
|
16
21
|
export type StringifiedArgs = string;
|
|
@@ -34,7 +39,7 @@ export type FragmentSubscription<TReadFromStore extends UnknownTReadFromStore> =
|
|
|
34
39
|
export type AnyChangesToRecordSubscription = {
|
|
35
40
|
readonly kind: 'AnyChangesToRecord';
|
|
36
41
|
readonly callback: () => void;
|
|
37
|
-
readonly recordLink:
|
|
42
|
+
readonly recordLink: StoreLink;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
export type AnyRecordSubscription = {
|
|
@@ -73,18 +78,23 @@ export type IsographEnvironment = {
|
|
|
73
78
|
|
|
74
79
|
export type MissingFieldHandler = (
|
|
75
80
|
storeRecord: StoreRecord,
|
|
76
|
-
root:
|
|
81
|
+
root: StoreLink,
|
|
77
82
|
fieldName: string,
|
|
78
83
|
arguments_: { [index: string]: any } | null,
|
|
79
84
|
variables: Variables | null,
|
|
80
|
-
) =>
|
|
85
|
+
) => StoreLink | undefined;
|
|
81
86
|
|
|
82
87
|
export type IsographNetworkFunction = (
|
|
83
|
-
|
|
88
|
+
operation: IsographOperation | IsographPersistedOperation,
|
|
84
89
|
variables: Variables,
|
|
85
90
|
) => Promise<any>;
|
|
86
91
|
|
|
87
|
-
export
|
|
92
|
+
export interface Link<T extends TypeName> extends StoreLink {
|
|
93
|
+
readonly __link: Brand<DataId, T>;
|
|
94
|
+
readonly __typename: T;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type StoreLink = {
|
|
88
98
|
readonly __link: DataId;
|
|
89
99
|
readonly __typename: TypeName;
|
|
90
100
|
};
|
|
@@ -99,7 +109,7 @@ export type DataTypeValue =
|
|
|
99
109
|
| string
|
|
100
110
|
| null
|
|
101
111
|
// Singular linked fields:
|
|
102
|
-
|
|
|
112
|
+
| StoreLink
|
|
103
113
|
// Plural scalar and linked fields:
|
|
104
114
|
| DataTypeValue[];
|
|
105
115
|
|
|
@@ -158,7 +168,7 @@ export function createIsographStore(): IsographStore {
|
|
|
158
168
|
};
|
|
159
169
|
}
|
|
160
170
|
|
|
161
|
-
export function assertLink(link: DataTypeValue):
|
|
171
|
+
export function assertLink(link: DataTypeValue): StoreLink | null | undefined {
|
|
162
172
|
if (Array.isArray(link)) {
|
|
163
173
|
throw new Error('Unexpected array');
|
|
164
174
|
}
|
|
@@ -171,7 +181,7 @@ export function assertLink(link: DataTypeValue): Link | null | undefined {
|
|
|
171
181
|
throw new Error('Invalid link');
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
export function getLink(maybeLink: DataTypeValue):
|
|
184
|
+
export function getLink(maybeLink: DataTypeValue): StoreLink | null {
|
|
175
185
|
if (
|
|
176
186
|
maybeLink != null &&
|
|
177
187
|
typeof maybeLink === 'object' &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type AnyError = any;
|
|
2
2
|
|
|
3
|
-
export const NOT_SET
|
|
3
|
+
export const NOT_SET = Symbol('NOT_SET');
|
|
4
4
|
export type NotSet = typeof NOT_SET;
|
|
5
5
|
|
|
6
6
|
export type Result<T, E> =
|
|
@@ -18,31 +18,32 @@ export type Result<T, E> =
|
|
|
18
18
|
* Before the promise is resolved, value becomes non-null.
|
|
19
19
|
*/
|
|
20
20
|
export type PromiseWrapper<T, E = any> = {
|
|
21
|
-
readonly promise: Promise<T
|
|
21
|
+
readonly promise: Promise<Exclude<T, NotSet>>;
|
|
22
22
|
result: Result<Exclude<T, NotSet>, E> | NotSet;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export function wrapPromise<T>(
|
|
25
|
+
export function wrapPromise<T>(
|
|
26
|
+
promise: Promise<Exclude<T, NotSet>>,
|
|
27
|
+
): PromiseWrapper<T, unknown> {
|
|
26
28
|
// TODO confirm suspense works if the promise is already resolved.
|
|
27
29
|
const wrapper: PromiseWrapper<T, any> = { promise, result: NOT_SET };
|
|
28
30
|
promise
|
|
29
31
|
.then((v) => {
|
|
30
|
-
|
|
31
|
-
wrapper.result = { kind: 'Ok', value: v as any };
|
|
32
|
+
wrapper.result = { kind: 'Ok', value: v };
|
|
32
33
|
})
|
|
33
34
|
.catch((e) => {
|
|
34
|
-
|
|
35
|
-
wrapper.result = { kind: 'Err', error: e as any };
|
|
35
|
+
wrapper.result = { kind: 'Err', error: e };
|
|
36
36
|
});
|
|
37
37
|
return wrapper;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function wrapResolvedValue<T>(
|
|
40
|
+
export function wrapResolvedValue<T>(
|
|
41
|
+
value: Exclude<T, NotSet>,
|
|
42
|
+
): PromiseWrapper<T, never> {
|
|
41
43
|
return {
|
|
42
44
|
promise: Promise.resolve(value),
|
|
43
45
|
result: {
|
|
44
46
|
kind: 'Ok',
|
|
45
|
-
// @ts-expect-error one should not call wrapResolvedValue with NOT_SET
|
|
46
47
|
value,
|
|
47
48
|
},
|
|
48
49
|
};
|
|
@@ -51,8 +52,7 @@ export function wrapResolvedValue<T>(value: T): PromiseWrapper<T, never> {
|
|
|
51
52
|
export function readPromise<T, E>(p: PromiseWrapper<T, E>): T {
|
|
52
53
|
const { result } = p;
|
|
53
54
|
if (result !== NOT_SET) {
|
|
54
|
-
|
|
55
|
-
const resultKind = result as Result<T, any>;
|
|
55
|
+
const resultKind = result;
|
|
56
56
|
if (resultKind.kind === 'Ok') {
|
|
57
57
|
return resultKind.value;
|
|
58
58
|
} else {
|
|
@@ -73,11 +73,8 @@ export type PromiseState<T, E> =
|
|
|
73
73
|
export function getPromiseState<T, E>(
|
|
74
74
|
p: PromiseWrapper<T, E>,
|
|
75
75
|
): PromiseState<T, E> {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Safety: p.result is either NOT_SET or an actual value.
|
|
79
|
-
const resultKind = result as Result<T, any>;
|
|
80
|
-
return resultKind;
|
|
76
|
+
if (p.result !== NOT_SET) {
|
|
77
|
+
return p.result;
|
|
81
78
|
}
|
|
82
79
|
return {
|
|
83
80
|
kind: 'Pending',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StoreLink } from './IsographEnvironment';
|
|
2
2
|
import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function mergeUsingReaderAst(
|
|
5
5
|
field: ReaderScalarField | ReaderLinkedField,
|
|
6
6
|
oldItem: unknown,
|
|
7
7
|
newItem: unknown,
|
|
@@ -40,7 +40,7 @@ export function mergeUsingReaderAst(
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
function mergeArraysUsingReaderAst(
|
|
44
44
|
field: ReaderScalarField | ReaderLinkedField,
|
|
45
45
|
oldItems: ReadonlyArray<unknown>,
|
|
46
46
|
newItems: Array<unknown>,
|
|
@@ -101,9 +101,9 @@ export function mergeObjectsUsingReaderAst(
|
|
|
101
101
|
case 'Link': {
|
|
102
102
|
const key = field.alias;
|
|
103
103
|
// @ts-expect-error
|
|
104
|
-
const oldValue:
|
|
104
|
+
const oldValue: StoreLink = oldItemObject[key];
|
|
105
105
|
// @ts-expect-error
|
|
106
|
-
const newValue:
|
|
106
|
+
const newValue: StoreLink = newItemObject[key];
|
|
107
107
|
|
|
108
108
|
if (
|
|
109
109
|
oldValue.__link !== newValue.__link ||
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Suppress the TypeScript compiler warning for this branded-type trick.
|
|
2
|
+
// See discussion: https://github.com/microsoft/TypeScript/issues/202#issuecomment-436900738
|
|
3
|
+
// Pattern: “Brand<BaseType, Brand>” leverages TypeScript conditional and `infer` types to create a pseudo-nominal type,
|
|
4
|
+
// enabling BaseType to be treated as distinct only when tagged with Brand, even though Brand doesn't exist at runtime.
|
|
5
|
+
//
|
|
6
|
+
// Explanation:
|
|
7
|
+
// - Brand extends `symbol | string` acts as a “brand” identifier.
|
|
8
|
+
// - The type uses a conditional check `infer _ extends Brand ? BaseType : never` to strip out BaseType when the branding doesn't match.
|
|
9
|
+
// - This yields a branded type system: `Brand<string, "UserId">` is not accidentally assignable to `Brand<string, "ProductId">`.
|
|
10
|
+
//
|
|
11
|
+
// Usage: Helps enforce semantic distinctions (e.g., distinguishing user IDs from product IDs) even when their runtime values are both strings.
|
|
12
|
+
//
|
|
13
|
+
// Caveat: This is purely a compile-time trick—Brand is erased in emitted JavaScript, so runtime checks must rely on other mechanisms.
|
|
14
|
+
export type Brand<
|
|
15
|
+
BaseType,
|
|
16
|
+
Brand extends symbol | string,
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
> = infer _ extends Brand ? BaseType : never;
|
package/src/core/cache.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
NormalizationInlineFragment,
|
|
9
9
|
NormalizationLinkedField,
|
|
10
10
|
NormalizationScalarField,
|
|
11
|
-
RefetchQueryNormalizationArtifactWrapper,
|
|
12
11
|
type NormalizationAst,
|
|
13
12
|
type NormalizationAstLoader,
|
|
14
13
|
type NormalizationAstNodes,
|
|
@@ -27,15 +26,15 @@ import {
|
|
|
27
26
|
DataTypeValue,
|
|
28
27
|
FragmentSubscription,
|
|
29
28
|
getLink,
|
|
30
|
-
Link,
|
|
31
29
|
ROOT_ID,
|
|
30
|
+
StoreLink,
|
|
32
31
|
StoreRecord,
|
|
33
32
|
type IsographEnvironment,
|
|
34
33
|
type TypeName,
|
|
35
34
|
} from './IsographEnvironment';
|
|
36
35
|
import { logMessage } from './logging';
|
|
37
36
|
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
|
38
|
-
import { wrapResolvedValue } from './PromiseWrapper';
|
|
37
|
+
import { wrapPromise, wrapResolvedValue } from './PromiseWrapper';
|
|
39
38
|
import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
|
|
40
39
|
import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
|
|
41
40
|
import { Argument, ArgumentValue } from './util';
|
|
@@ -93,15 +92,31 @@ export function getOrCreateCacheForArtifact<
|
|
|
93
92
|
variables: ExtractParameters<TReadFromStore>,
|
|
94
93
|
fetchOptions?: FetchOptions<TClientFieldValue>,
|
|
95
94
|
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
let cacheKey = '';
|
|
96
|
+
switch (entrypoint.networkRequestInfo.operation.kind) {
|
|
97
|
+
case 'Operation':
|
|
98
|
+
cacheKey =
|
|
99
|
+
entrypoint.networkRequestInfo.operation.text +
|
|
100
|
+
JSON.stringify(stableCopy(variables));
|
|
101
|
+
break;
|
|
102
|
+
case 'PersistedOperation':
|
|
103
|
+
cacheKey =
|
|
104
|
+
entrypoint.networkRequestInfo.operation.operationId +
|
|
105
|
+
JSON.stringify(stableCopy(variables));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
99
108
|
const factory = () => {
|
|
109
|
+
const readerWithRefetchQueries =
|
|
110
|
+
entrypoint.readerWithRefetchQueries.kind ===
|
|
111
|
+
'ReaderWithRefetchQueriesLoader'
|
|
112
|
+
? wrapPromise(entrypoint.readerWithRefetchQueries.loader())
|
|
113
|
+
: wrapResolvedValue(entrypoint.readerWithRefetchQueries);
|
|
100
114
|
const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
|
|
101
115
|
environment,
|
|
102
116
|
entrypoint,
|
|
103
117
|
variables,
|
|
104
|
-
|
|
118
|
+
readerWithRefetchQueries,
|
|
119
|
+
fetchOptions ?? null,
|
|
105
120
|
);
|
|
106
121
|
|
|
107
122
|
const itemCleanupPair: ItemCleanupPair<
|
|
@@ -109,12 +124,7 @@ export function getOrCreateCacheForArtifact<
|
|
|
109
124
|
> = [
|
|
110
125
|
{
|
|
111
126
|
kind: 'FragmentReference',
|
|
112
|
-
readerWithRefetchQueries
|
|
113
|
-
kind: 'ReaderWithRefetchQueries',
|
|
114
|
-
readerArtifact: entrypoint.readerWithRefetchQueries.readerArtifact,
|
|
115
|
-
nestedRefetchQueries:
|
|
116
|
-
entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
|
117
|
-
}),
|
|
127
|
+
readerWithRefetchQueries,
|
|
118
128
|
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
119
129
|
variables,
|
|
120
130
|
networkRequest: networkRequest,
|
|
@@ -147,8 +157,7 @@ export function normalizeData(
|
|
|
147
157
|
normalizationAst: NormalizationAstNodes,
|
|
148
158
|
networkResponse: NetworkResponseObject,
|
|
149
159
|
variables: Variables,
|
|
150
|
-
|
|
151
|
-
root: Link,
|
|
160
|
+
root: StoreLink,
|
|
152
161
|
): EncounteredIds {
|
|
153
162
|
const encounteredIds: EncounteredIds = new Map();
|
|
154
163
|
|
|
@@ -169,7 +178,6 @@ export function normalizeData(
|
|
|
169
178
|
newStoreRecord,
|
|
170
179
|
root,
|
|
171
180
|
variables,
|
|
172
|
-
nestedRefetchQueries,
|
|
173
181
|
encounteredIds,
|
|
174
182
|
);
|
|
175
183
|
|
|
@@ -197,7 +205,7 @@ export function subscribeToAnyChange(
|
|
|
197
205
|
|
|
198
206
|
export function subscribeToAnyChangesToRecord(
|
|
199
207
|
environment: IsographEnvironment,
|
|
200
|
-
recordLink:
|
|
208
|
+
recordLink: StoreLink,
|
|
201
209
|
callback: () => void,
|
|
202
210
|
): () => void {
|
|
203
211
|
const subscription = {
|
|
@@ -232,7 +240,7 @@ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
|
|
232
240
|
|
|
233
241
|
export function onNextChangeToRecord(
|
|
234
242
|
environment: IsographEnvironment,
|
|
235
|
-
recordLink:
|
|
243
|
+
recordLink: StoreLink,
|
|
236
244
|
): Promise<void> {
|
|
237
245
|
return new Promise((resolve) => {
|
|
238
246
|
const unsubscribe = subscribeToAnyChangesToRecord(
|
|
@@ -251,20 +259,28 @@ export function onNextChangeToRecord(
|
|
|
251
259
|
//
|
|
252
260
|
// That's probably okay to ignore. We don't, however, want to prevent
|
|
253
261
|
// updating other subscriptions if one subscription had missing data.
|
|
254
|
-
function withErrorHandling<T>(
|
|
262
|
+
function withErrorHandling<T>(
|
|
263
|
+
environment: IsographEnvironment,
|
|
264
|
+
f: (t: T) => void,
|
|
265
|
+
): (t: T) => void {
|
|
255
266
|
return (t) => {
|
|
256
267
|
try {
|
|
257
268
|
return f(t);
|
|
258
|
-
} catch {
|
|
269
|
+
} catch (e) {
|
|
270
|
+
logMessage(environment, () => ({
|
|
271
|
+
kind: 'ErrorEncounteredInWithErrorHandling',
|
|
272
|
+
error: e,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
259
275
|
};
|
|
260
276
|
}
|
|
261
277
|
|
|
262
|
-
function callSubscriptions(
|
|
278
|
+
export function callSubscriptions(
|
|
263
279
|
environment: IsographEnvironment,
|
|
264
280
|
recordsEncounteredWhenNormalizing: EncounteredIds,
|
|
265
281
|
) {
|
|
266
282
|
environment.subscriptions.forEach(
|
|
267
|
-
withErrorHandling((subscription) => {
|
|
283
|
+
withErrorHandling(environment, (subscription) => {
|
|
268
284
|
switch (subscription.kind) {
|
|
269
285
|
case 'FragmentSubscription': {
|
|
270
286
|
// TODO if there are multiple components subscribed to the same
|
|
@@ -379,9 +395,8 @@ function normalizeDataIntoRecord(
|
|
|
379
395
|
normalizationAst: NormalizationAstNodes,
|
|
380
396
|
networkResponseParentRecord: NetworkResponseObject,
|
|
381
397
|
targetParentRecord: StoreRecord,
|
|
382
|
-
targetParentRecordLink:
|
|
398
|
+
targetParentRecordLink: StoreLink,
|
|
383
399
|
variables: Variables,
|
|
384
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
385
400
|
mutableEncounteredIds: EncounteredIds,
|
|
386
401
|
): RecordHasBeenUpdated {
|
|
387
402
|
let recordHasBeenUpdated = false;
|
|
@@ -406,7 +421,6 @@ function normalizeDataIntoRecord(
|
|
|
406
421
|
targetParentRecord,
|
|
407
422
|
targetParentRecordLink,
|
|
408
423
|
variables,
|
|
409
|
-
nestedRefetchQueries,
|
|
410
424
|
mutableEncounteredIds,
|
|
411
425
|
);
|
|
412
426
|
recordHasBeenUpdated =
|
|
@@ -421,7 +435,6 @@ function normalizeDataIntoRecord(
|
|
|
421
435
|
targetParentRecord,
|
|
422
436
|
targetParentRecordLink,
|
|
423
437
|
variables,
|
|
424
|
-
nestedRefetchQueries,
|
|
425
438
|
mutableEncounteredIds,
|
|
426
439
|
);
|
|
427
440
|
recordHasBeenUpdated =
|
|
@@ -437,7 +450,7 @@ function normalizeDataIntoRecord(
|
|
|
437
450
|
}
|
|
438
451
|
}
|
|
439
452
|
if (recordHasBeenUpdated) {
|
|
440
|
-
let encounteredRecordsIds =
|
|
453
|
+
let encounteredRecordsIds = insertEmptySetIfMissing(
|
|
441
454
|
mutableEncounteredIds,
|
|
442
455
|
targetParentRecordLink.__typename,
|
|
443
456
|
);
|
|
@@ -447,7 +460,7 @@ function normalizeDataIntoRecord(
|
|
|
447
460
|
return recordHasBeenUpdated;
|
|
448
461
|
}
|
|
449
462
|
|
|
450
|
-
export function
|
|
463
|
+
export function insertEmptySetIfMissing<K, V>(map: Map<K, Set<V>>, key: K) {
|
|
451
464
|
let result = map.get(key);
|
|
452
465
|
if (result === undefined) {
|
|
453
466
|
result = new Set();
|
|
@@ -487,9 +500,8 @@ function normalizeLinkedField(
|
|
|
487
500
|
astNode: NormalizationLinkedField,
|
|
488
501
|
networkResponseParentRecord: NetworkResponseObject,
|
|
489
502
|
targetParentRecord: StoreRecord,
|
|
490
|
-
targetParentRecordLink:
|
|
503
|
+
targetParentRecordLink: StoreLink,
|
|
491
504
|
variables: Variables,
|
|
492
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
493
505
|
mutableEncounteredIds: EncounteredIds,
|
|
494
506
|
): RecordHasBeenUpdated {
|
|
495
507
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
|
@@ -513,7 +525,7 @@ function normalizeLinkedField(
|
|
|
513
525
|
|
|
514
526
|
if (Array.isArray(networkResponseData)) {
|
|
515
527
|
// TODO check astNode.plural or the like
|
|
516
|
-
const dataIds: (
|
|
528
|
+
const dataIds: (StoreLink | null)[] = [];
|
|
517
529
|
for (let i = 0; i < networkResponseData.length; i++) {
|
|
518
530
|
const networkResponseObject = networkResponseData[i];
|
|
519
531
|
if (networkResponseObject == null) {
|
|
@@ -527,7 +539,6 @@ function normalizeLinkedField(
|
|
|
527
539
|
targetParentRecordLink,
|
|
528
540
|
variables,
|
|
529
541
|
i,
|
|
530
|
-
nestedRefetchQueries,
|
|
531
542
|
mutableEncounteredIds,
|
|
532
543
|
);
|
|
533
544
|
|
|
@@ -554,7 +565,6 @@ function normalizeLinkedField(
|
|
|
554
565
|
targetParentRecordLink,
|
|
555
566
|
variables,
|
|
556
567
|
null,
|
|
557
|
-
nestedRefetchQueries,
|
|
558
568
|
mutableEncounteredIds,
|
|
559
569
|
);
|
|
560
570
|
|
|
@@ -586,9 +596,8 @@ function normalizeInlineFragment(
|
|
|
586
596
|
astNode: NormalizationInlineFragment,
|
|
587
597
|
networkResponseParentRecord: NetworkResponseObject,
|
|
588
598
|
targetParentRecord: StoreRecord,
|
|
589
|
-
targetParentRecordLink:
|
|
599
|
+
targetParentRecordLink: StoreLink,
|
|
590
600
|
variables: Variables,
|
|
591
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
592
601
|
mutableEncounteredIds: EncounteredIds,
|
|
593
602
|
): RecordHasBeenUpdated {
|
|
594
603
|
const typeToRefineTo = astNode.type;
|
|
@@ -600,7 +609,6 @@ function normalizeInlineFragment(
|
|
|
600
609
|
targetParentRecord,
|
|
601
610
|
targetParentRecordLink,
|
|
602
611
|
variables,
|
|
603
|
-
nestedRefetchQueries,
|
|
604
612
|
mutableEncounteredIds,
|
|
605
613
|
);
|
|
606
614
|
return hasBeenModified;
|
|
@@ -610,7 +618,7 @@ function normalizeInlineFragment(
|
|
|
610
618
|
|
|
611
619
|
function dataIdsAreTheSame(
|
|
612
620
|
existingValue: DataTypeValue,
|
|
613
|
-
newDataIds: (
|
|
621
|
+
newDataIds: (StoreLink | null)[],
|
|
614
622
|
): boolean {
|
|
615
623
|
if (Array.isArray(existingValue)) {
|
|
616
624
|
if (newDataIds.length !== existingValue.length) {
|
|
@@ -635,10 +643,9 @@ function normalizeNetworkResponseObject(
|
|
|
635
643
|
environment: IsographEnvironment,
|
|
636
644
|
astNode: NormalizationLinkedField,
|
|
637
645
|
networkResponseData: NetworkResponseObject,
|
|
638
|
-
targetParentRecordLink:
|
|
646
|
+
targetParentRecordLink: StoreLink,
|
|
639
647
|
variables: Variables,
|
|
640
648
|
index: number | null,
|
|
641
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
642
649
|
mutableEncounteredIds: EncounteredIds,
|
|
643
650
|
): DataId /* The id of the modified or newly created item */ {
|
|
644
651
|
const newStoreRecordId = getDataIdOfNetworkResponse(
|
|
@@ -668,7 +675,6 @@ function normalizeNetworkResponseObject(
|
|
|
668
675
|
newStoreRecord,
|
|
669
676
|
{ __link: newStoreRecordId, __typename: __typename },
|
|
670
677
|
variables,
|
|
671
|
-
nestedRefetchQueries,
|
|
672
678
|
mutableEncounteredIds,
|
|
673
679
|
);
|
|
674
680
|
|
|
@@ -760,13 +766,14 @@ function getStoreKeyChunkForArgumentValue(
|
|
|
760
766
|
}
|
|
761
767
|
|
|
762
768
|
function getStoreKeyChunkForArgument(argument: Argument, variables: Variables) {
|
|
763
|
-
|
|
769
|
+
const [argumentName, argumentValue] = argument;
|
|
770
|
+
let chunk = getStoreKeyChunkForArgumentValue(argumentValue, variables);
|
|
764
771
|
|
|
765
772
|
if (typeof chunk === 'object') {
|
|
766
773
|
chunk = JSON.stringify(stableCopy(chunk));
|
|
767
774
|
}
|
|
768
775
|
|
|
769
|
-
return `${FIRST_SPLIT_KEY}${
|
|
776
|
+
return `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${chunk}`;
|
|
770
777
|
}
|
|
771
778
|
|
|
772
779
|
function getNetworkResponseKey(
|
|
@@ -831,7 +838,7 @@ export const THIRD_SPLIT_KEY = '__';
|
|
|
831
838
|
|
|
832
839
|
// Returns a key to look up an item in the store
|
|
833
840
|
function getDataIdOfNetworkResponse(
|
|
834
|
-
parentRecordLink:
|
|
841
|
+
parentRecordLink: StoreLink,
|
|
835
842
|
dataToNormalize: NetworkResponseObject,
|
|
836
843
|
astNode: NormalizationLinkedField,
|
|
837
844
|
variables: Variables,
|
package/src/core/check.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Variables } from './FragmentReference';
|
|
|
4
4
|
import {
|
|
5
5
|
getLink,
|
|
6
6
|
IsographEnvironment,
|
|
7
|
-
|
|
7
|
+
StoreLink,
|
|
8
8
|
StoreRecord,
|
|
9
9
|
} from './IsographEnvironment';
|
|
10
10
|
import { logMessage } from './logging';
|
|
@@ -30,14 +30,14 @@ export type CheckResult =
|
|
|
30
30
|
}
|
|
31
31
|
| {
|
|
32
32
|
kind: 'MissingData';
|
|
33
|
-
record:
|
|
33
|
+
record: StoreLink;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
export function check(
|
|
37
37
|
environment: IsographEnvironment,
|
|
38
38
|
normalizationAst: NormalizationAstNodes,
|
|
39
39
|
variables: Variables,
|
|
40
|
-
root:
|
|
40
|
+
root: StoreLink,
|
|
41
41
|
): CheckResult {
|
|
42
42
|
const recordsById = (environment.store[root.__typename] ??= {});
|
|
43
43
|
const newStoreRecord = (recordsById[root.__link] ??= {});
|
|
@@ -61,7 +61,7 @@ function checkFromRecord(
|
|
|
61
61
|
normalizationAst: NormalizationAstNodes,
|
|
62
62
|
variables: Variables,
|
|
63
63
|
record: StoreRecord,
|
|
64
|
-
recordLink:
|
|
64
|
+
recordLink: StoreLink,
|
|
65
65
|
): CheckResult {
|
|
66
66
|
normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
|
|
67
67
|
switch (normalizationAstNode.kind) {
|
|
@@ -16,7 +16,11 @@ export function getOrCreateCachedComponent(
|
|
|
16
16
|
networkRequestOptions: NetworkRequestReaderOptions,
|
|
17
17
|
): React.FC<any> {
|
|
18
18
|
// We create startUpdate outside of component to make it stable
|
|
19
|
-
const startUpdate = createStartUpdate(
|
|
19
|
+
const startUpdate = createStartUpdate(
|
|
20
|
+
environment,
|
|
21
|
+
fragmentReference,
|
|
22
|
+
networkRequestOptions,
|
|
23
|
+
);
|
|
20
24
|
|
|
21
25
|
return (environment.componentCache[
|
|
22
26
|
stableIdForFragmentReference(fragmentReference, componentName)
|
package/src/core/entrypoint.ts
CHANGED
|
@@ -17,11 +17,39 @@ export type ReaderWithRefetchQueries<
|
|
|
17
17
|
readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
export type ReaderWithRefetchQueriesLoader<
|
|
21
|
+
TReadFromStore extends UnknownTReadFromStore,
|
|
22
|
+
TClientFieldValue,
|
|
23
|
+
> = {
|
|
24
|
+
readonly kind: 'ReaderWithRefetchQueriesLoader';
|
|
25
|
+
readonly loader: () => Promise<
|
|
26
|
+
ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
|
|
27
|
+
>;
|
|
28
|
+
};
|
|
29
|
+
|
|
20
30
|
export type NetworkRequestInfo<TNormalizationAst> = {
|
|
21
31
|
readonly kind: 'NetworkRequestInfo';
|
|
22
|
-
readonly
|
|
32
|
+
readonly operation: IsographOperation | IsographPersistedOperation;
|
|
23
33
|
readonly normalizationAst: TNormalizationAst;
|
|
24
34
|
};
|
|
35
|
+
|
|
36
|
+
export type IsographOperation = {
|
|
37
|
+
readonly kind: 'Operation';
|
|
38
|
+
readonly text: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type IsographPersistedOperation = {
|
|
42
|
+
readonly kind: 'PersistedOperation';
|
|
43
|
+
readonly operationId: string;
|
|
44
|
+
readonly extraInfo: IsographPersistedOperationExtraInfo | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type IsographPersistedOperationExtraInfo = {
|
|
48
|
+
readonly kind: 'PersistedOperationExtraInfo';
|
|
49
|
+
readonly operationName: string | null;
|
|
50
|
+
readonly operationKind: 'Query' | 'Mutation' | 'Subscription';
|
|
51
|
+
};
|
|
52
|
+
|
|
25
53
|
// This type should be treated as an opaque type.
|
|
26
54
|
export type IsographEntrypoint<
|
|
27
55
|
TReadFromStore extends UnknownTReadFromStore,
|
|
@@ -30,10 +58,9 @@ export type IsographEntrypoint<
|
|
|
30
58
|
> = {
|
|
31
59
|
readonly kind: 'Entrypoint';
|
|
32
60
|
readonly networkRequestInfo: NetworkRequestInfo<TNormalizationAst>;
|
|
33
|
-
readonly readerWithRefetchQueries:
|
|
34
|
-
TReadFromStore,
|
|
35
|
-
TClientFieldValue
|
|
36
|
-
>;
|
|
61
|
+
readonly readerWithRefetchQueries:
|
|
62
|
+
| ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
|
|
63
|
+
| ReaderWithRefetchQueriesLoader<TReadFromStore, TClientFieldValue>;
|
|
37
64
|
readonly concreteType: TypeName;
|
|
38
65
|
};
|
|
39
66
|
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
IsographEnvironment,
|
|
8
8
|
IsographStore,
|
|
9
9
|
StoreRecord,
|
|
10
|
-
type
|
|
10
|
+
type StoreLink,
|
|
11
11
|
type TypeName,
|
|
12
12
|
} from './IsographEnvironment';
|
|
13
13
|
|
|
14
14
|
export type RetainedQuery = {
|
|
15
15
|
readonly normalizationAst: NormalizationAstNodes;
|
|
16
16
|
readonly variables: {};
|
|
17
|
-
readonly root:
|
|
17
|
+
readonly root: StoreLink;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export type DidUnretainSomeQuery = boolean;
|
|
@@ -117,7 +117,7 @@ function recordReachableIdsFromRecord(
|
|
|
117
117
|
const linkKey = getParentRecordKey(selection, variables ?? {});
|
|
118
118
|
const linkedFieldOrFields = currentRecord[linkKey];
|
|
119
119
|
|
|
120
|
-
const links:
|
|
120
|
+
const links: StoreLink[] = [];
|
|
121
121
|
if (Array.isArray(linkedFieldOrFields)) {
|
|
122
122
|
for (const maybeLink of linkedFieldOrFields) {
|
|
123
123
|
const link = assertLink(maybeLink);
|