graphql 2.3.8 → 2.4.0

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis.rb +1 -1
  7. data/lib/graphql/current.rb +52 -0
  8. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  9. data/lib/graphql/dataloader/source.rb +6 -3
  10. data/lib/graphql/dataloader.rb +35 -11
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  12. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  13. data/lib/graphql/execution/interpreter/runtime.rb +23 -19
  14. data/lib/graphql/execution/interpreter.rb +2 -0
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/schema_type.rb +4 -19
  17. data/lib/graphql/invalid_null_error.rb +1 -1
  18. data/lib/graphql/language/comment.rb +18 -0
  19. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  20. data/lib/graphql/language/lexer.rb +15 -12
  21. data/lib/graphql/language/nodes.rb +24 -16
  22. data/lib/graphql/language/parser.rb +14 -1
  23. data/lib/graphql/language/printer.rb +23 -7
  24. data/lib/graphql/language.rb +6 -5
  25. data/lib/graphql/query/context.rb +4 -2
  26. data/lib/graphql/query/null_context.rb +1 -5
  27. data/lib/graphql/query.rb +49 -20
  28. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  29. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  30. data/lib/graphql/rubocop.rb +2 -0
  31. data/lib/graphql/schema/addition.rb +1 -0
  32. data/lib/graphql/schema/always_visible.rb +6 -3
  33. data/lib/graphql/schema/argument.rb +14 -1
  34. data/lib/graphql/schema/build_from_definition.rb +9 -1
  35. data/lib/graphql/schema/directive/flagged.rb +1 -1
  36. data/lib/graphql/schema/enum.rb +49 -15
  37. data/lib/graphql/schema/enum_value.rb +9 -1
  38. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  39. data/lib/graphql/schema/field.rb +91 -36
  40. data/lib/graphql/schema/input_object.rb +20 -7
  41. data/lib/graphql/schema/interface.rb +21 -4
  42. data/lib/graphql/schema/introspection_system.rb +3 -2
  43. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  44. data/lib/graphql/schema/member/has_arguments.rb +9 -5
  45. data/lib/graphql/schema/member/has_fields.rb +5 -5
  46. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  47. data/lib/graphql/schema/printer.rb +1 -0
  48. data/lib/graphql/schema/resolver.rb +3 -4
  49. data/lib/graphql/schema/validator/all_validator.rb +2 -0
  50. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  51. data/lib/graphql/schema/visibility/migration.rb +186 -0
  52. data/lib/graphql/schema/visibility/profile.rb +523 -0
  53. data/lib/graphql/schema/visibility.rb +75 -0
  54. data/lib/graphql/schema/warden.rb +80 -21
  55. data/lib/graphql/schema.rb +254 -85
  56. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  57. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  58. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  59. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  60. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  61. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  62. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  63. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  64. data/lib/graphql/static_validation/validation_context.rb +15 -0
  65. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  66. data/lib/graphql/subscriptions.rb +3 -1
  67. data/lib/graphql/testing/helpers.rb +7 -3
  68. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  69. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  70. data/lib/graphql/version.rb +1 -1
  71. data/lib/graphql.rb +3 -0
  72. metadata +28 -7
  73. data/lib/graphql/schema/subset.rb +0 -397
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47e1762bc5aa144c21841bf1671a63b2180b8dacd4145688bc6f131ceb2a8f4a
4
- data.tar.gz: 9dd05b04b955e83c95cb4ad5d959fb68a49796417ca1759606be21cabc7bfd39
3
+ metadata.gz: 26e43b0bc48317698ed17f8a11498c19a7a4a0df9d33fdc9785edc47ae1147b6
4
+ data.tar.gz: 74402e930ebe03a451bc2bdb82285642aeeba7222ab491dd9329400fc852eb41
5
5
  SHA512:
