Created
March 30, 2020 18:02
-
-
Save ersinakinci/7afe33f576c457ecfc7e2c30af4b7f9b to your computer and use it in GitHub Desktop.
AST to Fauna query with fragment spread and non-null support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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