graphql 1.10.13 → 1.11.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/graphql_controller.erb +11 -9
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/directive.rb +4 -0
  5. data/lib/graphql/execution/interpreter.rb +1 -1
  6. data/lib/graphql/execution/interpreter/runtime.rb +6 -4
  7. data/lib/graphql/execution/multiplex.rb +1 -2
  8. data/lib/graphql/field.rb +4 -0
  9. data/lib/graphql/input_object_type.rb +4 -0
  10. data/lib/graphql/introspection/schema_type.rb +3 -3
  11. data/lib/graphql/invalid_null_error.rb +18 -0
  12. data/lib/graphql/language/nodes.rb +1 -0
  13. data/lib/graphql/language/visitor.rb +2 -2
  14. data/lib/graphql/pagination/connection.rb +18 -13
  15. data/lib/graphql/pagination/connections.rb +17 -4
  16. data/lib/graphql/query.rb +1 -2
  17. data/lib/graphql/schema.rb +22 -16
  18. data/lib/graphql/schema/build_from_definition.rb +7 -12
  19. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  20. data/lib/graphql/schema/enum_value.rb +1 -0
  21. data/lib/graphql/schema/field.rb +63 -77
  22. data/lib/graphql/schema/field/connection_extension.rb +42 -32
  23. data/lib/graphql/schema/loader.rb +19 -1
  24. data/lib/graphql/schema/member/has_arguments.rb +3 -1
  25. data/lib/graphql/schema/member/has_fields.rb +15 -5
  26. data/lib/graphql/schema/mutation.rb +4 -0
  27. data/lib/graphql/schema/object.rb +1 -1
  28. data/lib/graphql/schema/resolver.rb +20 -0
  29. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -1
  30. data/lib/graphql/schema/subscription.rb +1 -1
  31. data/lib/graphql/schema/union.rb +29 -0
  32. data/lib/graphql/schema/warden.rb +0 -1
  33. data/lib/graphql/static_validation/literal_validator.rb +7 -7
  34. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  35. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  36. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
  37. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  38. data/lib/graphql/subscriptions.rb +41 -8
  39. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
  40. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  41. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  42. data/lib/graphql/subscriptions/event.rb +16 -1
  43. data/lib/graphql/subscriptions/serialize.rb +22 -4
  44. data/lib/graphql/subscriptions/subscription_root.rb +3 -1
  45. data/lib/graphql/tracing.rb +1 -27
  46. data/lib/graphql/tracing/platform_tracing.rb +25 -15
  47. data/lib/graphql/tracing/statsd_tracing.rb +14 -14
  48. data/lib/graphql/version.rb +1 -1
  49. metadata +4 -2
@@ -45,8 +45,10 @@ module GraphQL
45
45
  @resolve_hash[type_name_s][field_name.to_s] = resolve_fn
46
46
  end
47
47
  when Proc
48
- # for example, __resolve_type
48
+ # for example, "resolve_type"
49
49
  @resolve_hash[type_name_s] = fields
50
+ else
51
+ raise ArgumentError, "Unexpected resolve hash value for #{type_name.inspect}: #{fields.inspect} (#{fields.class})"
50
52
  end
51
53
  end
52
54
 
@@ -41,6 +41,7 @@ module GraphQL
41
41
 
42
42
  def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
43
43
  @graphql_name = graphql_name.to_s
44
+ GraphQL::NameValidator.validate!(@graphql_name)
44
45
  @description = desc || description
45
46
  @value = value.nil? ? @graphql_name : value
46
47
  @deprecation_reason = deprecation_reason
@@ -36,9 +36,18 @@ module GraphQL
36
36
  # @return [Symbol] The method on the type to look up
37
37
  attr_reader :resolver_method
38
38
 
39
- # @return [Class] The type that this field belongs to
39
+ # @return [Class] The thing this field was defined on (type, mutation, resolver)
40
40
  attr_accessor :owner
41
41
 
42
+ # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
43
+ def owner_type
44
+ @owner_type ||= if owner < GraphQL::Schema::Mutation
45
+ owner.payload_type
46
+ else
47
+ owner
48
+ end
49
+ end
50
+
42
51
  # @return [Symbol] the original name of the field, passed in by the user