6
- metadata.gz: 3bfe0cf261815aed7f739eb797f0c400c5ec97d4caf4f2ab760a67392fb51be2fceefcde7f45ad62d49d5216d7ea19ccf43b8360162a4862d70268115b7395cd
7
- data.tar.gz: 50a2a283a121761a4aae7174587e312b9ed39215101f18e9a14ce006844ca22b81572381a9636d63ecee9e7d7a15c62c996f6e736479b80aaec77684904cd392
6
+ metadata.gz: f1d97c4397ca8410f6b62c3ea2de9a0a4b18ca610adcb92de9676c5377b00a071cbff4fbd0396760eea9ab359e4b0b8cdfff1630c9fa83de51cfd31b0b96307f
7
+ data.tar.gz: d9250a9ad1d57f40e7b0151484f77a66e11a361f8b0f50938f42219eb8322a9b4dc59c59102f4b49f62a28e90f03b8f9e0cf5b2799b5fd5080dbcf443cebac15
@@ -45,6 +45,13 @@ module Graphql
45
45
  # post "/graphql", to: "graphql#execute"
46
46
  # ```
47
47
  #
48
+ # Add ActiveRecord::QueryLogs metadata:
49
+ # ```ruby
50
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
51
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
52
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
53
+ # ```
54
+ #
48
55
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
49
56
  #
50
57
  # Use `--skip-graphiql` to skip `graphiql-rails` installation.
@@ -92,6 +99,11 @@ module Graphql
92
99
  default: false,
93
100
  desc: "Use GraphQL Playground over Graphiql as IDE"
94
101
 
102
+ class_option :skip_query_logs,
103
+ type: :boolean,
104
+ default: false,
105
+ desc: "Skip ActiveRecord::QueryLogs hooks in config/application.rb"
106
+
95
107
  # These two options are taken from Rails' own generators'
96
108
  class_option :api,
97
109
  type: :boolean,
@@ -180,6 +192,40 @@ RUBY
180
192
  install_relay
181
193
  end
182
194
 
