How to return a DQL query result from a custom lambda resolver

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 uidid
  • 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…