graphql 2.0.27 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  22. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  23. data/lib/graphql/analysis/ast.rb +15 -11
  24. data/lib/graphql/dataloader/source.rb +7 -0
  25. data/lib/graphql/dataloader.rb +38 -10
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  27. data/lib/graphql/execution/interpreter/runtime.rb +95 -254
  28. data/lib/graphql/execution/interpreter.rb +0 -6
  29. data/lib/graphql/execution/lookahead.rb +1 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  31. data/lib/graphql/introspection/entry_points.rb +2 -2
  32. data/lib/graphql/language/block_string.rb +28 -16
  33. data/lib/graphql/language/definition_slice.rb +1 -1
  34. data/lib/graphql/language/document_from_schema_definition.rb +36 -35
  35. data/lib/graphql/language/nodes.rb +2 -2
  36. data/lib/graphql/language/printer.rb +294 -145
  37. data/lib/graphql/language/sanitized_printer.rb +20 -22
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +20 -81
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/pagination/connection.rb +23 -1
  42. data/lib/graphql/query/context/scoped_context.rb +101 -0
  43. data/lib/graphql/query/context.rb +32 -98
  44. data/lib/graphql/query.rb +2 -19
  45. data/lib/graphql/rake_task.rb +3 -12
  46. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  47. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  48. data/lib/graphql/schema/field/scope_extension.rb +7 -1
  49. data/lib/graphql/schema/field.rb +7 -4
  50. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  51. data/lib/graphql/schema/introspection_system.rb +2 -0
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  53. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  54. data/lib/graphql/schema/member/has_fields.rb +4 -1
  55. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  56. data/lib/graphql/schema/member/scoped.rb +19 -0
  57. data/lib/graphql/schema/object.rb +8 -0
  58. data/lib/graphql/schema/printer.rb +8 -7
  59. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  60. data/lib/graphql/schema/resolver.rb +4 -0
  61. data/lib/graphql/schema/scalar.rb +3 -3
  62. data/lib/graphql/schema/subscription.rb +11 -4
  63. data/lib/graphql/schema/warden.rb +87 -89
  64. data/lib/graphql/schema.rb +125 -55
  65. data/lib/graphql/static_validation/all_rules.rb +1 -1
  66. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  67. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  68. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  69. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  70. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  71. data/lib/graphql/static_validation/validation_context.rb +5 -5
  72. data/lib/graphql/static_validation.rb +0 -1
  73. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  74. data/lib/graphql/subscriptions.rb +11 -6
  75. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  76. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  77. data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
  78. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  79. data/lib/graphql/version.rb +1 -1
  80. data/lib/graphql.rb +1 -2
  81. metadata +23 -20
  82. data/lib/graphql/filter.rb +0 -59
  83. data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -37,10 +37,12 @@ require "graphql/schema/directive/skip"
37
37
  require "graphql/schema/directive/feature"
38
38
  require "graphql/schema/directive/flagged"
39
39
  require "graphql/schema/directive/transform"
40
+ require "graphql/schema/directive/specified_by"
40
41
  require "graphql/schema/type_membership"
41
42
 
42
43
  require "graphql/schema/resolver"
43
44
  require "graphql/schema/mutation"
45
+ require "graphql/schema/has_single_input_argument"
44
46
  require "graphql/schema/relay_classic_mutation"
45
47
  require "graphql/schema/subscription"
46
48
 
@@ -144,6 +146,19 @@ module GraphQL
144
146
  @subscriptions = new_implementation
145
147
  end
146
148
 
149
+ # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
150
+ def default_trace_mode(new_mode = nil)
151
+ if new_mode
152
+ @default_trace_mode = new_mode
153
+ elsif defined?(@default_trace_mode)
154
+ @default_trace_mode
155
+ elsif superclass.respond_to?(:default_trace_mode)
156
+ superclass.default_trace_mode
157
+ else
158
+ :default
159
+ end
160
+ end
161
+
147
162
  def trace_class(new_class = nil)
148
163
  if new_class
149
164
  trace_mode(:default, new_class)
@@ -156,41 +171,65 @@ module GraphQL
156
171
 
157
172
  # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
158
173
  def trace_class_for(mode)
159
- @trace_modes ||= {}
160
- @trace_modes[mode] ||= begin
161
- case mode
162
- when :default
163
- superclass_base_class = if superclass.respond_to?(:trace_class_for)
164
- superclass.trace_class_for(mode)
165
- else
166
- GraphQL::Tracing::Trace
167
- end
168
- Class.new(superclass_base_class)
169
- when :default_backtrace
170
- schema_base_class = trace_class_for(:default)
171
- Class.new(schema_base_class) do
172
- include(GraphQL::Backtrace::Trace)
173
- end
174
- else
175
- mods = trace_modules_for(mode)
176
- Class.new(trace_class_for(:default)) do
177
- mods.any? && include(*mods)
178
- end
179
- end
180
- end
174
+ own_trace_modes[mode] ||
175
+ (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode) : (own_trace_modes[mode] = build_trace_mode(mode)))
181
176
  end
