1
0
Fork 0
forked from OpenNeo/impress
impress/src/server/ssr-graphql.js

103 lines
3.2 KiB
JavaScript
Raw Normal View History

const { InMemoryCache } = require("@apollo/client");
const { ApolloServer, gql } = require("apollo-server");
const { config } = require("./index");
const server = new ApolloServer(config);
async function loadGraphqlQuery({ query, variables = {} }) {
// Edit the query to serve our needs, then send a local in-memory request to
// a simple `ApolloServer` instance just for SSR.
const convertedQuery = addTypenameToSelections(removeClientOnlyFields(query));
const { data, errors } = await server.executeOperation({
query: convertedQuery,
variables,
});
// To get the cache data, we build a new temporary cache object, write this
// query result to it, and dump it out. (Building a normalized cache state is
// really tricky, this simplifies it a lot without bringing in the weight of
// a whole client!)
const cache = new InMemoryCache();
cache.writeQuery({ query, variables, data });
const graphqlState = cache.extract();
// We return the data, errors, and cache state: we figure callers will almost
// always want the errors and state, and may also want the data!
return { data, errors, graphqlState };
}
/**
* addTypenameToSelections recursively adds __typename to every selection set
* in the query, and returns a copy. This enables us to use the query data to
* populate a cache!
*/
function addTypenameToSelections(node) {
if (node.kind === "SelectionSet") {
return {
...node,
selections: [
{
kind: "Field",
name: {
kind: "Name",
value: "__typename",
arguments: [],
directives: [],
},
},
...node.selections.map((s) => addTypenameToSelections(s)),
],
};
} else if (node.selectionSet != null) {
return {
...node,
selectionSet: addTypenameToSelections(node.selectionSet),
};
} else if (node.kind === "Document") {
return {
...node,
definitions: node.definitions.map((d) => addTypenameToSelections(d)),
};
} else {
return node;
}
}
/**
* removeClientOnlyFields recursively removes any fields marked with `@client`
* in the given GraphQL document node, and returns a new copy. This enables us
* to borrow queries and fragments from the client, and ignore the fields they
* won't need preloaded for SSR. (This isn't just an optimization: the server
* can't handle the `@client` directive and the query will fail if present!)
*/
function removeClientOnlyFields(node) {
if (node.kind === "SelectionSet") {
return {
...node,
selections: node.selections
.filter(
(selection) =>
!(
selection.kind === "Field" &&
selection.directives.some((d) => d.name.value === "client")
)
)
.map((selection) => removeClientOnlyFields(selection)),
};
} else if (node.selectionSet != null) {
return { ...node, selectionSet: removeClientOnlyFields(node.selectionSet) };
} else if (node.kind === "Document") {
return {
...node,
definitions: node.definitions.map((d) => removeClientOnlyFields(d)),
};
} else {
return node;
}
}
module.exports = {
loadGraphqlQuery,
gql,
};