Fez Vrasta
Fez Vrasta

Reputation: 14835

Implement Relay-style node query field with Prisma/Postgres?

I'm trying to implement the node field for my Relay compliant server, as defined here.

I'm using Prisma, Yoga, and TypeGraphQL.

I can't understand how to perform a Prisma query so that it searches across all my schemas, I'm also not sure if I have to do anything specific with Prisma to ensure the UUID are unique across tables?

Upvotes: 0

Views: 80

Answers (2)

Fez Vrasta
Fez Vrasta

Reputation: 14835

I'm providing an answer, even though it's not complete. It still provides a way to implement a node resolver, although with some manual work needed to keep it running.

This is my current implementation, it relies on the assumption every node query will include a fragment on the GraphQL object Relay is trying to fetch, for example:

query MyQuery {
    node(id: "2467aacc-3ac7-45a4-a4a4-e2b14587c2c6") {
    id
    ... on CreatorNode {
      name
    }
  }
}

So even though Prisma doesn't have global identifiers I can understand what model to fetch using the GraphQL query AST as source.

import { DefinitionNode, SelectionNode, parse } from "graphql";
import { Arg, Ctx, Query, Resolver } from "type-graphql";

import { prisma } from "@/prisma";
import { RelayNode } from "@/types/relay-node";
import { Prisma } from "@generated/prisma-client";

import { GraphQLContext } from "..";
import { CreatorNode, ProductPriceNode } from "./creators";

/**
 * Node Resolver
 */
@Resolver()
export class NodesResolver {
  @Query(() => RelayNode, { nullable: true })
  async node(@Arg("id") id: string, @Ctx() ctx: GraphQLContext) {
    if (ctx.params.query == null) {
      return null;
    }

    const fragmentTypes = extractFragmentTypes(ctx.params.query);
    const firstFragmentType = fragmentTypes[0];

    if (firstFragmentType == null) {
      return null;
    }

    const modelName = lowerCaseFirstChar(
      firstFragmentType.replace(/Node$/, ""),
    ) as TPrismaModels;

    // @ts-expect-error This is a dynamic property access
    const node = await prisma[modelName].findUnique({ where: { id } });


    let instance;

    switch (modelName) {
      case "creator":
        instance = new CreatorNode();
        break;
      case "productPrice":
        instance = new ProductPriceNode();
        break;
      default:
        throw new Error(
          `Unknown model: ${modelName}, make sure to implement it in the node resolver.`,
        );
    }

    for (const key in node) {
      instance[key as keyof typeof instance] = node[key];
    }

    return instance;
  }
}


export const resolvers = [NodesResolver] as const;

/**
 * Utilities
 */
function extractFragmentTypes(query: string): string[] {
  const fragmentTypes: string[] = [];
  const ast = parse(query);

  // Traverse the AST to find fragment definitions
  function extractFragments(node: DefinitionNode | SelectionNode) {
    console.log(node);
    if (node.kind === "InlineFragment" && node.typeCondition) {
      fragmentTypes.push(node.typeCondition.name.value);
    }
    if ("selectionSet" in node && node.selectionSet) {
      node.selectionSet.selections.forEach((selection) => {
        extractFragments(selection);
      });
    }
  }

  ast.definitions.forEach((definition) => {
    extractFragments(definition);
  });

  return fragmentTypes;
}

function lowerCaseFirstChar(s: string): string {
  return s[0].toLowerCase() + s.slice(1);
}

type LowercaseFirstChar<S extends string> =
  S extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}` : S;
type TPrismaModels = LowercaseFirstChar<keyof typeof Prisma.ModelName>;


The problem is I have to manually list every single node object type here, I would like a general purpose solution.

Upvotes: 0

biruk belay
biruk belay

Reputation: 1

you can use a library @devoxa/prisma-relay-cursor-connection

import { findManyCursorConnection } from '@devoxa/prisma-relay-cursor-connection'

 //pagArgs is an input you get from your frontend, you can get it as a parameter if this is inside a function
 const { first, last, before, after } = pagArg
const where = {
  ...filter
}
const baseArgs = {
  include: {
    author: true,
    
    flags: true
  },
  where: where,
  orderBy: orderBy ? { [orderBy.field]: orderBy.direction } : undefined
}
const result = await findManyCursorConnection(
        (args) =>
          this.prisma.post.findMany({
            ...args,
            ...baseArgs
          }),
        () =>
          this.prisma.post.count({
            where: where
          }),
        {
          first,
          last,
          before,
          after
        }
      )

it does every thing else for you

Upvotes: -1

Related Questions