GraphQL 指標連線規範
本規範旨在為 GraphQL 客戶端提供一種選項,以便透過 GraphQL 伺服器一致地處理分頁最佳實務,並支援相關的中繼資料。本規範建議將此模式稱為「連線 (Connections)」,並以標準化的方式公開。
在查詢中,連線模型為切片和分頁結果集提供了一種標準機制。
在回應中,連線模型提供了一種提供指標的標準方式,以及一種告知客戶端何時有更多結果可用的方式。
以下是上述所有四種功能的查詢範例
{
user {
id
name
friends(first: 10, after: "opaqueCursor") {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
}
在這個範例中,friends
是一個連線。該查詢示範了上述四個功能
- 切片是透過
friends
的first
引數來完成。這要求連線傳回 10 位朋友。 - 分頁是透過
friends
的after
引數來完成。我們傳入了一個指標,因此我們要求伺服器傳回該指標之後的朋友。 - 對於連線中的每個邊緣,我們都要求一個指標。這個指標是不透明的字串,正是我們傳遞給
after
引數以從此邊緣之後開始分頁的內容。 - 我們要求了
hasNextPage
;這會告訴我們是否有更多邊緣可用,或者我們是否已到達此連線的末尾。
本規範的這一節描述了有關連線的正式要求。
1保留類型
符合本規範的 GraphQL 伺服器必須保留某些類型和類型名稱,以支援連線的分頁模型。特別是,本規範為以下類型建立了指南
- 任何名稱以「Connection」結尾的物件。
- 一個名為
PageInfo
的物件。
2連線類型
根據本規範,任何名稱以「Connection」結尾的類型都被視為連線類型。連線類型必須是 GraphQL 規範「類型系統」章節中定義的「物件」。
2.1欄位
連線類型必須具有名為 edges
和 pageInfo
的欄位。它們可能具有與連線相關的其他欄位,如架構設計者認為合適。
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欄位
邊緣類型必須具有名為 node
和 cursor
的欄位。它們可能具有與邊緣相關的其他欄位,如架構設計者認為合適。
3.1.1節點
「邊緣類型」必須包含一個名為 node
的欄位。此欄位必須傳回 Scalar、Enum、Object、Interface、Union 或圍繞這些類型之一的非空值包裝器。值得注意的是,此欄位不能傳回列表。
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分頁演算法
為了確定要傳回哪些邊緣,連線會評估 before
和 after
指標來篩選邊緣,然後評估 first
來對邊緣進行切片,然後評估 last
來對邊緣進行切片。
更正式地說
- 令 edges 為呼叫 ApplyCursorsToEdges(allEdges, before, after) 的結果。
- 如果已設定 first
- 如果 first 小於 0
- 拋出錯誤。
- 如果 edges 的長度大於 first
- 將 edges 切片,使其長度為 first,方法是從 edges 的末尾移除邊緣。
- 如果 first 小於 0
- 如果已設定 last
- 如果 last 小於 0
- 拋出錯誤。
- 如果 edges 的長度大於 last
- 將 edges 切片,使其長度為 last,方法是從 edges 的開頭移除邊緣。
- 如果 last 小於 0
- 傳回 edges。
- 將 edges 初始化為 allEdges。
- 如果已設定 after
- 令 afterEdge 為 edges 中其 cursor 等於 after 參數的邊 (edge)。
- 如果 afterEdge 存在
- 移除 edges 中所有在 afterEdge 之前(包含 afterEdge)的元素。
- 如果設定了 before
- 令 beforeEdge 為 edges 中其 cursor 等於 before 參數的邊 (edge)。
- 如果 beforeEdge 存在
- 移除 edges 中所有在 beforeEdge 之後(包含 beforeEdge)的元素。
- 傳回 edges。
5PageInfo
伺服器必須提供一個名為 PageInfo
的型別。
5.1欄位
PageInfo
必須包含欄位 hasPreviousPage
和 hasNextPage
,兩者皆回傳非空 (non-null) 的布林值。它也必須包含欄位 startCursor
和 endCursor
,兩者皆回傳不透明的字串。如果沒有結果,欄位 startCursor
和 endCursor
可以為空值。
hasPreviousPage
用於指示在客戶端參數定義的集合之前是否存在更多的邊 (edge)。如果客戶端使用 last
/before
進行分頁,則如果存在先前的邊,伺服器必須回傳 true,否則回傳 false。如果客戶端使用 first
/after
進行分頁,則如果在 after
之前存在邊,客戶端可以回傳 true(如果它可以有效率地執行此操作),否則可以回傳 false。更正式地說:
- 如果已設定 last
- 令 edges 為呼叫 ApplyCursorsToEdges(allEdges, before, after) 的結果。
- 如果 edges 包含超過 last 個元素,則回傳 true,否則回傳 false。
- 如果已設定 after
- 如果伺服器可以有效率地判斷在 after 之前存在元素,則回傳 true。
- 回傳 false。
hasNextPage
用於指示在客戶端參數定義的集合之後是否存在更多的邊 (edge)。如果客戶端使用 first
/after
進行分頁,則如果存在更進一步的邊,伺服器必須回傳 true,否則回傳 false。如果客戶端使用 last
/before
進行分頁,則如果存在更遠離 before
的邊,客戶端可以回傳 true(如果它可以有效率地執行此操作),否則可以回傳 false。更正式地說:
- 如果已設定 first
- 令 edges 為呼叫 ApplyCursorsToEdges(allEdges, before, after) 的結果。
- 如果 edges 包含超過 first 個元素,則回傳 true,否則回傳 false。
- 如果設定了 before
- 如果伺服器可以有效率地判斷在 before 之後存在元素,則回傳 true。
- 回傳 false。
startCursor
和 endCursor
必須分別是 edges
中第一個和最後一個節點對應的游標 (cursor)。
startCursor
和 endCursor
,而是依賴於選擇每個邊的 cursor
;Relay Modern 開始選擇 startCursor
和 endCursor
來節省頻寬(因為它不使用任何中間的游標)。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
}
}
]
}
}
}