relay-runtime 20.1.0 → 21.0.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/experimental.js +1 -1
- package/experimental.js.flow +8 -8
- package/handlers/connection/ConnectionHandler.js.flow +5 -5
- package/handlers/connection/ConnectionInterface.js.flow +1 -1
- package/index.js +1 -1
- package/index.js.flow +125 -62
- package/lib/experimental.js +3 -3
- package/lib/index.js +105 -57
- package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
- package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
- package/lib/mutations/commitMutation.js +8 -8
- package/lib/mutations/validateMutation.js +4 -4
- package/lib/query/GraphQLTag.js +3 -3
- package/lib/query/fetchQuery.js +15 -3
- package/lib/store/DataChecker.js +38 -4
- package/lib/store/NormalizationEngine.js +373 -0
- package/lib/store/OperationExecutor.js +172 -113
- package/lib/store/RelayConcreteVariables.js +1 -1
- package/lib/store/RelayErrorTrie.js +2 -2
- package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
- package/lib/store/RelayModernEnvironment.js +26 -19
- package/lib/store/RelayModernRecord.js +18 -8
- package/lib/store/RelayModernSelector.js +9 -9
- package/lib/store/RelayModernStore.js +152 -43
- package/lib/store/RelayPublishQueue.js +1 -1
- package/lib/store/RelayReader.js +76 -38
- package/lib/store/RelayRecordSource.js +6 -0
- package/lib/store/RelayReferenceMarker.js +2 -1
- package/lib/store/RelayResponseNormalizer.js +88 -55
- package/lib/store/RelayStoreSubscriptions.js +34 -10
- package/lib/store/RelayStoreUtils.js +8 -1
- package/lib/store/ResolverFragments.js +2 -2
- package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
- package/lib/store/observeFragmentExperimental.js +17 -1
- package/lib/store/observeQueryExperimental.js +2 -2
- package/lib/subscription/requestSubscription.js +3 -3
- package/lib/util/RelayError.js +3 -0
- package/lib/util/RelayFeatureFlags.js +6 -2
- package/lib/util/RelayReplaySubject.js +4 -4
- package/lib/util/handlePotentialSnapshotErrors.js +2 -2
- package/lib/util/stableCopy.js +2 -2
- package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
- package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
- package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
- package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
- package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
- package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
- package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
- package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
- package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
- package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
- package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
- package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
- package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
- package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
- package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
- package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
- package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
- package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
- package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
- package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
- package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
- package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +113 -0
- package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
- package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
- package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
- package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
- package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
- package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
- package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
- package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
- package/llm-docs/api-reference/types/Disposable.mdx +4 -0
- package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
- package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
- package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
- package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
- package/llm-docs/community/learning-resources.mdx +64 -0
- package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
- package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
- package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
- package/llm-docs/debugging/relay-devtools.mdx +73 -0
- package/llm-docs/debugging/why-null.mdx +116 -0
- package/llm-docs/editor-support.mdx +55 -0
- package/llm-docs/error-reference/unknown-field.mdx +36 -0
- package/llm-docs/getting-started/babel-plugin.mdx +31 -0
- package/llm-docs/getting-started/compiler-config.mdx +25 -0
- package/llm-docs/getting-started/compiler.mdx +82 -0
- package/llm-docs/getting-started/lint-rules.mdx +87 -0
- package/llm-docs/getting-started/production.mdx +30 -0
- package/llm-docs/getting-started/quick-start.mdx +213 -0
- package/llm-docs/glossary/glossary.mdx +1040 -0
- package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
- package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
- package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
- package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
- package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
- package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
- package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
- package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
- package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
- package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
- package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
- package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
- package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
- package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
- package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
- package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
- package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
- package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
- package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
- package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
- package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
- package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
- package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
- package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
- package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
- package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
- package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
- package/llm-docs/guides/alias-directive.mdx +160 -0
- package/llm-docs/guides/catch-directive.mdx +167 -0
- package/llm-docs/guides/client-schema-extensions.mdx +208 -0
- package/llm-docs/guides/codemods.mdx +66 -0
- package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
- package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
- package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
- package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
- package/llm-docs/guides/document-comparison.mdx +106 -0
- package/llm-docs/guides/graphql-server-specification.mdx +453 -0
- package/llm-docs/guides/network-layer.mdx +69 -0
- package/llm-docs/guides/persisted-queries.mdx +328 -0
- package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
- package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
- package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
- package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
- package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
- package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
- package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
- package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
- package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
- package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
- package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
- package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
- package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
- package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
- package/llm-docs/guides/required-directive.mdx +240 -0
- package/llm-docs/guides/semantic-nullability.mdx +93 -0
- package/llm-docs/guides/testing-relay-components.mdx +642 -0
- package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
- package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
- package/llm-docs/guides/type-emission.mdx +414 -0
- package/llm-docs/home.mdx +32 -0
- package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
- package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
- package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
- package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
- package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
- package/llm-docs/principles-and-architecture/videos.mdx +50 -0
- package/llm-docs/tutorial/arrays-lists.mdx +126 -0
- package/llm-docs/tutorial/fragments-1.mdx +487 -0
- package/llm-docs/tutorial/graphql.mdx +172 -0
- package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
- package/llm-docs/tutorial/intro.mdx +58 -0
- package/llm-docs/tutorial/mutations-updates.mdx +624 -0
- package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
- package/llm-docs/tutorial/queries-1.mdx +267 -0
- package/llm-docs/tutorial/queries-2.mdx +389 -0
- package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +7 -7
- package/multi-actor-environment/ActorUtils.js.flow +1 -1
- package/multi-actor-environment/MultiActorEnvironment.js.flow +12 -8
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +3 -3
- package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
- package/mutations/RelayRecordProxy.js.flow +8 -11
- package/mutations/RelayRecordSourceMutator.js.flow +4 -4
- package/mutations/RelayRecordSourceProxy.js.flow +4 -4
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
- package/mutations/applyOptimisticMutation.js.flow +2 -2
- package/mutations/commitMutation.js.flow +20 -16
- package/mutations/createUpdatableProxy.js.flow +19 -19
- package/mutations/readUpdatableFragment.js.flow +3 -3
- package/mutations/readUpdatableQuery.js.flow +3 -3
- package/mutations/validateMutation.js.flow +7 -7
- package/network/RelayNetworkTypes.js.flow +4 -4
- package/network/RelayObservable.js.flow +16 -14
- package/network/RelayQueryResponseCache.js.flow +3 -3
- package/network/wrapNetworkWithLogObserver.js.flow +1 -1
- package/package.json +2 -1
- package/query/GraphQLTag.js.flow +22 -10
- package/query/fetchQuery.js.flow +23 -10
- package/query/fetchQuery_DEPRECATED.js.flow +1 -1
- package/store/DataChecker.js.flow +43 -9
- package/store/NormalizationEngine.js.flow +779 -0
- package/store/OperationExecutor.js.flow +173 -70
- package/store/RelayConcreteVariables.js.flow +5 -5
- package/store/RelayErrorTrie.js.flow +12 -12
- package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
- package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
- package/store/RelayModernEnvironment.js.flow +41 -26
- package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
- package/store/RelayModernOperationDescriptor.js.flow +1 -1
- package/store/RelayModernRecord.js.flow +46 -22
- package/store/RelayModernSelector.js.flow +21 -21
- package/store/RelayModernStore.js.flow +219 -58
- package/store/RelayOperationTracker.js.flow +2 -2
- package/store/RelayOptimisticRecordSource.js.flow +2 -2
- package/store/RelayPublishQueue.js.flow +21 -12
- package/store/RelayReader.js.flow +130 -58
- package/store/RelayRecordSource.js.flow +10 -0
- package/store/RelayRecordState.js.flow +1 -1
- package/store/RelayReferenceMarker.js.flow +5 -4
- package/store/RelayResponseNormalizer.js.flow +130 -54
- package/store/RelayStoreSubscriptions.js.flow +52 -8
- package/store/RelayStoreTypes.js.flow +153 -64
- package/store/RelayStoreUtils.js.flow +15 -7
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.js.flow +12 -12
- package/store/StoreInspector.js.flow +6 -7
- package/store/cloneRelayHandleSourceField.js.flow +1 -1
- package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
- package/store/createRelayContext.js.flow +1 -1
- package/store/createRelayLoggingContext.js.flow +4 -4
- package/store/defaultGetDataID.js.flow +2 -2
- package/store/isRelayModernEnvironment.js.flow +4 -2
- package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
- package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
- package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
- package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
- package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
- package/store/observeFragmentExperimental.js.flow +49 -20
- package/store/observeQueryExperimental.js.flow +5 -5
- package/store/readInlineData.js.flow +4 -4
- package/store/waitForFragmentExperimental.js.flow +3 -3
- package/subscription/requestSubscription.js.flow +7 -7
- package/util/NormalizationNode.js.flow +34 -32
- package/util/ReaderNode.js.flow +32 -30
- package/util/RelayConcreteNode.js.flow +5 -5
- package/util/RelayError.js.flow +4 -1
- package/util/RelayFeatureFlags.js.flow +21 -1
- package/util/RelayProfiler.js.flow +1 -1
- package/util/RelayReplaySubject.js.flow +3 -3
- package/util/RelayRuntimeTypes.js.flow +11 -11
- package/util/createPayloadFor3DField.js.flow +9 -5
- package/util/deepFreeze.js.flow +2 -2
- package/util/getFragmentIdentifier.js.flow +1 -1
- package/util/getPaginationMetadata.js.flow +1 -1
- package/util/getPaginationVariables.js.flow +1 -1
- package/util/getPendingOperationsForFragment.js.flow +2 -2
- package/util/getRefetchMetadata.js.flow +6 -5
- package/util/getValueAtPath.js.flow +3 -3
- package/util/handlePotentialSnapshotErrors.js.flow +5 -5
- package/util/isEmptyObject.js.flow +1 -1
- package/util/isPromise.js.flow +2 -2
- package/util/isScalarAndEqual.js.flow +1 -1
- package/util/recycleNodesInto.js.flow +2 -2
- package/util/registerEnvironmentWithDevTools.js.flow +1 -1
- package/util/shallowFreeze.js.flow +1 -1
- package/util/stableCopy.js.flow +5 -5
- package/util/withProvidedVariables.js.flow +14 -10
|
@@ -0,0 +1,779 @@
|
|
|
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
|
+
* @flow strict-local
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall relay
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
import type {GraphQLResponseWithData} from '../network/RelayNetworkTypes';
|
|
15
|
+
import type {
|
|
16
|
+
NormalizationLinkedField,
|
|
17
|
+
NormalizationOperation,
|
|
18
|
+
NormalizationRootNode,
|
|
19
|
+
} from '../util/NormalizationNode';
|
|
20
|
+
import type {Variables} from '../util/RelayRuntimeTypes';
|
|
21
|
+
import type {NormalizationOptions} from './RelayResponseNormalizer';
|
|
22
|
+
import type {
|
|
23
|
+
DeferPlaceholder,
|
|
24
|
+
FollowupPayload,
|
|
25
|
+
HandleFieldPayload,
|
|
26
|
+
IncrementalDataPlaceholder,
|
|
27
|
+
ModuleImportPayload,
|
|
28
|
+
MutableRecordSource,
|
|
29
|
+
NormalizationSelector,
|
|
30
|
+
NormalizeResponseFunction,
|
|
31
|
+
OperationLoader,
|
|
32
|
+
Record,
|
|
33
|
+
RecordSourceProxy,
|
|
34
|
+
RelayResponsePayload,
|
|
35
|
+
StreamPlaceholder,
|
|
36
|
+
} from './RelayStoreTypes';
|
|
37
|
+
|
|
38
|
+
const {stableCopy} = require('../util/stableCopy');
|
|
39
|
+
const {generateClientID} = require('./ClientID');
|
|
40
|
+
const defaultGetDataID = require('./defaultGetDataID');
|
|
41
|
+
const RelayModernRecord = require('./RelayModernRecord');
|
|
42
|
+
const {createNormalizationSelector} = require('./RelayModernSelector');
|
|
43
|
+
const {ROOT_ID, ROOT_TYPE, getStorageKey} = require('./RelayStoreUtils');
|
|
44
|
+
|
|
45
|
+
function err(message: string): Error {
|
|
46
|
+
const e = new Error(message);
|
|
47
|
+
void e.stack;
|
|
48
|
+
return e;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stableStringify(value: unknown): string {
|
|
52
|
+
return JSON.stringify(stableCopy(value)) ?? '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type NormalizationResult = Readonly<{
|
|
56
|
+
payloads: ReadonlyArray<RelayResponsePayload>,
|
|
57
|
+
// Each pending module resolves to its own NormalizationResult so the caller
|
|
58
|
+
// can recursively drain nested incremental data (extra payloads + further
|
|
59
|
+
// pending modules) produced by an async @module load. Mirrors the recursion
|
|
60
|
+
// that OperationExecutor performs synchronously via `_processPayloadFollowups`.
|
|
61
|
+
pendingModules: ReadonlyArray<Promise<NormalizationResult>>,
|
|
62
|
+
}>;
|
|
63
|
+
|
|
64
|
+
type Config = Readonly<{
|
|
65
|
+
getDataID?: (fieldValue: {+[string]: unknown}, typeName: string) => unknown,
|
|
66
|
+
normalizeResponse: NormalizeResponseFunction,
|
|
67
|
+
operation: NormalizationOperation,
|
|
68
|
+
operationLoader?: ?OperationLoader,
|
|
69
|
+
treatMissingFieldsAsNull?: boolean,
|
|
70
|
+
variables: Variables,
|
|
71
|
+
}>;
|
|
72
|
+
|
|
73
|
+
type ParentEntry = {
|
|
74
|
+
fieldPayloads: Array<HandleFieldPayload>,
|
|
75
|
+
record: Record,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Per-request normalization engine. Normalizes raw server responses and
|
|
80
|
+
* returns pre-normalized RelayResponsePayload objects (with isPreNormalized:
|
|
81
|
+
* true) that OperationExecutor can commit directly to the store, bypassing
|
|
82
|
+
* the normalization pass it would otherwise perform.
|
|
83
|
+
*
|
|
84
|
+
* Handles initial responses, @defer chunks, @stream items, and @module
|
|
85
|
+
* followups. Tracks per-request state for incremental delivery including
|
|
86
|
+
* placeholder registration, response buffering, and parent record caching.
|
|
87
|
+
*
|
|
88
|
+
* Returns results synchronously via arrays. Async @module loads are returned
|
|
89
|
+
* as Promises — the caller bridges them to the sink.
|
|
90
|
+
*/
|
|
91
|
+
class NormalizationEngine {
|
|
92
|
+
_normalizeResponse: NormalizeResponseFunction;
|
|
93
|
+
_operationLoader: ?OperationLoader;
|
|
94
|
+
_options: NormalizationOptions;
|
|
95
|
+
_rootSelector: NormalizationSelector;
|
|
96
|
+
_useExecTimeResolvers: boolean;
|
|
97
|
+
|
|
98
|
+
// Per-request incremental delivery state
|
|
99
|
+
_placeholders: Map<string, IncrementalDataPlaceholder>;
|
|
100
|
+
_bufferedResponses: Map<string, Array<GraphQLResponseWithData>>;
|
|
101
|
+
_parentRecords: Map<string, ParentEntry>;
|
|
102
|
+
_serverComplete: boolean;
|
|
103
|
+
|
|
104
|
+
constructor(config: Config) {
|
|
105
|
+
this._normalizeResponse = config.normalizeResponse;
|
|
106
|
+
this._operationLoader = config.operationLoader ?? null;
|
|
107
|
+
this._rootSelector = {
|
|
108
|
+
dataID: ROOT_ID,
|
|
109
|
+
node: config.operation,
|
|
110
|
+
variables: config.variables,
|
|
111
|
+
};
|
|
112
|
+
this._options = {
|
|
113
|
+
deferDeduplicatedFields: false,
|
|
114
|
+
getDataID: config.getDataID ?? defaultGetDataID,
|
|
115
|
+
log: null,
|
|
116
|
+
path: [],
|
|
117
|
+
treatMissingFieldsAsNull: config.treatMissingFieldsAsNull ?? false,
|
|
118
|
+
};
|
|
119
|
+
this._useExecTimeResolvers =
|
|
120
|
+
config.operation.use_exec_time_resolvers ??
|
|
121
|
+
config.operation.exec_time_resolvers_enabled_provider?.get() === true ??
|
|
122
|
+
false;
|
|
123
|
+
this._placeholders = new Map();
|
|
124
|
+
this._bufferedResponses = new Map();
|
|
125
|
+
this._parentRecords = new Map();
|
|
126
|
+
this._serverComplete = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Process an initial (non-incremental) server response. Normalizes the
|
|
131
|
+
* response, registers any incremental placeholders, processes @module
|
|
132
|
+
* followups, and flushes buffered responses.
|
|
133
|
+
*
|
|
134
|
+
* Returns an array of payloads (the primary result plus any flushed
|
|
135
|
+
* buffered responses) and an array of Promises for async @module loads.
|
|
136
|
+
*/
|
|
137
|
+
processResponse(response: GraphQLResponseWithData): NormalizationResult {
|
|
138
|
+
const payload = this._normalizeResponse(
|
|
139
|
+
response,
|
|
140
|
+
this._rootSelector,
|
|
141
|
+
ROOT_TYPE,
|
|
142
|
+
this._options,
|
|
143
|
+
this._useExecTimeResolvers,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const extraPayloads: Array<RelayResponsePayload> = [];
|
|
147
|
+
const pendingModules: Array<Promise<NormalizationResult>> = [];
|
|
148
|
+
|
|
149
|
+
// Register incremental placeholders and flush any buffered responses
|
|
150
|
+
if (
|
|
151
|
+
payload.incrementalPlaceholders != null &&
|
|
152
|
+
payload.incrementalPlaceholders.length > 0
|
|
153
|
+
) {
|
|
154
|
+
this._registerPlaceholders(
|
|
155
|
+
payload.incrementalPlaceholders,
|
|
156
|
+
payload.source,
|
|
157
|
+
payload.fieldPayloads,
|
|
158
|
+
extraPayloads,
|
|
159
|
+
pendingModules,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Process module followups
|
|
164
|
+
if (
|
|
165
|
+
payload.followupPayloads != null &&
|
|
166
|
+
payload.followupPayloads.length > 0
|
|
167
|
+
) {
|
|
168
|
+
this._processFollowups(
|
|
169
|
+
payload.followupPayloads,
|
|
170
|
+
extraPayloads,
|
|
171
|
+
pendingModules,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const primaryPayload: RelayResponsePayload = {
|
|
176
|
+
...payload,
|
|
177
|
+
followupPayloads: null,
|
|
178
|
+
incrementalPlaceholders: null,
|
|
179
|
+
isFinal: this._computeIsFinal(payload.isFinal),
|
|
180
|
+
isPreNormalized: true,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
payloads: [primaryPayload, ...extraPayloads],
|
|
185
|
+
pendingModules,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Process an incremental response (@defer chunk or @stream item).
|
|
191
|
+
* Matches the response to a registered placeholder by label + path.
|
|
192
|
+
*
|
|
193
|
+
* Returns null if the response was buffered (no placeholder yet).
|
|
194
|
+
* Otherwise returns payloads array and pending module Promises.
|
|
195
|
+
*/
|
|
196
|
+
processIncrementalResponse(
|
|
197
|
+
response: GraphQLResponseWithData,
|
|
198
|
+
): ?NormalizationResult {
|
|
199
|
+
const label: ?string = response.label;
|
|
200
|
+
const path: ?ReadonlyArray<string | number> = response.path;
|
|
201
|
+
|
|
202
|
+
if (label == null || path == null) {
|
|
203
|
+
throw err(
|
|
204
|
+
'NormalizationEngine: Expected incremental response to have ' +
|
|
205
|
+
'`label` and `path` properties.',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const isDefer = label.indexOf('$defer$') !== -1;
|
|
210
|
+
const pathKey = isDefer
|
|
211
|
+
? path.map(String).join('.')
|
|
212
|
+
: path.slice(0, -2).map(String).join('.');
|
|
213
|
+
const key = makeKey(label, pathKey);
|
|
214
|
+
|
|
215
|
+
const placeholder = this._placeholders.get(key);
|
|
216
|
+
|
|
217
|
+
if (placeholder == null) {
|
|
218
|
+
// Buffer: response arrived before placeholder was registered
|
|
219
|
+
let buffer = this._bufferedResponses.get(key);
|
|
220
|
+
if (buffer == null) {
|
|
221
|
+
buffer = [];
|
|
222
|
+
this._bufferedResponses.set(key, buffer);
|
|
223
|
+
}
|
|
224
|
+
buffer.push(response);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (placeholder.kind === 'defer') {
|
|
229
|
+
return this._processDefer(response, path, placeholder);
|
|
230
|
+
} else {
|
|
231
|
+
return this._processStream(response, path, placeholder);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Mark the server stream as complete for isFinal computation.
|
|
237
|
+
*/
|
|
238
|
+
setServerComplete(): void {
|
|
239
|
+
this._serverComplete = true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Whether the server is complete AND all incremental data has been received.
|
|
244
|
+
*/
|
|
245
|
+
isFinal(): boolean {
|
|
246
|
+
return (
|
|
247
|
+
this._serverComplete &&
|
|
248
|
+
this._bufferedResponses.size === 0 &&
|
|
249
|
+
this._placeholders.size === 0
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Private: @defer handling
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
_processDefer(
|
|
258
|
+
response: GraphQLResponseWithData,
|
|
259
|
+
_path: ReadonlyArray<unknown>,
|
|
260
|
+
placeholder: DeferPlaceholder,
|
|
261
|
+
): NormalizationResult {
|
|
262
|
+
const payload = this._normalizeResponse(
|
|
263
|
+
response,
|
|
264
|
+
placeholder.selector,
|
|
265
|
+
placeholder.typeName,
|
|
266
|
+
{
|
|
267
|
+
...this._options,
|
|
268
|
+
deferDeduplicatedFields: true,
|
|
269
|
+
path: placeholder.path,
|
|
270
|
+
},
|
|
271
|
+
this._useExecTimeResolvers,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const extraPayloads: Array<RelayResponsePayload> = [];
|
|
275
|
+
const pendingModules: Array<Promise<NormalizationResult>> = [];
|
|
276
|
+
|
|
277
|
+
// Register nested placeholders (recursive @defer)
|
|
278
|
+
if (
|
|
279
|
+
payload.incrementalPlaceholders != null &&
|
|
280
|
+
payload.incrementalPlaceholders.length > 0
|
|
281
|
+
) {
|
|
282
|
+
this._registerPlaceholders(
|
|
283
|
+
payload.incrementalPlaceholders,
|
|
284
|
+
payload.source,
|
|
285
|
+
payload.fieldPayloads,
|
|
286
|
+
extraPayloads,
|
|
287
|
+
pendingModules,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Process nested followups (recursive @module)
|
|
292
|
+
if (
|
|
293
|
+
payload.followupPayloads != null &&
|
|
294
|
+
payload.followupPayloads.length > 0
|
|
295
|
+
) {
|
|
296
|
+
this._processFollowups(
|
|
297
|
+
payload.followupPayloads,
|
|
298
|
+
extraPayloads,
|
|
299
|
+
pendingModules,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Replay handle field payloads from parent
|
|
304
|
+
// TODO(skyyao): emit parent fieldPayloads as a separate payload (with empty
|
|
305
|
+
// source) like OperationExecutor._processDeferResponse so handles run
|
|
306
|
+
// against the just-committed updated parent record.
|
|
307
|
+
const parentID = placeholder.selector.dataID;
|
|
308
|
+
const parentEntry = this._parentRecords.get(parentID);
|
|
309
|
+
let fieldPayloads = payload.fieldPayloads;
|
|
310
|
+
if (parentEntry != null && parentEntry.fieldPayloads.length > 0) {
|
|
311
|
+
fieldPayloads = (fieldPayloads ?? []).concat(parentEntry.fieldPayloads);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const primaryPayload: RelayResponsePayload = {
|
|
315
|
+
...payload,
|
|
316
|
+
fieldPayloads,
|
|
317
|
+
followupPayloads: null,
|
|
318
|
+
incrementalPlaceholders: null,
|
|
319
|
+
isFinal: this._computeIsFinal(payload.isFinal),
|
|
320
|
+
isPreNormalized: true,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
payloads: [primaryPayload, ...extraPayloads],
|
|
325
|
+
pendingModules,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Private: @stream handling
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
_processStream(
|
|
334
|
+
response: GraphQLResponseWithData,
|
|
335
|
+
path: ReadonlyArray<unknown>,
|
|
336
|
+
placeholder: StreamPlaceholder,
|
|
337
|
+
): NormalizationResult {
|
|
338
|
+
const {node, parentID, variables} = placeholder;
|
|
339
|
+
|
|
340
|
+
// Find the LinkedField where @stream was applied
|
|
341
|
+
const field = node.selections[0];
|
|
342
|
+
if (
|
|
343
|
+
field == null ||
|
|
344
|
+
field.kind !== 'LinkedField' ||
|
|
345
|
+
field.plural !== true
|
|
346
|
+
) {
|
|
347
|
+
throw err(
|
|
348
|
+
'NormalizationEngine: Expected @stream to be used on a plural field.',
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const {
|
|
353
|
+
fieldPayloads,
|
|
354
|
+
itemID,
|
|
355
|
+
itemIndex,
|
|
356
|
+
prevIDs,
|
|
357
|
+
relayPayload,
|
|
358
|
+
storageKey,
|
|
359
|
+
} = this._normalizeStreamItem(
|
|
360
|
+
response,
|
|
361
|
+
parentID,
|
|
362
|
+
field,
|
|
363
|
+
variables,
|
|
364
|
+
path,
|
|
365
|
+
placeholder.path,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Build store updater for concurrent modification detection
|
|
369
|
+
const storeUpdater = (store: RecordSourceProxy) => {
|
|
370
|
+
const currentParentRecord = store.get(parentID);
|
|
371
|
+
if (currentParentRecord == null) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const currentItems = currentParentRecord.getLinkedRecords(storageKey);
|
|
375
|
+
if (currentItems == null) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
currentItems.length !== prevIDs.length ||
|
|
380
|
+
currentItems.some(
|
|
381
|
+
(item, i) => prevIDs[i] !== (item && item.getDataID()),
|
|
382
|
+
)
|
|
383
|
+
) {
|
|
384
|
+
return; // Concurrent modification -- drop stale stream data
|
|
385
|
+
}
|
|
386
|
+
const nextItems = [...currentItems];
|
|
387
|
+
nextItems[itemIndex] = store.get(itemID);
|
|
388
|
+
currentParentRecord.setLinkedRecords(nextItems, storageKey);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Replay handle field payloads from parent
|
|
392
|
+
// TODO(skyyao): emit parent fieldPayloads as a separate payload (with empty
|
|
393
|
+
// source) like OperationExecutor._processStreamResponse so handles run
|
|
394
|
+
// against the just-committed updated parent record.
|
|
395
|
+
let mergedFieldPayloads = relayPayload.fieldPayloads;
|
|
396
|
+
if (fieldPayloads.length > 0) {
|
|
397
|
+
mergedFieldPayloads = (mergedFieldPayloads ?? []).concat(fieldPayloads);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
payloads: [
|
|
402
|
+
{
|
|
403
|
+
...relayPayload,
|
|
404
|
+
fieldPayloads: mergedFieldPayloads,
|
|
405
|
+
followupPayloads: null,
|
|
406
|
+
incrementalPlaceholders: null,
|
|
407
|
+
isFinal: this._computeIsFinal(relayPayload.isFinal),
|
|
408
|
+
isPreNormalized: true,
|
|
409
|
+
storeUpdater,
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
pendingModules: [],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_normalizeStreamItem(
|
|
417
|
+
response: GraphQLResponseWithData,
|
|
418
|
+
parentID: string,
|
|
419
|
+
field: NormalizationLinkedField,
|
|
420
|
+
variables: Variables,
|
|
421
|
+
path: ReadonlyArray<unknown>,
|
|
422
|
+
normalizationPath: ReadonlyArray<string>,
|
|
423
|
+
): {
|
|
424
|
+
fieldPayloads: Array<HandleFieldPayload>,
|
|
425
|
+
itemID: string,
|
|
426
|
+
itemIndex: number,
|
|
427
|
+
prevIDs: Array<?string>,
|
|
428
|
+
relayPayload: RelayResponsePayload,
|
|
429
|
+
storageKey: string,
|
|
430
|
+
} {
|
|
431
|
+
const {data} = response;
|
|
432
|
+
if (typeof data !== 'object') {
|
|
433
|
+
throw err(
|
|
434
|
+
'NormalizationEngine: Expected the GraphQL @stream payload `data` ' +
|
|
435
|
+
'value to be an object.',
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const responseKey = field.alias ?? field.name;
|
|
439
|
+
const storageKey = getStorageKey(field, variables);
|
|
440
|
+
|
|
441
|
+
const parentEntry = this._parentRecords.get(parentID);
|
|
442
|
+
if (parentEntry == null) {
|
|
443
|
+
throw err(
|
|
444
|
+
'NormalizationEngine: Expected the parent record `' +
|
|
445
|
+
parentID +
|
|
446
|
+
'` for @stream data to exist.',
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const {fieldPayloads, record: parentRecord} = parentEntry;
|
|
450
|
+
|
|
451
|
+
const prevIDs = RelayModernRecord.getLinkedRecordIDs(
|
|
452
|
+
parentRecord,
|
|
453
|
+
storageKey,
|
|
454
|
+
);
|
|
455
|
+
if (prevIDs == null) {
|
|
456
|
+
throw err(
|
|
457
|
+
'NormalizationEngine: Expected record `' +
|
|
458
|
+
parentID +
|
|
459
|
+
'` to have fetched field `' +
|
|
460
|
+
field.name +
|
|
461
|
+
'` with @stream.',
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const finalPathEntry = path[path.length - 1];
|
|
466
|
+
const itemIndex = parseInt(finalPathEntry, 10);
|
|
467
|
+
if (itemIndex !== finalPathEntry || itemIndex < 0) {
|
|
468
|
+
throw err(
|
|
469
|
+
'NormalizationEngine: Expected path for @stream to end in a ' +
|
|
470
|
+
'positive integer index, got `' +
|
|
471
|
+
String(finalPathEntry) +
|
|
472
|
+
'`',
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const typeName = field.concreteType ?? (data as $FlowFixMe).__typename;
|
|
477
|
+
if (typeof typeName !== 'string') {
|
|
478
|
+
throw err(
|
|
479
|
+
'NormalizationEngine: Expected @stream field `' +
|
|
480
|
+
field.name +
|
|
481
|
+
'` to have a __typename.',
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const getDataID = this._options.getDataID;
|
|
486
|
+
const itemID =
|
|
487
|
+
(typeof getDataID === 'function'
|
|
488
|
+
? getDataID(data as $FlowFixMe, typeName)
|
|
489
|
+
: null) ??
|
|
490
|
+
prevIDs?.[itemIndex] ??
|
|
491
|
+
generateClientID(parentID, storageKey, itemIndex);
|
|
492
|
+
if (typeof itemID !== 'string') {
|
|
493
|
+
throw err(
|
|
494
|
+
'NormalizationEngine: Expected id of elements of field `' +
|
|
495
|
+
storageKey +
|
|
496
|
+
'` to be strings.',
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const selector = createNormalizationSelector(field, itemID, variables);
|
|
501
|
+
|
|
502
|
+
const nextParentRecord = RelayModernRecord.clone(parentRecord);
|
|
503
|
+
const nextIDs = [...prevIDs];
|
|
504
|
+
nextIDs[itemIndex] = itemID;
|
|
505
|
+
RelayModernRecord.setLinkedRecordIDs(nextParentRecord, storageKey, nextIDs);
|
|
506
|
+
this._parentRecords.set(parentID, {
|
|
507
|
+
fieldPayloads,
|
|
508
|
+
record: nextParentRecord,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const relayPayload = this._normalizeResponse(
|
|
512
|
+
response,
|
|
513
|
+
selector,
|
|
514
|
+
typeName,
|
|
515
|
+
{
|
|
516
|
+
...this._options,
|
|
517
|
+
path: [...normalizationPath, responseKey, String(itemIndex)],
|
|
518
|
+
},
|
|
519
|
+
this._useExecTimeResolvers,
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
fieldPayloads,
|
|
524
|
+
itemID,
|
|
525
|
+
itemIndex,
|
|
526
|
+
prevIDs,
|
|
527
|
+
relayPayload,
|
|
528
|
+
storageKey,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// Private: placeholder registration
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
_registerPlaceholders(
|
|
537
|
+
placeholders: ReadonlyArray<IncrementalDataPlaceholder>,
|
|
538
|
+
source: MutableRecordSource,
|
|
539
|
+
fieldPayloads: ?ReadonlyArray<HandleFieldPayload>,
|
|
540
|
+
outPayloads: Array<RelayResponsePayload>,
|
|
541
|
+
outPendingModules: Array<Promise<NormalizationResult>>,
|
|
542
|
+
): void {
|
|
543
|
+
for (let i = 0; i < placeholders.length; i++) {
|
|
544
|
+
const placeholder = placeholders[i];
|
|
545
|
+
const {label, path} = placeholder;
|
|
546
|
+
const pathKey = path.map(String).join('.');
|
|
547
|
+
const key = makeKey(label, pathKey);
|
|
548
|
+
this._placeholders.set(key, placeholder);
|
|
549
|
+
|
|
550
|
+
// Cache parent record for @stream concurrent modification detection
|
|
551
|
+
// and for @defer handle field replay
|
|
552
|
+
let parentID: string;
|
|
553
|
+
if (placeholder.kind === 'stream') {
|
|
554
|
+
parentID = placeholder.parentID;
|
|
555
|
+
} else {
|
|
556
|
+
parentID = placeholder.selector.dataID;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const parentRecord = source.get(parentID);
|
|
560
|
+
if (parentRecord == null) {
|
|
561
|
+
throw err(
|
|
562
|
+
'NormalizationEngine: Expected record `' + parentID + '` to exist.',
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const parentPayloads = (fieldPayloads ?? []).filter(
|
|
567
|
+
(fieldPayload: HandleFieldPayload) => {
|
|
568
|
+
const fieldID = generateClientID(
|
|
569
|
+
fieldPayload.dataID,
|
|
570
|
+
fieldPayload.fieldKey,
|
|
571
|
+
);
|
|
572
|
+
return fieldPayload.dataID === parentID || fieldID === parentID;
|
|
573
|
+
},
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const previousEntry = this._parentRecords.get(parentID);
|
|
577
|
+
if (previousEntry != null) {
|
|
578
|
+
const nextRecord = RelayModernRecord.update(
|
|
579
|
+
previousEntry.record,
|
|
580
|
+
parentRecord,
|
|
581
|
+
);
|
|
582
|
+
const handlePayloads = new Map<string, HandleFieldPayload>();
|
|
583
|
+
// TODO(skyyao): use cheaper structural key (dataID + fieldKey + handleKey)
|
|
584
|
+
// to skip serializing args/handleArgs; apply the same optimization to
|
|
585
|
+
// OperationExecutor.js:1317.
|
|
586
|
+
for (let j = 0; j < previousEntry.fieldPayloads.length; j++) {
|
|
587
|
+
const p = previousEntry.fieldPayloads[j];
|
|
588
|
+
handlePayloads.set(stableStringify(p), p);
|
|
589
|
+
}
|
|
590
|
+
for (let j = 0; j < parentPayloads.length; j++) {
|
|
591
|
+
const p = parentPayloads[j];
|
|
592
|
+
handlePayloads.set(stableStringify(p), p);
|
|
593
|
+
}
|
|
594
|
+
this._parentRecords.set(parentID, {
|
|
595
|
+
fieldPayloads: Array.from(handlePayloads.values()),
|
|
596
|
+
record: nextRecord,
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
this._parentRecords.set(parentID, {
|
|
600
|
+
fieldPayloads: parentPayloads,
|
|
601
|
+
record: parentRecord,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Flush any buffered responses that arrived before this placeholder
|
|
606
|
+
const buffered = this._bufferedResponses.get(key);
|
|
607
|
+
if (buffered != null) {
|
|
608
|
+
this._bufferedResponses.delete(key);
|
|
609
|
+
for (let j = 0; j < buffered.length; j++) {
|
|
610
|
+
const resp = buffered[j];
|
|
611
|
+
let result: ?NormalizationResult;
|
|
612
|
+
if (placeholder.kind === 'defer') {
|
|
613
|
+
result = this._processDefer(resp, resp.path ?? [], placeholder);
|
|
614
|
+
} else {
|
|
615
|
+
result = this._processStream(resp, resp.path ?? [], placeholder);
|
|
616
|
+
}
|
|
617
|
+
if (result != null) {
|
|
618
|
+
outPayloads.push(...result.payloads);
|
|
619
|
+
outPendingModules.push(...result.pendingModules);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
// Private: @module followup handling
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
|
|
630
|
+
_processFollowups(
|
|
631
|
+
followups: ReadonlyArray<FollowupPayload>,
|
|
632
|
+
outPayloads: Array<RelayResponsePayload>,
|
|
633
|
+
outPendingModules: Array<Promise<NormalizationResult>>,
|
|
634
|
+
): void {
|
|
635
|
+
for (let i = 0; i < followups.length; i++) {
|
|
636
|
+
const followup = followups[i];
|
|
637
|
+
if (followup.kind === 'ModuleImportPayload') {
|
|
638
|
+
this._processModuleImport(followup, outPayloads, outPendingModules);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
_processModuleImport(
|
|
644
|
+
followup: ModuleImportPayload,
|
|
645
|
+
outPayloads: Array<RelayResponsePayload>,
|
|
646
|
+
outPendingModules: Array<Promise<NormalizationResult>>,
|
|
647
|
+
): void {
|
|
648
|
+
const operationLoader = this._operationLoader;
|
|
649
|
+
if (operationLoader == null) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Try sync first
|
|
654
|
+
const node: ?NormalizationRootNode = operationLoader.get(
|
|
655
|
+
followup.operationReference,
|
|
656
|
+
);
|
|
657
|
+
if (node != null) {
|
|
658
|
+
const result = this._normalizeFollowup(followup, node);
|
|
659
|
+
outPayloads.push(...result.payloads);
|
|
660
|
+
outPendingModules.push(...result.pendingModules);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Async: return a Promise that resolves to the full NormalizationResult,
|
|
665
|
+
// preserving any nested payloads and further pending modules produced by
|
|
666
|
+
// the followup. The caller drains pendingModules recursively (see
|
|
667
|
+
// ReactiveQueryExecutionNode_EXPERIMENTAL.executeWithNetwork) — this
|
|
668
|
+
// mirrors OperationExecutor's recursive `_processPayloadFollowups`.
|
|
669
|
+
const emptyResult: NormalizationResult = {
|
|
670
|
+
payloads: [],
|
|
671
|
+
pendingModules: [],
|
|
672
|
+
};
|
|
673
|
+
outPendingModules.push(
|
|
674
|
+
operationLoader
|
|
675
|
+
.load(followup.operationReference)
|
|
676
|
+
.then((loadedNode: ?NormalizationRootNode) =>
|
|
677
|
+
loadedNode != null
|
|
678
|
+
? this._normalizeFollowup(followup, loadedNode)
|
|
679
|
+
: emptyResult,
|
|
680
|
+
)
|
|
681
|
+
// TODO(skyyao): surface async @module load errors instead of silently
|
|
682
|
+
// swallowing — OperationExecutor terminates execution via sink.error().
|
|
683
|
+
.catch((_error: unknown) => emptyResult),
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
_normalizeFollowup(
|
|
688
|
+
followup: ModuleImportPayload,
|
|
689
|
+
node: NormalizationRootNode,
|
|
690
|
+
): NormalizationResult {
|
|
691
|
+
const operationNode =
|
|
692
|
+
node.kind === 'SplitOperation' ? node : node.operation;
|
|
693
|
+
// TODO(skyyao): for SplitOperation followups, bind variables via
|
|
694
|
+
// getLocalVariables(followup.variables, operationNode.argumentDefinitions,
|
|
695
|
+
// followup.args) like OperationExecutor._normalizeFollowupPayload — current
|
|
696
|
+
// pass-through breaks @module argument binding.
|
|
697
|
+
// TODO(skyyao): propagate is_final extension on the synthetic response when
|
|
698
|
+
// in loading_final state, to enable nested defer/3D processing on
|
|
699
|
+
// non-streaming server mode (see OperationExecutor.js:864-870).
|
|
700
|
+
const selector = createNormalizationSelector(
|
|
701
|
+
operationNode,
|
|
702
|
+
followup.dataID,
|
|
703
|
+
followup.variables,
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
const payload = this._normalizeResponse(
|
|
707
|
+
{data: followup.data} as $FlowFixMe,
|
|
708
|
+
selector,
|
|
709
|
+
followup.typeName,
|
|
710
|
+
{
|
|
711
|
+
...this._options,
|
|
712
|
+
path: followup.path,
|
|
713
|
+
},
|
|
714
|
+
this._useExecTimeResolvers,
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
const extraPayloads: Array<RelayResponsePayload> = [];
|
|
718
|
+
const pendingModules: Array<Promise<NormalizationResult>> = [];
|
|
719
|
+
|
|
720
|
+
// Register nested placeholders
|
|
721
|
+
if (
|
|
722
|
+
payload.incrementalPlaceholders != null &&
|
|
723
|
+
payload.incrementalPlaceholders.length > 0
|
|
724
|
+
) {
|
|
725
|
+
this._registerPlaceholders(
|
|
726
|
+
payload.incrementalPlaceholders,
|
|
727
|
+
payload.source,
|
|
728
|
+
payload.fieldPayloads,
|
|
729
|
+
extraPayloads,
|
|
730
|
+
pendingModules,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Process nested followups
|
|
735
|
+
if (
|
|
736
|
+
payload.followupPayloads != null &&
|
|
737
|
+
payload.followupPayloads.length > 0
|
|
738
|
+
) {
|
|
739
|
+
this._processFollowups(
|
|
740
|
+
payload.followupPayloads,
|
|
741
|
+
extraPayloads,
|
|
742
|
+
pendingModules,
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const primaryPayload: RelayResponsePayload = {
|
|
747
|
+
...payload,
|
|
748
|
+
followupPayloads: null,
|
|
749
|
+
incrementalPlaceholders: null,
|
|
750
|
+
isFinal: this._computeIsFinal(payload.isFinal),
|
|
751
|
+
isPreNormalized: true,
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
payloads: [primaryPayload, ...extraPayloads],
|
|
756
|
+
pendingModules,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ---------------------------------------------------------------------------
|
|
761
|
+
// Private: completion tracking
|
|
762
|
+
// ---------------------------------------------------------------------------
|
|
763
|
+
|
|
764
|
+
_computeIsFinal(serverIsFinal: boolean): boolean {
|
|
765
|
+
return (
|
|
766
|
+
(serverIsFinal || this._serverComplete) &&
|
|
767
|
+
this._bufferedResponses.size === 0
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Build a unique key for matching incremental responses to placeholders.
|
|
774
|
+
*/
|
|
775
|
+
function makeKey(label: string, pathKey: string): string {
|
|
776
|
+
return `${label}::${pathKey}`;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
module.exports = NormalizationEngine;
|