graphql 1.12.15 → 1.12.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  3. data/lib/graphql/dataloader/source.rb +20 -0
  4. data/lib/graphql/dataloader.rb +39 -16
  5. data/lib/graphql/define/instance_definable.rb +1 -1
  6. data/lib/graphql/deprecated_dsl.rb +11 -3
  7. data/lib/graphql/execution/interpreter/runtime.rb +34 -25
  8. data/lib/graphql/integer_encoding_error.rb +18 -2
  9. data/lib/graphql/introspection/input_value_type.rb +6 -0
  10. data/lib/graphql/pagination/connections.rb +5 -0
  11. data/lib/graphql/query/validation_pipeline.rb +1 -1
  12. data/lib/graphql/query.rb +4 -0
  13. data/lib/graphql/schema/argument.rb +71 -28
  14. data/lib/graphql/schema/field.rb +14 -4
  15. data/lib/graphql/schema/input_object.rb +5 -9
  16. data/lib/graphql/schema/member/has_arguments.rb +90 -44
  17. data/lib/graphql/schema/resolver.rb +24 -59
  18. data/lib/graphql/schema/subscription.rb +25 -7
  19. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  20. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  21. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  22. data/lib/graphql/schema/validator/format_validator.rb +2 -1
  23. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  24. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  25. data/lib/graphql/schema/validator/numericality_validator.rb +12 -2
  26. data/lib/graphql/schema/validator.rb +36 -25
  27. data/lib/graphql/schema.rb +18 -5
  28. data/lib/graphql/static_validation/base_visitor.rb +3 -0
  29. data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
  30. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  31. data/lib/graphql/static_validation/validation_context.rb +6 -1
  32. data/lib/graphql/static_validation/validator.rb +15 -12
  33. data/lib/graphql/string_encoding_error.rb +13 -3
  34. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
  35. data/lib/graphql/subscriptions/event.rb +6 -21
  36. data/lib/graphql/subscriptions/serialize.rb +1 -1
  37. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  38. data/lib/graphql/types/big_int.rb +5 -1
  39. data/lib/graphql/types/int.rb +1 -1
  40. data/lib/graphql/types/string.rb +1 -1
  41. data/lib/graphql/unauthorized_error.rb +1 -1
  42. data/lib/graphql/version.rb +1 -1
  43. data/lib/graphql.rb +15 -11
  44. data/readme.md +1 -1
  45. metadata +5 -3
@@ -161,7 +161,7 @@ module GraphQL
161
161
 
162
162
  accepts_definitions \
163
163
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
164
- :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
164
+ :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
165
165
  :orphan_types, :resolve_type, :type_error, :parse_error,
166
166
  :error_bubbling,
167
167
  :raise_definition_error,
@@ -200,7 +200,7 @@ module GraphQL
200
200
  attr_accessor \
201
201
  :query, :mutation, :subscription,
202
202
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
203
- :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
203
+ :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
204
204
  :orphan_types, :directives,
205
205
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
206
206
  :cursor_encoder,
@@ -552,7 +552,7 @@ module GraphQL
552
552
  end
553
553
  end
554
554
 
555
- # @see [GraphQL::Schema::Warden] Resticted access to root types
555
+ # @see [GraphQL::Schema::Warden] Restricted access to root types
556
556
  # @return [GraphQL::ObjectType, nil]
557
557
  def root_type_for_operation(operation)
558
558
  case operation
@@ -934,6 +934,7 @@ module GraphQL
934
934
  schema_defn.mutation = mutation && mutation.graphql_definition
935
935
  schema_defn.subscription = subscription && subscription.graphql_definition
936
936
  schema_defn.validate_timeout = validate_timeout
937
+ schema_defn.validate_max_errors = validate_max_errors
937
938
  schema_defn.max_complexity = max_complexity
938
939
  schema_defn.error_bubbling = error_bubbling
939
940
  schema_defn.max_depth = max_depth
@@ -1073,7 +1074,7 @@ module GraphQL
1073
1074
  end
1074
1075
  end
1075
1076
 
1076
- # @see [GraphQL::Schema::Warden] Resticted access to root types
1077
+ # @see [GraphQL::Schema::Warden] Restricted access to root types
1077
1078
  # @return [GraphQL::ObjectType, nil]
1078
1079
  def root_type_for_operation(operation)
1079
1080
  case operation
@@ -1290,10 +1291,22 @@ module GraphQL
1290
1291
  validator_opts = { schema: self }
1291
1292
  rules && (validator_opts[:rules] = rules)
1292
1293
  validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
