Giter Site home page Giter Site logo

devoxa / prisma-relay-cursor-connection Goto Github PK

View Code? Open in Web Editor NEW
261.0 261.0 19.0 2.79 MB

Extend Prisma's `findMany` method to support Relay Cursor Connections.

JavaScript 1.14% TypeScript 98.86%
cursor graphql hacktoberfest pagination prisma prisma-client prisma2 relay

prisma-relay-cursor-connection's People

Contributors

ahmetuysal avatar allcontributors[bot] avatar dependabot-preview[bot] avatar dependabot[bot] avatar igo avatar jeffgukang avatar jeongsd avatar kareem-medhat avatar kervin5 avatar kodiakhq[bot] avatar marinarierav avatar nicksrandall avatar orta avatar queicherius avatar renovate-bot avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

prisma-relay-cursor-connection's Issues

`nodes` field incompatible with custom edge

The addition of the nodes field in #432 is a super useful change when working on the frontend side, but unfortunately it doesn't work great when using custom edges.

With custom edges, the nodes field contains an array of the records instead of an array of the node specified in recordToEdge:

  {
    recordToEdge: (record) => ({
      node: record.foo,
      extraEdgeField: 'Bar',
    }),
  }

The typing of nodes is correct, but not the actual value inside.

This use case is bit more complex so I wasn't able to find the change necessary to make things work ๐Ÿ˜•

In the meantime, I use this workaround:

const result = await findManyCursorConnection...

result.nodes = result.edges.map((edge) => edge.node);

return result;

Also, I notice that the hasRequestedField when requesting all records wasn't updated to check for nodes. Right now, it only checks for edges and totalCount.

} else {
// Execute the underlying query operations
records = hasRequestedField('edges') ? await findMany({}) : []
totalCount = hasRequestedField('totalCount') ? await aggregate() : -1
// Since we are getting all records, there are no pages
hasNextPage = false
hasPreviousPage = false
}

Feature Request: Custom Edge Fields

GraphQL Cursor Connections Specification allows additional fields in Edges. Currently it is not possible to have custom edge fields since the package directly returns
edges: nodes.map((node) => ({ cursor: encodeCursor(node, options), node })), in findManyCursorConnection function.
I have custom edge fields in some connections and I basically duplicated your implementation to implement those connections.

I think this could be a useful feature for this package. What do you think?

Thank you for your work, it really helped me.

Querying over a relationship table

Hey team, how is it going?

Great job on this library! I've been playing around with it and it will make my life easy peasy! :)

I have a question though: I have a chat project and we have a connection for chats that user is currently a member, and for performance issues I need to query over a relationship table instead of the chat table itself... model would be something like:
chat <-> chat_user <-> user

I didn't fully understand what would be the right implementation if I want to query over chat_user table but returning a connection of chats instead, can you help me with that?

Here's a simplified version of my schema.prisma: (hiding a bunch of fields just for demonstration purposes)

model chat {
  id                        String               @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  name                      String?
  image_url                 String?
}

model chat_user {
  chat_id                 String    @db.Uuid
  user_id                 String    @db.Uuid
  last_opened             DateTime  @default(now()) @db.Timestamptz(6)
  created_at              DateTime? @default(now()) @db.Timestamptz(6)
  is_admin                Boolean   @default(false)
}

model user {
  id                 String               @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  first_name         String
  last_name          String
}

Thanks so much!

Handling Exception when Query Error in Callback Functions

Hi, team

I wonder about the function findManyCursorConnection that doesn't support handling outside the try catch block that I want to use with custom exception handling when the user passes any malformed query to the backend. Here is an example of my statement that not working with try catch below:

image

Thank you!

Make `nodes` an optional return

It would be nice to be able to opt out of the nodes return value for use-cases where it's not needed. Requires a different function return type based on an options property.

Workaround

const result = await findManyCursorConnection(/* ... */)
delete result.nodes

Original conversion #432 (comment) and following.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Repository problems

These problems occurred while renovating this repository. View logs.

  • WARN: Encrypted value is using deprecated PKCS1 padding, please change to using PGP encryption.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/push.yml
  • actions/checkout v4
  • actions/setup-node v4
  • buildjet/cache v4
  • codecov/codecov-action v4
npm
package.json
  • graphql-fields 2.0.3
  • @devoxa/eslint-config 3.0.11
  • @devoxa/prettier-config 2.0.3
  • @prisma/client 5.13.0
  • @swc/core 1.5.0
  • @swc/jest 0.2.36
  • @types/graphql-fields 1.3.9
  • @types/jest 29.5.12
  • eslint 8.57.0
  • jest 29.7.0
  • prettier 3.2.5
  • prisma 5.13.0
  • typescript 5.4.5
  • @prisma/client ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