182
177
 
183
178
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
184
- # `:default` is used when no `trace_mode: ...` is requested.
179
+ # {default_trace_mode} is used when no `trace_mode: ...` is requested.
180
+ #
181
+ # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)`
182
+ # unless `trace_mode` is explicitly given. (This class will not recieve any default trace modules.)
183
+ #
184
+ # Subclasses of the schema will use `trace_class` as a base class for this mode and those
185
+ # subclass also will _not_ receive default tracing modules.
186
+ #
185
187
  # @param mode_name [Symbol]
186
188
  # @param trace_class [Class] subclass of GraphQL::Tracing::Trace
187
189
  # @return void
188
190
  def trace_mode(mode_name, trace_class)
189
- @trace_modes ||= {}
190
- @trace_modes[mode_name] = trace_class
191
+ own_trace_modes[mode_name] = trace_class
191
192
  nil
192
193
  end
193
194
 
195
+ def own_trace_modes
196
+ @own_trace_modes ||= {}
197
+ end
198
+
199
+ module DefaultTraceClass
200
+ end
201
+
202
+ private_constant :DefaultTraceClass
203
+
204
+ def build_trace_mode(mode)
205
+ case mode
206
+ when :default
207
+ # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
208
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
209
+ Class.new(base_class) do
210
+ include DefaultTraceClass
211
+ end
212
+ when :default_backtrace
213
+ schema_base_class = trace_class_for(:default)
214
+ Class.new(schema_base_class) do
215
+ include(GraphQL::Backtrace::Trace)
216
+ end
217
+ else
218
+ # First, see if the superclass has a custom-defined class for this.
219
+ # Then, if it doesn't, use this class's default trace
220
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default)
221
+ # Prepare the default trace class if it hasn't been initialized yet
222
+ base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
223
+ mods = trace_modules_for(mode)
224
+ if base_class < DefaultTraceClass
225
+ mods = trace_modules_for(:default) + mods
226
+ end
227
+ Class.new(base_class) do
228
+ mods.any? && include(*mods)
229
+ end
230
+ end
231
+ end
232
+
194
233
  def own_trace_modules
195
234
  @own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
196
235
  end
@@ -222,7 +261,7 @@ module GraphQL
222
261
  # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
223
262
  # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
224
263
  # @return [Hash] GraphQL result