1293
- res = validator.validate(query, timeout: validate_timeout)
1294
+ res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors)
1294
1295
  res[:errors]
1295
1296
  end
1296
1297
 
1298
+ attr_writer :validate_max_errors
1299
+
1300
+ def validate_max_errors(new_validate_max_errors = nil)
1301
+ if new_validate_max_errors
1302
+ @validate_max_errors = new_validate_max_errors
1303
+ elsif defined?(@validate_max_errors)
1304
+ @validate_max_errors
1305
+ else
1306
+ find_inherited_value(:validate_max_errors)
1307
+ end
1308
+ end
1309
+
1297
1310
  attr_writer :max_complexity
1298
1311
 
1299
1312
  def max_complexity(max_complexity = nil)
@@ -205,6 +205,9 @@ module GraphQL
205
205
  private
206
206
 
207
207
  def add_error(error, path: nil)
208
+ if @context.too_many_errors?
209
+ throw :too_many_validation_errors
210
+ end
208
211
  error.path ||= (path || @path.dup)
209
212
  context.errors << error
210
213
  end
@@ -193,26 +193,26 @@ module GraphQL
193
193
  if node1.name != node2.name
194
194
  errored_nodes = [node1.name, node2.name].sort.join(" or ")
195
195
  msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
196
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
196
+ add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
197
197
  msg,
198
198
  nodes: [node1, node2],
199
199
  path: [],
200
200
  field_name: response_key,
201
201
  conflicts: errored_nodes
202
- )
202
+ ))
203
203
  end
204
204
 
205
205
  if !same_arguments?(node1, node2)
206
206
  args = [serialize_field_args(node1), serialize_field_args(node2)]
207
207
  conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
208
208
  msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
209
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
209
+ add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
210
210
  msg,
211
211
  nodes: [node1, node2],
212
212
  path: [],
213
213
  field_name: response_key,
214
214
  conflicts: conflicts
215
- )
215
+ ))
216
216
  end
217
217
  end
218
218
 
@@ -7,12 +7,12 @@ module GraphQL
7
7
  dependency_map = context.dependencies
8
8
  dependency_map.cyclical_definitions.each do |defn|
9
9
  if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
10
- context.errors << GraphQL::StaticValidation::FragmentsAreFiniteError.new(
10
+ add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new(
11
11
  "Fragment #{defn.name} contains an infinite loop",
12
12
  nodes: defn.node,
13
13
  path: defn.path,
14
14
  name: defn.name
15
- )
15
+ ))
16
16
  end
17
17
  end
18
18
  end
@@ -19,10 +19,11 @@ module GraphQL
19
19
 
20
20
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
21
 
22
- def initialize(query, visitor_class)
22
+ def initialize(query, visitor_class, max_errors)
23
23
  @query = query
24
24
  @literal_validator = LiteralValidator.new(context: query.context)
25
25
  @errors = []
26
+ @max_errors = max_errors || Float::INFINITY
26
27
  @on_dependency_resolve_handlers = []
27
28
  @visitor = visitor_class.new(document, self)
28
29
  end
@@ -38,6 +39,10 @@ module GraphQL
38
39
  def validate_literal(ast_value, type)
39
40
  @literal_validator.validate(ast_value, type)
40
41
  end
42
+
43
+ def too_many_errors?
44
+ @errors.length >= @max_errors
45
+ end
41
46
  end
42
47
  end
43
48
  end
@@ -24,8 +24,9 @@ module GraphQL
24
24
  # @param query [GraphQL::Query]
25
25
  # @param validate [Boolean]
26
26
  # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
27
+ # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
27
28
  # @return [Array<Hash>]
28
- def validate(query, validate: true, timeout: nil)
29
+ def validate(query, validate: true, timeout: nil, max_errors: nil)
29
30
  query.trace("validate", { validate: validate, query: query }) do
30
31
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
31
32
  errors = if validate == false && can_skip_rewrite
@@ -34,25 +35,27 @@ module GraphQL
34
35
  rules_to_use = validate ? @rules : []
35
36
  visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
36
37
 
37
- context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
38
+ context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
38
39
 
39
40
  begin
40
41
  # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
41
42
  # A timeout value of 0 or nil will execute the block without any timeout.
42
43
  Timeout::timeout(timeout) do
43
- # Attach legacy-style rules.
44
- # Only loop through rules if it has legacy-style rules
45
- unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
46
- legacy_rules.each do |rule_class_or_module|
47
- if rule_class_or_module.method_defined?(:validate)
48
- GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
49
- GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
50
- rule_class_or_module.new.validate(context)
44
+ catch(:too_many_validation_errors) do
45
+ # Attach legacy-style rules.
46
+ # Only loop through rules if it has legacy-style rules
47
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
48
+ legacy_rules.each do |rule_class_or_module|
49
+ if rule_class_or_module.method_defined?(:validate)
50
+ GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
51
+ GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
52
+ rule_class_or_module.new.validate(context)
53
+ end
51
54
  end
