Alright, not sure if this is the right approach but since nobody has or wants to respond to those (my) questions in this forum, I post my solution in case someone else runs into the same problem.
DISCLAIMER - I’m not sure if this is the right way of doing it, but at least it works for me.
Summary of the task
When performing a DQL query inside eg. a custom lambda resolver which then should be returned back to a GraphQL frontend, the DQL result needs to be parsed. According to the GraphQL specs, not only the mandatory fields have to be returned but also __typename
which uniquely identifies each type in the GraphQL schema.
Since Dgraph treats Interfaces and Unions as types, a DQL query for a type
can return an array of Dgraph Types (in fact dgraph.type
, which is always an array). In order to return the appropriate typename we therefore have to “extract” the right typename from the array.
Solution
Step 1
Before bundling my lambda with webpack I parse the schema file (in my case schema.graphql
) and extract all interface names. I then create a file which exports an array of those interface names.
export const parseInterfacesFromSchema = () => {
const schema = readFileSync(`${monorepoEnvironment.dgraphSchemaFileLocation}`, "utf-8")
.split("\n")
.filter(a => a.includes("interface"));
const interfaces = schema.map(t => t.split(" ")[1]);
const file = createWriteStream(`${monorepoEnvironment.clientSchemaLocation}/interfaces.ts`);
file.on("error", e => {
throw new Error(e.message);
});
file.write("/**\r\n");
file.write(" * List of interfaces in the current schema. We need this for corrent __typename\r\n");
file.write(" * parsing when returning DQL queries.\r\n");
file.write(" *\r\n");
file.write(" * This file has been autogenerated by 'parseInterfaceFromSchema'.\r\n");
file.write(" */\r\n");
file.write("export const interfaces = [\r\n");
interfaces.forEach(i => file.write(`\t"${i}",\r\n`));
file.write("];");
file.end();
};
Step 2
This step happens when resolving the lambda. I parse the DQL result and
- change
uid
→ id
- remove all types from the predicate, so
Cat.name
becomes name
- rename the key
dgraph.type
to __typename
- remove all interface types from the
dgraph.type
array which leaves me with the correct typename
Here’s the parser:
import { interfaces } from 'path/to/interfaces.ts';
export const dqlToGraphQl: DqlToGraphQl = params => {
let newObject = params.assignNew && {};
Object.entries(params.object).forEach(([key, value]) => {
let newKey = key;
let newValue = value;
// Convert `uid` --> `id`
if (newKey === "uid") {
newKey = "id";
}
// Parse typenames
else if (newKey === "dgraph.type") {
newKey = "__typename";
const types = newValue as string[];
// Set the type value
newValue = types[0];
// Strip interfaces from type if interfaces are implemented
if (types.length > 1) {
newValue = types.filter(type => !interfaces.includes(type))[0];
}
}
// Strip typenames from predicates and return field name
else {
newKey = key.split(".")[1];
}
// Value is a regular object
if (typeof newValue === "object" && !Array.isArray(newValue) && newValue !== null) {
dqlToGraphQl({ object: newValue, types: params.types });
}
// Value is an Array
if (Array.isArray(newValue)) {
newValue.forEach(val => {
if (typeof val === "object" && !Array.isArray(val) && val !== null) {
dqlToGraphQl({ object: val, types: params.types });
}
});
}
delete Object.assign(newObject || params.object, { [newKey]: newValue })[key];
});
return newObject;
};
Hope this helps!
If someone from the Core Team finds some time to read this and is willing to comment on this solution, I’d be more than happy!
I’m pretty sure there’s a better solution…