简体   繁体   English

使用基于 GraphQL Cursor 的分页避免代码重复

[英]Avoid Code Duplication with GraphQL Cursor based Pagination

I've been looking all over for an answer to this and I've been banging my head on the wall.我一直在寻找这个问题的答案,但我一直在用头撞墙。 I wrote a cursor based pagination example that works well with graphql and the thing is I thought I would do the same thing with authors, that I did with books and the only way I can figure out how to do this is to completely duplicate everything.我写了一个基于光标的分页示例,它与 graphql 配合得很好,问题是我想我会对作者做同样的事情,我对书籍所做的事情,我能弄清楚如何做到这一点的唯一方法是完全复制所有内容。 On the root query there is quite a long chunk of code handling the pagination and I would hate to do that all over for the authors endpoint but I can't seem to find a way to do this while reusing the code在根查询上有很长一段代码处理分页,我不想为作者端点做这一切,但我似乎无法在重用代码的同时找到一种方法来做到这一点

Here is the code这是代码

const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const {
    GraphQLSchema,
    GraphQLObjectType,
    GraphQLString,
    GraphQLList,
    GraphQLInt,
    GraphQLNonNull
} = require('graphql')

const {
    PageType,
    convertNodeToCursor,
    convertCursorToNodeId
} = require('./pagination')

const app = express()

const authors = [
    { id: 1, name: "Author 1"},
    { id: 2, name: "Author 2"},
    { id: 3, name: "Author 3"}
]

const books = [
    { id: 1, title: "Book 1", authorId: 1 },
    { id: 2, title: "Book 2", authorId: 1 },
    { id: 3, title: "Book 3", authorId: 1 },
    { id: 4, title: "Book 4", authorId: 2 },
    { id: 5, title: "Book 5", authorId: 2 },
    { id: 6, title: "Book 6", authorId: 2 },
    { id: 7, title: "Book 7", authorId: 3 },
    { id: 8, title: "Book 8", authorId: 3 },
    { id: 9, title: "Book 9", authorId: 3 }
]




const Book = new GraphQLObjectType({
    name: 'Book',
    description: 'this is a book',
    fields: () => ({
        id: { type: GraphQLNonNull(GraphQLInt) },
        title: { type: GraphQLNonNull(GraphQLString) },
        authorId: { type: GraphQLNonNull(GraphQLInt) },
        author: {
            type: Author,
            resolve: ({authorId}) => {
                return authors.find(author => author.id === authorId)
            }
        }
    })
})

const Author = new GraphQLObjectType({
    name: 'Author',
    description: 'this represents the author of a book',
    fields: () => ({
        id: { type: GraphQLNonNull(GraphQLInt) },
        name: { type: GraphQLNonNull(GraphQLString) },
        books: { 
            type: GraphQLList(Book),
            resolve: ({id}) => {
                return books.filter(book => book.authorId === id)
            }
        }
    })
})



const RootQuery = new GraphQLObjectType({
    name: 'RootQueryType',
    description: 'this is the root query',
    fields: () => ({
        book: {
            type: Book,
            description: 'a single book',
            args: {
                id: { type: GraphQLInt }
            },
            resolve: (_, { id }) => {
                return books.find(book => book.id === id)
            }
        },
        author: {
            type: Author,
            description: 'a single author',
            args: {
                id: { type: GraphQLInt },
            },
            resolve: (_, { id }) => {
                return authors.find(author => author.id === id)
            }
        },
        books: {
            type: PageType(Book),
            description: 'a list of books',
            args: {
                first: { type: GraphQLInt },
                afterCursor: { type: GraphQLString }
            },
            resolve: (_, { first, afterCursor }) => {
                let afterIndex = 0

                if (typeof afterCursor === 'string') {
                    let nodeId = convertCursorToNodeId(afterCursor)
                    let nodeIndex = books.findIndex(book => book.id === nodeId)
                    if (nodeIndex >= 0) {
                        afterIndex = nodeIndex + 1 
                    }
                }
                    
                const slicedData = books.slice(afterIndex, afterIndex + first)
                console.log('sliced data: ', slicedData)
                const edges = slicedData.map(node => ({
                    node,
                    cursor: convertNodeToCursor(node)
                }))

                let startCursor = null
                let endCursor = null
                if (edges.length > 0) {
                    startCursor = convertNodeToCursor(edges[0].node)
                    endCursor = convertNodeToCursor(edges[edges.length - 1].node)
                }

                let hasNextPage = books.length > afterIndex + first

                return {
                    totalCount: books.length,
                    edges,
                    pageInfo: {
                        startCursor,
                        endCursor,
                        hasNextPage
                    }
                }
            }

        }
    })
})