195
+ if !options[:skip_query_logs]
196
+ config_file = "config/application.rb"
197
+ current_app_rb = File.read(Rails.root.join(config_file))
198
+ existing_log_tags_pattern = /config.active_record.query_log_tags = \[\n?(\s*:[a-z_]+,?\s*\n?|\s*#[^\]]*\n)*/m
199
+ existing_log_tags = existing_log_tags_pattern.match(current_app_rb)
200
+ if existing_log_tags && behavior == :invoke
201
+ code = <<-RUBY
202
+ # GraphQL-Ruby query log tags:
203
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
204
+ current_graphql_field: -> { GraphQL::Current.field&.path },
205
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
206
+ RUBY
207
+ if !existing_log_tags.to_s.end_with?(",")
208
+ code = ",\n#{code} "
209
+ end
210
+ # Try to insert this code _after_ any plain symbol entries in the array of query log tags:
211
+ after_code = existing_log_tags_pattern
212
+ else
213
+ code = <<-RUBY
214
+ config.active_record.query_log_tags_enabled = true
215
+ config.active_record.query_log_tags = [
216
+ # Rails query log tags:
217
+ :application, :controller, :action, :job,
218
+ # GraphQL-Ruby query log tags:
219
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
220
+ current_graphql_field: -> { GraphQL::Current.field&.path },
221
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
222
+ ]
223
+ RUBY
224
+ after_code = "class Application < Rails::Application\n"
225
+ end
226
+ insert_into_file(config_file, code, after: after_code)
227
+ end
228
+
183
229
  if gemfile_modified?
184
230
  say "Gemfile has been modified, make sure you `bundle install`"
185
231
  end
@@ -18,7 +18,7 @@ module Graphql
18
18
  class_option :orm, banner: "NAME", type: :string, required: true,
19
19
  desc: "ORM to generate the controller for"
20
20
 
21
- class_option 'namespaced_types',
21
+ class_option :namespaced_types,
22
22
  type: :boolean,
23
23
  required: false,
24
24
  default: false,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Resolvers
3
5
  class BaseResolver < GraphQL::Schema::Resolver
@@ -11,7 +11,7 @@ module Graphql
11
11
  class TypeGeneratorBase < Rails::Generators::NamedBase
12
12
  include Core
13
13
 
14
- class_option 'namespaced_types',
14
+ class_option :namespaced_types,
15
15
  type: :boolean,
16
16
  required: false,
17
17
  default: false,
@@ -81,7 +81,7 @@ module GraphQL
81
81
  end
82
82
  rescue Timeout::Error
83
83
  [GraphQL::AnalysisError.new("Timeout on validation of query")]
84
- rescue GraphQL::UnauthorizedError
84
+ rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
85
85
  # This error was raised during analysis and will be returned the client before execution
86
86
  []
87
87
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ # This module exposes Fiber-level runtime information.
5
+ #
6
+ # It won't work across unrelated fibers, although it will work in child Fibers.
7
+ #
8
+ # @example Setting Up ActiveRecord::QueryLogs
9
+ #
10
+ # config.active_record.query_log_tags = [
11
+ # :namespaced_controller,
12
+ # :action,
13
+ # :job,
14
+ # # ...
15
+ # {
16
+ # # GraphQL runtime info:
17
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
18
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
19
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
20
+ # # ...
21
+ # },
22
+ # ]
23
+ #
24
+ module Current
25
+ # @return [String, nil] Comma-joined operation names for the currently-running {Multiplex}. `nil` if all operations are anonymous.
26
+ def self.operation_name
27
+ if (m = Fiber[:__graphql_current_multiplex])
28
+ m.context[:__graphql_current_operation_name] ||= begin
29
+ names = m.queries.map { |q| q.selected_operation_name }
30
+ if names.all?(&:nil?)
31
+ nil
32
+ else
33
+ names.join(",")
34
+ end
35
+ end
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ # @see GraphQL::Field#path for a string identifying this field
42
+ # @return [GraphQL::Field, nil] The currently-running field, if there is one.
43
+ def self.field
44
+ Thread.current[:__graphql_runtime_info]&.values&.first&.current_field
45
+ end
46
+
47
+ # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
48
+ def self.dataloader_source_class
49
+ Fiber[:__graphql_current_dataloader_source]&.class
50
+ end
51
+ end
52
+ end
@@ -12,6 +12,7 @@ module GraphQL
12
12
  end
13
13
 
14
14
  def run
15
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
15
16
  job_fibers = []
16
17
  next_job_fibers = []
17
18
  source_tasks = []
@@ -23,7 +24,7 @@ module GraphQL
23
24
  first_pass = false
24
25
  fiber_vars = get_fiber_variables
25
26
 
26
- while (f = (job_fibers.shift || spawn_job_fiber))
27
+ while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber)))
27
28
  if f.alive?
28
29
  finished = run_fiber(f)
29
30
  if !finished
@@ -37,7 +38,7 @@ module GraphQL
37
38
  Sync do |root_task|
38
39
  set_fiber_variables(fiber_vars)
39
40
  while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
40
- while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
41
+ while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition))))
41
42
  if task.alive?
42
43
  root_task.yield # give the source task a chance to run
43
44
  next_source_tasks << task
@@ -98,7 +98,7 @@ module GraphQL
98
98
  while pending_result_keys.any? { |key| !@results.key?(key) }
99
99
  iterations += 1
100
100
  if iterations > MAX_ITERATIONS
101
- raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
101
+ raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
102
102
  end
103
103
  @dataloader.yield
104
104
  end
@@ -186,8 +186,11 @@ This key should have been loaded already. This is a bug in GraphQL::Dataloader,
186
186
  ERR
187
187
  end
188
188
  result = @results[key]
189
-
190
- raise result if result.class <= StandardError
189
+ if result.is_a?(StandardError)
190
+ # Dup it because the rescuer may modify it.
191
+ # (This happens for GraphQL::ExecutionErrors, at least)
192
+ raise result.dup
193
+ end
191
194
 
192
195
  result
193
196
  end
@@ -24,18 +24,23 @@ module GraphQL
24
24
  #
25
25
  class Dataloader
26
26
  class << self
27
- attr_accessor :default_nonblocking
27
+ attr_accessor :default_nonblocking, :default_fiber_limit
28
28
  end
29
29
 
