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