GraphQL 游标连接规范

本规范旨在为 GraphQL 客户端提供一种选择,使其能够通过 GraphQL 服务器一致地处理 分页最佳实践,并支持相关元数据。本规范建议将此模式称为“连接”,并以标准化的方式公开它们。

在查询中,连接模型提供了一种标准机制来切片和分页结果集。

在响应中,连接模型提供了一种标准方法来提供游标,以及一种告知客户端何时有更多结果的方法。

以下查询是所有四种方法的示例。

{
  user {
    id
    name
    friends(first: 10, after: "opaqueCursor") {
      edges {
        cursor
        node {
          id
          name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
  }
}

在本例中,friends 是一个连接。该查询演示了上述四个功能。

  • 使用 friendsfirst 参数进行切片。这要求连接返回 10 个朋友。
  • 使用 friendsafter 参数进行分页。我们传入了一个游标,因此我们要求服务器返回该游标之后的友谊。
  • 对于连接中的每个边,我们都请求了一个游标。此游标是一个不透明的字符串,正是我们将在 after 参数中传递的,以便从该边之后开始分页。
  • 我们要求 hasNextPage;这将告诉我们是否有更多边可用,或者我们是否已到达此连接的末尾。

本规范的这一部分描述了连接周围的正式要求。

1保留类型

符合本规范的 GraphQL 服务器必须保留某些类型和类型名称,以支持连接的分页模型。特别是,本规范为以下类型创建了指南。

2连接类型

任何名称以“Connection”结尾的类型在本规范中都被视为连接类型。连接类型必须是 GraphQL 规范“类型系统”部分中定义的“对象”。

2.1字段

连接类型必须具有名为 edgespageInfo 的字段。它们可以具有与连接相关的其他字段,这取决于架构设计者认为合适。

2.1.1

“连接类型”必须包含一个名为 edges 的字段。此字段必须返回一个列表类型,该列表类型包装一个边类型,其中边类型的要求在下面的“边类型”部分中定义。

2.1.2PageInfo

“连接类型”必须包含一个名为 pageInfo 的字段。此字段必须返回一个非空 PageInfo 对象,如下面的“PageInfo”部分中所定义。

2.2内省

如果 ExampleConnection 存在于类型系统中,它将是一个连接,因为它的名称以“Connection”结尾。如果此连接的边类型名为 ExampleEdge,那么正确实现上述要求的服务器将接受以下内省查询,并返回提供的响应。

{
  __type(name: "ExampleConnection") {
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

返回

{
  "data": {
    "__type": {
      "fields": [
        // May contain other items
        {
          "name": "pageInfo",
          "type": {
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "PageInfo",
              "kind": "OBJECT"
            }
          }
        },
        {
          "name": "edges",
          "type": {
            "name": null,
            "kind": "LIST",
            "ofType": {
              "name": "ExampleEdge",
              "kind": "OBJECT"
            }
          }
        }
      ]
    }
  }
}

3边类型

连接类型 edges 字段以列表形式返回的类型在本规范中被视为边类型。边类型必须是 GraphQL 规范“类型系统”部分中定义的“对象”。

3.1字段

边类型必须具有名为 nodecursor 的字段。它们可以具有与边相关的其他字段,这取决于架构设计者认为合适。

3.1.1节点

“边类型”必须包含一个名为 node 的字段。此字段必须返回标量、枚举、对象、接口、联合或这些类型之一的非空包装器。值得注意的是,此字段不能返回列表。

注意 该命名与后面部分中描述的“节点”接口和“节点”根字段相呼应。符合规范的客户端如果此字段返回实现 Node 的对象,则可以执行某些优化,但是,这并非符合规范的严格要求。

3.1.2游标

“边类型”必须包含一个名为 cursor 的字段。此字段必须返回一个序列化为字符串的类型;这可能是一个字符串,一个围绕字符串的非空包装器,一个序列化为字符串的自定义标量,或者一个围绕序列化为字符串的自定义标量的非空包装器。

此字段返回的任何类型在本文档的其余部分中都将被称为游标类型

客户端应该将此字段的结果视为不透明,但将在下面的“参数”部分中将其传回服务器。

3.2内省

如果 ExampleEdge 是我们架构中的一个边类型,它返回“Example”对象,那么正确实现上述要求的服务器将接受以下内省查询,并返回提供的响应。

{
  __type(name: "ExampleEdge") {
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

返回

{
  "data": {
    "__type": {
      "fields": [
        // May contain other items
        {
          "name": "node",
          "type": {
            "name": "Example",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "cursor",
          "type": {
            // This shows the cursor type as String!, other types are possible
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "String",
              "kind": "SCALAR"
            }
          }
        }
      ]
    }
  }
}

4参数

返回连接类型的字段必须包含正向分页参数、反向分页参数或两者。这些分页参数允许客户端在返回边集之前将其切片。

4.1正向分页参数

要启用正向分页,需要两个参数。

  • first 接受一个非负整数。
  • after 接受游标类型,如 cursor 字段部分中所述。

服务器应该使用这两个参数来修改连接返回的边,返回 after 游标之后的边,并且最多返回 first 个边。

您通常应该为 after 传递上一页最后一个边的 cursor

4.2反向分页参数

要启用反向分页,需要两个参数。

  • last 接受一个非负整数。
  • before 接受游标类型,如 cursor 字段部分中所述。

服务器应该使用这两个参数来修改连接返回的边,返回 before 游标之前的边,并且最多返回 last 个边。

您通常应该为 before 传递下一页第一个边的 cursor

4.3边顺序

您可以根据您的业务逻辑任意排序边,并且可以根据本规范未涵盖的其他参数确定排序。但排序必须在页面之间保持一致,重要的是,使用 first/after 时边的排序应与使用 last/before 时相同,其他所有参数相等。 在使用 last/before 时不应反转。更正式地说。

  • 当使用 before: cursor 时,最靠近 cursor 的边必须在结果 edges 中排在最后
  • 当使用 after: cursor 时,最靠近 cursor 的边必须在结果 edges 中排在最前

4.4分页算法

为了确定要返回哪些边,连接会评估 beforeafter 游标以过滤边,然后评估 first 来切片边,然后评估 last 来切片边。

注意 强烈建议不要同时包含 firstlast 的值,因为这很可能会导致令人困惑的查询和结果。PageInfo 部分对此进行了更详细的介绍。

更正式地说。

EdgesToReturn(allEdges, before, after, first, last)
  1. edges 为调用 ApplyCursorsToEdges(allEdges, before, after) 的结果。
  2. 如果设置了 first
    1. 如果 first 小于 0
      1. 抛出错误。
    2. 如果 edges 的长度大于 first
      1. 通过从 edges 的末尾删除边来将 edges 的长度切片为 first
  3. 如果设置了 last
    1. 如果 last 小于 0
      1. 抛出错误。
    2. 如果 edges 的长度大于 last
      1. 通过从 edges 的开头删除边来将 edges 的长度切片为 last
  4. 返回 edges
ApplyCursorsToEdges(allEdges, before, after)
  1. 初始化 edgesallEdges
  2. 如果设置了 after
    1. afterEdgeedges 中其 cursor 等于 after 参数的边。
    2. 如果存在 afterEdge
      1. 删除 edges 中在 afterEdge 之前和包括 afterEdge 在内的所有元素。
  3. 如果设置了 before
    1. beforeEdgeedges 中其 cursor 等于 before 参数的边。
    2. 如果存在 beforeEdge
      1. 删除 edges 中在 beforeEdge 之后和包括 beforeEdge 在内的所有元素。
  4. 返回 edges

5PageInfo

服务器必须提供一个名为 PageInfo 的类型。

5.1字段

PageInfo 必须包含字段 hasPreviousPagehasNextPage,这两个字段都返回非空布尔值。它还必须包含字段 startCursorendCursor,这两个字段都返回不透明字符串。如果没有任何结果,则字段 startCursorendCursor 可以为 null。

hasPreviousPage 用于指示在客户端参数定义的集合之前是否存在更多边。如果客户端正在使用 last/before 进行分页,那么如果存在之前的边,则服务器必须返回 true,否则返回 false。如果客户端正在使用 first/after 进行分页,那么如果在 after 之前存在边,则客户端可以返回 true(如果它能够高效地做到这一点),否则可以返回 false。更正式地说。

HasPreviousPage(allEdges, before, after, first, last)
  1. 如果设置了 last
    1. edges 为调用 ApplyCursorsToEdges(allEdges, before, after) 的结果。
    2. 如果 edges 包含超过 last 个元素,则返回 true,否则返回 false
  2. 如果设置了 after
    1. 如果服务器可以有效地确定 after 之前存在元素,则返回 true
  3. 返回 false

hasNextPage 用于指示在客户端参数定义的集合之后是否存在更多边。如果客户端使用 first/after 进行分页,则如果存在更多边,服务器必须返回 true,否则返回 false。如果客户端使用 last/before 进行分页,则如果存在比 before 更远的边,客户端可能会返回 true,如果它可以高效地做到这一点,否则可能会返回 false。更正式地说

HasNextPage(allEdges, before, after, first, last)
  1. 如果设置了 first
    1. edges 为调用 ApplyCursorsToEdges(allEdges, before, after) 的结果。
    2. 如果 edges 包含超过 first 个元素,则返回 true,否则返回 false
  2. 如果设置了 before
    1. 如果服务器可以有效地确定 before 之后存在元素,则返回 true
  3. 返回 false
注意firstlast 都包含时,这两个字段都应该根据上述算法设置,但它们与分页相关的意义变得不清楚。这是不鼓励使用 firstlast 进行分页的原因之一。

startCursorendCursor 必须分别是 edges 中第一个和最后一个节点对应的游标。

注意 由于此规范是针对 Relay Classic 创建的,因此值得注意的是 Relay Legacy 没有定义 startCursorendCursor,而是依赖于选择每个边的 cursor;Relay Modern 开始选择 startCursorendCursor 来节省带宽(因为它不使用任何中间游标)。

5.2内省

正确实现上述要求的服务器将接受以下内省查询,并返回提供的响应

{
  __type(name: "PageInfo") {
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

返回

{
  "data": {
    "__type": {
      "fields": [
        // May contain other fields.
        {
          "name": "hasNextPage",
          "type": {
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "Boolean",
              "kind": "SCALAR"
            }
          }
        },
        {
          "name": "hasPreviousPage",
          "type": {
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "Boolean",
              "kind": "SCALAR"
            }
          }
        },
        {
          "name": "startCursor",
          "type": {
            "name": "String",
            "kind": "SCALAR",
            "ofType": null
          }
        },
        {
          "name": "endCursor",
          "type": {
            "name": "String",
            "kind": "SCALAR",
            "ofType": null
          }
        }
      ]
    }
  }
}

§索引

  1. ApplyCursorsToEdges
  2. EdgesToReturn
  3. HasNextPage
  4. HasPreviousPage
  1. 1保留类型
  2. 2连接类型
    1. 2.1字段
      1. 2.1.1
      2. 2.1.2PageInfo
    2. 2.2内省
  3. 3边类型
    1. 3.1字段
      1. 3.1.1节点
      2. 3.1.2游标
    2. 3.2内省
  4. 4参数
    1. 4.1正向分页参数
    2. 4.2反向分页参数
    3. 4.3边顺序
    4. 4.4分页算法
  5. 5PageInfo
    1. 5.1字段
    2. 5.2内省
  6. §索引