การรัน Queries หลายรายการพร้อมกัน
การรัน Queries หลายรายการพร้อมกันการดำเนินการหลาย queries

การดำเนินการหลาย queries

Included in the “Power Extensions” bundle

รวมหลาย queries เข้าเป็น query เดียว แบ่งปันสถานะระหว่างกัน และดำเนินการตามลำดับที่ร้องขอ

คำอธิบาย

การดำเนินการหลาย queries จะรวมหลาย queries เข้าเป็น query เดียว โดยรับประกันว่าจะถูกดำเนินการตามลำดับที่ร้องขอเดียวกัน ออเปอเรชันสามารถสื่อสารสถานะระหว่างกันผ่านตัวแปรไดนามิก ซึ่งถูกคำนวณเพียงครั้งเดียวแต่สามารถอ่านได้หลายครั้งตลอดทั้งเอกสาร

query SomeQuery {
  id @export(as: "rootID")
}
 
query AnotherQuery
  @depends(on: "SomeQuery")
{
  _echo(value: $rootID )
}

ฟีเจอร์นี้มีประโยชน์หลายประการ:

  • ช่วยเพิ่มประสิทธิภาพ: แทนที่จะดำเนินการ query ต่อ GraphQL เซิร์ฟเวอร์ แล้วรอการตอบกลับ จากนั้นใช้ผลลัพธ์นั้นเพื่อดำเนินการ query อีกตัวหนึ่ง เราสามารถรวม queries เข้าด้วยกันเป็นหนึ่งเดียวและดำเนินการในคำขอเดียว จึงหลีกเลี่ยงความหน่วงจากการเชื่อมต่อ HTTP หลายครั้งได้
  • ช่วยให้เราจัดการ GraphQL queries ของเราเป็นออเปอเรชันแบบอะตอมมิก (หรือหน่วยตรรกะ) ที่ขึ้นต่อกัน และสามารถดำเนินการแบบมีเงื่อนไขโดยอิงจากผลลัพธ์ของออเปอเรชันก่อนหน้าได้

การดำเนินการหลาย queries แตกต่างจากการทำ query batching ซึ่ง GraphQL เซิร์ฟเวอร์ก็ดำเนินการหลาย queries ในคำขอเดียวเช่นกัน แต่ queries เหล่านั้นเพียงถูกดำเนินการทีละตัวต่อกันอย่างเป็นอิสระจากกัน

ไดเรกทีฟที่ถูกเปิดใช้งาน

เมื่อเปิดใช้งานการดำเนินการหลาย queries ไดเรกทีฟต่อไปนี้จะพร้อมใช้งานใน GraphQL schema:

  • @depends (ไดเรกทีฟออเปอเรชัน): เพื่อให้ออเปอเรชัน (ไม่ว่าจะเป็น query หรือ mutation) ระบุว่าออเปอเรชันอื่นใดต้องถูกดำเนินการก่อน
  • @export (ไดเรกทีฟฟิลด์): เพื่อเอ็กซ์พอร์ตค่าของฟิลด์บางตัวจาก query หนึ่งเป็นตัวแปรไดนามิก เพื่อใช้เป็นอินพุตให้กับฟิลด์หรือไดเรกทีฟใน query อีกตัวหนึ่ง
  • @exportFrom (ไดเรกทีฟฟิลด์): คล้ายกับ @export แต่ใช้เพื่อเอ็กซ์พอร์ตค่าของตัวแปรไดนามิกแบบมีขอบเขต (ที่ส่งผ่าน @passOnwards(as: "...") หรือ @applyField(passOnwardsAs: "..."))
  • @deferredExport (ไดเรกทีฟฟิลด์): คล้ายกับ @export แต่ใช้ร่วมกับ Multi-Field Directives

นอกจากนี้ ไดเรกทีฟ @include และ @skip ก็พร้อมใช้งานเป็นไดเรกทีฟออเปอเรชันด้วย (โดยปกติแล้วเป็นไดเรกทีฟฟิลด์เท่านั้น) และสามารถใช้เพื่อดำเนินการออเปอเรชันแบบมีเงื่อนไขหากเป็นไปตามเงื่อนไขบางอย่าง

@depends