225
- def as_json(only: nil, except: nil, context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
264
+ def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
226
265
  introspection_query = Introspection.query(
227
266
  include_deprecated_args: include_deprecated_args,
228
267
  include_schema_description: include_schema_description,
@@ -231,16 +270,14 @@ module GraphQL
231
270
  include_specified_by_url: include_specified_by_url,
232
271
  )
233
272
 
234
- execute(introspection_query, only: only, except: except, context: context).to_h
273
+ execute(introspection_query, context: context).to_h
235
274
  end
236
275
 
237
276
  # Return the GraphQL IDL for the schema
238
277
  # @param context [Hash]
239
- # @param only [<#call(member, ctx)>]
240
- # @param except [<#call(member, ctx)>]
241
278
  # @return [String]
242
- def to_definition(only: nil, except: nil, context: {})
243
- GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
279
+ def to_definition(context: {})
280
+ GraphQL::Schema::Printer.print_schema(self, context: context)
244
281
  end
245
282
 
246
283
  # Return the GraphQL::Language::Document IDL AST for the schema
@@ -268,20 +305,6 @@ module GraphQL
268
305
  @find_cache[path] ||= @finder.find(path)
269
306
  end
270
307
 
271
- def default_filter
272
- GraphQL::Filter.new(except: default_mask)
273
- end
274
-
275
- def default_mask(new_mask = nil)
276
- if new_mask
277
- line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
278
- GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
279
- @own_default_mask = new_mask
280
- else
281
- @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
282
- end
283
- end
284
-
285
308
  def static_validator
286
309
  GraphQL::StaticValidation::Validator.new(schema: self)
287
310
  end
@@ -717,9 +740,10 @@ module GraphQL
717
740
 
718
741
  attr_writer :max_depth
719
742
 
720
- def max_depth(new_max_depth = nil)
743
+ def max_depth(new_max_depth = nil, count_introspection_fields: true)
721
744
  if new_max_depth
722
745
  @max_depth = new_max_depth
746
+ @count_introspection_fields = count_introspection_fields
723
747
  elsif defined?(@max_depth)
724
748
  @max_depth
725
749
  else
@@ -727,6 +751,14 @@ module GraphQL
727
751
  end
728
752
  end
729
753
 
754
+ def count_introspection_fields
755
+ if defined?(@count_introspection_fields)
756
+ @count_introspection_fields
757
+ else
758
+ find_inherited_value(:count_introspection_fields, true)
759
+ end
760
+ end
761
+
730
762
  def disable_introspection_entry_points
731
763
  @disable_introspection_entry_points = true
732
764
  # TODO: this clears the cache made in `def types`. But this is not a great solution.
@@ -776,7 +808,16 @@ module GraphQL
776
808
  own_orphan_types.concat(new_orphan_types.flatten)
777
809
  end
778
810
 
779
- find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types
811
+ inherited_ot = find_inherited_value(:orphan_types, nil)
812
+ if inherited_ot
813
+ if own_orphan_types.any?
814
+ inherited_ot + own_orphan_types
815
+ else
816
+ inherited_ot
817
+ end
818
+ else
819
+ own_orphan_types
820
+ end
780
821
  end
781
822
 
782
823
  def default_execution_strategy
@@ -882,6 +923,11 @@ module GraphQL
882
923
  if self == GraphQL::Schema
883
924
  child_class.directives(default_directives.values)
884
925
  end
926
+ # Make sure the child class has these built out, so that
927
+ # subclasses can be modified by later calls to `trace_with`
928
+ own_trace_modes.each do |name, _class|
929
+ child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
930
+ end
885
931
  child_class.singleton_class.prepend(ResolveTypeWithType)
886
932
  super
887
933
  end
@@ -978,7 +1024,12 @@ module GraphQL
978
1024
  new_directives.flatten.each { |d| directive(d) }
979
1025
  end
980
1026
 
981
- find_inherited_value(:directives, default_directives).merge(own_directives)
1027
+ inherited_dirs = find_inherited_value(:directives, default_directives)
1028
+ if own_directives.any?
1029
+ inherited_dirs.merge(own_directives)
1030
+ else
1031
+ inherited_dirs
1032
+ end
982
1033
  end
983
1034
 
984
1035
  # Attach a single directive to this schema
@@ -994,11 +1045,13 @@ module GraphQL
994
1045
  "skip" => GraphQL::Schema::Directive::Skip,
995
1046
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
996
1047
  "oneOf" => GraphQL::Schema::Directive::OneOf,
1048
+ "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy,
997
1049
  }.freeze
998
1050
  end
999
1051
 
1000
1052
  def tracer(new_tracer)
1001
- if !(trace_class_for(:default) < GraphQL::Tracing::CallLegacyTracers)
1053
+ default_trace = trace_class_for(:default)
1054
+ if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1002
1055
  trace_with(GraphQL::Tracing::CallLegacyTracers)
1003
1056
  end
1004
1057
 
@@ -1020,10 +1073,20 @@ module GraphQL
1020
1073
  if mode.is_a?(Array)
1021
1074
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
1022
1075
  else
1023
- tc = trace_class_for(mode)
1076
+ tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1024
1077
  tc.include(trace_mod)
1025
- if mode != :default
1026
- own_trace_modules[mode] << trace_mod
1078
+ own_trace_modules[mode] << trace_mod
1079
+
1080
+ if mode == :default
1081
+ # This module is being added as a default tracer. If any other mode classes
1082
+ # have already been created, but get their default behavior from a superclass,
1083
+ # Then mix this into this schema's subclass.
1084
+ # (But don't mix it into mode classes that aren't default-based.)
1085
+ own_trace_modes.each do |other_mode_name, other_mode_class|
1086
+ if other_mode_class < DefaultTraceClass && !(other_mode_class < trace_mod)
1087
+ other_mode_class.include(trace_mod)
1088
+ end
1089
+ end
1027
1090
  end
1028
1091
  t_opts = trace_options_for(mode)
1029
1092
  t_opts.merge!(options)
@@ -1046,6 +1109,8 @@ module GraphQL
1046
1109
 
1047
1110
  # Create a trace instance which will include the trace modules specified for the optional mode.
1048
1111
  #
1112
+ # If no `mode:` is given, then {default_trace_mode} will be used.
1113
+ #
1049
1114
  # @param mode [Symbol] Trace modules for this trade mode will be included
1050
1115
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1051
1116
  # @return [Tracing::Trace]