43
52
  attr_reader :original_name
44
53
 
@@ -191,9 +200,10 @@ module GraphQL
191
200
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
192
201
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
193
202
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
+ # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
194
204
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
195
205
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
196
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
206
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
197
207
  if name.nil?
198
208
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
199
209
  end
@@ -237,8 +247,8 @@ module GraphQL
237
247
  end
238
248
 
239
249
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
240
- method_name = method || hash_key || @underscored_name
241
- resolver_method ||= @underscored_name.to_sym
250
+ method_name = method || hash_key || name_s
251
+ resolver_method ||= name_s.to_sym
242
252
 
243
253
  @method_str = method_name.to_s
244
254
  @method_sym = method_name.to_sym
@@ -251,6 +261,7 @@ module GraphQL
251
261
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
252
262
  @introspection = introspection
253
263
  @extras = extras
264
+ @broadcastable = broadcastable
254
265
  @resolver_class = resolver_class
255
266
  @scope = scope
256
267
  @trace = trace
@@ -295,6 +306,13 @@ module GraphQL
295
306
  end
296
307
  end
297
308
 
309
+ # If true, subscription updates with this field can be shared between viewers
310
+ # @return [Boolean, nil]
311
+ # @see GraphQL::Subscriptions::BroadcastAnalyzer
312
+ def broadcastable?
313
+ @broadcastable
314
+ end
315
+
298
316
  # @param text [String]
299
317
  # @return [String]
300
318
  def description(text = nil)
@@ -534,7 +552,7 @@ module GraphQL
534
552
  @resolve_proc.call(extended_obj, args, ctx)
535
553
  end
536
554
  else
537
- public_send_field(after_obj, ruby_args, ctx)
555
+ public_send_field(after_obj, ruby_args, query_ctx)
538
556
  end
539
557
  else
540
558
  err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
@@ -558,30 +576,7 @@ module GraphQL
558
576
  application_object = object.object
559
577
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
560
578
  if is_authorized
561
- # Apply field extensions
562
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
563
- field_receiver = if @resolver_class
564
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
565
- extended_obj.object
566
- else
567
- extended_obj
568
- end
569
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
570
- else
571
- extended_obj
572
- end
573
-
574
- if field_receiver.respond_to?(@resolver_method)
575
- # Call the method with kwargs, if there are any
576
- if extended_args.any?
577
- field_receiver.public_send(@resolver_method, **extended_args)
578
- else
579
- field_receiver.public_send(@resolver_method)
580
- end
581
- else
582
- resolve_field_method(field_receiver, extended_args, ctx)
583
- end
584
- end
579
+ public_send_field(object, args, ctx)
585
580
  else
586
581
  err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
587
582
  ctx.schema.unauthorized_field(err)
@@ -597,43 +592,6 @@ module GraphQL
597
592
  err
598
593
  end
599
594
 
600
- # Find a way to resolve this field, checking:
601
- #
602
- # - Hash keys, if the wrapped object is a hash;
603
- # - A method on the wrapped object;
604
- # - Or, raise not implemented.
605
- #
606
- # This can be overridden by defining a method on the object type.
607
- # @param obj [GraphQL::Schema::Object]
608
- # @param ruby_kwargs [Hash<Symbol => Object>]
609
- # @param ctx [GraphQL::Query::Context]
610
- def resolve_field_method(obj, ruby_kwargs, ctx)
611
- if obj.object.is_a?(Hash)
612
- inner_object = obj.object
613
- if inner_object.key?(@method_sym)
614
- inner_object[@method_sym]
615
- else
616
- inner_object[@method_str]
617
- end
618
- elsif obj.object.respond_to?(@method_sym)
619
- if ruby_kwargs.any?
620
- obj.object.public_send(@method_sym, **ruby_kwargs)
621
- else
622
- obj.object.public_send(@method_sym)
623
- end
624
- else
625
- raise <<-ERR
626
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
627
-
628
- - `#{obj.class}##{@resolver_method}`, which did not exist
629
- - `#{obj.object.class}##{@method_sym}`, which did not exist
630
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
631
-
632
- To implement this field, define one of the methods above (and check for typos)
633
- ERR
634
- end
635
- end
636
-
637
595
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
638
596
  def fetch_extra(extra_name, ctx)
