The purpose of Apollo Link is to define an extensible standard interface for modifying control flow of GraphQL queries and fetching GraphQL results.
To that end, Apollo Link describes an interface containing a single method, request
, that connects a GraphQL Operation
to an Observable
containing the results of the operation.
Operations that return a single result can easily be mapped to a promise, since the observable's next
function is called only once.
In the general case, Apollo Link uses observables to support GraphQL subscriptions and live queries.
The apollo-link's Observable
follows the ECMAScript proposal.
A basic link is visualized as follows:
To support a range of use cases with simple links, Apollo Links are designed as a modular system that composes links together into chains.
Links communicate down their chain using a forward
callback that passes an operation to the next link's request
and returns the resulting observable.
This example chain contains an Intermediate Link that modifies the operation and passes it to forward
.
forward
then calls the Base Link's request
on the operation, which returns an Observable.
This Observable is returned from forward
and the Intermediate Link can subscribe and respond to the result.
This interaction is visualized below:
Apollo Links are designed to be used as an isomorphic stand-alone extensible GraphQL client. In addition, links fit all current and anticipated needs of Apollo Client. As such, links are targeted towards the following use-cases:
- Do a simple GraphQL query with it that resolves only once
- Do a simple GraphQL mutation that resolves only once
- Do a subscription
- Do a GraphQL query that contains @defer, @stream or @live
- Support custom annotations, such as @connection
- Modify query, variables, operationName and context
- Use persistent queries
- Include extra metadata to server along with queries
- Include component sending query
- Send time stamp of query leaving transport
- Receive extra metadata with the query result
- Send caching information
- Modify headers as middleware currently does (uses apollo-fetch)
- Add authentication headers
- Support afterware to modify response or add side effects (uses apollo-fetch)
- Log queries
- Perform analytics
- Logout on 401
- Use HTTP transport, websocket transport, hybrid transport
- Use alternative transports
- File System
- DDP
- Mock partial or full response
- Route operations to alternate endpoints
- Partial operations to REST endpoint
- Partial operations to GraphQL endpoint
- Partial operations to Mock
- Split single operation into multiple operations
- Race one operation to local storage and another to network
- Per request retries based on network status or response
- Polling
- Batching at the transport/fetch layer
- Deduplicate of on-the-wire requests
- Re-route and modify errors
- transform network error into GraphQL error
- Network layer caching
- Denormalized
- Normalized
- Local Storage
- IndexDB
If you think of more use-cases, please open a PR adding to this list and make sure to clearly explain your use-case.
http-link sends an operation over HTTP to a server at a constructor specified URI.
Additionally, the constructor accepts an optional fetch function and defaults to ApolloFetch
from apollo-fetch.
HTTP specific behavior, such as middleware or afterware, is encapsulated in this fetch function.
In this manner, http-link can function as a stand-alone GraphQL client:
const httpLink = new HttpLink({ uri: 'http://api.githunt.com/graphql' });
const responseObservable = httpLink.request({
query,
variables,
operationName,
context,
});
responseObservable.subscribe({
next: data => console.log('received data', data),
error: error => console.error(error),
complete: () => console.log('request complete'),
});
To illustrate composing links, this example shows a polling link that sends an operation to an http link on a specified interval.
Link
is a library of static helper functions that connects links together to form a chain.
import { Link } from 'apollo-link';
Link.chain([
new PollingLink({
interval: 10000, //Polling Interval in ms
}),
new HttpLink({ uri: 'http://api.githunt.com/graphql' }),
])
In this example, each operation is routed through PollingLink
to HttpLink
and returned as an observable to PollingLink
.
The operation enters PollingLink
's request
method, which would utilize the forward
callback to pass the operation to the HttpLink
.
This snippet shows the general flow of polling a request, omitting the details around how subscriber
notifies pollingObservable
and error/completion handling.
//Inside PollingLink class
request(operation, forward) {
//forward makes a request call on the next link in the chain
const httpResult = forward(operation);
//subscriber would notify pollingObservable of the result
httpResult.subscribe(this.subscriber);
//forward can be called multiple times, so make the request on the interval
setTimeout(() => this.request(operation, forward), this.interval);
//subscriber passes results to the pollingObservable
return this.pollingObservable;
}
Additionally, Link.chain
adds a compatibility layer that accepts a string as well as a GraphQL AST.
Before passing the Operation
to the first link, Link.chain
parses the query string, ensuring links only need to work with an AST.
With this interface, a chain can be the network stack for most GraphQL clients, including GraphiQL and Apollo Client.
The behavior of Link.chain
can also be modified to include operations that occurring in between each link, such as logging.
In addition to the query, operationName, and variables, Operation
includes a context, which is modified and passed down the chain.
In addition, this context is sent to the server to through the query body.
In this example, context
is used by a PollingLink
to tell a CachingLink
whether a request should be returned from the cache or the network.
The Apollo Link interface contains a single method:
ApolloLink {
request: (operation: Operation, forward?: NextLink) => Observable<FetchResult>
}
NextLink = (operation: Operation) => Observable<FetchResult>
An Operation
contains all the information necessary to execute a GraphQL query, including the GraphQL AST:
Operation {
query?: DocumentNode,
operationName?: string,
variables?: object,
context?: object,
}
An Observable
is returned from request
and notifies a Subscriber
on the life-cycle of a GraphQL request:
Observable {
//Causes the underlying producer to start
subscribe: (Subscriber<FetchResult>) => UnsubscribeHandler
subscribe: (
next: (FetchResult) => void,
error: (any) => void,
complete: () => void,
) => Subscription
A Subscriber
is passed to subscribe
Subscriber<T> {
next?: (T) => void,
error?: (any) => void,
complete?: () => void,
}
Subscription
is returned from subscribe
.
closed
returns if this Subscription
is terminated by an unsubscribe
, complete
, or error
Subscription {
unsubscribe: () => void;
get closed: () => boolean;
}
FetchResult
is passed to the next
callback of Subscriber
FetchResult {
data?: object,
errors?: object[],
extensions?: any,
context?:object,
}
Here is a list of planned links with selected diagrams:
- Websocket: subscriptions support
- Batching-*: support for batching operations with a transport
- Polling: repeats requests on a specified interval
- Caching: returns data if result in the cache and stores data in cache on response
- Catch: catch all errors and modify the Observable to return something else
- Dedup: saves query signatures that are currently on the wire and returns the result for all of those queries
- Retry: error callback causes the request to retry
- Mock: returns fake data for all or part of a request ← implement with BaseLink (eg. executes
graphql
)
- Split: split operations between links depending on a function passed in
- Hybrid: uses split-link to fill query and mutations with http and subscriptions with websockets
The planned design for split adding another static function to Link
with the following signature.
Link.split(
test: (Operation) => boolean,
left: ApolloLink[],
right: ApolloLink[],
),
Suggestions are welcome, including other strategies or naming schemes.
An example usage would be:
Link.chain([
new RetryLink(),
Link.split(
isQueryOrMutation,
[ new HttpLink() ],
[ new WebSocketLink() ],
),
])
- Promise Wrapper
- Backwards-compatibility Wrapper: exposes
query
,mutate
, andsubscribe
Throughout the design process, a couple questions have surfaced that may prompt conversation. If you want to see something here, please open a PR with your proposed change or an Issue for discussion.
Should we pass the context from the response (for example relevant HTTP headers or status code) to the next
callback of the observable like this:
next({
data,
errors,
extensions,
context
})
or like this:
next({
result: {
data,
errors,
extensions
},
context
})
Should we include status()
the Link interface that could contain user defined data and an enum for state?
enum State {
cold
started
stopped
errored
completed
}
If status
exists and an Observable
has terminated, should subscribe
throw an error or call complete immediately, avoiding memory leaks.
The current functions provided:
- Link
- chain
- asPromiseWrapper
- LinkUtils
- toSubscriber
Proposed additions include:
- Pull information from the Operation as part of
Operation
orLinkUtil
library- hasQuery
- hasMutation
- hasSubscription
- getQuery
- getMutation
- getSubscription
- annotation support?
- Observable additions as required or optional part of interface and implemented in
AbstractObservable
map
filter
catch
finally
Currently GraphQL errors are returned as data to next
.
In the case of a network error, error
is called.
Currently, everything in apollo-link
is asynchronous.
There have been some abstract thoughts around providing some sort of synchronous adapter or cache.
Use cases and other thoughts are welcome!