relay-runtime 20.1.1 → 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 +44 -20
- 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 +129 -57
- 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 +125 -57
- 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,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: document-comparison
|
|
3
|
+
title: Document Comparison
|
|
4
|
+
slug: /guides/document-comparison/
|
|
5
|
+
description: Relay guide to document comparison
|
|
6
|
+
keywords:
|
|
7
|
+
- compare
|
|
8
|
+
- diff
|
|
9
|
+
- ir
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
import DocsRating from '@site/src/core/DocsRating';
|
|
13
|
+
|
|
14
|
+
:::note
|
|
15
|
+
This feature is **experimental** and the API may change in future versions.
|
|
16
|
+
:::
|
|
17
|
+
|
|
18
|
+
The Relay compiler includes an experimental feature for comparing the intermediate representations (IR) of two GraphQL documents. This feature determines whether one document is a subset of another and reports any missing selections.
|
|
19
|
+
|
|
20
|
+
This feature is particularly useful for comparing documents generated by LLMs where exact match of LLM generated documents and static expected documents is neither attainable or desirable, due to the nature of LLMs. In those cases, you could:
|
|
21
|
+
* Specify (via `--left`) an expected document with minimally required selections, and determine if the LLM generated document (via `--right`) contains all required selections, via the subset test that the command performs.
|
|
22
|
+
* Optionally, pass exepected document via `--right` and LLM generated document via `--left` to determine whether LLM generated documents contains extra selections than expected, aka. overfetching.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
> relay experimental-compare-document-ir --help
|
|
28
|
+
EXPERIMENTAL! Compare intermediate representations (IR) of documents passed via left and right, outputs selections that exists in left but not in right.
|
|
29
|
+
|
|
30
|
+
Usage: relay experimental-compare-document-ir [OPTIONS] --left <LEFT> --right <RIGHT>
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--left <LEFT> Document in string, must contain exactly 1 operation
|
|
34
|
+
--right <RIGHT> Document in string, must contain exactly 1 operation
|
|
35
|
+
--schemaPaths <SCHEMA_PATHS>... Path(s) to the full schema file(s) to convert documents to intermediate representation (IR) for comparison
|
|
36
|
+
-h, --help Print help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
Detailed information about missing selection(s) from the right document, including line number and path.
|
|
42
|
+
|
|
43
|
+
## How It Works
|
|
44
|
+
|
|
45
|
+
The 2 documents (left and right) are first transformed to their Intermediate Representation (IR), then to **Normalized Tree** form, for comparison. Each node of the tree corresponds to a selection in the original document, and is checked for its existence in the other tree.
|
|
46
|
+
|
|
47
|
+
### Normalized Trees
|
|
48
|
+
|
|
49
|
+
Each IR is transformed into a normalized tree structure that:
|
|
50
|
+
- **Inlines fragments**: Fragment spreads and inline fragments are inlined, while type conditions specified by the fragment are encoded in the node.
|
|
51
|
+
- **De-aliases fields**: Converts aliased fields to their original names (e.g., `my_id: id` → `id`)
|
|
52
|
+
- **Deduplicates fields**: Removes duplicate identical fields
|
|
53
|
+
- **Tracks type conditions**: Records which concrete object types can reach each scalar field at the leaf level
|
|
54
|
+
|
|
55
|
+
The normalized tree mirrors the JSON response structure of the query.
|
|
56
|
+
|
|
57
|
+
#### Tree Node Identity
|
|
58
|
+
Consists of
|
|
59
|
+
- Path from operation root to the corresponding selection
|
|
60
|
+
- Field arguments of the corresponding selection (if applicable)
|
|
61
|
+
- Type condition: the type that the enclosing object must be, for the corresponding selection to be included in the response. E.g. with `... on A { id }`, the type condition of selection `id` is `A` if `A` is a concrete type, or a set of types that are `A` if `A` is an abstract type.
|
|
62
|
+
|
|
63
|
+
#### Tree Node Existence
|
|
64
|
+
|
|
65
|
+
A selection exists in a tree if:
|
|
66
|
+
- An identical selection is found
|
|
67
|
+
- A superset of the selection is found. A selection is a superset of another if:
|
|
68
|
+
- It allows the same selection and more, with query variables. E.g. `user(id: $id)` is a superset of `user(id: 100)` because variable `$id` can be set to arbitrary values, including `100`, at runtime.
|
|
69
|
+
- Its type condition is a superset of another.
|
|
70
|
+
|
|
71
|
+
## Example
|
|
72
|
+
|
|
73
|
+
**Left document:**
|
|
74
|
+
```graphql
|
|
75
|
+
query A {
|
|
76
|
+
viewer {
|
|
77
|
+
timezone_estimate {
|
|
78
|
+
display_name
|
|
79
|
+
gmt_offset
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Right document:**
|
|
86
|
+
```graphql
|
|
87
|
+
query A {
|
|
88
|
+
viewer {
|
|
89
|
+
timezone_estimate {
|
|
90
|
+
display_name
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Output**
|
|
97
|
+
```text
|
|
98
|
+
[INFO] Found 1 missing selection(s):
|
|
99
|
+
4 │ display_name
|
|
100
|
+
5 │ gmt_offset
|
|
101
|
+
│ ^^^^^^^^^^
|
|
102
|
+
6 │ }
|
|
103
|
+
* query -> viewer -> timezone_estimate -> gmt_offset
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Explore [tests](https://github.com/facebook/relay/blob/main/compiler/crates/graphql-ir-diff/tests/ir_diff_test.rs) for more examples.
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: graphql-server-specification
|
|
3
|
+
title: GraphQL Server Specification
|
|
4
|
+
slug: /guides/graphql-server-specification/
|
|
5
|
+
description: Relay GraphQL server specification guide
|
|
6
|
+
keywords:
|
|
7
|
+
- GraphQL
|
|
8
|
+
- server
|
|
9
|
+
- specification
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
import DocsRating from '@site/src/core/DocsRating';
|
|
13
|
+
|
|
14
|
+
The goal of this document is to specify the assumptions that Relay makes about a GraphQL server and demonstrate them through an example GraphQL schema.
|
|
15
|
+
|
|
16
|
+
Table of Contents:
|
|
17
|
+
|
|
18
|
+
- [Preface](#preface)
|
|
19
|
+
- [Schema](#schema)
|
|
20
|
+
- [Object Identification](#object-identification)
|
|
21
|
+
- [Connections](#connections)
|
|
22
|
+
- [Further Reading](#further-reading)
|
|
23
|
+
|
|
24
|
+
## Preface
|
|
25
|
+
|
|
26
|
+
The two core assumptions that Relay makes about a GraphQL server are that it provides:
|
|
27
|
+
|
|
28
|
+
1. A mechanism for refetching an object.
|
|
29
|
+
2. A description of how to page through connections.
|
|
30
|
+
|
|
31
|
+
This example demonstrates all two of these assumptions. This example is not comprehensive, but it is designed to quickly introduce these core assumptions, to provide some context before diving into the more detailed specification of the library.
|
|
32
|
+
|
|
33
|
+
The premise of the example is that we want to use GraphQL to query for information about ships and factions in the original Star Wars trilogy.
|
|
34
|
+
|
|
35
|
+
It is assumed that the reader is already familiar with [GraphQL](http://graphql.org/); if not, the README for [GraphQL.js](https://github.com/graphql/graphql-js) is a good place to start.
|
|
36
|
+
|
|
37
|
+
It is also assumed that the reader is already familiar with [Star Wars](https://en.wikipedia.org/wiki/Star_Wars); if not, the 1977 version of Star Wars is a good place to start, though the 1997 Special Edition will serve for the purposes of this document.
|
|
38
|
+
|
|
39
|
+
## Schema
|
|
40
|
+
|
|
41
|
+
The schema described below will be used to demonstrate the functionality that a GraphQL server used by Relay should implement. The two core types are a faction and a ship in the Star Wars universe, where a faction has many ships associated with it.
|
|
42
|
+
|
|
43
|
+
```graphql
|
|
44
|
+
interface Node {
|
|
45
|
+
id: ID!
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Faction implements Node {
|
|
49
|
+
id: ID!
|
|
50
|
+
name: String
|
|
51
|
+
ships: ShipConnection
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type Ship implements Node {
|
|
55
|
+
id: ID!
|
|
56
|
+
name: String
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ShipConnection {
|
|
60
|
+
edges: [ShipEdge]
|
|
61
|
+
pageInfo: PageInfo!
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type ShipEdge {
|
|
65
|
+
cursor: String!
|
|
66
|
+
node: Ship
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type PageInfo {
|
|
70
|
+
hasNextPage: Boolean!
|
|
71
|
+
hasPreviousPage: Boolean!
|
|
72
|
+
startCursor: String
|
|
73
|
+
endCursor: String
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type Query {
|
|
77
|
+
rebels: Faction
|
|
78
|
+
empire: Faction
|
|
79
|
+
node(id: ID!): Node
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Object Identification
|
|
84
|
+
|
|
85
|
+
Both `Faction` and `Ship` have identifiers that we can use to refetch them. We expose this capability to Relay through the `Node` interface and the `node` field on the root query type.
|
|
86
|
+
|
|
87
|
+
The `Node` interface contains a single field, `id`, which is an `ID!`. The `node` root field takes a single argument, an `ID!`, and returns a `Node`. These two work in concert to allow refetching; if we pass the `id` returned in that field to the `node` field, we get the object back.
|
|
88
|
+
|
|
89
|
+
Let's see this in action, and query for the ID of the rebels:
|
|
90
|
+
|
|
91
|
+
```graphql
|
|
92
|
+
query RebelsQuery {
|
|
93
|
+
rebels {
|
|
94
|
+
id
|
|
95
|
+
name
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
returns
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"rebels": {
|
|
105
|
+
"id": "RmFjdGlvbjox",
|
|
106
|
+
"name": "Alliance to Restore the Republic"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
So now we know the ID of the Rebels in our system. We can now refetch them:
|
|
112
|
+
|
|
113
|
+
```graphql
|
|
114
|
+
query RebelsRefetchQuery {
|
|
115
|
+
node(id: "RmFjdGlvbjox") {
|
|
116
|
+
id
|
|
117
|
+
... on Faction {
|
|
118
|
+
name
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
returns
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"node": {
|
|
129
|
+
"id": "RmFjdGlvbjox",
|
|
130
|
+
"name": "Alliance to Restore the Republic"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If we do the same thing with the Empire, we'll find that it returns a different ID, and we can refetch it as well:
|
|
136
|
+
|
|
137
|
+
```graphql
|
|
138
|
+
query EmpireQuery {
|
|
139
|
+
empire {
|
|
140
|
+
id
|
|
141
|
+
name
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
yields
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"empire": {
|
|
151
|
+
"id": "RmFjdGlvbjoy",
|
|
152
|
+
"name": "Galactic Empire"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
and
|
|
158
|
+
|
|
159
|
+
```graphql
|
|
160
|
+
query EmpireRefetchQuery {
|
|
161
|
+
node(id: "RmFjdGlvbjoy") {
|
|
162
|
+
id
|
|
163
|
+
... on Faction {
|
|
164
|
+
name
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
yields
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"node": {
|
|
175
|
+
"id": "RmFjdGlvbjoy",
|
|
176
|
+
"name": "Galactic Empire"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The `Node` interface and `node` field assume globally unique IDs for this refetching. A system without globally unique IDs can usually synthesize them by combining the type with the type-specific ID, which is what was done in this example.
|
|
182
|
+
|
|
183
|
+
The IDs we got back were base64 strings. IDs are designed to be opaque (the only thing that should be passed to the `id` argument on `node` is the unaltered result of querying `id` on some object in the system), and base64ing a string is a useful convention in GraphQL to remind viewers that the string is an opaque identifier.
|
|
184
|
+
|
|
185
|
+
Complete details on how the server should behave are available in the [GraphQL Object Identification](https://graphql.org/learn/global-object-identification/) best practices guide in the GraphQL site.
|
|
186
|
+
|
|
187
|
+
## Connections
|
|
188
|
+
|
|
189
|
+
A faction has many ships in the Star Wars universe. Relay contains functionality to make manipulating one-to-many relationships easy, using a standardized way of expressing these one-to-many relationships. This standard connection model offers ways of slicing and paginating through the connection.
|
|
190
|
+
|
|
191
|
+
Let's take the rebels, and ask for their first ship:
|
|
192
|
+
|
|
193
|
+
```graphql
|
|
194
|
+
query RebelsShipsQuery {
|
|
195
|
+
rebels {
|
|
196
|
+
name
|
|
197
|
+
ships(first: 1) {
|
|
198
|
+
edges {
|
|
199
|
+
node {
|
|
200
|
+
name
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
yields
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"rebels": {
|
|
213
|
+
"name": "Alliance to Restore the Republic",
|
|
214
|
+
"ships": {
|
|
215
|
+
"edges": [
|
|
216
|
+
{
|
|
217
|
+
"node": {
|
|
218
|
+
"name": "X-Wing"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
That used the `first` argument to `ships` to slice the result set down to the first one. But what if we wanted to paginate through it? On each edge, a cursor will be exposed that we can use to paginate. Let's ask for the first two this time, and get the cursor as well:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
query MoreRebelShipsQuery {
|
|
231
|
+
rebels {
|
|
232
|
+
name
|
|
233
|
+
ships(first: 2) {
|
|
234
|
+
edges {
|
|
235
|
+
cursor
|
|
236
|
+
node {
|
|
237
|
+
name
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
and we get back
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
|
|
249
|
+
{
|
|
250
|
+
"rebels": {
|
|
251
|
+
"name": "Alliance to Restore the Republic",
|
|
252
|
+
"ships": {
|
|
253
|
+
"edges": [
|
|
254
|
+
{
|
|
255
|
+
"cursor": "YXJyYXljb25uZWN0aW9uOjA=",
|
|
256
|
+
"node": {
|
|
257
|
+
"name": "X-Wing"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"cursor": "YXJyYXljb25uZWN0aW9uOjE=",
|
|
262
|
+
"node": {
|
|
263
|
+
"name": "Y-Wing"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Notice that the cursor is a base64 string. That's the pattern from earlier: the server is reminding us that this is an opaque string. We can pass this string back to the server as the `after` argument to the `ships` field, which will let us ask for the next three ships after the last one in the previous result:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
query EndOfRebelShipsQuery {
|
|
277
|
+
rebels {
|
|
278
|
+
name
|
|
279
|
+
ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
|
|
280
|
+
edges {
|
|
281
|
+
cursor
|
|
282
|
+
node {
|
|
283
|
+
name
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
gives us
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
{
|
|
297
|
+
"rebels": {
|
|
298
|
+
"name": "Alliance to Restore the Republic",
|
|
299
|
+
"ships": {
|
|
300
|
+
"edges": [
|
|
301
|
+
{
|
|
302
|
+
"cursor": "YXJyYXljb25uZWN0aW9uOjI=",
|
|
303
|
+
"node": {
|
|
304
|
+
"name": "A-Wing"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"cursor": "YXJyYXljb25uZWN0aW9uOjM=",
|
|
309
|
+
"node": {
|
|
310
|
+
"name": "Millennium Falcon"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"cursor": "YXJyYXljb25uZWN0aW9uOjQ=",
|
|
315
|
+
"node": {
|
|
316
|
+
"name": "Home One"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Sweet! Let's keep going and get the next four!
|
|
326
|
+
|
|
327
|
+
```graphql
|
|
328
|
+
query RebelsQuery {
|
|
329
|
+
rebels {
|
|
330
|
+
name
|
|
331
|
+
ships(first: 4 after: "YXJyYXljb25uZWN0aW9uOjQ=") {
|
|
332
|
+
edges {
|
|
333
|
+
cursor
|
|
334
|
+
node {
|
|
335
|
+
name
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
yields
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"rebels": {
|
|
348
|
+
"name": "Alliance to Restore the Republic",
|
|
349
|
+
"ships": {
|
|
350
|
+
"edges": []
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Hm. There were no more ships; guess there were only five in the system for the rebels. It would have been nice to know that we'd reached the end of the connection, without having to do another round trip in order to verify that. The connection model exposes this capability with a type called `PageInfo`. So let's issue the two queries that got us ships again, but this time ask for `hasNextPage`:
|
|
357
|
+
|
|
358
|
+
```graphql
|
|
359
|
+
query EndOfRebelShipsQuery {
|
|
360
|
+
rebels {
|
|
361
|
+
name
|
|
362
|
+
originalShips: ships(first: 2) {
|
|
363
|
+
edges {
|
|
364
|
+
node {
|
|
365
|
+
name
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
pageInfo {
|
|
369
|
+
hasNextPage
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
moreShips: ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
|
|
373
|
+
edges {
|
|
374
|
+
node {
|
|
375
|
+
name
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
pageInfo {
|
|
379
|
+
hasNextPage
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
and we get back
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"rebels": {
|
|
391
|
+
"name": "Alliance to Restore the Republic",
|
|
392
|
+
"originalShips": {
|
|
393
|
+
"edges": [
|
|
394
|
+
{
|
|
395
|
+
"node": {
|
|
396
|
+
"name": "X-Wing"
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"node": {
|
|
401
|
+
"name": "Y-Wing"
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
"pageInfo": {
|
|
406
|
+
"hasNextPage": true
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
"moreShips": {
|
|
410
|
+
"edges": [
|
|
411
|
+
{
|
|
412
|
+
"node": {
|
|
413
|
+
"name": "A-Wing"
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"node": {
|
|
418
|
+
"name": "Millennium Falcon"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"node": {
|
|
423
|
+
"name": "Home One"
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
"pageInfo": {
|
|
428
|
+
"hasNextPage": false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
So on the first query for ships, GraphQL told us there was a next page, but on the next one, it told us we'd reached the end of the connection.
|
|
436
|
+
|
|
437
|
+
Relay uses all of this functionality to build out abstractions around connections, to make these easy to work with efficiently without having to manually manage cursors on the client.
|
|
438
|
+
|
|
439
|
+
Complete details on how the server should behave are available in the [GraphQL Cursor Connections](https://relay.dev/graphql/connections.htm) spec.
|
|
440
|
+
|
|
441
|
+
## GraphQL Conf Talk
|
|
442
|
+
|
|
443
|
+
Sabrina Wasserman, an engineer at Meta, gave a talk at GraphQL Conf 2024 deriving the GraphQL connection spec, and explaining how having the behavior of list pagination specified can enable powerful tooling for clients broadly, not just Relay.
|
|
444
|
+
|
|
445
|
+
<iframe src="https://www.youtube-nocookie.com/embed/PGBC-0E-kco" width={640} height={360} allowFullScreen={true} frameBorder="0" />
|
|
446
|
+
|
|
447
|
+
## Further Reading
|
|
448
|
+
|
|
449
|
+
This concludes the overview of the GraphQL Server Specifications. For the detailed requirements of a Relay-compliant GraphQL server, a more formal description of the [Relay cursor connection](https://relay.dev/graphql/connections.htm) model, the [GraphQL global object identification](https://graphql.org/learn/global-object-identification/) model are all available.
|
|
450
|
+
|
|
451
|
+
To see code implementing the specification, the [GraphQL.js Relay library](https://github.com/graphql/graphql-relay-js) provides helper functions for creating nodes and connections; that repository's [`__tests__`](https://github.com/graphql/graphql-relay-js/tree/main/src/__tests__) folder contains an implementation of the above example as integration tests for the repository.
|
|
452
|
+
|
|
453
|
+
<DocsRating />
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: network-layer
|
|
3
|
+
title: Network Layer
|
|
4
|
+
slug: /guides/network-layer/
|
|
5
|
+
description: Relay guide to the network layer
|
|
6
|
+
keywords:
|
|
7
|
+
- network
|
|
8
|
+
- caching
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
import DocsRating from '@site/src/core/DocsRating';
|
|
12
|
+
|
|
13
|
+
In order to know how to access your GraphQL server, Relay requires developers to provide an object implementing the `INetwork` interface when creating an instance of a [Relay Environment](../api-reference/relay-runtime/relay-environment.mdx). The environment uses this network layer to execute queries, mutations, and (if your server supports them) subscriptions. This allows developers to use whatever transport (HTTP, WebSockets, etc) and authentication is most appropriate for their application, decoupling the environment from the particulars of each application's network configuration.
|
|
14
|
+
|
|
15
|
+
Currently the easiest way to create a network layer is via a helper from the `relay-runtime` package:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import {
|
|
19
|
+
Environment,
|
|
20
|
+
Network,
|
|
21
|
+
RecordSource,
|
|
22
|
+
Store,
|
|
23
|
+
} from 'relay-runtime';
|
|
24
|
+
|
|
25
|
+
// Define a function that fetches the results of an operation (query/mutation/etc)
|
|
26
|
+
// and returns its results as a Promise:
|
|
27
|
+
function fetchQuery(
|
|
28
|
+
operation,
|
|
29
|
+
variables,
|
|
30
|
+
cacheConfig,
|
|
31
|
+
uploadables,
|
|
32
|
+
) {
|
|
33
|
+
return fetch('/graphql', {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
// Add authentication and other headers here
|
|
37
|
+
'content-type': 'application/json'
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
query: operation.text, // GraphQL text from input
|
|
41
|
+
variables,
|
|
42
|
+
}),
|
|
43
|
+
}).then(response => {
|
|
44
|
+
return response.json();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create a network layer from the fetch function
|
|
49
|
+
const network = Network.create(fetchQuery);
|
|
50
|
+
const store = new Store(new RecordSource())
|
|
51
|
+
|
|
52
|
+
const environment = new Environment({
|
|
53
|
+
network,
|
|
54
|
+
store
|
|
55
|
+
// ... other options
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export default environment;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
:::warning
|
|
62
|
+
This is just a basic example to help you get started. Features like `@stream`, `@defer` or Persisted Queries will require additional logic be added to your `fetchQuery` function.
|
|
63
|
+
:::
|
|
64
|
+
|
|
65
|
+
## Advanced Network Layer
|
|
66
|
+
|
|
67
|
+
The network layer provides a place for developers to customize the data fetching behaviors. For example, adding network level caching, telemetry or support for uploading form data for mutations.
|
|
68
|
+
|
|
69
|
+
<DocsRating />
|