639
597
  if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
@@ -704,24 +662,52 @@ module GraphQL
704
662
  end
705
663
  end
706
664
 
707
- def public_send_field(obj, ruby_kwargs, field_ctx)
708
- query_ctx = field_ctx.query.context
709
- with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
665
+ def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
666
+ with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
710
667
  if @resolver_class
711
- if extended_obj.is_a?(GraphQL::Schema::Object)
712
- extended_obj = extended_obj.object
668
+ if obj.is_a?(GraphQL::Schema::Object)
669
+ obj = obj.object
713
670
  end
714
- extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self)
671
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
715
672
  end
716
673
 
717
- if extended_obj.respond_to?(@resolver_method)
718
- if extended_args.any?
719
- extended_obj.public_send(@resolver_method, **extended_args)
674
+ # Find a way to resolve this field, checking:
675
+ #
676
+ # - A method on the type instance;
677
+ # - Hash keys, if the wrapped object is a hash;
678
+ # - A method on the wrapped object;
679
+ # - Or, raise not implemented.
680
+ #
681
+ if obj.respond_to?(@resolver_method)
682
+ # Call the method with kwargs, if there are any
683
+ if ruby_kwargs.any?
684
+ obj.public_send(@resolver_method, **ruby_kwargs)
685
+ else
686
+ obj.public_send(@resolver_method)
687
+ end
688
+ elsif obj.object.is_a?(Hash)
689
+ inner_object = obj.object
690
+ if inner_object.key?(@method_sym)
691
+ inner_object[@method_sym]
720
692
  else
721
- extended_obj.public_send(@resolver_method)
693
+ inner_object[@method_str]
694
+ end
695
+ elsif obj.object.respond_to?(@method_sym)
696
+ if ruby_kwargs.any?
697
+ obj.object.public_send(@method_sym, **ruby_kwargs)
698
+ else
699
+ obj.object.public_send(@method_sym)
722
700
  end
723
701
  else
724
- resolve_field_method(extended_obj, extended_args, query_ctx)
702
+ raise <<-ERR
703
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
704
+
705
+ - `#{obj.class}##{@resolver_method}`, which did not exist
706
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
707
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
708
+
709
+ To implement this field, define one of the methods above (and check for typos)
710
+ ERR
725
711
  end
726
712
  end
727
713
  end
@@ -22,39 +22,49 @@ module GraphQL
22
22
  end
23
23
 
24
24
  def after_resolve(value:, object:, arguments:, context:, memo:)
25
- if value.is_a? GraphQL::ExecutionError
26
- # This isn't even going to work because context doesn't have ast_node anymore
27
- context.add_error(value)
28
- nil
29
- elsif value.nil?
30
- nil
31
- elsif value.is_a?(GraphQL::Pagination::Connection)
32
- # update the connection with some things that may not have been provided
33
- value.context ||= context
34
- value.first_value ||= arguments[:first]
35
- value.after_value ||= arguments[:after]
36
- value.last_value ||= arguments[:last]
37
- value.before_value ||= arguments[:before]
38
- if field.has_max_page_size? && !value.has_max_page_size_override?
39
- value.max_page_size = field.max_page_size
25
+ # rename some inputs to avoid conflicts inside the block
26
+ maybe_lazy = value
27
+ value = nil
28
+ context.schema.after_lazy(maybe_lazy) do |resolved_value|
29
+ value = resolved_value
30
+ if value.is_a? GraphQL::ExecutionError
31
+ # This isn't even going to work because context doesn't have ast_node anymore
32
+ context.add_error(value)
33
+ nil
34
+ elsif value.nil?
35
+ nil
36
+ elsif value.is_a?(GraphQL::Pagination::Connection)
37
+ # update the connection with some things that may not have been provided
38
+ value.context ||= context
39
+ value.parent ||= object.object
40
+ value.first_value ||= arguments[:first]
41
+ value.after_value ||= arguments[:after]
42
+ value.last_value ||= arguments[:last]
43
+ value.before_value ||= arguments[:before]
44
+ if field.has_max_page_size? && !value.has_max_page_size_override?
45
+ value.max_page_size = field.max_page_size
46
+ end
47
+ if (custom_t = context.schema.connections.edge_class_for_field(@field))
48
+ value.edge_class = custom_t
49
+ end
50
+ value
51
+ elsif context.schema.new_connections?
52
+ wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
53
+ context.schema.connections.wrap(field, object.object, value, arguments, context, wrappers: wrappers)
54
+ else
55
+ if object.is_a?(GraphQL::Schema::Object)
56
+ object = object.object
57
+ end
58
+ connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
59
+ connection_class.new(
60
+ value,
61
+ arguments,
62
+ field: field,
63
+ max_page_size: field.max_page_size,
64
+ parent: object,
65
+ context: context,
66
+ )
40
67
  end