CustomEdge generates typescript error

I use prisma-relay-cursor-connection with my NestJS backend, I created a function

async findCategories(): Promise<StoryCategoryConnection> {
    return await findManyCursorConnection(
      (x) =>
        this.prisma.storyCategory.findMany({
          ...x,
          include: {
            stories: { take: 1, include: { media: true }, orderBy: { updatedAt: 'desc' } },
          },
          orderBy: { updatedAt: 'desc' },
        }),
      () => this.prisma.storyCategory.count(),
      null,
      {
        recordToEdge: ({ stories, ...record }) => ({ node: { ...record, thumbnail: stories[0]?.media } }),
      },
    );
  }

The problem is I get an error in recordToEdge function where I add thumbnail that is a media of the last updated relation story. The error says

Object literal may only specify known properties, and 'thumbnail' does not exist in type '{ stories: ({ media: { id: string; createdAt: Date; updatedAt: Date; name: string; mimeType: string; url: string; frontId: string; backId: string; proofOfAddressId: string; proofOfFundsId: string; storyId: string; }; } & { ...; })[]; } & { ...; }'.ts(2353)
interfaces.d.ts(28, 5): The expected type comes from property 'node' which is declared here on type 'Omit<Edge<{ stories: ({ media: { id: string; createdAt: Date; updatedAt: Date; name: string; mimeType: string; url: string; frontId: string; backId: string; proofOfAddressId: string; proofOfFundsId: string; storyId: string; }; } & { ...; })[]; } & { ...; }>, "cursor">'

Error disappears when I return the list of stories in recordToEdge function, but I don't want to do it, I can also fix that passing
<any, StoryCategory> as an TS argument to findManyCursorConnection but I don't want to use any and Prisma doesn't generate relations as types in e.g. StoryCategory type from prisma schema so I can't pass PrismaStoryCategory as argument instead of any because generic function doesn't see relation and I don't want to create more types than I must

It would be lovely if I could pass type I want as a first argument in findManyCursorConnection or pass it anywhere it would be possible to strictly type my edge type

btw. this problem is weird because I also did it in another function

return await findManyCursorConnection(
      (x) =>
        this.prisma.game.findMany({
          ...x,
          where,
          orderBy: { [field]: direction },
          include: { gamesOnCountries: { include: { country: true } }, category: true },
        }),
      () => this.prisma.game.count({ where }),
      { after, first, before, last },
      {
        recordToEdge: (record) => ({
          node: { ...record, countries: record.gamesOnCountries.map(({ country }) => country) },
        }),
      },
    );

And I don't return gamesOnCountries relation and typescript doesn't scream about it's not present in returned object

Another thing I spotted is that when I use recordToEdge function, type returned from findManyCursorConnection doesn't modify type returned as node, it's still a type returned from prisma query

Thank you for this library

Hi!

Just wanted to say thank you for this library! As a heavy user of apollo-client, I wanted to try Relay for a while, and the thought of implementing relay-style pagination was a big mental drain... Until I found your library! :)

Thanks again

Get count only?

Would it be possible to only bring back the count and not have to grab any records? I currently have to pass either first or last with a positive value. I was wondering if it would be possible to pass 0 so I could only bring back count?

[Question] Can I use numeric value for cursor??

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

interface TestObject {
  id: number;
}

const result = await findManyCursorConnection<TestObject, TestObject>(
  (args) => tests.findMany(args),
  () => tests.count(),
  { first: {first}, after: {after} }
)

I want use numeric value for unique value
For that, I set my own TestObject type when using findManyCursorConnection
But, after is always needed string value
Can I use numeric unique?

GraphQL dependency on 2.1.0

2.1.0:

Starting compilation in watch mode...

node_modules/@devoxa/prisma-relay-cursor-connection/dist/src/interfaces.d.ts:1:36 - error TS2307: Cannot find module 'graphql' or its corresponding type declarations.

1 import { GraphQLResolveInfo } from 'graphql';

This doesn't happen in 2.0.2.

Suggestion: order by cursor?

Hi there,

I am new to cursor-based pagination, especially relay-style. However, as I understand, it is mandatory to order by the cursor to get predictable results, and this is by default not added to the sorting of the prisma query. Would it be an idea to add order by default, as I feel like it is assumable you want this when using this library?

Override Prisma TypeGraphQL generated array properties?

Hi, thanks for the library!

I have an auto generated ObjectType like the following:

@ObjectType()
export class Product {
  @Field()
  id!: string;

  @Field(() => Price[])
  prices!: Price[]
}

