@firsthandjs/data-apollo
v0.12.2
Published
Loaders for Apollo Client, for @firsthandjs/data. Takes your client; never configures it.
Maintainers
Readme
@firsthandjs/data-apollo
Apollo Client as a client for @firsthandjs/data.
npm install @firsthandjs/data-apollo0.65 kB gzip. It has no dependency on Apollo and no peer dependency either —
the three methods it uses are declared structurally. That also keeps your copy
of graphql the only copy, which is a category of afternoon worth avoiding.
import { gql } from '@apollo/client';
import { createApolloClient } from '@firsthandjs/data-apollo';
import { useAction, useResource } from '@firsthandjs/data';
import InvoicesDocument from './invoices.gql';
export const billing = createApolloClient(apollo, gql, {
// Read per request and untracked: the token may change, and a resource must
// not depend on it.
headers: () => ({ authorization: `Bearer ${session.token.peek()}` }),
});
function Invoices() {
const invoices = useResource(({ request }) =>
billing.query(InvoicesDocument, { month: month.value })(request),
);
return <List items={invoices.data.value?.invoices ?? []} />;
}createApolloClient(client, parse, options?) takes the parse function too —
gql from @apollo/client, or parse from graphql — because Apollo wants a
parsed DocumentNode and that parser should be yours, not ours.
The client
billing.query(Document, variables); // a loader; declares @tag
billing.mutate(Document, variables); // a loader; declares @invalidates
billing.watch(Document, variables); // what `fromObservable` takes
billing.with({ options: { errorPolicy: 'all' } }); // a variationAn operation with required variables cannot be called without them: the generated types travel with the document.
The decision this package leaves to you: the cache
Apollo requires an InMemoryCache, and it is a normalising cache — a
different thing from a store of resources. There are two ways to run them
together, and only two:
Apollo as transport. The resources are your state.
forceis passed on asfetchPolicy: 'network-only', so an invalidation reaches past the cache; setno-cacheon the client if you want it out of the way entirely.Apollo as the store. Use
watchinstead. One write in Apollo's cache then updates every view of that entity at the same moment, which is what a per-call-site resource cannot do:const user = fromObservable(...billing.watch(UserDocument, { id: props.id }));
If you do give this client a cache, its keys carry the operation, the variables
(in any order they were written) and an identity — the authorization header
by default, or a scope you give.
What is not on the list is both caches at once, with the same data living in two places under two invalidation rules. See ADR-0022.
Tags come out of the document
@firsthandjs/data/vite reads @tag and @invalidates at build time and
strips them, so what reaches Apollo is a plain GraphQL document. A query
declares its @tag directives before the request goes out; a mutation declares
its @invalidates — and inside an action the request's tags are the store's
invalidation, so nothing at the call site wires them:
const pay = useAction((id: string, { request }) => billing.mutate(PayDocument, { id })(request));MIT licensed. See the data guide.