52
55
  end
53
- end
54
56
 
55
- context.visitor.visit
57
+ context.visitor.visit
58
+ end
56
59
  end
57
60
  rescue Timeout::Error
58
61
  handle_timeout(query, context)
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class StringEncodingError < GraphQL::RuntimeTypeError
4
- attr_reader :string
5
- def initialize(str)
4
+ attr_reader :string, :field, :path
5
+ def initialize(str, context:)
6
6
  @string = str
7
- super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
7
+ @field = context[:current_field]
8
+ @path = context[:current_path]
9
+ message = "String #{str.inspect} was encoded as #{str.encoding}".dup
10
+ if @path
11
+ message << " @ #{@path.join(".")}"
12
+ end
13
+ if @field
14
+ message << " (#{@field.path})"
15
+ end
16
+ message << ". GraphQL requires an encoding compatible with UTF-8."
17
+ super(message)
8
18
  end
9
19
  end
10
20
  end
@@ -127,7 +127,13 @@ module GraphQL
127
127
  # It will receive notifications when events come in
128
128
  # and re-evaluate the query locally.
129
129
  def write_subscription(query, events)
130
- channel = query.context.fetch(:channel)
130
+ unless (channel = query.context[:channel])
131
+ raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\
132
+ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\
133
+ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\
134
+ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\
135
+ "GraphiQL via `graphiql-rails` may not work out of box (#1051)."
136
+ end
131
137
  subscription_id = query.context[:subscription_id] ||= build_id
132
138
  stream = stream_subscription_name(subscription_id)
133
139
  channel.stream_from(stream)
@@ -29,26 +29,10 @@ module GraphQL
29
29
  end
30
30
 
31
31
  # @return [String] an identifier for this unit of subscription
32
- def self.serialize(name, arguments, field, scope:)
33
- normalized_args = case arguments
34
- when GraphQL::Query::Arguments
35
- arguments
36
- when Hash
37
- if field.is_a?(GraphQL::Schema::Field)
38
- stringify_args(field, arguments)
39
- else
40
- GraphQL::Query::LiteralInput.from_arguments(
41
- arguments,
42
- field,
43
- nil,
44
- )
45
- end
46
- else
47
- raise ArgumentError, "Unexpected arguments: #{arguments}, must be Hash or GraphQL::Arguments"
48
- end
49
-
50
- sorted_h = stringify_args(field, normalized_args.to_h)
51
- Serialize.dump_recursive([scope, name, sorted_h])
32
+ def self.serialize(_name, arguments, field, scope:)
33
+ subscription = field.resolver || GraphQL::Schema::Subscription
34
+ normalized_args = stringify_args(field, arguments.to_h)
35
+ subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
52
36
  end
53
37
 
54
38
  # @return [String] a logical identifier for this event. (Stable when the query is broadcastable.)
@@ -70,6 +54,7 @@ module GraphQL
70
54
  class << self
71
55
  private
72
56
  def stringify_args(arg_owner, args)
57
+ arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
73
58
  case args
74
59
  when Hash
75
60
  next_args = {}
@@ -80,11 +65,11 @@ module GraphQL
80
65
 
81
66
  if arg_defn
82
67
  normalized_arg_name = camelized_arg_name
68
+ next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
83
69
  else
84
70
  normalized_arg_name = arg_name
85
71
  arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
86
72
  end
87
-
88
73
  next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
89
74
  end
90
75
  # Make sure they're deeply sorted
@@ -9,7 +9,7 @@ module GraphQL
9
9
  SYMBOL_KEY = "__sym__"
10
10
  SYMBOL_KEYS_KEY = "__sym_keys__"
11
11
  TIMESTAMP_KEY = "__timestamp__"
12
- TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00'
12
+ TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
13
13
  OPEN_STRUCT_KEY = "__ostruct__"
14
14
 
15
15
  module_function
@@ -14,7 +14,22 @@ module GraphQL
14
14
  "execute_query_lazy" => "execute.graphql",
15
15
  }
16
16
 
17
+ # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
18
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
+ # It can also be specified per-query with `context[:set_appsignal_action_name]`.
20
+ def initialize(options = {})
21
+ @set_action_name = options.fetch(:set_action_name, false)
22
+ super
23
+ end
24
+
17
25
  def platform_trace(platform_key, key, data)