เมื่อเอกสาร GraphQL มีหลายออเปอเรชัน เราจะระบุให้เซิร์ฟเวอร์ทราบว่าจะดำเนินการตัวใดผ่านพารามิเตอร์ URL ?operationName=... มิฉะนั้นออเปอเรชันสุดท้ายจะถูกดำเนินการ

เริ่มจากออเปอเรชันแรกนี้ เซิร์ฟเวอร์จะรวบรวมออเปอเรชันทั้งหมดที่ต้องดำเนินการ ซึ่งกำหนดโดยการเพิ่มไดเรกทีฟ depends(on: [...]) และดำเนินการตามลำดับที่สอดคล้องกันโดยเคารพการขึ้นต่อกัน

อาร์กิวเมนต์ไดเรกทีฟ operations รับอาร์เรย์ของชื่อออเปอเรชัน ([String]) หรือเราสามารถระบุชื่อออเปอเรชันเดียว (String) ก็ได้

ใน query นี้ เราส่ง ?operationName=Four และออเปอเรชันที่ถูกดำเนินการ (ไม่ว่าจะเป็น query หรือ mutation) จะเป็น ["One", "Two", "Three", "Four"]:

mutation One {
  # Do something ...
}
 
mutation Two {
  # Do something ...
}
 
query Three @depends(on: ["One", "Two"]) {
  # Do something ...
}
 
query Four @depends(on: "Three") {
  # Do something ...
}

@export

ไดเรกทีฟ @export จะเอ็กซ์พอร์ตค่าของฟิลด์ (หรือชุดของฟิลด์) ไปยังตัวแปรไดนามิก เพื่อใช้เป็นอินพุตในฟิลด์หรือ query จาก query อีกตัวหนึ่ง

ตัวอย่างเช่น ใน query นี้เราเอ็กซ์พอร์ตชื่อของผู้ใช้ที่ล็อกอินอยู่ และใช้ค่านี้เพื่อค้นหาโพสต์ที่มีสตริงนี้ (โปรดสังเกตว่าตัวแปร $loggedInUserName เนื่องจากเป็นไดนามิก จึงไม่จำเป็นต้องถูกกำหนดในออเปอเรชัน FindPosts):

query GetLoggedInUserName {
  me {
    name @export(as: "loggedInUserName")
  }
}
 
query FindPosts @depends(on: "GetLoggedInUserName") {
  posts(filter: { search: $loggedInUserName }) {
    id
  }
}

@exportFrom

คล้ายกับ @export แต่แทนที่จะเอ็กซ์พอร์ตค่าของฟิลด์ มันจะเอ็กซ์พอร์ตค่าของตัวแปรไดนามิกแบบมีขอบเขต ซึ่งส่งผ่าน @passOnwards(as: "...") หรือ @applyField(passOnwardsAs: "...")

ตัวอย่างเช่น ใน query นี้เราใช้ @applyField เพื่อแก้ไของค์ประกอบในอาร์เรย์และกำหนดค่าใหม่นี้ให้กับตัวแปรไดนามิกแบบมีขอบเขต $replaced จากนั้นเราใช้ @exportFrom เพื่อทำให้ค่านั้นสามารถเข้าถึงได้แบบโกลบอลผ่านตัวแปรไดนามิก $replacedList เพื่อให้สามารถดึงค่าได้จาก query ถัดไป

query One {    
  originalList: _echo(value: ["Hello everyone", "How are you?"])
    @underEachArrayItem(
      passValueOnwardsAs: "value"
      affectDirectivesUnderPos: [1, 2]
    )
      @applyField(
        name: "_strReplace"
        arguments: {
          search: " "
          replaceWith: "-"
          in: $value
        },
        passOnwardsAs: "replaced"
      )
      @exportFrom(
        scopedDynamicVariable: $replaced,
        as: "replacedList"
      )
}
 
query Two @depends(on: "One") {
  transformedList: _echo(value: $replacedList)
}

ซึ่งจะให้ผลลัพธ์ดังนี้:

{
  "data": {
    "originalList": [
      "Hello everyone",
      "How are you?"
    ],
    "transformedList": [
      "Hello-everyone",
      "How-are-you?"
    ]
  }
}

@deferredExport

เมื่อเปิดใช้งานฟีเจอร์ Multi-Field Directives และเราเอ็กซ์พอร์ตค่าของหลายฟิลด์ลงในดิกชันนารี ให้ใช้ @deferredExport แทน @export เพื่อรับประกันว่าไดเรกทีฟทั้งหมดจากแต่ละฟิลด์ที่เกี่ยวข้องได้ถูกดำเนินการแล้วก่อนที่จะเอ็กซ์พอร์ตค่าของฟิลด์