I would like to make prices a connection, but if I do the following TypeScript complains with Property 'prices' in type 'ProductNode' is not assignable to the same property in base type 'Product'.

@ObjectType()
export class ProductNode extends Product {
  @Field(() => ProductPriceConnection)
  prices!: ProductPriceConnection;
}

I also get the same error when I define the ResolverInterface.

Is there a canonical way to address this class of issues or the only way is to use implements rather than extends and re-define the whole object type manually?

Thanks.

Customize Connection

Is there a way to customize the Connection object?

I would like to add a pageCursors property with cursors resolved to page numbers to assist in instances of traditional pagination, similar to the approach described in this article.

...or at least play with it, anyway. I suspect a hybrid approach which could alternately use offsets instead of cursors when a page number is supplied would probably be the most efficient in the short term :)

Prisma aggregate values are being stripped from the response

Hello ๐Ÿ‘‹

Thanks for this awesome lib, I was looking into implement this functionality by hand when I discovered it.
While playing with it I realised I was missing the _count and _avg from the response.

Here's the normal query which works fine:

course.findMany({
    orderBy: {
      createdAt: "desc",
    },
    where,
    include: {
      _count: {
        select: {
          lesson: true,
        },
      },
    },
  });

Here's the query using the plugin:

await findManyCursorConnection<
      Course,
      Pick<Prisma.CourseWhereUniqueInput, "eid">
    >(
      (args) => {
        return client.course.findMany({
          ...args,
          orderBy: {
            createdAt: "desc",
          },
          where,
          include: {
            _count: {
              select: {
                lesson: true,
              },
            },
          },
        });
      },
      () => client.course.count({ where }),
      { first, after },
      {
        getCursor: (record) => ({ eid: record.eid }),
        encodeCursor,
        decodeCursor,
      }
    );

image

Is this something you're looking to add in the future? I can also have a look into it when I get some time.

Thanks ๐Ÿ™

Broken when using orderBy

Hey thanks for that library. Seems that I found an bug: when using with orderBy I only get a small set if data (16 from 101 entries). How can that be? How to fix that?

const a = await findManyCursorConnection(
  (args) => {
    return this.prisma.story.findMany({
      ...args,
      where,
      orderBy: orderBy ? { [orderBy.field]: orderBy.direction } : undefined,
      include: { authors: true, sources: true, topics: true, mood: true },
    });
  },
  () => this.prisma.story.count({ where }),
  { first, last, before, after }
);

Create pagination with prisma-relay-cursor-connection id is number

I want to create pagination using prisma-relay-cursor-connection I'm using Postgresql with prisma in nestjs app
The id is number how we can handle pagination in this case

 async GetPage() {
    return await findManyCursorConnection(
      args => this.Prisma.rue.findMany(args),
      () => this.Prisma.rue.count(),
      {},
     
    );
  }

TS Erorro :
Argument of type 'PrismaFindManyArguments<{ id: string; }>' is not assignable to parameter of type '{ select?: RueSelect; include?: RueInclude; where?: RueWhereInput; orderBy?: Enumerable; cursor?: RueWhereUniqueInput; take?: number; skip?: number; distinct?: Enumerable<...>; }'.
The types of 'cursor.id' are incompatible between these types.
Type 'string' is not assignable to type 'number'.ts(2345)

return type of `findManyCursorConnection` is incorrect

We recently added ability to support custom edge fields. However, return type of findManyCursorConnection function is still Promise<Connection<Node>>. I think we should update it to reflect those changes. I will open a PR soon but wanted to ask if this was intended.

Types not matching, even though I follow the documentation

Problem

I want to use the library like the documentation instructs me to, but the types don't match.
Is the library up to date with the latest Prisma Client? (or am I doing something wrong?)
I'm using NestJS.

Versions

{
  "dependencies": {
    "@devoxa/prisma-relay-cursor-connection": "^2.0.3"
    "@prisma/client": "3.7.0"
  },
  "devDependencies": {
    "prisma": "^3.7.0"
  },
}

Error

src/apps/apps.service.ts:30:9 - error TS2322: Type 'PrismaPromise<App[]>' is not assignable to type 'Promise<{ id: string; }[]>'.
  Types of property 'then' are incompatible.
    Type '<TResult1 = App[], TResult2 = never>(onfulfilled?: (value: App[]) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<...>' is not assignable to type '<TResult1 = { id: string; }[], TResult2 = never>(onfulfilled?: (value: { id: string; }[]) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<...>'.
      Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
        Types of parameters 'value' and 'value' are incompatible.
          Type 'App[]' is not assignable to type '{ id: string; }[]'.
            Type 'App' is not assignable to type '{ id: string; }'.
              Types of property 'id' are incompatible.
                Type 'number' is not assignable to type 'string'.

