import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client"; import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev"; import { createPersistedQueryLink } from "apollo-link-persisted-queries"; import { buildImpress2020Url } from "./impress-2020-config"; // Use Apollo's error messages in development. if (process.env["NODE_ENV"] === "development") { loadErrorMessages(); loadDevMessages(); } // Teach Apollo to load certain fields from the cache, to avoid extra network // requests. This happens a lot - e.g. reusing data from item search on the // outfit immediately! const typePolicies = { Query: { fields: { closetList: (_, { args, toReference }) => { return toReference({ __typename: "ClosetList", id: args.id }, true); }, items: (_, { args, toReference }) => { return args.ids.map((id) => toReference({ __typename: "Item", id }, true), ); }, item: (_, { args, toReference }) => { return toReference({ __typename: "Item", id: args.id }, true); }, petAppearanceById: (_, { args, toReference }) => { return toReference({ __typename: "PetAppearance", id: args.id }, true); }, species: (_, { args, toReference }) => { return toReference({ __typename: "Species", id: args.id }, true); }, color: (_, { args, toReference }) => { return toReference({ __typename: "Color", id: args.id }, true); }, outfit: (_, { args, toReference }) => { return toReference({ __typename: "Outfit", id: args.id }, true); }, user: (_, { args, toReference }) => { return toReference({ __typename: "User", id: args.id }, true); }, }, }, Item: { fields: { appearanceOn: (appearance, { args, readField, toReference }) => { // If we already have this exact appearance in the cache, serve it! if (appearance) { return appearance; } const { speciesId, colorId, altStyleId } = args; console.debug( "[appearanceOn] seeking cached appearance", speciesId, colorId, altStyleId, readField("id"), ); // If this is an alt style, don't try to mess with clever caching. // (Note that, if it's already in the cache, the first condition will // catch that! This won't *always* force a fresh load!) if (altStyleId != null) { return undefined; } // Otherwise, we're going to see if this is a standard color, in which // case we can reuse the standard color appearance if we already have // it! This helps for fast loading when switching between standard // colors. const speciesStandardBodyId = readField( "standardBodyId", toReference({ __typename: "Species", id: speciesId }), ); const colorIsStandard = readField( "isStandard", toReference({ __typename: "Color", id: colorId }), ); if (speciesStandardBodyId == null || colorIsStandard == null) { // We haven't loaded all the species/colors into cache yet. We might // be loading them, depending on the page? Either way, return // `undefined`, meaning we don't know how to serve this from cache. // This will cause us to start loading it from the server. console.debug("[appearanceOn] species/colors not loaded yet"); return undefined; } if (colorIsStandard) { const itemId = readField("id"); console.debug( "[appearanceOn] standard color, will read:", `item-${itemId}-body-${speciesStandardBodyId}`, ); return toReference({ __typename: "ItemAppearance", id: `item-${itemId}-body-${speciesStandardBodyId}`, }); } else { console.debug("[appearanceOn] non-standard color, failure"); // This isn't a standard color, so we don't support special // cross-color caching for it. Return `undefined`, meaning we don't // know how to serve this from cache. This will cause us to start // loading it from the server. return undefined; } }, currentUserOwnsThis: (cachedValue, { readField }) => { if (cachedValue != null) { return cachedValue; } // Do we know what items this user owns? If so, scan for this item. const currentUserRef = readField("currentUser", { __ref: "ROOT_QUERY", }); if (!currentUserRef) { return undefined; } const thisItemId = readField("id"); const itemsTheyOwn = readField("itemsTheyOwn", currentUserRef); if (!itemsTheyOwn) { return undefined; } const theyOwnThisItem = itemsTheyOwn.some( (itemRef) => readField("id", itemRef) === thisItemId, ); return theyOwnThisItem; }, currentUserWantsThis: (cachedValue, { readField }) => { if (cachedValue != null) { return cachedValue; } // Do we know what items this user owns? If so, scan for this item. const currentUserRef = readField("currentUser", { __ref: "ROOT_QUERY", }); if (!currentUserRef) { return undefined; } const thisItemId = readField("id"); const itemsTheyWant = readField("itemsTheyWant", currentUserRef); if (!itemsTheyWant) { return undefined; } const theyWantThisItem = itemsTheyWant.some( (itemRef) => readField("id", itemRef) === thisItemId, ); return theyWantThisItem; }, }, }, ClosetList: { fields: { // When loading the updated contents of a list, replace it entirely. items: { merge: false }, }, }, }; const cache = new InMemoryCache({ typePolicies }); const httpLink = createHttpLink({ uri: buildImpress2020Url("/api/graphql"), }); const link = createPersistedQueryLink({ useGETForHashedQueries: true, }).concat(httpLink); /** * apolloClient is the global Apollo Client instance we use for GraphQL * queries. This is how we communicate with the server! */ const apolloClient = new ApolloClient({ link, cache, connectToDevTools: true, }); export default apolloClient;