ตัวอย่างเช่น ใน query นี้ ฟิลด์แรกมีไดเรกทีฟ @strUpperCase ถูกนำมาใช้ และฟิลด์ที่สองมี @strTitleCase เมื่อดำเนินการ @deferredExport ค่าที่ถูกเอ็กซ์พอร์ตจะมีไดเรกทีฟเหล่านี้ถูกนำมาใช้:

query One {
  id @strUpperCase # Will be exported as "ROOT"
  again: id @strTitleCase # Will be exported as "Root"
    @deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}
 
query Two @depends(on: "One") {
  mirrorProps: _echo(value: $props)
}

ให้ผลลัพธ์ดังนี้:

{
  "data": {
    "id": "ROOT",
    "again": "Root",
    "mirrorProps": {
      "id": "ROOT",
      "again": "Root"
    }
  }
}

@skip และ @include (ในออเปอเรชัน)

เมื่อเปิดใช้งานการดำเนินการหลาย queries ไดเรกทีฟ @include และ @skip ก็พร้อมใช้งานเป็นไดเรกทีฟออเปอเรชันด้วย และสามารถใช้เพื่อดำเนินการออเปอเรชันแบบมีเงื่อนไขหากเป็นไปตามเงื่อนไขบางอย่าง

ตัวอย่างเช่น ใน query นี้ ออเปอเรชัน CheckIfPostExists จะเอ็กซ์พอร์ตตัวแปรไดนามิก $postExists และเฉพาะเมื่อค่าของมันเป็น true เท่านั้น มิวเทชัน ExecuteOnlyIfPostExists จึงจะถูกดำเนินการ:

query CheckIfPostExists($id: ID!) {
  # Initialize the dynamic variable to `false`
  postExists: _echo(value: false) @export(as: "postExists")
 
  post(by: { id: $id }) {
    # Found the Post => Set dynamic variable to `true`
    postExists: _echo(value: true) @export(as: "postExists")
  }
}
 
mutation ExecuteOnlyIfPostExists
  @depends(on: "CheckIfPostExists")
  @include(if: $postExists)
{
  # Do something...
}

ผลลัพธ์ของตัวแปรไดนามิก

@export สามารถสร้างผลลัพธ์ที่แตกต่างกันได้ 6 แบบ โดยอิงจากการรวมกันของ:

  • ค่าของอาร์กิวเมนต์ type (SINGLE, LIST หรือ DICTIONARY)
  • ไดเรกทีฟถูกนำมาใช้กับฟิลด์เดียว หรือกับหลายฟิลด์ (ผ่านโมดูล Multi-Field Directives)

ผลลัพธ์ที่เป็นไปได้ 6 แบบมีดังนี้:

  1. ประเภท SINGLE:
    1. ฟิลด์เดียว
    2. มัลติฟิลด์
  2. ประเภท LIST:
    1. ฟิลด์เดียว
    2. มัลติฟิลด์
  3. ประเภท DICTIONARY:
    1. ฟิลด์เดียว
    2. มัลติฟิลด์

ประเภท SINGLE / ฟิลด์เดียว

ผลลัพธ์จะเป็นค่าเดียวเมื่อส่งพารามิเตอร์ type: SINGLE (ซึ่งถูกตั้งเป็นค่าเริ่มต้น)

ใน query นี้:

query {
  post(by: { id: 1 }) {
    title @export(as: "postTitle", type: SINGLE)
  }
}

…ตัวแปรไดนามิก $postTitle จะมีค่า:

"Hello world!"

โปรดสังเกตว่าหาก SINGLE ถูกนำมาใช้กับอาร์เรย์ของเอนทิตี ค่าของเอนทิตีตัวสุดท้ายจะเป็นค่าที่ถูกเอ็กซ์พอร์ต

ใน query นี้:

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postTitle", type: SINGLE)
  }
}

…ตัวแปรไดนามิก $postTitle จะมีค่าของโพสต์ที่มี ID 5:

"Everything good?"

ประเภท SINGLE / มัลติฟิลด์

หาก @export ถูกนำมาใช้กับหลายฟิลด์ (โดยการเพิ่มพารามิเตอร์ affectAdditionalFieldsUnderPos ที่มาจากโมดูล Multi-Field Directives) ค่าที่ถูกตั้งบนตัวแปรไดนามิกจะเป็นดิกชันนารีของ { key: field alias, value: field value } (ประเภท JSONObject)

query นี้:

query {
  post(by: { id: 1 }) {
    title
    content
      @export(
        as: "postData",
        type: SINGLE,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

…เอ็กซ์พอร์ตตัวแปรไดนามิก $postData ด้วยค่า:

{
  "title": "Hello world!",
  "content": "Lorem ipsum."
}

ประเภท LIST / ฟิลด์เดียว

ตัวแปรไดนามิกจะมีอาร์เรย์ที่มีค่าของฟิลด์จากเอนทิตีทั้งหมดที่ถูก query (จากฟิลด์ที่ครอบอยู่) โดยการส่งพารามิเตอร์ type: LIST

เมื่อรัน query นี้ (ซึ่งเอนทิตีที่ถูก query คือโพสต์ที่มี ID 1 และ 5):

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postTitles", type: LIST)
  }
}

…ตัวแปรไดนามิก $postTitles จะมีค่า:

[
  "Hello world!",
  "Everything good?"
]

ประเภท LIST / มัลติฟิลด์

เราจะได้อาร์เรย์ของดิกชันนารี (ประเภท JSONObject) ซึ่งแต่ละตัวมีค่าของฟิลด์ที่ไดเรกทีฟถูกนำมาใช้

query นี้:

query {
  posts(filter: { ids: [1, 5] }) {
    title
    content
      @export(
        as: "postsData",
        type: LIST,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

…เอ็กซ์พอร์ตตัวแปรไดนามิก $postsData ด้วยค่า:

[
  {
    "title": "Hello world!",
    "content": "Lorem ipsum."
  },
  {
    "title": "Everything good?",
    "content": "Quisque convallis libero in sapien pharetra tincidunt."
  }
]

ประเภท DICTIONARY / ฟิลด์เดียว

ตัวแปรไดนามิกจะมีดิกชันนารี (ประเภท JSONObject) โดยมี ID จากเอนทิตีที่ถูก query เป็นคีย์ และค่าของฟิลด์เป็นค่า โดยการส่งพารามิเตอร์ type: DICTIONARY

query นี้:

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postIDTitles", type: DICTIONARY)
  }
}

…เอ็กซ์พอร์ตตัวแปรไดนามิก $postIDTitles ด้วยค่า:

{
  "1": "Hello world!",
  "5": "Everything good?"
}

ประเภท DICTIONARY / มัลติฟิลด์

ในการรวมกันนี้ เราเอ็กซ์พอร์ตดิกชันนารีของดิกชันนารี: { key: entity ID, value: { key: field alias, value: field value } } (โดยใช้ประเภท JSONObject ที่จะมีรายการประเภท JSONObject)

query นี้:

query {
  posts(filter: { ids: [1, 5] }) {
    title
    content
      @export(
        as: "postsIDProperties",
        type: DICTIONARY,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

…เอ็กซ์พอร์ตตัวแปรไดนามิก $postsIDProperties ด้วยค่า:

{
  "1": {
    "title": "Hello world!",
    "content": "Lorem ipsum."
  },
  "5": {
    "title": "Everything good?",
    "content": "Quisque convallis libero in sapien pharetra tincidunt."
  }
}

การเอ็กซ์พอร์ตค่าเมื่อวนซ้ำอาร์เรย์หรือออบเจกต์ JSON

@export เคารพคาร์ดินาลิตีจากเมตาไดเรกทีฟที่ครอบอยู่

โดยเฉพาะอย่างยิ่ง เมื่อใดก็ตามที่ @export ถูกซ้อนอยู่ใต้เมตาไดเรกทีฟที่วนซ้ำบนรายการอาร์เรย์หรือพร็อพเพอร์ตีของออบเจกต์ JSON (เช่น @underEachArrayItem และ @underEachJSONObjectProperty) ค่าที่ถูกเอ็กซ์พอร์ตจะเป็นอาร์เรย์

query นี้:

{
  post(by: { id: 19 }) {
    coreContentAttributeBlocks: blockFlattenedDataItems(
      filterBy: { include: "core/heading" }
    )
      @underEachArrayItem
        @underJSONObjectProperty(
          by: { path: "attributes.content" },
        )
          @export(
            as: "contentAttributes",
          )
  }
}

…สร้าง $contentAttributes ด้วยค่า:

[
  "List Block",
  "Columns Block",
  "Columns inside Columns (nested inner blocks)",
  "Life is so rich",
  "Life is so dynamic"
]

ในทางกลับกัน query เดียวกันที่เข้าถึงรายการเฉพาะในอาร์เรย์แทนการวนซ้ำทั้งหมด (โดยการแทนที่ @underEachArrayItem ด้วย @underArrayItem(index: 0)) จะเอ็กซ์พอร์ตค่าเดียว

query นี้:

{
  post(by: { id: 19 }) {
    coreContentAttributeBlocks: blockFlattenedDataItems(
      filterBy: { include: "core/heading" }
    )
      @underArrayItem(index: 0)
        @underJSONObjectProperty(
          by: { path: "attributes.content" },
        )
          @export(
            as: "contentAttributes",
          )
  }
}

…สร้าง $contentAttributes ด้วยค่า:

"List Block"

ลำดับการดำเนินการของไดเรกทีฟ

หากมีไดเรกทีฟอื่นก่อน @export ค่าที่ถูกเอ็กซ์พอร์ตจะสะท้อนการแก้ไขจากไดเรกทีฟก่อนหน้าเหล่านั้น

ตัวอย่างเช่น ใน query นี้ ขึ้นอยู่กับว่า @export เกิดขึ้นก่อนหรือหลัง @strUpperCase ผลลัพธ์จะแตกต่างกัน:

query One {
  id
    # First export "root", only then will be converted to "ROOT"
    @export(as: "id")
    @strUpperCase
 
  again: id
    # First convert to "ROOT" and then export this value
    @strUpperCase
    @export(as: "again")
}
 
query Two @depends(on: "One") {
  mirrorID: _echo(value: $id)
  mirrorAgain: _echo(value: $again)
}

ให้ผลลัพธ์ดังนี้:

{
  "data": {
    "id": "ROOT",
    "again": "ROOT",
    "mirrorID": "root",
    "mirrorAgain": "ROOT"
  }
}

การดำเนินการใน Persisted Queries

เมื่อ GraphQL query มีหลายออเปอเรชันใน Persisted Query เราสามารถเรียกใช้เอนด์พอยต์ที่สอดคล้องกันโดยส่งพารามิเตอร์ URL ?operationName=... พร้อมชื่อของออเปอเรชันที่จะดำเนินการ มิฉะนั้นออเปอเรชันสุดท้ายจะถูกดำเนินการ

ตัวอย่างเช่น เพื่อดำเนินการออเปอเรชัน GetPostsContainingString ใน Persisted Query ที่มีเอนด์พอยต์ /graphql-query/posts-with-user-name/ เราต้องเรียกใช้:

https://mysite.com/graphql-query/posts-with-user-name/?operationName=GetPostsContainingString

ตัวอย่าง

นำเข้าเนื้อหาจากเอนด์พอยต์ API ภายนอก:

query FetchDataFromExternalEndpoint
{
  _sendJSONObjectItemHTTPRequest(input: { url: "https://site.com/wp-json/wp/posts/1" } )
    @export(as: "externalData")
    @remove
}
 
query ManipulateDataIntoInput @depends(on: "FetchDataFromExternalEndpoint")
{
  title: _objectProperty(
    object: $externalData,
    by: {
      path: "title.rendered"
    }
  ) @export(as: "postTitle")
 
  excerpt: _objectProperty(
    object: $externalData,
    by: {
      key: "excerpt"
    }
  ) @export(as: "postExcerpt")
}
 
mutation CreatePost @depends(on: "ManipulateDataIntoInput")
{
  createPost(input: {
    title: $postTitle
    excerpt: $postExcerpt
  }) {
    id
  }
}

ดึงข้อมูลของโพสต์ แปลงข้อมูล แล้วจัดเก็บอีกครั้ง:

query GetPostData(
  $postId: ID!
) {
  post(by: {id: $postId}) {
    id
    title @export(as: "postTitle")
    rawContent @export(as: "postContent")
  }
}
 
query AdaptPostData(
  $replaceFrom: String!,
  $replaceTo: String!
)
  @depends(on: "GetPostData")
{
  adaptedPostTitle: _strReplace(
    search: $replaceFrom
    replaceWith: $replaceTo
    in: $postTitle
  )
    @export(as: "adaptedPostTitle")
 
  adaptedPostContent: _strReplace(
    search: $replaceFrom
    replaceWith: $replaceTo
    in: $postContent
  )
    @export(as: "adaptedPostContent")
}
 
mutation StoreAdaptedPostData(
  $postId: ID!
)
  @depends(on: "AdaptPostData")
{
  updatePost(input: {
    id: $postId,
    title: $adaptedPostTitle,
    contentAs: { html: $adaptedPostContent },
  }) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    post {
      id
      title
      rawContent
    }
  }
}

อัปเดตโพสต์หากมีอยู่ หรือแสดงข้อความแสดงข้อผิดพลาดหากไม่มี:

query GetPost($id: ID!) {
  post(by:{id: $id}) {
    id
    title
  }
  _notNull(value: $__post) @export(as: "postExists")
}
 
query FailIfPostNotExists($id: ID!)
  @skip(if: $postExists)
  @depends(on: "GetPost")
{
  errorMessage: _sprintf(
    string: "There is no post with ID '%s'",
    values: [$id]
  ) @remove
  _fail(
    message: $__errorMessage
    data: {
      id: $id
    }
  ) @remove
}
 
mutation UpdatePost($id: ID!, $postTitle: String)
  @include(if: $postExists)
  @depends(on: "GetPost")
{
  updatePost(input: {
    id: $id,
    title: $postTitle,
  }) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    post {
      id
      title
      rawContent
    }
  }
}
 
query MaybeUpdatePost
  @depends(on: [
      "FailIfPostNotExists",
      "UpdatePost"
  ])
{
  id @remove
}

ล็อกอินผู้ใช้ก่อนดำเนินการมิวเทชัน และล็อกเอาต์ทันทีหลังจากนั้น:

mutation LogUserIn(
  $username: String!
  $password: String!
) {
  loginUser(by: {
    credentials: {
      usernameOrEmail: $username,
      password: $password
    }
  }) @remove {
    status
    user {
      id
      username
    }
  }
}
 
mutation AddComment(
  $customPostId: ID!
  $commentContent: HTML!
)
  @depends(on: "LogUserIn")
{
  addCommentToCustomPost(input: {
    customPostID: $customPostId,
    commentAs: { html: $commentContent }
  }) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    comment {
      id
      parent {
        id
      }
      content
      date
      author {
        name
        email
      }
    }
  }
}
 
mutation LogUserOut
  @depends(on: "AddComment")
{
  logoutUser @remove {
    status
    userID
  }
}
 
query ExecuteAllAddCommentOperations
  @depends(on: "LogUserOut")
{
  id @remove
}

ล็อกอินผู้ใช้แบบมีเงื่อนไขก่อนดำเนินการมิวเทชัน หากมีการระบุ:

query ExportUserLogin(
  $username: String
) {
  _notNull(value: $username)
    @export(as: "hasUsername")
    @remove
}
 
mutation MaybeLogUserIn(
  $username: String
  $password: String
)
  @depends(on: "ExportUserLogin")
  @include(if: $hasUsername)
{
  loginUser(by: {
    credentials: {
      usernameOrEmail: $username,
      password: $password
    }
  }) @remove {
    status
    user {
      id
      username
    }
  }
}
 
mutation AddComment(
  $customPostId: ID!
  $commentContent: HTML!
)
  @depends(on: "MaybeLogUserIn")
{
  addCommentToCustomPost(input: {
    customPostID: $customPostId,
    commentAs: { html: $commentContent }
  }) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    comment {
      id
      parent {
        id
      }
      content
      date
      author {
        name
        email
      }
    }
  }
}
 
mutation MaybeLogUserOut
  @depends(on: "AddComment")
  @include(if: $hasUsername)
{
  logoutUser @remove {
    status
    userID
  }
}
 
query ExecuteAllAddCommentOperations
  @depends(on: "MaybeLogUserOut")
{
  id @remove
}

GraphQL spec

ฟังก์ชันนี้ปัจจุบันยังไม่เป็นส่วนหนึ่งของ GraphQL spec แต่ได้มีการร้องขอแล้ว: