Skip to content

Latest commit

 

History

History
290 lines (216 loc) · 5.61 KB

README.md

File metadata and controls

290 lines (216 loc) · 5.61 KB

Graphlyte

Coverage report

Craft composable GraphQL queries using a Ruby DSL.

Parse GraphQL documents into a simple Ruby AST.

Manipulate, inspect and transform GraphQL documents as data, not opaque strings.

Installation

in your Gemfile

gem "graphlyte"

Queries can be constructed using the selection-builder DSL. This involves defining blocks that receive a SelectionBuilder object and using that to build a selection. The builder supports a method-missing based API that allows fields to be named directly for convenience.

# Basic query
query = Graphlyte.query do |q|
  q.all_todos do |todo|
    # specify fields for your query, using method-missing
    todo.id
    todo.status
    todo.title

    # or pass field names as values
    todo.select!(:open)
  end
end

puts query

produces:

query {
  allTodos { id status title open }
}

Send it across the wire

require "rest-client" #or whatever api client you wish

RestClient.post("http://localhost", query.request_body, { "Content-Type" => "application/json"})

Fragments / Nested Fragments

extra_fields = Graphlyte.fragment('extraFields', on: "Todo") do |todo|
  todo.id
  todo.status
end

# pass a block parameter if you want to merge/spread fieldsets or fragments
todo = Graphlyte.fragment('todoFields', on: "Todo") do |f|
  f.title
  f << extra_fields
end

query = Graphlyte.query do |q|
  q.all_todos todo
end

puts query

produces:

query {
  allTodos {
    ...todoFields
  }
}

fragment todoFields on Todo {
  title
  ...extraFields
}

fragment extraFields on Todo { id status }

Input arguments and aliases

Arguments may be passed as plain Ruby values, and aliases specified with alias:

query = Graphlyte.query do |q|
  q.User(id: 123).alias("sean") do
    _1.id
  end
  q.User(id: 456).alias("bob") do
    _1.id
  end
end

puts query

Produces:

query {
  sean: User(id: 123) { id }
  bob: User(id: 456) { id }
}

Variables

Symbols as arguments indicate a named, un-typed variable. Variable type is determined when you pass an argument value to it:

query = Graphlyte.query do |q|
  q.all_todos(per_page: :per_page, page: :pages) do
    q.status
    q.title
  end
end

puts query.request_body(per_page: 1, pages: 1)

Produces:

{
  "query": "query($perPage: Int!, $pages: Int!) {\n  status\n  title\n allTodos(perPage: $perPage , page: $pages)\n}",
  "variables":{"perPage":1,"pages":1}
}

If the argument value is not a Ruby primitive, or it does not map to the expected type, variables can be defined explicitly:

sean_id = Graphlyte.var('ID', :sean_id)

sean = Graphlyte.fragment("userFields", on: "Query") do |q|
  q.User(id: sean_id, &:name)
end

query = Graphlyte.query do |q|
  q.all_todos(filter: Graphlyte.var('TodoFilter', :filter)) do |t|
    t.status
    t.title
  end

  q << sean
end

puts query.request_body(filter: { ids: [1]}, sean_id: 123)

Produces:

{
  "query":"query($filter: TodoFilter, $seanId: ID) {\n  allTodos(filter: $filter) { status title}\n  ...userFields\n}\n\nfragment userFields on Query {\n  User(id: $seanId) { name }\n}",
  "variables":{"filter":{"ids":[1]},"seanId":123}
}

Graphlyte::Selector: modifying queries

The selector class provides path-based modification of queries:

query = Graphlyte.parse(<<~GQL)
  query name($projectPath: ID!, $commitSha: String) {
    project(fullPath: $projectPath, sha: $commitSha) {
      createdAt
      pipelines(sha: $commitSha) {
        nodes {
          status
        }
      }
    }
  }
GQL

selector = Graphlyte::Selector.new.
  at('project.pipelines.nodes.status', &:remove).
  at('project.pipelines.nodes') do |node| node.append do |n|
      n.downstream { |ds| ds.nodes(&:active) }
    end
  end

puts selector.edit(query)

Produces:

query name($projectPath: ID!, $commitSha: String) {
  project(fullPath: $projectPath, sha: $commitSha) {
    createdAt
    pipelines(sha: $commitSha) {
      nodes {
        downstream {
          nodes { active }
        }
      }
    }
  }
}

Listing variables

Call Document#variable_references to list all the used variables. This is useful when you don't know all of the variables that a query expects.

Example:

doc = Graphlyte.parse(<<~GQL)
  query Pipelines($projectPath: ID!, $commitSha: String) {
    project(fullPath: $projectPath) {
      createdAt
      pipelines(sha: $commitSha) {
        nodes { ...pipelineFields }
      }
    }
  }

  mutation StopPipeline($id: ID!) {
    stopPipeline(id: $id) {
      errors
      pipeline { ...pipelineFields }
    }
  }

  fragment pipelineFields on Pipeline {
      id
      status
  }
GQL

puts doc.variable_references['Pipelines'].map(&:variable).inspect
puts doc.variable_references['StopPipeline'].map(&:variable).inspect

Produces:

["projectPath", "commitSha"]
["id"]

Parsing

There is full parsing support for the GraphQL specification. Call Graphlyte.parse to parse GraphQL text documents. See Graphlyte::Syntax for definitions of the AST.

Purpose

This library aims to be a client agnostic implementation for parsing, building, inspecting and manipulating GraphQL documents.

Todo

  • more documentation
  • support schema validation

Running tests

To run tests, you will need to install a node module to spin up a GraphQL server.

cd fixture && npm i

After installing you should be able to run the tests

rspec