26
+ if key == "execute_query"
27
+ set_this_txn_name = data[:query].context[:set_appsignal_action_name]
28
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
29
+ Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
30
+ end
31
+ end
32
+
18
33
  Appsignal.instrument(platform_key) do
19
34
  yield
20
35
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string."
7
7
 
8
8
  def self.coerce_input(value, _ctx)
9
- value && Integer(value)
9
+ value && parse_int(value)
10
10
  rescue ArgumentError
11
11
  nil
12
12
  end
@@ -14,6 +14,10 @@ module GraphQL
14
14
  def self.coerce_result(value, _ctx)
15
15
  value.to_i.to_s
16
16
  end
17
+
18
+ def self.parse_int(value)
19
+ value.is_a?(Numeric) ? value : Integer(value, 10)
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  if value >= MIN && value <= MAX
26
26
  value
27
27
  else
28
- err = GraphQL::IntegerEncodingError.new(value)
28
+ err = GraphQL::IntegerEncodingError.new(value, context: ctx)
29
29
  ctx.schema.type_error(err, ctx)
30
30
  end
31
31
  end
@@ -15,7 +15,7 @@ module GraphQL
15
15
  str.encode!(Encoding::UTF_8)
16
16
  end
17
17
  rescue EncodingError
18
- err = GraphQL::StringEncodingError.new(str)
18
+ err = GraphQL::StringEncodingError.new(str, context: ctx)
19
19
  ctx.schema.type_error(err, ctx)
20
20
  end
21
21
 
@@ -12,7 +12,7 @@ module GraphQL
12
12
  attr_reader :type
13
13
 
14
14
  # @return [GraphQL::Query::Context] the context for the current query
15
- attr_reader :context
15
+ attr_accessor :context
16
16
 
17
17
  def initialize(message = nil, object: nil, type: nil, context: nil)
18
18
  if message.nil? && object.nil? && type.nil?
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.15"
3
+ VERSION = "1.12.19"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -57,22 +57,26 @@ module GraphQL
57
57
  end
58
58
 
59
59
  # Support Ruby 2.2 by implementing `-"str"`. If we drop 2.2 support, we can remove this backport.
60
- module StringDedupBackport
61
- refine String do
62
- def -@
63
- if frozen?
64
- self
65
- else
66
- self.dup.freeze
60
+ if !String.method_defined?(:-@)
61
+ module StringDedupBackport
62
+ refine String do
63
+ def -@
64
+ if frozen?
65
+ self
66
+ else
67
+ self.dup.freeze
68
+ end
67
69
  end
68
70
  end
69
71
  end
70
72
  end
71
73
 
72
- module StringMatchBackport
73
- refine String do
74
- def match?(pattern)
75
- self =~ pattern
74
+ if !String.method_defined?(:match?)
75
+ module StringMatchBackport
76
+ refine String do
77
+ def match?(pattern)
78
+ self =~ pattern
79
+ end
76
80
  end
77
81
  end
78
82
  end
data/readme.md CHANGED
@@ -44,6 +44,6 @@ I also sell [GraphQL::Pro](https://graphql.pro) which provides several features
44
44
 
45
45
  ## Getting Involved
46
46
 
47
- - __Say hi & ask questions__ in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) or [on Twitter](https://twitter.com/rmosolgo)!
47
+ - __Say hi & ask questions__ in the #graphql-ruby channel on [Discord](https://discord.com/invite/xud7bH9) or [on Twitter](https://twitter.com/rmosolgo)!
48
48
  - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues).
49
49
  - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development).
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.15
4
+ version: 1.12.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-23 00:00:00.000000000 Z
11
+ date: 2021-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -544,6 +544,8 @@ files:
544
544
  - lib/graphql/schema/unique_within_type.rb
545
545
  - lib/graphql/schema/validation.rb
546
546
  - lib/graphql/schema/validator.rb
547
+ - lib/graphql/schema/validator/allow_blank_validator.rb
548
+ - lib/graphql/schema/validator/allow_null_validator.rb
547
549
  - lib/graphql/schema/validator/exclusion_validator.rb
548
550
  - lib/graphql/schema/validator/format_validator.rb
549
551
  - lib/graphql/schema/validator/inclusion_validator.rb
@@ -700,7 +702,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
700
702
  - !ruby/object:Gem::Version
701
703
  version: '0'
702
704
  requirements: []
703
- rubygems_version: 3.2.15
705
+ rubygems_version: 3.2.22
704
706
  signing_key:
705
707
  specification_version: 4
706
708
  summary: A GraphQL language and runtime for Ruby