Skip to content

Instantly share code, notes, and snippets.

@ersinakinci
Created March 30, 2020 18:02
Show Gist options
  • Save ersinakinci/7afe33f576c457ecfc7e2c30af4b7f9b to your computer and use it in GitHub Desktop.
Save ersinakinci/7afe33f576c457ecfc7e2c30af4b7f9b to your computer and use it in GitHub Desktop.
AST to Fauna query with fragment spread and non-null support
import {
TypeInfo,
visit,
visitWithTypeInfo,
isLeafType,
isNonNullType,
GraphQLList
} from "graphql";
import { query as q } from "faunadb-fql-lib";
const reduceToObject = fields =>
q.Reduce(
q.Lambda(["acc", "val"], q.Merge(q.Var("acc"), q.Var("val"))),
{},
fields
);
const nestedQuery = (query, fields, isList) => {
if (isList) {
return q.Map(query, q.Lambda("_item_", reduceToObject(fields)));
} else {
return q.Let(
{
_item_: query
},
reduceToObject(fields)
);
}
};
const defaultEmbedQuery = (fieldName, isList) =>
q.Let(
{
ref: q.Select(["data", `${fieldName}Ref`], q.Var("_item_"), null)
},
q.If(q.IsNull(q.Var("ref")), null, q.Get(q.Var("ref")))
);
export const astToFaunaQuery = (ast, query) => {
try {
const { fragments, operation, schema, fieldName } = ast;
const typeInfo = new TypeInfo(schema);
const fragmentQueries = {};
const visitor = {
// Transform fragments into FQL queries
FragmentDefinition: {
leave: (node, key, parent, path) => {
const name = node.name.value;
const type = typeInfo.getType();
const isList = type instanceof GraphQLList;
const field = typeInfo.getFieldDef();
// Store the transformed fragment to be used during a second pass of
// the visitor, at which point the query generated here will get
// inserted into the overall query by FragmentSpread.
fragmentQueries[name] = reduceToObject(node.selectionSet.selections);
return node;
}
},
// Use the queries that were created by FragmentDefinition
FragmentSpread: {
leave: (node, key, parent, path) => {
const fragmentName = node.name.value;
return fragmentQueries[fragmentName];
}
},
InlineFragment: {
leave: (node, key, parent, path) => {
// console.log("InlineFragment");
// console.log(node);
// console.log(key);
// console.log(parent);
// console.log(path);
const type = typeInfo.getType();
return q.If(
// @ts-ignore
type.fqlTypeCheck(q, q.Var("_item_")),
reduceToObject(node.selectionSet.selections),
{}
);
}
},
Field: {
leave: (node, key, parent, path) => {
const name = node.name.value;
const type = typeInfo.getType();
const isLeaf = isNonNullType(type)
? isLeafType(type.ofType)
: isLeafType(type);
const isList = type instanceof GraphQLList;
// If name === fieldName then this is the root.
if (name === fieldName) {
return nestedQuery(query, node.selectionSet.selections, isList);
} else if (isLeaf) {
const field = typeInfo.getFieldDef();
let selector;
if (name === "__typename") {
return { [name]: type.toString() };
}
// @ts-ignore 2
if (field.fql) {
// @ts-ignore 2
return { [name]: field.fql(q) };
}
if (name === "id") {
selector = ["ref"];
} else if (name === "ts") {
selector = ["ts"];
} else {
selector = ["data", name];
}
return {
[name]: q.Select(selector, q.Var("_item_"), null)
};
} else {
const field = typeInfo.getFieldDef();
let relQuery;
// @ts-ignore 2
if (field.fql) {
// @ts-ignore 2
relQuery = field.fql(q);
} else {
relQuery = defaultEmbedQuery(name, isList);
}
return {
[name]: nestedQuery(
relQuery,
node.selectionSet.selections,
isList
)
};
}
}
}
};
// Transform fragments into FQL queries before transforming the operation.
// The resulting queries will be used by FragmentSpread above.
Object.keys(fragments).forEach(key => {
fragmentQueries[key] = visit(
fragments[key],
visitWithTypeInfo(typeInfo, visitor)
).selectionSet.selections[0];
});
const res = visit(operation, visitWithTypeInfo(typeInfo, visitor));
return res.selectionSet.selections[0];
} catch (err) {
console.log(err);
throw err;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment