graphql 1.11.7 → 1.12.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +7 -5
  3. data/lib/generators/graphql/relay.rb +55 -0
  4. data/lib/generators/graphql/relay_generator.rb +20 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/graphql.rb +38 -4
  12. data/lib/graphql/analysis/analyze_query.rb +7 -0
  13. data/lib/graphql/analysis/ast.rb +11 -2
  14. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  15. data/lib/graphql/backtrace.rb +28 -19
  16. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  17. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  18. data/lib/graphql/backtrace/table.rb +22 -3
  19. data/lib/graphql/backtrace/traced_error.rb +0 -1
  20. data/lib/graphql/backtrace/tracer.rb +37 -10
  21. data/lib/graphql/backwards_compatibility.rb +2 -1
  22. data/lib/graphql/base_type.rb +1 -1
  23. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  24. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  25. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  26. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  27. data/lib/graphql/dataloader.rb +208 -0
  28. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  29. data/lib/graphql/dataloader/request.rb +19 -0
  30. data/lib/graphql/dataloader/request_all.rb +19 -0
  31. data/lib/graphql/dataloader/source.rb +107 -0
  32. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  33. data/lib/graphql/define/instance_definable.rb +32 -2
  34. data/lib/graphql/define/type_definer.rb +5 -5
  35. data/lib/graphql/deprecated_dsl.rb +7 -2
  36. data/lib/graphql/deprecation.rb +13 -0
  37. data/lib/graphql/enum_type.rb +2 -0
  38. data/lib/graphql/execution/errors.rb +4 -0
  39. data/lib/graphql/execution/execute.rb +7 -0
  40. data/lib/graphql/execution/interpreter.rb +11 -7
  41. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  42. data/lib/graphql/execution/interpreter/arguments_cache.rb +37 -14
  43. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  44. data/lib/graphql/execution/interpreter/resolve.rb +33 -25
  45. data/lib/graphql/execution/interpreter/runtime.rb +173 -123
  46. data/lib/graphql/execution/multiplex.rb +36 -23
  47. data/lib/graphql/function.rb +4 -0
  48. data/lib/graphql/input_object_type.rb +2 -0
  49. data/lib/graphql/interface_type.rb +3 -1
  50. data/lib/graphql/internal_representation/document.rb +2 -2
  51. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  52. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  53. data/lib/graphql/object_type.rb +2 -2
  54. data/lib/graphql/pagination/connection.rb +5 -1
  55. data/lib/graphql/pagination/connections.rb +6 -16
  56. data/lib/graphql/parse_error.rb +0 -1
  57. data/lib/graphql/query.rb +10 -2
  58. data/lib/graphql/query/arguments.rb +1 -1
  59. data/lib/graphql/query/arguments_cache.rb +0 -1
  60. data/lib/graphql/query/context.rb +4 -2
  61. data/lib/graphql/query/executor.rb +0 -1
  62. data/lib/graphql/query/null_context.rb +3 -2
  63. data/lib/graphql/query/serial_execution.rb +1 -0
  64. data/lib/graphql/query/variable_validation_error.rb +1 -1
  65. data/lib/graphql/relay/base_connection.rb +7 -0
  66. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  67. data/lib/graphql/relay/connection_type.rb +1 -1
  68. data/lib/graphql/relay/mutation.rb +1 -0
  69. data/lib/graphql/relay/node.rb +3 -0
  70. data/lib/graphql/relay/type_extensions.rb +2 -0
  71. data/lib/graphql/scalar_type.rb +2 -0
  72. data/lib/graphql/schema.rb +64 -26
  73. data/lib/graphql/schema/argument.rb +86 -7
  74. data/lib/graphql/schema/build_from_definition.rb +139 -51
  75. data/lib/graphql/schema/directive.rb +76 -0
  76. data/lib/graphql/schema/directive/flagged.rb +57 -0
  77. data/lib/graphql/schema/enum.rb +3 -0
  78. data/lib/graphql/schema/enum_value.rb +12 -6
  79. data/lib/graphql/schema/field.rb +40 -16
  80. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  81. data/lib/graphql/schema/find_inherited_value.rb +3 -1
  82. data/lib/graphql/schema/input_object.rb +39 -24
  83. data/lib/graphql/schema/interface.rb +1 -0
  84. data/lib/graphql/schema/member.rb +4 -0
  85. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  86. data/lib/graphql/schema/member/build_type.rb +3 -3
  87. data/lib/graphql/schema/member/has_arguments.rb +54 -49
  88. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  89. data/lib/graphql/schema/member/has_directives.rb +98 -0
  90. data/lib/graphql/schema/member/has_fields.rb +1 -4
  91. data/lib/graphql/schema/member/has_validators.rb +31 -0
  92. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  93. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  94. data/lib/graphql/schema/middleware_chain.rb +1 -1
  95. data/lib/graphql/schema/object.rb +11 -0
  96. data/lib/graphql/schema/printer.rb +5 -4
  97. data/lib/graphql/schema/resolver.rb +7 -0
  98. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  99. data/lib/graphql/schema/subscription.rb +19 -1
  100. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  101. data/lib/graphql/schema/validation.rb +4 -2
  102. data/lib/graphql/schema/validator.rb +163 -0
  103. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  104. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  105. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  106. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  107. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  108. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  109. data/lib/graphql/static_validation/validator.rb +4 -0
  110. data/lib/graphql/subscriptions.rb +17 -20
  111. data/lib/graphql/subscriptions/event.rb +0 -1
  112. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  113. data/lib/graphql/subscriptions/serialize.rb +0 -1
  114. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  115. data/lib/graphql/tracing.rb +2 -2
  116. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  117. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/relay.rb +11 -3
  120. data/lib/graphql/types/relay/base_connection.rb +2 -92
  121. data/lib/graphql/types/relay/base_edge.rb +2 -35
  122. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  123. data/lib/graphql/types/relay/default_relay.rb +27 -0
  124. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  125. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  126. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  127. data/lib/graphql/types/relay/node.rb +2 -4
  128. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  129. data/lib/graphql/types/relay/node_field.rb +1 -19
  130. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  131. data/lib/graphql/types/relay/page_info.rb +2 -14
  132. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  133. data/lib/graphql/union_type.rb +2 -0
  134. data/lib/graphql/upgrader/member.rb +1 -0
  135. data/lib/graphql/upgrader/schema.rb +1 -0
  136. data/lib/graphql/version.rb +1 -1
  137. metadata +50 -93
  138. data/lib/graphql/types/relay/base_field.rb +0 -22
  139. data/lib/graphql/types/relay/base_interface.rb +0 -29
  140. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Dataloader
5
+ # The default implementation of dataloading -- all no-ops.
6
+ #
7
+ # The Dataloader interface isn't public, but it enables
8
+ # simple internal code while adding the option to add Dataloader.
9
+ class NullDataloader < Dataloader
10
+ # These are all no-ops because code was
11
+ # executed sychronously.
12
+ def run; end
13
+ def yield; end
14
+
15
+ def append_job
16
+ yield
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Dataloader
4
+ # @see Source#request which returns an instance of this
5
+ class Request
6
+ def initialize(source, key)
7
+ @source = source
8
+ @key = key
9
+ end
10
+
11
+ # Call this method to cause the current Fiber to wait for the results of this request.
12
+ #
13
+ # @return [Object] the object loaded for `key`
14
+ def load
15
+ @source.load(@key)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Dataloader
4
+ # @see Source#request_all which returns an instance of this.
5
+ class RequestAll < Request
6
+ def initialize(source, keys)
7
+ @source = source
8
+ @keys = keys
9
+ end
10
+
11
+ # Call this method to cause the current Fiber to wait for the results of this request.
12
+ #
13
+ # @return [Array<Object>] One object for each of `keys`
14
+ def load
15
+ @source.load_all(@keys)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Dataloader
5
+ class Source
6
+ # Called by {Dataloader} to prepare the {Source}'s internal state
7
+ # @api private
8
+ def setup(dataloader)
9
+ @pending_keys = []
10
+ @results = {}
11
+ @dataloader = dataloader
12
+ end
13
+
14
+ attr_reader :dataloader
15
+
16
+ # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
17
+ def request(key)
18
+ if !@results.key?(key)
19
+ @pending_keys << key
20
+ end
21
+ Dataloader::Request.new(self, key)
22
+ end
23
+
24
+ # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
25
+ def request_all(keys)
26
+ pending_keys = keys.select { |k| !@results.key?(k) }
27
+ @pending_keys.concat(pending_keys)
28
+ Dataloader::RequestAll.new(self, keys)
29
+ end
30
+
31
+ # @param key [Object] A loading key which will be passed to {#fetch} if it isn't already in the internal cache.
32
+ # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
33
+ def load(key)
34
+ if @results.key?(key)
35
+ result_for(key)
36
+ else
37
+ @pending_keys << key
38
+ sync
39
+ result_for(key)
40
+ end
41
+ end
42
+
43
+ # @param keys [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
44
+ # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded.
45
+ def load_all(keys)
46
+ if keys.any? { |k| !@results.key?(k) }
47
+ pending_keys = keys.select { |k| !@results.key?(k) }
48
+ @pending_keys.concat(pending_keys)
49
+ sync
50
+ end
51
+
52
+ keys.map { |k| result_for(k) }
53
+ end
54
+
55
+ # Subclasses must implement this method to return a value for each of `keys`
56
+ # @param keys [Array<Object>] keys passed to {#load}, {#load_all}, {#request}, or {#request_all}
57
+ # @return [Array<Object>] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`.
58
+ def fetch(keys)
59
+ # somehow retrieve these from the backend
60
+ raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
61
+ end
62
+
63
+ # Wait for a batch, if there's anything to batch.
64
+ # Then run the batch and update the cache.
65
+ # @return [void]
66
+ def sync
67
+ @dataloader.yield
68
+ end
69
+
70
+ # @return [Boolean] True if this source has any pending requests for data.
71
+ def pending?
72
+ @pending_keys.any?
73
+ end
74
+
75
+ # Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
76
+ # @api private
77
+ # @return [void]
78
+ def run_pending_keys
79
+ return if @pending_keys.empty?
80
+ fetch_keys = @pending_keys.uniq
81
+ @pending_keys = []
82
+ results = fetch(fetch_keys)
83
+ fetch_keys.each_with_index do |key, idx|
84
+ @results[key] = results[idx]
85
+ end
86
+ rescue StandardError => error
87
+ fetch_keys.each { |key| @results[key] = error }
88
+ ensure
89
+ nil
90
+ end
91
+
92
+ private
93
+
94
+ # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
95
+ # @param key [Object] key passed to {#load} or {#load_all}
96
+ # @return [Object] The result from {#fetch} for `key`.
97
+ # @api private
98
+ def result_for(key)
99
+ result = @results[key]
100
+
101
+ raise result if result.class <= StandardError
102
+
103
+ result
104
+ end
105
+ end
106
+ end
107
+ end
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module AssignGlobalIdField
5
5
  def self.call(type_defn, field_name, **field_kwargs)
6
6
  resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn)
7
- GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve)
7
+ GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::DEPRECATED_ID_TYPE.to_non_null_type, resolve: resolve)
8
8
  end
9
9
  end
10
10
  end
@@ -3,6 +3,23 @@ module GraphQL
3
3
  module Define
4
4
  # @api deprecated
5
5
  module InstanceDefinable
6
+ module DeprecatedDefine
7
+ def define(**kwargs, &block)
8
+ deprecated_caller = caller(1, 1).first
9
+ if deprecated_caller.include?("lib/graphql")
10
+ deprecated_caller = caller(2, 10).find { |c| !c.include?("lib/graphql") }
11
+ end
12
+
13
+ if deprecated_caller
14
+ GraphQL::Deprecation.warn <<-ERR
15
+ #{self}.define will be removed in GraphQL-Ruby 2.0; use a class-based definition instead. See https://graphql-ruby.org/schema/class_based_api.html.
16
+ -> called from #{deprecated_caller}
17
+ ERR
18
+ end
19
+ deprecated_define(**kwargs, &block)
20
+ end
21
+ end
22
+
6
23
  def self.included(base)
7
24
  base.extend(ClassMethods)
8
25
  base.ensure_defined(:metadata)
@@ -14,7 +31,7 @@ module GraphQL
14
31
  end
15
32
 
16
33
  # @api deprecated
17
- def define(**kwargs, &block)
34
+ def deprecated_define(**kwargs, &block)
18
35
  # make sure the previous definition_proc was executed:
19
36
  ensure_defined
20
37
  stash_dependent_methods
@@ -22,11 +39,16 @@ module GraphQL
22
39
  nil
23
40
  end
24
41
 
42
+ # @api deprecated
43
+ def define(**kwargs, &block)
44
+ deprecated_define(**kwargs, &block)
45
+ end
46
+
25
47
  # @api deprecated
26
48
  def redefine(**kwargs, &block)
27
49
  ensure_defined
28
50
  new_inst = self.dup
29
- new_inst.define(**kwargs, &block)
51
+ new_inst.deprecated_define(**kwargs, &block)
30
52
  new_inst
31
53
  end
32
54
 
@@ -125,8 +147,16 @@ module GraphQL
125
147
  module ClassMethods
126
148
  # Create a new instance
127
149
  # and prepare a definition using its {.definitions}.
150
+ # @api deprecated
128
151
  # @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions`
129
152
  # @param block [Proc] Block which calls helper methods from `accepts_definitions`
153
+ def deprecated_define(**kwargs, &block)
154
+ instance = self.new
155
+ instance.deprecated_define(**kwargs, &block)
156
+ instance
157
+ end
158
+
159
+ # @api deprecated
130
160
  def define(**kwargs, &block)
131
161
  instance = self.new
132
162
  instance.define(**kwargs, &block)
@@ -7,11 +7,11 @@ module GraphQL
7
7
  class TypeDefiner
8
8
  include Singleton
9
9
  # rubocop:disable Naming/MethodName
10
- def Int; GraphQL::INT_TYPE; end
11
- def String; GraphQL::STRING_TYPE; end
12
- def Float; GraphQL::FLOAT_TYPE; end
13
- def Boolean; GraphQL::BOOLEAN_TYPE; end
14
- def ID; GraphQL::ID_TYPE; end
10
+ def Int; GraphQL::DEPRECATED_INT_TYPE; end
11
+ def String; GraphQL::DEPRECATED_STRING_TYPE; end
12
+ def Float; GraphQL::DEPRECATED_FLOAT_TYPE; end
13
+ def Boolean; GraphQL::DEPRECATED_BOOLEAN_TYPE; end
14
+ def ID; GraphQL::DEPRECATED_ID_TYPE; end
15
15
  # rubocop:enable Naming/MethodName
16
16
 
17
17
  # Make a {ListType} which wraps the input type
@@ -4,13 +4,13 @@ module GraphQL
4
4
  #
5
5
  # 1. Scoped by file (CRuby only), add to the top of the file:
6
6
  #
7
- # using GraphQL::DeprecatedDSL
7
+ # using GraphQL::DeprecationDSL
8
8
  #
9
9
  # (This is a "refinement", there are also other ways to scope it.)
10
10
  #
11
11
  # 2. Global application, add before schema definition:
12
12
  #
13
- # GraphQL::DeprecatedDSL.activate
13
+ # GraphQL::DeprecationDSL.activate
14
14
  #
15
15
  module DeprecatedDSL
16
16
  TYPE_CLASSES = [
@@ -23,12 +23,17 @@ module GraphQL
23
23
  ]
24
24
 
25
25
  def self.activate
26
+ deprecated_caller = caller(1, 1).first
27
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` and remove `.activate` from #{deprecated_caller}"
26
28
  TYPE_CLASSES.each { |c| c.extend(Methods) }
27
29
  GraphQL::Schema::List.include(Methods)
28
30
  GraphQL::Schema::NonNull.include(Methods)
29
31
  end
32
+
30
33
  module Methods
31
34
  def !
35
+ deprecated_caller = caller(1, 1).first
36
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` at #{deprecated_caller}"
32
37
  to_non_null_type
33
38
  end
34
39
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Deprecation
5
+ def self.warn(message)
6
+ if defined?(ActiveSupport::Deprecation)
7
+ ActiveSupport::Deprecation.warn(message)
8
+ else
9
+ Kernel.warn(message)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class EnumType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
6
8
  ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result)
7
9
  attr_accessor :ast_node
@@ -18,6 +18,10 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
+ if schema.plugins.any? { |(plugin, kwargs)| plugin == self }
22
+ definition_line = caller(2, 1).first
23
+ GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
24
+ end
21
25
  schema.error_handler = self.new(schema)
22
26
  end
23
27
 
@@ -18,7 +18,14 @@ module GraphQL
18
18
  # @api private
19
19
  PROPAGATE_NULL = PropagateNull.new
20
20
 
21
+ def self.use(schema_class)
22
+ schema_class.query_execution_strategy(self)
23
+ schema_class.mutation_execution_strategy(self)
24
+ schema_class.subscription_execution_strategy(self)
25
+ end
26
+
21
27
  def execute(ast_operation, root_type, query)
28
+ GraphQL::Deprecation.warn "#{self.class} will be removed in GraphQL-Ruby 2.0, please upgrade to the Interpreter: https://graphql-ruby.org/queries/interpreter.html"
22
29
  result = resolve_root_selection(query)
23
30
  lazy_resolve_root_selection(result, **{query: query})
24
31
  GraphQL::Execution::Flatten.call(query.context)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "fiber"
2
3
  require "graphql/execution/interpreter/argument_value"
3
4
  require "graphql/execution/interpreter/arguments"
4
5
  require "graphql/execution/interpreter/arguments_cache"
@@ -22,12 +23,15 @@ module GraphQL
22
23
  end
23
24
 
24
25
  def self.use(schema_class)
25
- schema_class.interpreter = true
26
- schema_class.query_execution_strategy(GraphQL::Execution::Interpreter)
27
- schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter)
28
- schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter)
29
- schema_class.add_subscription_extension_if_necessary
30
- GraphQL::Schema::Object.include(HandlesRawValue)
26
+ if schema_class.interpreter?
27
+ definition_line = caller(2, 1).first
28
+ GraphQL::Deprecation.warn("GraphQL::Execution::Interpreter is now the default; remove `use GraphQL::Execution::Interpreter` from the schema definition (#{definition_line})")
29
+ else
30
+ schema_class.query_execution_strategy(self)
31
+ schema_class.mutation_execution_strategy(self)
32
+ schema_class.subscription_execution_strategy(self)
33
+ schema_class.add_subscription_extension_if_necessary
34
+ end
31
35
  end
32
36
 
33
37
  def self.begin_multiplex(multiplex)
@@ -91,7 +95,7 @@ module GraphQL
91
95
  end
92
96
  final_values.compact!
93
97
  tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
94
- Interpreter::Resolve.resolve_all(final_values)
98
+ Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
95
99
  end
96
100
  queries.each do |query|
97
101
  runtime = query.context.namespace(:interpreter)[:runtime]
@@ -5,6 +5,9 @@ module GraphQL
5
5
  class Interpreter
6
6
  # A wrapper for argument hashes in GraphQL queries.
7
7
  #
8
+ # This object is immutable so that the runtime code can be sure that
9
+ # modifications don't leak from one use to another
10
+ #
8
11
  # @see GraphQL::Query#arguments_for to get access to these objects.
9
12
  class Arguments
10
13
  extend Forwardable
@@ -14,26 +17,43 @@ module GraphQL
14
17
  # This hash is the one used at runtime.
15
18
  #
16
19
  # @return [Hash<Symbol, Object>]
17
- def keyword_arguments
18
- @keyword_arguments ||= begin
19
- kwargs = {}
20
- argument_values.each do |name, arg_val|
21
- kwargs[name] = arg_val.value
22
- end
23
- kwargs
24
- end
25
- end
20
+ attr_reader :keyword_arguments
26
21
 
27
22
  # @param argument_values [nil, Hash{Symbol => ArgumentValue}]
28
- def initialize(argument_values:)
29
- @argument_values = argument_values
23
+ # @param keyword_arguments [nil, Hash{Symbol => Object}]
24
+ def initialize(keyword_arguments: nil, argument_values:)
30
25
  @empty = argument_values.nil? || argument_values.empty?
26
+ # This is only present when `extras` have been merged in:
27
+ if keyword_arguments
28
+ # This is a little crazy. We expect the `:argument_details` extra to _include extras_,
29
+ # but the object isn't created until _after_ extras are put together.
30
+ # So, we have to use a special flag here to say, "at the last minute, add yourself to the keyword args."
31
+ #
32
+ # Otherwise:
33
+ # - We can't access the final Arguments instance _while_ we're preparing extras
34
+ # - After we _can_ access it, it's frozen, so we can't add anything.
35
+ #
36
+ # So, this flag gives us a chance to sneak it in before freezing, _and_ while we have access
37
+ # to the new Arguments instance itself.
38
+ if keyword_arguments[:argument_details] == :__arguments_add_self
39
+ keyword_arguments[:argument_details] = self
40
+ end
41
+ @keyword_arguments = keyword_arguments.freeze
42
+ elsif !@empty
43
+ @keyword_arguments = {}
44
+ argument_values.each do |name, arg_val|
45
+ @keyword_arguments[name] = arg_val.value
46
+ end
47
+ @keyword_arguments.freeze
48
+ else
49
+ @keyword_arguments = NO_ARGS
50
+ end
51
+ @argument_values = argument_values ? argument_values.freeze : NO_ARGS
52
+ freeze
31
53
  end
32
54
 
33
55
  # @return [Hash{Symbol => ArgumentValue}]
34
- def argument_values
35
- @argument_values ||= {}
36
- end
56
+ attr_reader :argument_values
37
57
 
38
58
  def empty?
39
59
  @empty
@@ -45,6 +65,23 @@ module GraphQL
45
65
  def inspect
46
66
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
47
67
  end
68
+
69
+ # Create a new arguments instance which includes these extras.
70
+ #
71
+ # This is called by the runtime to implement field `extras: [...]`
72
+ #
73
+ # @param extra_args [Hash<Symbol => Object>]
74
+ # @return [Interpreter::Arguments]
75
+ # @api private
76
+ def merge_extras(extra_args)
77
+ self.class.new(
78
+ argument_values: argument_values,
79
+ keyword_arguments: keyword_arguments.merge(extra_args)
80
+ )
81
+ end
82
+
83
+ NO_ARGS = {}.freeze
84
+ EMPTY = self.new(argument_values: nil, keyword_arguments: NO_ARGS).freeze
48
85
  end
49
86
  end
50
87
  end