relay-runtime 13.0.0-rc.0 → 13.0.1
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/LICENSE +1 -1
- package/README.md +67 -0
- package/handlers/RelayDefaultHandlerProvider.js.flow +1 -1
- package/handlers/connection/ConnectionHandler.js.flow +1 -1
- package/handlers/connection/ConnectionInterface.js.flow +1 -1
- package/handlers/connection/MutationHandlers.js.flow +1 -1
- package/index.js +2 -2
- package/index.js.flow +5 -1
- package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
- package/lib/handlers/connection/ConnectionHandler.js +1 -1
- package/lib/handlers/connection/ConnectionInterface.js +1 -1
- package/lib/handlers/connection/MutationHandlers.js +1 -1
- package/lib/index.js +8 -2
- package/lib/multi-actor-environment/ActorIdentifier.js +1 -1
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
- package/lib/multi-actor-environment/ActorUtils.js +1 -1
- package/lib/multi-actor-environment/MultiActorEnvironment.js +1 -1
- package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +1 -1
- package/lib/multi-actor-environment/index.js +1 -1
- package/lib/mutations/RelayDeclarativeMutationConfig.js +1 -1
- package/lib/mutations/RelayRecordProxy.js +1 -1
- package/lib/mutations/RelayRecordSourceMutator.js +1 -1
- package/lib/mutations/RelayRecordSourceProxy.js +1 -1
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -1
- package/lib/mutations/applyOptimisticMutation.js +1 -1
- package/lib/mutations/commitLocalUpdate.js +1 -1
- package/lib/mutations/commitMutation.js +1 -1
- package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +25 -16
- package/lib/mutations/validateMutation.js +1 -1
- package/lib/network/ConvertToExecuteFunction.js +1 -1
- package/lib/network/RelayNetwork.js +8 -4
- package/lib/network/RelayNetworkTypes.js +1 -1
- package/lib/network/RelayObservable.js +1 -1
- package/lib/network/RelayQueryResponseCache.js +1 -1
- package/lib/network/wrapNetworkWithLogObserver.js +1 -1
- package/lib/query/GraphQLTag.js +1 -1
- package/lib/query/PreloadableQueryRegistry.js +1 -1
- package/lib/query/fetchQuery.js +4 -1
- package/lib/query/fetchQueryInternal.js +1 -1
- package/lib/query/fetchQuery_DEPRECATED.js +1 -1
- package/lib/store/ClientID.js +1 -1
- package/lib/store/DataChecker.js +1 -1
- package/lib/store/OperationExecutor.js +1 -1
- package/lib/store/RelayConcreteVariables.js +14 -4
- package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
- package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
- package/lib/store/RelayModernEnvironment.js +1 -1
- package/lib/store/RelayModernFragmentSpecResolver.js +1 -1
- package/lib/store/RelayModernOperationDescriptor.js +2 -2
- package/lib/store/RelayModernRecord.js +1 -1
- package/lib/store/RelayModernSelector.js +1 -1
- package/lib/store/RelayModernStore.js +1 -1
- package/lib/store/RelayOperationTracker.js +1 -1
- package/lib/store/RelayOptimisticRecordSource.js +1 -1
- package/lib/store/RelayPublishQueue.js +1 -1
- package/lib/store/RelayReader.js +1 -3
- package/lib/store/RelayRecordSource.js +1 -1
- package/lib/store/RelayRecordState.js +1 -1
- package/lib/store/RelayReferenceMarker.js +1 -1
- package/lib/store/RelayResponseNormalizer.js +1 -1
- package/lib/store/RelayStoreReactFlightUtils.js +1 -1
- package/lib/store/RelayStoreSubscriptions.js +1 -1
- package/lib/store/RelayStoreTypes.js +1 -1
- package/lib/store/RelayStoreUtils.js +1 -1
- package/lib/store/ResolverCache.js +1 -1
- package/lib/store/ResolverFragments.js +3 -3
- package/lib/store/StoreInspector.js +1 -1
- package/lib/store/TypeID.js +1 -1
- package/lib/store/ViewerPattern.js +1 -1
- package/lib/store/cloneRelayHandleSourceField.js +1 -1
- package/lib/store/cloneRelayScalarHandleSourceField.js +1 -1
- package/lib/store/createFragmentSpecResolver.js +1 -1
- package/lib/store/createRelayContext.js +1 -1
- package/lib/store/defaultGetDataID.js +1 -1
- package/lib/store/defaultRequiredFieldLogger.js +1 -1
- package/lib/store/hasOverlappingIDs.js +1 -1
- package/lib/store/isRelayModernEnvironment.js +1 -1
- package/lib/store/normalizeRelayPayload.js +1 -1
- package/lib/store/readInlineData.js +1 -1
- package/lib/subscription/requestSubscription.js +1 -3
- package/lib/util/JSResourceTypes.flow.js +1 -1
- package/lib/util/NormalizationNode.js +1 -1
- package/lib/util/ReaderNode.js +1 -1
- package/lib/util/RelayConcreteNode.js +1 -1
- package/lib/util/RelayDefaultHandleKey.js +1 -1
- package/lib/util/RelayError.js +1 -1
- package/lib/util/RelayFeatureFlags.js +1 -2
- package/lib/util/RelayProfiler.js +1 -1
- package/lib/util/RelayReplaySubject.js +1 -1
- package/lib/util/RelayRuntimeTypes.js +1 -1
- package/lib/util/StringInterner.js +1 -1
- package/lib/util/createPayloadFor3DField.js +1 -1
- package/lib/util/deepFreeze.js +1 -1
- package/lib/util/generateID.js +1 -1
- package/lib/util/getFragmentIdentifier.js +1 -1
- package/lib/util/getOperation.js +1 -1
- package/lib/util/getPaginationMetadata.js +1 -1
- package/lib/util/getPaginationVariables.js +1 -1
- package/lib/util/getPendingOperationsForFragment.js +1 -1
- package/lib/util/getRefetchMetadata.js +1 -1
- package/lib/util/getRelayHandleKey.js +1 -1
- package/lib/util/getRequestIdentifier.js +1 -1
- package/lib/util/getValueAtPath.js +1 -1
- package/lib/util/isEmptyObject.js +1 -1
- package/lib/util/isPromise.js +1 -1
- package/lib/util/isScalarAndEqual.js +1 -1
- package/lib/util/recycleNodesInto.js +1 -1
- package/lib/util/registerEnvironmentWithDevTools.js +1 -1
- package/lib/util/reportMissingRequiredFields.js +1 -1
- package/lib/util/resolveImmediate.js +1 -1
- package/lib/util/stableCopy.js +1 -1
- package/lib/util/withDuration.js +1 -1
- package/lib/util/withProvidedVariables.js +29 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +1 -1
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -1
- package/multi-actor-environment/ActorUtils.js.flow +1 -1
- package/multi-actor-environment/MultiActorEnvironment.js.flow +1 -1
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +1 -1
- package/multi-actor-environment/index.js.flow +1 -1
- package/mutations/RelayDeclarativeMutationConfig.js.flow +1 -2
- package/mutations/RelayRecordProxy.js.flow +1 -1
- package/mutations/RelayRecordSourceMutator.js.flow +1 -1
- package/mutations/RelayRecordSourceProxy.js.flow +1 -1
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +1 -1
- package/mutations/applyOptimisticMutation.js.flow +1 -1
- package/mutations/commitLocalUpdate.js.flow +1 -1
- package/mutations/commitMutation.js.flow +1 -1
- package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +55 -48
- package/mutations/validateMutation.js.flow +1 -1
- package/network/ConvertToExecuteFunction.js.flow +1 -1
- package/network/RelayNetwork.js.flow +8 -4
- package/network/RelayNetworkTypes.js.flow +1 -1
- package/network/RelayObservable.js.flow +1 -1
- package/network/RelayQueryResponseCache.js.flow +1 -1
- package/network/wrapNetworkWithLogObserver.js.flow +2 -3
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +2 -2
- package/query/PreloadableQueryRegistry.js.flow +3 -2
- package/query/fetchQuery.js.flow +16 -12
- package/query/fetchQueryInternal.js.flow +1 -1
- package/query/fetchQuery_DEPRECATED.js.flow +1 -1
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +3 -3
- package/store/ClientID.js.flow +1 -1
- package/store/DataChecker.js.flow +1 -1
- package/store/OperationExecutor.js.flow +1 -1
- package/store/RelayConcreteVariables.js.flow +14 -3
- package/store/RelayExperimentalGraphResponseHandler.js.flow +121 -0
- package/store/RelayExperimentalGraphResponseTransform.js.flow +470 -0
- package/store/RelayModernEnvironment.js.flow +1 -1
- package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
- package/store/RelayModernOperationDescriptor.js.flow +6 -2
- package/store/RelayModernRecord.js.flow +1 -1
- package/store/RelayModernSelector.js.flow +1 -1
- package/store/RelayModernStore.js.flow +1 -2
- package/store/RelayOperationTracker.js.flow +1 -1
- package/store/RelayOptimisticRecordSource.js.flow +1 -1
- package/store/RelayPublishQueue.js.flow +1 -1
- package/store/RelayReader.js.flow +1 -8
- package/store/RelayRecordSource.js.flow +1 -1
- package/store/RelayRecordState.js.flow +1 -1
- package/store/RelayReferenceMarker.js.flow +1 -1
- package/store/RelayResponseNormalizer.js.flow +1 -1
- package/store/RelayStoreReactFlightUtils.js.flow +1 -1
- package/store/RelayStoreSubscriptions.js.flow +1 -1
- package/store/RelayStoreTypes.js.flow +1 -1
- package/store/RelayStoreUtils.js.flow +1 -1
- package/store/ResolverCache.js.flow +1 -1
- package/store/ResolverFragments.js.flow +3 -3
- package/store/StoreInspector.js.flow +1 -1
- package/store/TypeID.js.flow +1 -1
- package/store/ViewerPattern.js.flow +1 -1
- package/store/cloneRelayHandleSourceField.js.flow +1 -1
- package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
- package/store/createFragmentSpecResolver.js.flow +1 -1
- package/store/createRelayContext.js.flow +1 -1
- package/store/defaultGetDataID.js.flow +1 -1
- package/store/defaultRequiredFieldLogger.js.flow +1 -1
- package/store/hasOverlappingIDs.js.flow +1 -1
- package/store/isRelayModernEnvironment.js.flow +1 -1
- package/store/normalizeRelayPayload.js.flow +1 -1
- package/store/readInlineData.js.flow +1 -1
- package/subscription/requestSubscription.js.flow +1 -2
- package/util/JSResourceTypes.flow.js.flow +1 -1
- package/util/NormalizationNode.js.flow +1 -1
- package/util/ReaderNode.js.flow +1 -1
- package/util/RelayConcreteNode.js.flow +3 -1
- package/util/RelayDefaultHandleKey.js.flow +1 -1
- package/util/RelayError.js.flow +1 -1
- package/util/RelayFeatureFlags.js.flow +1 -3
- package/util/RelayProfiler.js.flow +1 -1
- package/util/RelayReplaySubject.js.flow +1 -1
- package/util/RelayRuntimeTypes.js.flow +1 -1
- package/util/StringInterner.js.flow +1 -1
- package/util/createPayloadFor3DField.js.flow +1 -1
- package/util/deepFreeze.js.flow +1 -1
- package/util/generateID.js.flow +1 -1
- package/util/getFragmentIdentifier.js.flow +1 -1
- package/util/getOperation.js.flow +1 -1
- package/util/getPaginationMetadata.js.flow +3 -6
- package/util/getPaginationVariables.js.flow +1 -1
- package/util/getPendingOperationsForFragment.js.flow +1 -1
- package/util/getRefetchMetadata.js.flow +3 -6
- package/util/getRelayHandleKey.js.flow +1 -1
- package/util/getRequestIdentifier.js.flow +1 -1
- package/util/getValueAtPath.js.flow +1 -1
- package/util/isEmptyObject.js.flow +1 -1
- package/util/isPromise.js.flow +1 -1
- package/util/isScalarAndEqual.js.flow +1 -1
- package/util/recycleNodesInto.js.flow +1 -1
- package/util/registerEnvironmentWithDevTools.js.flow +1 -1
- package/util/reportMissingRequiredFields.js.flow +1 -1
- package/util/resolveImmediate.js.flow +1 -1
- package/util/stableCopy.js.flow +1 -1
- package/util/withDuration.js.flow +1 -1
- package/util/withProvidedVariables.js.flow +36 -0
package/store/ClientID.js.flow
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
NormalizationOperation,
|
|
19
19
|
} from '../util/NormalizationNode';
|
|
20
20
|
import type {ReaderFragment} from '../util/ReaderNode';
|
|
21
|
+
import type {RequestParameters} from '../util/RelayConcreteNode';
|
|
21
22
|
import type {Variables} from '../util/RelayRuntimeTypes';
|
|
22
23
|
|
|
23
24
|
const {getArgumentValues} = require('./RelayStoreUtils');
|
|
@@ -76,12 +77,15 @@ function getFragmentVariables(
|
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
79
|
* Determines the variables that are in scope for a given operation given values
|
|
79
|
-
* for some/all of its arguments.
|
|
80
|
-
*
|
|
80
|
+
* for some/all of its arguments.
|
|
81
|
+
* - extraneous input variables are filtered from the output
|
|
82
|
+
* - missing variables are set to default values (if given in the
|
|
81
83
|
* operation's definition).
|
|
84
|
+
* - variables with provider modules are added
|
|
82
85
|
*/
|
|
83
86
|
function getOperationVariables(
|
|
84
87
|
operation: NormalizationOperation,
|
|
88
|
+
parameters: RequestParameters,
|
|
85
89
|
variables: Variables,
|
|
86
90
|
): Variables {
|
|
87
91
|
const operationVariables = {};
|
|
@@ -92,6 +96,13 @@ function getOperationVariables(
|
|
|
92
96
|
}
|
|
93
97
|
operationVariables[def.name] = value;
|
|
94
98
|
});
|
|
99
|
+
|
|
100
|
+
const providedVariables = parameters.providedVariables;
|
|
101
|
+
if (providedVariables != null) {
|
|
102
|
+
Object.keys(providedVariables).forEach((varName: string) => {
|
|
103
|
+
operationVariables[varName] = providedVariables[varName].get();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
95
106
|
return operationVariables;
|
|
96
107
|
}
|
|
97
108
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @emails oncall+relay
|
|
8
|
+
* @flow
|
|
9
|
+
* @format
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
DataChunk,
|
|
14
|
+
GraphModeResponse,
|
|
15
|
+
RecordChunk,
|
|
16
|
+
} from './RelayExperimentalGraphResponseTransform';
|
|
17
|
+
import type {MutableRecordSource, Record} from './RelayStoreTypes';
|
|
18
|
+
|
|
19
|
+
const RelayModernRecord = require('./RelayModernRecord');
|
|
20
|
+
const invariant = require('invariant');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Given a stream of GraphMode chunks, populate a MutableRecordSource.
|
|
24
|
+
*/
|
|
25
|
+
export function handleGraphModeResponse(
|
|
26
|
+
recordSource: MutableRecordSource,
|
|
27
|
+
response: GraphModeResponse,
|
|
28
|
+
): MutableRecordSource {
|
|
29
|
+
const handler = new GraphModeHandler(recordSource);
|
|
30
|
+
return handler.populateRecordSource(response);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class GraphModeHandler {
|
|
34
|
+
_recordSource: MutableRecordSource;
|
|
35
|
+
_streamIdToCacheKey: Map<number, string>;
|
|
36
|
+
constructor(recordSource: MutableRecordSource) {
|
|
37
|
+
this._recordSource = recordSource;
|
|
38
|
+
this._streamIdToCacheKey = new Map();
|
|
39
|
+
}
|
|
40
|
+
populateRecordSource(response: GraphModeResponse): MutableRecordSource {
|
|
41
|
+
for (const chunk of response) {
|
|
42
|
+
switch (chunk.$kind) {
|
|
43
|
+
case 'Record':
|
|
44
|
+
this._handleRecordChunk(chunk);
|
|
45
|
+
break;
|
|
46
|
+
case 'Extend': {
|
|
47
|
+
const cacheKey = this._lookupCacheKey(chunk.$streamID);
|
|
48
|
+
const record = this._recordSource.get(cacheKey);
|
|
49
|
+
invariant(
|
|
50
|
+
record != null,
|
|
51
|
+
`Expected to have a record for cache key ${cacheKey}`,
|
|
52
|
+
);
|
|
53
|
+
this._populateRecord(record, chunk);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'Complete':
|
|
57
|
+
this._streamIdToCacheKey.clear();
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
(chunk.$kind: empty);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return this._recordSource;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_handleRecordChunk(chunk: RecordChunk) {
|
|
67
|
+
const cacheKey = chunk.__id;
|
|
68
|
+
let record = this._recordSource.get(cacheKey);
|
|
69
|
+
if (record == null) {
|
|
70
|
+
record = RelayModernRecord.create(cacheKey, chunk.__typename);
|
|
71
|
+
this._recordSource.set(cacheKey, record);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._streamIdToCacheKey.set(chunk.$streamID, cacheKey);
|
|
75
|
+
this._populateRecord(record, chunk);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_populateRecord(parentRecord: Record, chunk: DataChunk) {
|
|
79
|
+
for (const [key, value] of Object.entries(chunk)) {
|
|
80
|
+
switch (key) {
|
|
81
|
+
case '$streamID':
|
|
82
|
+
case '$kind':
|
|
83
|
+
case '__typename':
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
if (
|
|
87
|
+
typeof value !== 'object' ||
|
|
88
|
+
value == null ||
|
|
89
|
+
Array.isArray(value)
|
|
90
|
+
) {
|
|
91
|
+
RelayModernRecord.setValue(parentRecord, key, value);
|
|
92
|
+
} else {
|
|
93
|
+
if (value.hasOwnProperty('__id')) {
|
|
94
|
+
// Singular
|
|
95
|
+
const streamID = ((value.__id: any): number);
|
|
96
|
+
const id = this._lookupCacheKey(streamID);
|
|
97
|
+
RelayModernRecord.setLinkedRecordID(parentRecord, key, id);
|
|
98
|
+
} else if (value.hasOwnProperty('__ids')) {
|
|
99
|
+
// Plural
|
|
100
|
+
const streamIDs = ((value.__ids: any): Array<number | null>);
|
|
101
|
+
const ids = streamIDs.map(sID => {
|
|
102
|
+
return sID == null ? null : this._lookupCacheKey(sID);
|
|
103
|
+
});
|
|
104
|
+
RelayModernRecord.setLinkedRecordIDs(parentRecord, key, ids);
|
|
105
|
+
} else {
|
|
106
|
+
invariant(false, 'Expected object to have either __id or __ids.');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_lookupCacheKey(streamID: number): string {
|
|
114
|
+
const cacheKey = this._streamIdToCacheKey.get(streamID);
|
|
115
|
+
invariant(
|
|
116
|
+
cacheKey != null,
|
|
117
|
+
`Expected to have a cacheKey for $streamID ${streamID}`,
|
|
118
|
+
);
|
|
119
|
+
return cacheKey;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @emails oncall+relay
|
|
8
|
+
* @flow
|
|
9
|
+
* @format
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
|
|
13
|
+
import type {PayloadData} from '../network/RelayNetworkTypes';
|
|
14
|
+
import type {
|
|
15
|
+
NormalizationField,
|
|
16
|
+
NormalizationLinkedField,
|
|
17
|
+
NormalizationNode,
|
|
18
|
+
} from '../util/NormalizationNode';
|
|
19
|
+
import type {DataID, Variables} from '../util/RelayRuntimeTypes';
|
|
20
|
+
import type {NormalizationOptions} from './RelayResponseNormalizer';
|
|
21
|
+
import type {GetDataID} from './RelayResponseNormalizer';
|
|
22
|
+
import type {
|
|
23
|
+
IncrementalDataPlaceholder,
|
|
24
|
+
NormalizationSelector,
|
|
25
|
+
} from './RelayStoreTypes';
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
CLIENT_EXTENSION,
|
|
29
|
+
CONDITION,
|
|
30
|
+
DEFER,
|
|
31
|
+
FRAGMENT_SPREAD,
|
|
32
|
+
INLINE_FRAGMENT,
|
|
33
|
+
LINKED_FIELD,
|
|
34
|
+
SCALAR_FIELD,
|
|
35
|
+
} = require('../util/RelayConcreteNode');
|
|
36
|
+
const {getLocalVariables} = require('./RelayConcreteVariables');
|
|
37
|
+
const {createNormalizationSelector} = require('./RelayModernSelector');
|
|
38
|
+
const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
|
|
39
|
+
const invariant = require('invariant');
|
|
40
|
+
const {generateClientID} = require('relay-runtime');
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This module is an experiment to explore a proposal normalized response format for GraphQL.
|
|
44
|
+
* See the Quip document: Canonical Normalized Response Format (“GraphMode”) Proposal
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* # TODO
|
|
49
|
+
*
|
|
50
|
+
* - [ ] Compute storage keys using method outlined in the proposal
|
|
51
|
+
* - [ ] Plural fields
|
|
52
|
+
* - [ ] Write a utility to populate the store using a GraphMode response.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
export type ScalarField = string | number | null;
|
|
56
|
+
export type LinkedField =
|
|
57
|
+
| {
|
|
58
|
+
__id: number,
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
__ids: Array<number | null>,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ChunkField = ScalarField | Array<ScalarField> | LinkedField;
|
|
65
|
+
|
|
66
|
+
export type ChunkFields = {
|
|
67
|
+
[string]: ChunkField,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type RecordChunk = {
|
|
71
|
+
$kind: 'Record',
|
|
72
|
+
$streamID: number,
|
|
73
|
+
__id: string,
|
|
74
|
+
__typename: string,
|
|
75
|
+
[string]: ChunkField,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type ExtendChunk = {
|
|
79
|
+
$kind: 'Extend',
|
|
80
|
+
$streamID: number,
|
|
81
|
+
[string]: ChunkField,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type CompleteChunk = {
|
|
85
|
+
$kind: 'Complete',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type DataChunk = RecordChunk | ExtendChunk;
|
|
89
|
+
|
|
90
|
+
export type GraphModeChunk = DataChunk | CompleteChunk;
|
|
91
|
+
|
|
92
|
+
export type GraphModeResponse = Iterable<GraphModeChunk>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Converts a JSON response (and Normalization AST) into a stream of GraphMode chunks
|
|
96
|
+
*
|
|
97
|
+
* The stream is modeled as a Generator in order to highlight the streaming
|
|
98
|
+
* nature of the response. Once a chunk is generated, it can be immediately flushed
|
|
99
|
+
* to the client.
|
|
100
|
+
*
|
|
101
|
+
* The response is traversed depth-first, meaning children are emitted before
|
|
102
|
+
* the parent. This allows parent objects to reference children using their
|
|
103
|
+
* `$streamID`.
|
|
104
|
+
*
|
|
105
|
+
* After each object is traversed, a chunk is emitted. The first time an object
|
|
106
|
+
* -- identified by its strong ID -- is encountered we emit a `Record`, and its
|
|
107
|
+
* `$streamID` is recorded. If that same object is encountered again later in
|
|
108
|
+
* the response, an `Extend` chunk is emitted, which includes any previously
|
|
109
|
+
* unsent fields. If no unsent fields are present in the second appearance of
|
|
110
|
+
* the new object, no chunk is emitted.
|
|
111
|
+
*
|
|
112
|
+
* ## State
|
|
113
|
+
*
|
|
114
|
+
* As we traverse we must maintain some state:
|
|
115
|
+
*
|
|
116
|
+
* - The next streamID
|
|
117
|
+
* - A mapping of cache keys to streamIDs
|
|
118
|
+
* - The set of fields which we've sent for each streamID. This allows us to
|
|
119
|
+
* avoid sending fields twice.
|
|
120
|
+
*/
|
|
121
|
+
export function normalizeResponse(
|
|
122
|
+
response: PayloadData,
|
|
123
|
+
selector: NormalizationSelector,
|
|
124
|
+
options: NormalizationOptions,
|
|
125
|
+
): GraphModeResponse {
|
|
126
|
+
const {node, variables, dataID} = selector;
|
|
127
|
+
const normalizer = new GraphModeNormalizer(variables, options);
|
|
128
|
+
return normalizer.normalizeResponse(node, dataID, response);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class GraphModeNormalizer {
|
|
132
|
+
_cacheKeyToStreamID: Map<string, number>;
|
|
133
|
+
_sentFields: Map<string, Set<string>>;
|
|
134
|
+
_getDataId: GetDataID;
|
|
135
|
+
_nextStreamID: number;
|
|
136
|
+
_getDataID: GetDataID;
|
|
137
|
+
_variables: Variables;
|
|
138
|
+
_path: Array<string>;
|
|
139
|
+
_incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
|
|
140
|
+
_actorIdentifier: ?ActorIdentifier;
|
|
141
|
+
constructor(variables: Variables, options: NormalizationOptions) {
|
|
142
|
+
this._actorIdentifier = options.actorIdentifier;
|
|
143
|
+
this._path = options.path ? [...options.path] : [];
|
|
144
|
+
this._getDataID = options.getDataID;
|
|
145
|
+
this._cacheKeyToStreamID = new Map();
|
|
146
|
+
this._sentFields = new Map();
|
|
147
|
+
this._nextStreamID = 0;
|
|
148
|
+
this._variables = variables;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_getStreamID() {
|
|
152
|
+
return this._nextStreamID++;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_getSentFields(cacheKey: string): Set<string> {
|
|
156
|
+
const maybeSent = this._sentFields.get(cacheKey);
|
|
157
|
+
if (maybeSent != null) {
|
|
158
|
+
return maybeSent;
|
|
159
|
+
}
|
|
160
|
+
const sent = new Set();
|
|
161
|
+
this._sentFields.set(cacheKey, sent);
|
|
162
|
+
return sent;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_getObjectType(data: PayloadData): string {
|
|
166
|
+
const typeName = (data: any)[TYPENAME_KEY];
|
|
167
|
+
invariant(
|
|
168
|
+
typeName != null,
|
|
169
|
+
'Expected a typename for record `%s`.',
|
|
170
|
+
JSON.stringify(data, null, 2),
|
|
171
|
+
);
|
|
172
|
+
return typeName;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// TODO: The GraphMode proposal outlines different approachs to derive keys. We
|
|
176
|
+
// can expriment with different approaches here.
|
|
177
|
+
_getStorageKey(selection: NormalizationField) {
|
|
178
|
+
return getStorageKey(selection, this._variables);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_getVariableValue(name: string): mixed {
|
|
182
|
+
invariant(
|
|
183
|
+
this._variables.hasOwnProperty(name),
|
|
184
|
+
'Unexpected undefined variable `%s`.',
|
|
185
|
+
name,
|
|
186
|
+
);
|
|
187
|
+
return this._variables[name];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
*normalizeResponse(
|
|
191
|
+
node: NormalizationNode,
|
|
192
|
+
dataID: DataID,
|
|
193
|
+
data: PayloadData,
|
|
194
|
+
): Generator<GraphModeChunk, void, void> {
|
|
195
|
+
const rootFields: ChunkFields = {};
|
|
196
|
+
yield* this._traverseSelections(node, data, rootFields, dataID, new Set());
|
|
197
|
+
|
|
198
|
+
const $streamID = this._getStreamID();
|
|
199
|
+
yield {
|
|
200
|
+
...rootFields,
|
|
201
|
+
$kind: 'Record',
|
|
202
|
+
$streamID,
|
|
203
|
+
__id: dataID,
|
|
204
|
+
__typename: ROOT_TYPE,
|
|
205
|
+
};
|
|
206
|
+
yield {
|
|
207
|
+
$kind: 'Complete',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
*_flushFields(
|
|
212
|
+
cacheKey: string,
|
|
213
|
+
typename: string,
|
|
214
|
+
fields: ChunkFields,
|
|
215
|
+
): Generator<GraphModeChunk, number, void> {
|
|
216
|
+
const maybeStreamID = this._cacheKeyToStreamID.get(cacheKey);
|
|
217
|
+
const $streamID = maybeStreamID ?? this._getStreamID();
|
|
218
|
+
if (maybeStreamID == null) {
|
|
219
|
+
this._cacheKeyToStreamID.set(cacheKey, $streamID);
|
|
220
|
+
// TODO: We could mutate `fields` rather than constructing a new
|
|
221
|
+
// chunk object, but it's hard to convince Flow that we've
|
|
222
|
+
// constructed a valid Chunk, and perf is not important for this
|
|
223
|
+
// experimental transform
|
|
224
|
+
yield {
|
|
225
|
+
...fields,
|
|
226
|
+
$kind: 'Record',
|
|
227
|
+
__typename: typename,
|
|
228
|
+
__id: cacheKey,
|
|
229
|
+
$streamID,
|
|
230
|
+
};
|
|
231
|
+
} else if (Object.keys(fields).length > 0) {
|
|
232
|
+
yield {...fields, $kind: 'Extend', $streamID};
|
|
233
|
+
}
|
|
234
|
+
return $streamID;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
*_traverseSelections(
|
|
238
|
+
node: NormalizationNode,
|
|
239
|
+
data: PayloadData,
|
|
240
|
+
parentFields: ChunkFields,
|
|
241
|
+
parentID: string,
|
|
242
|
+
sentFields: Set<string>,
|
|
243
|
+
): Generator<GraphModeChunk, void, void> {
|
|
244
|
+
const selections = node.selections;
|
|
245
|
+
|
|
246
|
+
for (const selection of selections) {
|
|
247
|
+
switch (selection.kind) {
|
|
248
|
+
case LINKED_FIELD: {
|
|
249
|
+
const responseKey = selection.alias ?? selection.name;
|
|
250
|
+
const fieldData = ((data[responseKey]: any): PayloadData);
|
|
251
|
+
|
|
252
|
+
const storageKey = this._getStorageKey(selection);
|
|
253
|
+
|
|
254
|
+
this._path.push(responseKey);
|
|
255
|
+
|
|
256
|
+
const fieldValue = yield* this._traverseLinkedField(
|
|
257
|
+
selection.plural,
|
|
258
|
+
fieldData,
|
|
259
|
+
storageKey,
|
|
260
|
+
selection,
|
|
261
|
+
parentID,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
this._path.pop();
|
|
265
|
+
|
|
266
|
+
// TODO: We could also opt to confirm that this matches the previously
|
|
267
|
+
// seen value.
|
|
268
|
+
if (sentFields.has(storageKey)) {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
parentFields[storageKey] = fieldValue;
|
|
273
|
+
sentFields.add(storageKey);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case SCALAR_FIELD: {
|
|
277
|
+
const responseKey = selection.alias ?? selection.name;
|
|
278
|
+
|
|
279
|
+
const storageKey = this._getStorageKey(selection);
|
|
280
|
+
|
|
281
|
+
// TODO: We could also opt to confirm that this matches the previously
|
|
282
|
+
// seen value.
|
|
283
|
+
if (sentFields.has(storageKey)) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
const fieldData = ((data[responseKey]: any): ChunkField);
|
|
287
|
+
|
|
288
|
+
parentFields[storageKey] = fieldData;
|
|
289
|
+
sentFields.add(storageKey);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case INLINE_FRAGMENT: {
|
|
293
|
+
const objType = this._getObjectType(data);
|
|
294
|
+
const {abstractKey} = selection;
|
|
295
|
+
if (abstractKey == null) {
|
|
296
|
+
if (objType !== selection.type) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
} else if (!data.hasOwnProperty(abstractKey)) {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
yield* this._traverseSelections(
|
|
303
|
+
selection,
|
|
304
|
+
data,
|
|
305
|
+
parentFields,
|
|
306
|
+
parentID,
|
|
307
|
+
sentFields,
|
|
308
|
+
);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case FRAGMENT_SPREAD: {
|
|
312
|
+
const prevVariables = this._variables;
|
|
313
|
+
this._variables = getLocalVariables(
|
|
314
|
+
this._variables,
|
|
315
|
+
selection.fragment.argumentDefinitions,
|
|
316
|
+
selection.args,
|
|
317
|
+
);
|
|
318
|
+
yield* this._traverseSelections(
|
|
319
|
+
selection.fragment,
|
|
320
|
+
data,
|
|
321
|
+
parentFields,
|
|
322
|
+
parentID,
|
|
323
|
+
sentFields,
|
|
324
|
+
);
|
|
325
|
+
this._variables = prevVariables;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case CONDITION:
|
|
329
|
+
const conditionValue = Boolean(
|
|
330
|
+
this._getVariableValue(selection.condition),
|
|
331
|
+
);
|
|
332
|
+
if (conditionValue === selection.passingValue) {
|
|
333
|
+
yield* this._traverseSelections(
|
|
334
|
+
selection,
|
|
335
|
+
data,
|
|
336
|
+
parentFields,
|
|
337
|
+
parentID,
|
|
338
|
+
sentFields,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
case DEFER:
|
|
343
|
+
const isDeferred =
|
|
344
|
+
selection.if === null || this._getVariableValue(selection.if);
|
|
345
|
+
if (isDeferred === false) {
|
|
346
|
+
// If defer is disabled there will be no additional response chunk:
|
|
347
|
+
// normalize the data already present.
|
|
348
|
+
yield* this._traverseSelections(
|
|
349
|
+
selection,
|
|
350
|
+
data,
|
|
351
|
+
parentFields,
|
|
352
|
+
parentID,
|
|
353
|
+
sentFields,
|
|
354
|
+
);
|
|
355
|
+
} else {
|
|
356
|
+
// Otherwise data *for this selection* should not be present: enqueue
|
|
357
|
+
// metadata to process the subsequent response chunk.
|
|
358
|
+
this._incrementalPlaceholders.push({
|
|
359
|
+
kind: 'defer',
|
|
360
|
+
data,
|
|
361
|
+
label: selection.label,
|
|
362
|
+
path: [...this._path],
|
|
363
|
+
selector: createNormalizationSelector(
|
|
364
|
+
selection,
|
|
365
|
+
parentID,
|
|
366
|
+
this._variables,
|
|
367
|
+
),
|
|
368
|
+
typeName: this._getObjectType(data),
|
|
369
|
+
actorIdentifier: this._actorIdentifier,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
case CLIENT_EXTENSION:
|
|
374
|
+
// Since we are only expecting to handle server responses, we can skip
|
|
375
|
+
// over client extensions.
|
|
376
|
+
break;
|
|
377
|
+
default:
|
|
378
|
+
throw new Error(`Unexpected selection type: ${selection.kind}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
*_traverseLinkedField(
|
|
384
|
+
plural: boolean,
|
|
385
|
+
fieldData: PayloadData,
|
|
386
|
+
storageKey: string,
|
|
387
|
+
selection: NormalizationLinkedField,
|
|
388
|
+
parentID: string,
|
|
389
|
+
index?: number,
|
|
390
|
+
): Generator<GraphModeChunk, ChunkField, void> {
|
|
391
|
+
if (fieldData == null) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (plural) {
|
|
396
|
+
invariant(
|
|
397
|
+
Array.isArray(fieldData),
|
|
398
|
+
`Expected fieldData to be an array. Got ${JSON.stringify(fieldData)}`,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const fieldValue = [];
|
|
402
|
+
for (const [i, itemData] of fieldData.entries()) {
|
|
403
|
+
this._path.push(String(i));
|
|
404
|
+
const itemValue = yield* this._traverseLinkedField(
|
|
405
|
+
false,
|
|
406
|
+
itemData,
|
|
407
|
+
storageKey,
|
|
408
|
+
selection,
|
|
409
|
+
parentID,
|
|
410
|
+
i,
|
|
411
|
+
);
|
|
412
|
+
this._path.pop();
|
|
413
|
+
fieldValue.push(itemValue);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const ids = fieldValue.map(value => {
|
|
417
|
+
if (value == null) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
invariant(
|
|
421
|
+
typeof value.__id === 'number',
|
|
422
|
+
'Expected objects in a plural linked field to have an __id.',
|
|
423
|
+
);
|
|
424
|
+
return value.__id;
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
return {__ids: ids};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
invariant(
|
|
431
|
+
typeof fieldData === 'object',
|
|
432
|
+
'Expected data for field `%s` to be an object.',
|
|
433
|
+
storageKey,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const objType = selection.concreteType ?? this._getObjectType(fieldData);
|
|
437
|
+
|
|
438
|
+
const nextID =
|
|
439
|
+
this._getDataID(fieldData, objType) ||
|
|
440
|
+
// Note: In RelayResponseNormalizer we try to access a cached
|
|
441
|
+
// version of the key before generating a new one. I'm not clear if
|
|
442
|
+
// that's a performance optimization (which would not be important
|
|
443
|
+
// here) or important for stable ids.
|
|
444
|
+
|
|
445
|
+
// TODO: The proposal does not yet specify how we handle objects
|
|
446
|
+
// without strong ids.
|
|
447
|
+
generateClientID(parentID, storageKey, index);
|
|
448
|
+
|
|
449
|
+
invariant(
|
|
450
|
+
typeof nextID === 'string',
|
|
451
|
+
'Expected id on field `%s` to be a string.',
|
|
452
|
+
storageKey,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const fields: ChunkFields = {};
|
|
456
|
+
|
|
457
|
+
// Yield any decendent record chunks, and mutatively populate direct fields.
|
|
458
|
+
yield* this._traverseSelections(
|
|
459
|
+
selection,
|
|
460
|
+
fieldData,
|
|
461
|
+
fields,
|
|
462
|
+
nextID,
|
|
463
|
+
this._getSentFields(nextID),
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const $streamID = yield* this._flushFields(nextID, objType, fields);
|
|
467
|
+
|
|
468
|
+
return {__id: $streamID};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
@@ -44,7 +44,11 @@ function createOperationDescriptor<TQuery: OperationType>(
|
|
|
44
44
|
dataID?: DataID = ROOT_ID,
|
|
45
45
|
): OperationDescriptor {
|
|
46
46
|
const operation = request.operation;
|
|
47
|
-
const operationVariables = getOperationVariables(
|
|
47
|
+
const operationVariables = getOperationVariables(
|
|
48
|
+
operation,
|
|
49
|
+
request.params,
|
|
50
|
+
variables,
|
|
51
|
+
);
|
|
48
52
|
const requestDescriptor = createRequestDescriptor(
|
|
49
53
|
request,
|
|
50
54
|
operationVariables,
|