MichalLytek / typegraphql-prisma

Prisma generator to emit TypeGraphQL types and CRUD resolvers from your Prisma schema
https://prisma.typegraphql.com
MIT License
888 stars 113 forks source link

Relay-compliant GraphQL schema #18

Open FluorescentHallucinogen opened 4 years ago

FluorescentHallucinogen commented 4 years ago

I'm thinking about migrating from Prisma 1 to Prisma 2 + TypeGraphQL, but just found that generated GraphQL schema (see e.g. generated-schema.graphql) is not Relay-compliant, e.g. there is no the interface called Node.

Is it comes from Prisma 2 or TypeGraphQL? Is it fixable?

MichalLytek commented 4 years ago

It comes from Prisma 2. For now TypeGraphQL uses schema and operations definitions directly from Prisma, there's not transform or translation layer.

However adding Node resolver and other relay's stuff would be a nice to have feature 👍

FluorescentHallucinogen commented 4 years ago

Ok, adding the Node interface looks achievable, but what about things like Cursor Connections, Edges, PageInfo, etc.? This looks not easy.

FluorescentHallucinogen commented 4 years ago

Just found https://github.com/devoxa/prisma-relay-cursor-connection.

MichalLytek commented 4 years ago

Yes, that's why it's a nice to have feature but requires more time and has lower priority for now.

dannydi12 commented 3 years ago

Just here to bump this feature request for pagination within the generated resolvers! I think what you guys are doing here is great and will greatly speed up development. I would heavily consider using this in my next project if pagination came right out of the box!

srosato commented 2 years ago

There is also https://github.com/johnsonjo4531/typegraphql-relay-connections but this one manually requires me to add the types using TypeGraphQL, which works great, but then I lose orderBy, where and etc (unless I implement it).

Seems like https://github.com/devoxa/prisma-relay-cursor-connection leverages this by extending findMany, but then it does not generate the object types and resolvers, so either way we are stuck implementing relay style pagination.

srosato commented 2 years ago

Actually by using https://github.com/johnsonjo4531/typegraphql-relay-connections it is possible to have something, even if it is not automatically generated. So for anyone interested here is how I did it. In my example, I removed backward pagination because my app does not need it.

Relay style pagination by passing findManyArgs:

import { Args, Ctx, ObjectType, Query, Resolver } from 'type-graphql';
import {
  ConnectionType,
  Cursor,
  EdgeType,
  ForwardPaginationArgs,
  NodesType,
  serializeCursor
} from 'typegraphql-relay-connections';
import { FindManyJobArgs, Job } from '@generated/type-graphql';
import { Context } from './context';

/*
 * Inspired from:
 * https://johnsonjo4531.github.io/typegraphql-relay-connections/docs/intro
 *
 * For now, backward pagination was removed.
 */

declare module 'typegraphql-relay-connections' {
  interface Cursor {
    id: string;
  }
}

@ObjectType()
export class JobEdge extends EdgeType(Job) {
}

@ObjectType()
export class JobConnection extends ConnectionType({
  edge: JobEdge,
  node: Job
}) {
}

@Resolver(Job)
export class PaginatedJobsResolver {
  @Query(() => JobConnection)
  async jobs(@Ctx() ctx: Context, @Args() paging: ForwardPaginationArgs, @Args() findManyArgs: FindManyJobArgs): Promise<JobConnection> {
    return getJobs(ctx, { paging, findManyArgs });
  }
}

export const getJobs = async ({ prisma }: Context, {
  paging,
  findManyArgs
}: { paging: ForwardPaginationArgs, findManyArgs: FindManyJobArgs }) => {
  const edges = edgesToReturn(
    (
      await prisma.job.findMany({
        ...findManyArgs,
        take: paging.first,
        cursor: paging.after
      })
    ).map((node) => ({
      node,
      cursor: {
        id: node.id
      }
    })),
    paging
  );

  return {
    edges,
    nodes: edges.map((e) => e.node),
    pageInfo: {
      hasNextPage: false, // todo
      hasPreviousPage: false, // todo
      endCursor: edges[edges.length - 1].cursor,
      startCursor: edges[0].cursor,
      count: edges.length
    }
  };
};

export function edgesToReturn<CursorType extends Cursor = Cursor, NodeType extends NodesType = unknown>(
  allEdges: {
    cursor: CursorType;
    node: NodeType;
  }[],
  { first, after }: ForwardPaginationArgs
) {
  const edges = applyCursorsToEdges(allEdges, { after });

  if (first !== null && typeof first !== 'undefined') {
    if (first < 0) {
      throw new Error('ForwardPagingArg property first cannot be less than 0');
    }

    if (edges.length > first) {
      // Yes that first parameter is negative on purpose, so that it splices from the end of the array.
      const numToDelete = Math.abs(edges.length - first);

      edges.splice(-numToDelete, numToDelete);
    }
  }

  return edges;
}

export function applyCursorsToEdges<CursorType extends Cursor = Cursor, NodeType extends NodesType = unknown>(
  allEdges: {
    cursor: CursorType;
    node: NodeType;
  }[],
  { after }: Pick<ForwardPaginationArgs, 'after'>
) {
  const edges = allEdges;

  if (after) {
    const afterEdge = edges.findIndex((x) => serializeCursor(x.cursor) === serializeCursor(after));

    if (afterEdge !== -1) {
      edges.splice(0, afterEdge + 1);
    }
  }

  return edges;
}
FezVrasta commented 2 months ago

I'm sorry to hijack this conversation, but it seems related. How's everyone handling the Relay node resolver? We don't have global ids with Prisma so how do you know what model to fetch when a node is queried?

I'm talking about the Query.node resolver that only takes the id as argument