@@ -1056,14 +1121,19 @@ module GraphQL
1056
1121
  trace_mode = if mode
1057
1122
  mode
1058
1123
  elsif target && target.context[:backtrace]
1059
- :default_backtrace
1124
+ if default_trace_mode != :default
1125
+ raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1126
+ else
1127
+ own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1128
+ :default_backtrace
1129
+ end
1060
1130
  else
1061
- :default
1131
+ default_trace_mode
1062
1132
  end
1063
1133
 
1064
1134
  base_trace_options = trace_options_for(trace_mode)
1065
1135
  trace_options = base_trace_options.merge(options)
1066
- trace_class_for_mode = trace_class_for(trace_mode)
1136
+ trace_class_for_mode = trace_class_for(trace_mode) || raise(ArgumentError, "#{self} has no trace class for mode: #{trace_mode.inspect}")
1067
1137
  trace_class_for_mode.new(**trace_options)
1068
1138
  end
1069
1139
 
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  # Default rules for {GraphQL::StaticValidation::Validator}
5
5
  #
6
- # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP}
6
+ # Order is important here. Some validators skip later hooks.
7
7
  # which stops the visit on that node. That way it doesn't try to find fields on types that
8
8
  # don't exist, etc.
9
9
  ALL_RULES = [
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class BaseVisitor < GraphQL::Language::Visitor
4
+ class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
7
  @object_types = []
@@ -111,7 +111,7 @@ module GraphQL
111
111
  # that required fields are missing
112
112
  required_field_names = @warden.arguments(type)
113
113
  .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
114
- .map(&:name)
114
+ .map!(&:name)
115
115
 
116
116
  present_field_names = ast_node.arguments.map(&:name)
117
117
  missing_required_field_names = required_field_names - present_field_names
@@ -340,7 +340,7 @@ module GraphQL
340
340
  selections.each do |node|
341
341
  case node
342
342
  when GraphQL::Language::Nodes::Field
343
- definition = context.query.get_field(owner_type, node.name)
343
+ definition = context.warden.get_field(owner_type, node.name)
344
344
  fields << Field.new(node, definition, owner_type, parents)
345
345
  when GraphQL::Language::Nodes::InlineFragment
346
346
  fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
@@ -21,7 +21,7 @@ module GraphQL
21
21
  present_argument_names = ast_node.arguments.map(&:name)
22
22
  required_argument_names = context.warden.arguments(defn)
23
23
  .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
24
- .map(&:name)
24
+ .map!(&:name)
25
25
 
26
26
  missing_names = required_argument_names - present_argument_names
27
27
  if missing_names.any?
@@ -36,7 +36,7 @@ module GraphQL
36
36
 
37
37
  required_fields = context.warden.arguments(parent_type)
38
38
  .select{|arg| arg.type.kind.non_null?}
39
- .map(&:graphql_name)
39
+ .map!(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)
42
42
  missing_fields = required_fields - present_fields
@@ -8,20 +8,20 @@ module GraphQL
8
8
  # It provides access to the schema & fragments which validators may read from.
9
9
  #
10
10
  # It holds a list of errors which each validator may add to.
11
- #
12
- # It also provides limited access to the {TypeStack} instance,
13
- # which tracks state as you climb in and out of different fields.
14
11
  class ValidationContext
15
12
  extend Forwardable
16
13
 
17
14
  attr_reader :query, :errors, :visitor,
18
15
  :on_dependency_resolve_handlers,
19
- :max_errors
16
+ :max_errors, :warden, :schema
17
+
20
18
 
21
- def_delegators :@query, :schema, :document, :fragments, :operations, :warden
19
+ def_delegators :@query, :document, :fragments, :operations
22
20
 
23
21
  def initialize(query, visitor_class, max_errors)
24
22
  @query = query
23
+ @warden = query.warden
24
+ @schema = query.schema
25
25
  @literal_validator = LiteralValidator.new(context: query.context)
26
26
  @errors = []
27
27
  @max_errors = max_errors || Float::INFINITY
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/static_validation/error"
3
3
  require "graphql/static_validation/definition_dependencies"
4
- require "graphql/static_validation/type_stack"
5
4
  require "graphql/static_validation/validator"
6
5
  require "graphql/static_validation/validation_context"
7
6
  require "graphql/static_validation/validation_timeout_error"
@@ -124,7 +124,8 @@ module GraphQL
124
124
  # This subscription was re-evaluated.
125
125
  # Send it to the specific stream where this client was waiting.
126
126
  def deliver(subscription_id, result)
127
- payload = { result: result.to_h, more: true }
127
+ has_more = !result.context.namespace(:subscriptions)[:final_update]
128
+ payload = { result: result.to_h, more: has_more }
128
129
  @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
129
130
  end
130
131
 
@@ -125,10 +125,10 @@ module GraphQL
125
125
  variables: variables,
126
126
  root_value: object,
127
127
  }
128
-
128
+
129
129
  # merge event's and query's context together
130
130
  context.merge!(event.context) unless event.context.nil? || context.nil?
131
-
131
+
132
132
  execute_options[:validate] = validate_update?(**execute_options)
133
133
  result = @schema.execute(**execute_options)
134
134
  subscriptions_context = result.context.namespace(:subscriptions)
@@ -136,11 +136,9 @@ module GraphQL
136
136
  result = nil
137
137
  end
138
138
 
139
- unsubscribed = subscriptions_context[:unsubscribed]
140
-
141
- if unsubscribed
139
+ if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
142
140
  # `unsubscribe` was called, clean up on our side
143
- # TODO also send `{more: false}` to client?
141
+ # The transport should also send `{more: false}` to client
144
142
  delete_subscription(subscription_id)
145
143
  result = nil
146
144
  end
@@ -164,7 +162,14 @@ module GraphQL
164
162
  res = execute_update(subscription_id, event, object)
165
163
  if !res.nil?
166
164
  deliver(subscription_id, res)
165
+
166
+ if res.context.namespace(:subscriptions)[:unsubscribed]
167
+ # `unsubscribe` was called, clean up on our side
168
+ # The transport should also send `{more: false}` to client
169
+ delete_subscription(subscription_id)
170
+ end
167
171
  end
172
+
168
173
  end
169
174
 
170
175
  # Event `event` occurred on `object`,
@@ -195,7 +195,7 @@ module GraphQL
195
195
  else
196
196
  [key, data[key]]
197
197
  end
198
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
198
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
199
199
  end
200
200
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
201
201
 
@@ -226,7 +226,7 @@ module GraphQL
226
226
  end
227
227
 
228
228
  def graphql_multiplex(data)
229
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
229
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
230
230
  multiplex_transaction_name(names) if names.size > 1
231
231
 
232
232
  [:Operations, names.join(', ')]
@@ -117,7 +117,7 @@ module GraphQL
117
117
  else
118
118
  [key, data[key]]
119
119
  end
120
- end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
120
+ end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
121
121
  end
122
122
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
123
123
 
@@ -148,7 +148,7 @@ module GraphQL
148
148
  end
149
149
 
150
150
  def graphql_multiplex(data)
151
- names = data.queries.map(&:operations).map(&:keys).flatten.compact
151
+ names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
152
152
  multiplex_transaction_name(names) if names.size > 1
153
153
 
154
154
  [:Operations, names.join(', ')]
@@ -67,9 +67,8 @@ module GraphQL
67
67
  type: [edge_type_class, null: edge_nullable],
68
68
  null: edges_nullable,
69
69
  description: "A list of edges.",
70
+ scope: false, # Assume that the connection was already scoped.
70
71
  connection: false,
71
- # Assume that the connection was scoped before this step:
72
- scope: false,
73
72
  }
74
73
 
75
74
  if field_options
@@ -170,6 +169,24 @@ module GraphQL
170
169
  obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
171
170
  end
172
171
  end
172
+
173
+ def edges
174
+ # Assume that whatever authorization needed to happen
175
+ # already happened at the connection level.
176
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
177
+ query_runtime_state = current_runtime_state[context.query]
178
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
179
+ @object.edges
180
+ end
181
+
182
+ def nodes
183
+ # Assume that whatever authorization needed to happen
184
+ # already happened at the connection level.
185
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
186
+ query_runtime_state = current_runtime_state[context.query]
187
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
188
+ @object.nodes
189
+ end
173
190
  end
174
191
  end
175
192
  end
@@ -12,6 +12,13 @@ module GraphQL
12
12
  child_class.node_nullable(true)
13
13
  end
14
14
 
15
+ def node
16
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
17
+ query_runtime_state = current_runtime_state[context.query]
18
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
19
+ @object.node
20
+ end
21
+
15
22
  module ClassMethods
16
23
  def inherited(child_class)
17
24
  super
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.27"
3
+ VERSION = "2.1.3"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -101,9 +101,8 @@ require "graphql/execution"
101
101
  require "graphql/pagination"
102
102
  require "graphql/schema"
103
103
  require "graphql/query"
104
- require "graphql/types"
105
104
  require "graphql/dataloader"
106
- require "graphql/filter"
105
+ require "graphql/types"
107
106
  require "graphql/static_validation"
108
107
  require "graphql/execution"
109
108
  require "graphql/schema/built_in_types"