30         this.prisma.app.findMany({
           ~~~~~~~~~~~~~~~~~~~~~~~~~~
31           ...args,
   ~~~~~~~~~~~~~~~~~~
32           where
   ~~~~~~~~~~~~~~~
33         }),
   ~~~~~~~~~~

  node_modules/@devoxa/prisma-relay-cursor-connection/dist/src/index.d.ts:7:73
    7 }, Node = Record, CustomEdge extends Edge<Node> = Edge<Node>>(findMany: (args: PrismaFindManyArguments<Cursor>) => Promise<Record[]>, aggregate: () => Promise<number>, args?: ConnectionArguments, pOptions?: Options<Record, Cursor, Node, CustomEdge>): Promise<Connection<Node, CustomEdge>>;
                                                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from the return type of this signature.

src/apps/apps.service.ts:30:34 - error TS2345: Argument of type '{ where: Prisma.AppWhereInput; cursor?: { id: string; }; take?: number; skip?: number; }' is not assignable to parameter of type '{ select?: AppSelect; include?: AppInclude; where?: AppWhereInput; orderBy?: Enumerable<AppOrderByWithRelationInput>; cursor?: AppWhereUniqueInput; take?: number; skip?: number; distinct?: Enumerable<...>; }'.
  The types of 'cursor.id' are incompatible between these types.
    Type 'string' is not assignable to type 'number'.

30         this.prisma.app.findMany({
                                    ~
31           ...args,
   ~~~~~~~~~~~~~~~~~~
32           where
   ~~~~~~~~~~~~~~~
33         }),
   ~~~~~~~~~

[10:04:17 AM] Found 2 errors. Watching for file changes.

Code

  async findAll(filterAppsDto: FilterAppsDto, baseFilterDto: BaseFilterDto) {
    const { name, bundleIdentifier } = filterAppsDto;

    const where: Prisma.AppWhereInput = {
      AND: [{ name: { contains: name } }, { bundleIdentifier: { contains: bundleIdentifier } }]
    };

    return findManyCursorConnection(
      args =>
        this.prisma.app.findMany({
          ...args,
          where
        }),
      () =>
        this.prisma.app.count({
          where
        }),
      baseFilterDto,
      {
        recordToEdge: app => ({
          node: app
        })
      }
    );
  }

Empty return

I was trying to query all records but it was returning an empty object

findPage() {
    return findManyCursorConnection(
      (args) =>
        this.prisma.product.findMany({ ...args, where: { published: true } }),
      () => this.prisma.product.count(),
      {},
    );
  }

That was the SQL request automatically generated by Prisma client

prisma:query

SELECT `main`.`Product`.`id`, `main`.`Product`.`createdAt`, `main`.`Product`.`updatedAt`, `main`.`Product`.`name`, `main`.`Product`.`description`, `main`.`Product`.`price`, `main`.`Product`.`sku`, `main`.`Product`.`published` FROM `main`.`Product` WHERE `main`.`Product`.`id` = ? LIMIT ? OFFSET ?

Support pageCursors for pagination with links to pages

From the end-user's code it is not possible to implement a user-interface like:

image

Without effectively re-creating this library (or drastically over-fetching).


For authors interested in adding this to the library, please first have a look at the conversation and review comments in #366.

[Question] 'first' is not working properly

  interface Doctors {
    Id: number;
    Name: string;
    Rating: number;
  }

  async findCursorByRatingAsync(
    after?: string,
    before?: string,
    first?: number,
    last?: number,
  ): Promise<Connection<Doctors, Edge<Doctors>>> {
    const baseArgs: any = {
      where: {
        Name: '11',
      },
      orderBy: {
        Rating: 'asc',
      },
    };

    const results = await findManyCursorConnection<Doctors, { id: number }>(
      () => this.prismaSvc.doctors.findMany(baseArgs),
      () => this.prismaSvc.doctors.count({ where: baseArgs.where }),
      { first, last, before, after },
      {
        getCursor: (record) => ({ id: record.Id }),
        encodeCursor: (cursor) => Buffer.from(JSON.stringify(cursor)).toString('base64'),
        decodeCursor: (cursor) => JSON.parse(Buffer.from(cursor, 'base64').toString('ascii')),
        recordToEdge: (record): DoctorPreviewCursorPage => ({
          node: toDoctorPreviewDTO(record),
        }),
      },
    );

    return results;
  }

Here is my code
I have Doctors object with numeric unique Id
for that, I used custom cursor features and
For aggregating some variables, I used recordToEdge for making DTO

In this situations,
When I have items more than 10 and set first: 3,
All 10 items are displayed in edges
I expected just 3 items displayed
What is wrong?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.