30
- NonblockingDataloader = Class.new(self) { self.default_nonblocking = true }
31
-
32
- def self.use(schema, nonblocking: nil)
33
- schema.dataloader_class = if nonblocking
30
+ def self.use(schema, nonblocking: nil, fiber_limit: nil)
31
+ dataloader_class = if nonblocking
34
32
  warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.")
35
- NonblockingDataloader
33
+ Class.new(self) { self.default_nonblocking = true }
36
34
  else
37
35
  self
38
36
  end
37
+
38
+ if fiber_limit
39
+ dataloader_class = Class.new(dataloader_class)
40
+ dataloader_class.default_fiber_limit = fiber_limit
41
+ end
42
+
43
+ schema.dataloader_class = dataloader_class
39
44
  end
40
45
 
41
46
  # Call the block with a Dataloader instance,
@@ -50,14 +55,18 @@ module GraphQL
50
55
  result
51
56
  end
52
57
 
53
- def initialize(nonblocking: self.class.default_nonblocking)
58
+ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
54
59
  @source_cache = Hash.new { |h, k| h[k] = {} }
55
60
  @pending_jobs = []
56
61
  if !nonblocking.nil?
57
62
  @nonblocking = nonblocking
58
63
  end
64
+ @fiber_limit = fiber_limit
59
65
  end
60
66
 
67
+ # @return [Integer, nil]
68
+ attr_reader :fiber_limit
69
+
61
70
  def nonblocking?
62
71
  @nonblocking
63
72
  end
@@ -178,6 +187,7 @@ module GraphQL
178
187
  end
179
188
 
180
189
  def run
190
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
181
191
  job_fibers = []
182
192
  next_job_fibers = []
183
193
  source_fibers = []
@@ -187,7 +197,7 @@ module GraphQL
187
197
  while first_pass || job_fibers.any?
188
198
  first_pass = false
189
199
 
190
- while (f = (job_fibers.shift || spawn_job_fiber))
200
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
191
201
  if f.alive?
192
202
  finished = run_fiber(f)
193
203
  if !finished
@@ -197,8 +207,8 @@ module GraphQL
197
207
  end
198
208
  join_queues(job_fibers, next_job_fibers)
199
209
 
