Reputation: 14835
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
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
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