graphql 2.0.27 → 2.1.3

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 (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"