200
- while source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
201
- while (f = source_fibers.shift || spawn_source_fiber)
210
+ while (source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
211
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
202
212
  if f.alive?
203
213
  finished = run_fiber(f)
204
214
  if !finished
@@ -242,6 +252,17 @@ module GraphQL
242
252
 
243
253
  private
244
254
 
255
+ def calculate_fiber_limit
256
+ total_fiber_limit = @fiber_limit || Float::INFINITY
257
+ if total_fiber_limit < 4
258
+ raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
259
+ end
260
+ total_fiber_limit -= 1 # deduct one fiber for `manager`
261
+ # Deduct at least one fiber for sources
262
+ jobs_fiber_limit = total_fiber_limit - 2
263
+ return jobs_fiber_limit, total_fiber_limit
264
+ end
265
+
245
266
  def join_queues(prev_queue, new_queue)
246
267
  @nonblocking && Fiber.scheduler.run
247
268
  prev_queue.concat(new_queue)
@@ -271,7 +292,10 @@ module GraphQL
271
292
 
272
293
  if pending_sources
273
294
  spawn_fiber do
274
- pending_sources.each(&:run_pending_keys)
295
+ pending_sources.each do |source|
296
+ Fiber[:__graphql_current_dataloader_source] = source
297
+ source.run_pending_keys
298
+ end
275
299
  end
276
300
  end
277
301
  end
@@ -8,22 +8,17 @@ module GraphQL
8
8
  @query = query
9
9
  @dataloader = query.context.dataloader
10
10
  @storage = Hash.new do |h, argument_owner|
11
- args_by_parent = if argument_owner.arguments_statically_coercible?
11
+ h[argument_owner] = if argument_owner.arguments_statically_coercible?
12
12
  shared_values_cache = {}
13
13
  Hash.new do |h2, ignored_parent_object|
14
14
  h2[ignored_parent_object] = shared_values_cache
15
- end
15
+ end.compare_by_identity
16
16
  else
17
17
  Hash.new do |h2, parent_object|
18
- args_by_node = {}
19
- args_by_node.compare_by_identity
20
- h2[parent_object] = args_by_node
21
- end
18
+ h2[parent_object] = {}.compare_by_identity
19
+ end.compare_by_identity
22
20
  end
23
- args_by_parent.compare_by_identity
24
- h[argument_owner] = args_by_parent
25
- end
26
- @storage.compare_by_identity
21
+ end.compare_by_identity
27
22
  end
28
23
 
29
24
  def fetch(ast_node, argument_owner, parent_object)
@@ -12,12 +12,16 @@ module GraphQL
12
12
  end
13
13
 
14
14
  def self.resolve_each_depth(lazies_at_depth, dataloader)
15
- depths = lazies_at_depth.keys
16
- depths.sort!
17
- next_depth = depths.first
18
- if next_depth
19
- lazies = lazies_at_depth[next_depth]
20
- lazies_at_depth.delete(next_depth)
15
+ smallest_depth = nil
16
+ lazies_at_depth.each_key do |depth_key|
17
+ smallest_depth ||= depth_key
18
+ if depth_key < smallest_depth
19
+ smallest_depth = depth_key
20
+ end
21
+ end
22
+
23
+ if smallest_depth
24
+ lazies = lazies_at_depth.delete(smallest_depth)
21
25
  if lazies.any?
22
26
  dataloader.append_job {
23
27
  lazies.each(&:value) # resolve these Lazy instances
@@ -53,8 +53,7 @@ module GraphQL
53
53
  end
54
54
  end
55
55
  # { Class => Boolean }
56
- @lazy_cache = {}
57
- @lazy_cache.compare_by_identity
56
+ @lazy_cache = {}.compare_by_identity
58
57
  end
59
58
 
60
59
  def final_result
@@ -208,22 +207,32 @@ module GraphQL
208
207
  finished_jobs = 0
209
208
  enqueued_jobs = gathered_selections.size
210
209
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
211
- @dataloader.append_job {
212
- evaluate_selection(
213
- result_name, field_ast_nodes_or_ast_node, selections_result
214
- )
215
- finished_jobs += 1
216
- if target_result && finished_jobs == enqueued_jobs
217
- selections_result.merge_into(target_result)
218
- end
219
- }
210
+
220
211
  # Field resolution may pause the fiber,
221
212
  # so it wouldn't get to the `Resolve` call that happens below.
222
213
  # So instead trigger a run from this outer context.
223
214
  if selections_result.graphql_is_eager
224
215
  @dataloader.clear_cache
225
- @dataloader.run
226
- @dataloader.clear_cache
216
+ @dataloader.run_isolated {
217
+ evaluate_selection(
218
+ result_name, field_ast_nodes_or_ast_node, selections_result
219
+ )
220
+ finished_jobs += 1
221
+ if target_result && finished_jobs == enqueued_jobs
222
+ selections_result.merge_into(target_result)
223
+ end
224
+ @dataloader.clear_cache
225
+ }
226
+ else
227
+ @dataloader.append_job {
228
+ evaluate_selection(
229
+ result_name, field_ast_nodes_or_ast_node, selections_result
230
+ )
231
+ finished_jobs += 1
232
+ if target_result && finished_jobs == enqueued_jobs
233
+ selections_result.merge_into(target_result)
234
+ end
235
+ }
227
236
  end
228
237
  end
229
238
  selections_result
@@ -727,12 +736,7 @@ module GraphQL
727
736
  end
728
737
 
729
738
  def get_current_runtime_state
730
- current_state = Thread.current[:__graphql_runtime_info] ||= begin
731
- per_query_state = {}
732
- per_query_state.compare_by_identity
733
- per_query_state
734
- end
735
-
739
+ current_state = Thread.current[:__graphql_runtime_info] ||= {}.compare_by_identity
736
740
  current_state[@query] ||= CurrentState.new
737
741
  end
738
742
 
@@ -34,6 +34,7 @@ module GraphQL
34
34
  end
35
35
 
36
36
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
37
+ Fiber[:__graphql_current_multiplex] = multiplex
37
38
  multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
38
39
  schema = multiplex.schema
39
40
  queries = multiplex.queries
@@ -136,6 +137,7 @@ module GraphQL
136
137
  queries.map { |q| q.result_values ||= {} }
137
138
  raise
138
139
  ensure
140
+ Fiber[:__graphql_current_multiplex] = nil
139
141
  queries.map { |query|
140
142
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
141
143
  if runtime
@@ -15,8 +15,8 @@ module GraphQL
15
15
  end
16
16
 
17
17
  def __type(name:)
18
- if context.types.reachable_type?(name)
19
- context.types.type(name)
18
+ if context.types.reachable_type?(name) && (type = context.types.type(name))
19
+ type
20
20
  elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name })
21
21
  type
22
22
  else
@@ -27,34 +27,19 @@ module GraphQL
27
27
  end
28
28
 
29
29
  def query_type
30
- permitted_root_type("query")
30
+ @context.types.query_root
31
31
  end
32
32
 
33
33
  def mutation_type
34
- permitted_root_type("mutation")
34
+ @context.types.mutation_root
35
35
  end
36
36
 
37
37
  def subscription_type
38
- permitted_root_type("subscription")
38
+ @context.types.subscription_root
39
39
  end
40
40
 
41
41
  def directives
42
- @context.types.directives
43
- end
44
-
45
- private
46
-
47
- def permitted_root_type(op_type)
48
- case op_type
49
- when "query"
50
- @context.types.query_root
51
- when "mutation"
52
- @context.types.mutation_root
53
- when "subcription"
54
- @context.types.subscription_root
55
- else
56
- nil
57
- end
42
+ @context.types.directives.sort_by(&:graphql_name)
58
43
  end
59
44
  end
60
45
  end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  end
40
40
 
41
41
  def inspect
42
- if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
42
+ if (name.nil? || parent_class&.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43
43
  "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44
44
  else
45
45
  super
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Language
4
+ module Comment
5
+ def self.print(str, indent: '')
6
+ lines = str.split("\n").map do |line|
7
+ comment_str = "".dup
8
+ comment_str << indent
9
+ comment_str << "# "
10
+ comment_str << line
11
+ comment_str.rstrip
12
+ end
13
+
14
+ lines.join("\n") + "\n"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -18,6 +18,7 @@ module GraphQL
18
18
  include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false
19
19
  )
20
20
  @schema = schema
21
+ @context = context
21
22
  @always_include_schema = always_include_schema
22
23
  @include_introspection_types = include_introspection_types
23
24
  @include_built_in_scalars = include_built_in_scalars
@@ -58,6 +59,7 @@ module GraphQL
58
59
 
59
60
  GraphQL::Language::Nodes::ObjectTypeDefinition.new(
60
61
  name: object_type.graphql_name,
62
+ comment: object_type.comment,
61
63
  interfaces: ints,
62
64
  fields: build_field_nodes(@types.fields(object_type)),
63
65
  description: object_type.description,
@@ -68,6 +70,7 @@ module GraphQL
68
70
  def build_field_node(field)
69
71
  GraphQL::Language::Nodes::FieldDefinition.new(
70
72
  name: field.graphql_name,
73
+ comment: field.comment,
71
74
  arguments: build_argument_nodes(@types.arguments(field)),
72
75
  type: build_type_name_node(field.type),
73
76
  description: field.description,
@@ -78,6 +81,7 @@ module GraphQL
78
81
  def build_union_type_node(union_type)
79
82
  GraphQL::Language::Nodes::UnionTypeDefinition.new(
80
83
  name: union_type.graphql_name,
84
+ comment: union_type.comment,
81
85
  description: union_type.description,
82
86
  types: @types.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) },
83
87
  directives: directives(union_type),
@@ -87,6 +91,7 @@ module GraphQL
87
91
  def build_interface_type_node(interface_type)
88
92
  GraphQL::Language::Nodes::InterfaceTypeDefinition.new(
89
93
  name: interface_type.graphql_name,
94
+ comment: interface_type.comment,
90
95
  interfaces: @types.interfaces(interface_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) },
91
96
  description: interface_type.description,
92
97
  fields: build_field_nodes(@types.fields(interface_type)),
@@ -97,6 +102,7 @@ module GraphQL
97
102
  def build_enum_type_node(enum_type)
98
103
  GraphQL::Language::Nodes::EnumTypeDefinition.new(
99
104
  name: enum_type.graphql_name,
105
+ comment: enum_type.comment,
100
106
  values: @types.enum_values(enum_type).sort_by(&:graphql_name).map do |enum_value|
101
107
  build_enum_value_node(enum_value)
102
108
  end,
@@ -108,6 +114,7 @@ module GraphQL
108
114
  def build_enum_value_node(enum_value)
109
115
  GraphQL::Language::Nodes::EnumValueDefinition.new(
110
116
  name: enum_value.graphql_name,
117
+ comment: enum_value.comment,
111
118
  description: enum_value.description,
112
119
  directives: directives(enum_value),
113
120
  )
@@ -116,6 +123,7 @@ module GraphQL
116
123
  def build_scalar_type_node(scalar_type)
117
124
  GraphQL::Language::Nodes::ScalarTypeDefinition.new(
118
125
  name: scalar_type.graphql_name,
126
+ comment: scalar_type.comment,
119
127
  description: scalar_type.description,
120
128
  directives: directives(scalar_type),
121
129
  )
@@ -130,6 +138,7 @@ module GraphQL
130
138
 
131
139
  argument_node = GraphQL::Language::Nodes::InputValueDefinition.new(
132
140
  name: argument.graphql_name,
141
+ comment: argument.comment,
133
142
  description: argument.description,
134
143
  type: build_type_name_node(argument.type),
135
144
  default_value: default_value,
@@ -142,6 +151,7 @@ module GraphQL
142
151
  def build_input_object_node(input_object)
143
152
  GraphQL::Language::Nodes::InputObjectTypeDefinition.new(
144
153
  name: input_object.graphql_name,
154
+ comment: input_object.comment,
145
155
  fields: build_argument_nodes(@types.arguments(input_object)),
146
156
  description: input_object.description,
147
157
  directives: directives(input_object),
@@ -259,7 +269,33 @@ module GraphQL
259
269
  end
260
270
  definitions = build_directive_nodes(dirs_to_build)
261
271
  all_types = @types.all_types
262
- type_nodes = build_type_definition_nodes(all_types + schema.extra_types)
272
+ type_nodes = build_type_definition_nodes(all_types)
273
+
274
+ if (ex_t = schema.extra_types).any?
275
+ dummy_query = Class.new(GraphQL::Schema::Object) do
276
+ graphql_name "DummyQuery"
277
+ (all_types + ex_t).each_with_index do |type, idx|
278
+ if !type.kind.input_object? && !type.introspection?
279
+ field "f#{idx}", type
280
+ end
281
+ end
282
+ end
283
+
284
+ extra_types_schema = Class.new(GraphQL::Schema) do
285
+ query(dummy_query)
286
+ end
287
+
288
+ extra_types_types = GraphQL::Query.new(extra_types_schema, "{ __typename }", context: @context).types # rubocop:disable Development/ContextIsPassedCop
289
+ # Temporarily replace `@types` with something from this example schema.
290
+ # It'd be much nicer to pass this in, but that would be a big refactor :S
291
+ prev_types = @types
292
+ @types = extra_types_types
293
+ type_nodes += build_type_definition_nodes(ex_t)
294
+ @types = prev_types
295
+ end
296
+
297
+ type_nodes.sort_by!(&:name)
298
+
263
299
  if @include_one_of
264
300
  # This may have been set to true when iterating over all types
265
301
  definitions.concat(build_directive_nodes([GraphQL::Schema::Directive::OneOf]))
@@ -282,9 +318,7 @@ module GraphQL
282
318
  types = types.reject { |type| type.kind.scalar? && type.default_scalar? }
283
319
  end
284
320
 
285
- types
286
- .map { |type| build_type_definition_node(type) }
287
- .sort_by(&:name)
321
+ types.map { |type| build_type_definition_node(type) }
288
322
  end
289
323
 
290
324
  def build_field_nodes(fields)