GraphQL 指標連線規範

本規範旨在為 GraphQL 客戶端提供一種選項,以便透過 GraphQL 伺服器一致地處理分頁最佳實務,並支援相關的中繼資料。本規範建議將此模式稱為「連線 (Connections)」,並以標準化的方式公開。

在查詢中,連線模型為切片和分頁結果集提供了一種標準機制。

在回應中,連線模型提供了一種提供指標的標準方式,以及一種告知客戶端何時有更多結果可用的方式。

以下是上述所有四種功能的查詢範例

{
  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 的欄位。此欄位必須傳回 Scalar、Enum、Object、Interface、Union 或圍繞這些類型之一的非空值包裝器。值得注意的是,此欄位不能傳回列表。

注意 此命名與本規範後續章節中描述的「節點」介面和「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 切片,使其長度為 first,方法是從 edges 的末尾移除邊緣。
  3. 如果已設定 last
    1. 如果 last 小於 0
      1. 拋出錯誤。
    2. 如果 edges 的長度大於 last
      1. edges 切片,使其長度為 last,方法是從 edges 的開頭移除邊緣。
  4. 傳回 edges
ApplyCursorsToEdges(allEdges, before, after)
  1. edges 初始化為 allEdges
  2. 如果已設定 after
    1. afterEdgeedges 中其 cursor 等於 after 參數的邊 (edge)。
    2. 如果 afterEdge 存在
      1. 移除 edges 中所有在 afterEdge 之前(包含 afterEdge)的元素。
  3. 如果設定了 before
    1. beforeEdgeedges 中其 cursor 等於 before 參數的邊 (edge)。
    2. 如果 beforeEdge 存在
      1. 移除 edges 中所有在 beforeEdge 之後(包含 beforeEdge)的元素。
  4. 傳回 edges

5PageInfo

伺服器必須提供一個名為 PageInfo 的型別。

5.1欄位

PageInfo 必須包含欄位 hasPreviousPagehasNextPage,兩者皆回傳非空 (non-null) 的布林值。它也必須包含欄位 startCursorendCursor,兩者皆回傳不透明的字串。如果沒有結果,欄位 startCursorendCursor 可以為空值。

hasPreviousPage 用於指示在客戶端參數定義的集合之前是否存在更多的邊 (edge)。如果客戶端使用 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 用於指示在客戶端參數定義的集合之後是否存在更多的邊 (edge)。如果客戶端使用 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 中第一個和最後一個節點對應的游標 (cursor)。

註記 由於此規範是考慮到 Relay Classic 而建立的,因此值得注意的是,Relay Legacy 沒有定義 startCursorendCursor,而是依賴於選擇每個邊的 cursor;Relay Modern 開始選擇 startCursorendCursor 來節省頻寬(因為它不使用任何中間的游標)。

5.2內省 (Introspection)

正確實作上述要求的伺服器將接受以下內省查詢,並回傳提供的回應

{
  __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. §索引