41
- value
42
- elsif context.schema.new_connections?
43
- wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
44
- context.schema.connections.wrap(field, value, arguments, context, wrappers: wrappers)
45
- else
46
- if object.is_a?(GraphQL::Schema::Object)
47
- object = object.object
48
- end
49
- connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
50
- connection_class.new(
51
- value,
52
- arguments,
53
- field: field,
54
- max_page_size: field.max_page_size,
55
- parent: object,
56
- context: context,
57
- )
58
68
  end
59
69
  end
60
70
  end
@@ -25,8 +25,15 @@ module GraphQL
25
25
  types[type["name"]] = type_object
26
26
  end
27
27
 
28
+ directives = []
29
+ schema.fetch("directives", []).each do |directive|
30
+ next if GraphQL::Schema.default_directives.include?(directive.fetch("name"))
31
+ directives << define_directive(directive, type_resolver)
32
+ end
33
+
28
34
  Class.new(GraphQL::Schema) do
29
35
  orphan_types(types.values)
36
+ directives(directives)
30
37
 
31
38
  def self.resolve_type(*)
32
39
  raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
@@ -98,7 +105,7 @@ module GraphQL
98
105
  value(
99
106
  enum_value["name"],
100
107
  description: enum_value["description"],
101
- deprecation_reason: enum_value["deprecation_reason"],
108
+ deprecation_reason: enum_value["deprecationReason"],
102
109
  )
103
110
  end
104
111
  end
@@ -147,6 +154,16 @@ module GraphQL
147
154
  end
148
155
  end
149
156
 
157
+ def define_directive(directive, type_resolver)
158
+ loader = self
159
+ Class.new(GraphQL::Schema::Directive) do
160
+ graphql_name(directive["name"])
161
+ description(directive["description"])
162
+ locations(*directive["locations"].map(&:to_sym))
163
+ loader.build_arguments(self, directive["args"], type_resolver)
164
+ end
165
+ end
166
+
150
167
  public
151
168
 
152
169
  def build_fields(type_defn, fields, type_resolver)
@@ -156,6 +173,7 @@ module GraphQL
156
173
  field_hash["name"],
157
174
  type: type_resolver.call(field_hash["type"]),
158
175
  description: field_hash["description"],
176
+ deprecation_reason: field_hash["deprecationReason"],
159
177
  null: true,
160
178
  camelize: false,
161
179
  ) do
@@ -60,7 +60,9 @@ module GraphQL
60
60
 
61
61
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
62
62
  def get_argument(argument_name)
63
- if (a = own_arguments[argument_name])
63
+ a = own_arguments[argument_name]
64
+
65
+ if a || !self.is_a?(Class)
64
66
  a
65
67
  else
66
68
  for ancestor in ancestors
@@ -47,20 +47,22 @@ module GraphQL
47
47
  # A list of GraphQL-Ruby keywords.
48
48
  #
49
49
  # @api private
50
- GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method, :raw_value]
50
+ GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value]
51
51
 
52
52
  # A list of field names that we should advise users to pick a different
53
53
  # resolve method name.
54
54
  #
55
55
  # @api private
56
- CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS)
56
+ CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods)
57
57
 
58
58
  # Register this field with the class, overriding a previous one if needed.
59
59
  # @param field_defn [GraphQL::Schema::Field]
60
60
  # @return [void]
61
- def add_field(field_defn)
62
- if CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.method_conflict_warning?
63
- warn "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
61
+ def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
62
+ # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
63
+ # that shows that no override value was given manually.
64
+ if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
65
+ warn(conflict_field_name_warning(field_defn))
64
66
  end
65
67
  own_fields[field_defn.name] = field_defn
66
68
  nil
@@ -92,6 +94,14 @@ module GraphQL
92
94
  def own_fields
93
95
  @own_fields ||= {}
94
96
  end
97
+
98
+ private
99
+
100
+ # @param [GraphQL::Schema::Field]
101
+ # @return [String] A warning to give when this field definition might conflict with a built-in method
102
+ def conflict_field_name_warning(field_defn)
103
+ "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
104
+ end
95
105
  end
96
106
  end
97
107
  end
@@ -78,6 +78,10 @@ module GraphQL
78
78
 
79
79
  private
80
80
 
81
+ def conflict_field_name_warning(field_defn)
82
+ "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `hash_key:` or `method:` to pick a different resolve behavior for this field (for example, `hash_key: :#{field_defn.resolver_method}_value`, and modify the return hash). Or use `method_conflict_warning: false` to suppress this warning."
83
+ end
84
+
81
85
  # Override this to attach self as `mutation`
82
86
  def generate_payload_type
83
87
  payload_class = super
@@ -74,7 +74,7 @@ module GraphQL
74
74
  # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
75
75
  # It should help with debugging and bug tracker integrations.
76
76
  def inherited(child_class)
77
- child_class.const_set(:InvalidNullError, Class.new(GraphQL::InvalidNullError))
77
+ child_class.const_set(:InvalidNullError, GraphQL::InvalidNullError.subclass_for(child_class))
78
78
  super
79
79
  end
80
80
 
@@ -40,6 +40,7 @@ module GraphQL
40
40
  @arguments_by_keyword[arg.keyword] = arg
41
41
  end
42
42
  @arguments_loads_as_type = self.class.arguments_loads_as_type
43
+ @prepared_arguments = nil
43
44
  end
44
45
 
45
46
  # @return [Object] The application object this field is being resolved on
@@ -51,6 +52,10 @@ module GraphQL
51
52
  # @return [GraphQL::Schema::Field]
52
53
  attr_reader :field
53
54
 
55
+ def arguments
56
+ @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
57
+ end
58
+
54
59
  # This method is _actually_ called by the runtime,
55
60
  # it does some preparation and then eventually calls
56
61
  # the user-defined `#resolve` method.
@@ -74,6 +79,7 @@ module GraphQL
74
79
  # for that argument, or may return a lazy object
75
80
  load_arguments_val = load_arguments(args)
76
81
  context.schema.after_lazy(load_arguments_val) do |loaded_args|
82
+ @prepared_arguments = loaded_args
77
83
  # Then call `authorized?`, which may raise or may return a lazy object
78
84
  authorized_val = if loaded_args.any?
79
85
  authorized?(**loaded_args)
@@ -250,6 +256,19 @@ module GraphQL
250
256
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
251
257
  end
252
258
 
259
+ def broadcastable(new_broadcastable)
260
+ @broadcastable = new_broadcastable
261
+ end
262
+
263
+ # @return [Boolean, nil]
264
+ def broadcastable?
265
+ if defined?(@broadcastable)
266
+ @broadcastable
267
+ else
268
+ (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
269
+ end
270
+ end
271
+
253
272
  def field_options
254
273
  {
255
274
  type: type_expr,
@@ -261,6 +280,7 @@ module GraphQL
261
280
  null: null,
262
281
  complexity: complexity,
263
282
  extensions: extensions,
283
+ broadcastable: broadcastable?,
264
284
  }
265
285
  end
266
286