const schema = new GraphQLSchema({
    query: RootQuery
})

app.use('/graphql', graphqlHTTP({
    schema,
    graphiql: true
}))

app.listen(3000, () => console.log('app running at http://localhost:3000/graphql'))

and I handle the pagination in another file here:我在这里处理另一个文件中的分页:

const {
    GraphQLString,
    GraphQLInt,
    GraphQLBoolean,
    GraphQLObjectType,
    GraphQLList,
} = require('graphql')


const Edge = (itemType) => {
    return new GraphQLObjectType({
        name: 'EdgeType',
        fields: () => ({
            node: { type: itemType },
            cursor: { type: GraphQLString }
        })
    })
}

const PageInfo = new GraphQLObjectType({
    name: 'PageInfoType',
    fields: () => ({
        startCursor: { type: GraphQLString },
        endCursor: { type: GraphQLString },
        hasNextPage: { type: GraphQLBoolean }
    })
})

const PageType = (itemType) => {
    return new GraphQLObjectType({
        name: 'PageType',
        fields: () => ({
            totalCount: { type: GraphQLInt },
            edges: { type: new GraphQLList(Edge(itemType)) },
            pageInfo: { type: PageInfo }
        })
    })
}



const convertNodeToCursor = (node) => {
    // Encoding the cursor value to Base 64 as suggested in GraphQL documentation
    return Buffer.from((node.id).toString()).toString('base64')
}

const convertCursorToNodeId = (cursor) => {
    // Decoding the cursor value from Base 64 to integer
    return parseInt(Buffer.from(cursor, 'base64').toString('ascii'))
}

module.exports = {
    PageType,
    convertNodeToCursor,
    convertCursorToNodeId
}

Now if I copy and paste the books endpoint and change it to authors, and change the type to PageType(Author) then I get another error:现在,如果我复制并粘贴书籍端点并将其更改为作者,并将类型更改为 PageType(Author) 那么我会收到另一个错误:

Schema must contain uniquely named types but contains multiple types named "PageType".

So this clearly isn't a solution either所以这显然也不是解决方案

You cannot have one EdgeType that contains Author s and another EdgeType that contains Books .你不能有一个EdgeType包含Author S和其他EdgeType包含Books Instead, you will need one AuthorEdge and one BookEdge type.相反,您将需要一个AuthorEdge和一个BookEdge类型。

The same holds for the PageType - there can't be two different types with different fields but the same name. PageType也是PageType ——不能有两种不同类型的不同字段但名称相同。

The solution is relatively simple though - if you dynamically generated these types in a function, also name them dynamically:不过,解决方案相对简单 - 如果您在函数中动态生成这些类型,也可以动态命名它们:

const Edge = (itemType) => {
    return new GraphQLObjectType({
        name: itemType.name + 'Edge',
//            ^^^^^^^^^^^^^^^^^^^^^^
        fields: () => ({
            node: { type: itemType },
            cursor: { type: GraphQLString }
        })
    })
}

const PageInfo = new GraphQLObjectType({
    name: 'PageInfo',
    fields: () => ({
        startCursor: { type: GraphQLString },
        endCursor: { type: GraphQLString },
        hasNextPage: { type: GraphQLBoolean }
    })
})

const PageType = (itemType) => {
    return new GraphQLObjectType({
        name: itemType.name + 'sPage',
//            ^^^^^^^^^^^^^^^^^^^^^^^
        fields: () => ({
            totalCount: { type: GraphQLInt },
            edges: { type: new GraphQLList(Edge(itemType)) },
            pageInfo: { type: PageInfo }
        })
    })
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM