graphql 2.5.9 → 2.5.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d6def5d638ca63c24df116117624ac5bd6e87555457a38fb893a43a1b15600f
4
- data.tar.gz: c74e1ae2276871b07decc70422ff48992d8a5c9c9bdbce23164b367c707fe078
3
+ metadata.gz: 13d11e14c49ac11e7f851c104641f4234ea464aa5aeb33a4ae50961f4da03e83
4
+ data.tar.gz: afc9ba506e4cf724b5ff508a73593330d37527461186cb92c264c411204fe13c
5
5
  SHA512:
6
- metadata.gz: 333fdffd66163025d3dce807a12f7b2aeaceb932b5bd739d077a4d6d9ccbee92e891a24e68a8fb94f3f1157edc93abc01ac71fbef4ef0b64e2fc043ea4119488
7
- data.tar.gz: c22ba9de28b9124a1d758ffd9f71a7b49da20780c55bc9ceda9fdb74d93042df17eeccb8b74cf7eba9a095bd9852a19e0673c85e473da509379f4520cb695da7
6
+ metadata.gz: f3196f1a70900ee0f2597ad54a16ecb960a7f8cefadda7aff81dce760aae2fad6e3a43c29fa3cd9a6792b82edfd34f6be70f9ae61ab1d6c98be01363acf618d9
7
+ data.tar.gz: 45bfc9181659e24ca4b1e5844942093293f60a13206670ae2c8bc657288a1c025a4dc348b1a0c702aa7bc5aaf80e2a83616db3807d735eee286dd6be4d2144fb
@@ -31,7 +31,12 @@ module GraphQL
31
31
  def fetch(records)
32
32
  record_classes = Set.new.compare_by_identity
33
33
  associated_classes = Set.new.compare_by_identity
34
+ scoped_fetch = !@scope.nil?
34
35
  records.each do |record|
36
+ if scoped_fetch
37
+ assoc = record.association(@association)
38
+ assoc.reset
39
+ end
35
40
  if record_classes.add?(record.class)
36
41
  reflection = record.class.reflect_on_association(@association)
37
42
  if !reflection.polymorphic? && reflection.klass
@@ -48,9 +53,16 @@ module GraphQL
48
53
 
49
54
  ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
50
55
 
51
- loaded_associated_records = records.map { |r| r.public_send(@association) }
56
+ loaded_associated_records = records.map { |r|
57
+ assoc = r.association(@association)
58
+ lar = assoc.target
59
+ if scoped_fetch
60
+ assoc.reset
61
+ end
62
+ lar
63
+ }
52
64
 
53
- if @scope.nil?
65
+ if !scoped_fetch
54
66
  # Don't cache records loaded via scope because they might have reduced `SELECT`s
55
67
  # Could check .select_values here?
56
68
  records_by_model = {}
@@ -9,8 +9,11 @@ module GraphQL
9
9
  class NullDataloader < Dataloader
10
10
  # These are all no-ops because code was
11
11
  # executed synchronously.
12
+
13
+ def initialize(*); end
12
14
  def run; end
13
15
  def run_isolated; yield; end
16
+ def clear_cache; end
14
17
  def yield(_source)
15
18
  raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
16
19
  end
@@ -19,6 +22,10 @@ module GraphQL
19
22
  yield
20
23
  nil
21
24
  end
25
+
26
+ def with(*)
27
+ raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -298,8 +298,6 @@ module GraphQL
298
298
  end
299
299
 
300
300
  call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
301
- finished_jobs = 0
302
- enqueued_jobs = gathered_selections.size
303
301
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
304
302
  # Field resolution may pause the fiber,
305
303
  # so it wouldn't get to the `Resolve` call that happens below.
@@ -310,12 +308,6 @@ module GraphQL
310
308
  evaluate_selection(
311
309
  result_name, field_ast_nodes_or_ast_node, selections_result
312
310
  )
313
- finished_jobs += 1
314
- if finished_jobs == enqueued_jobs
315
- if target_result
316
- selections_result.merge_into(target_result)
317
- end
318
- end
319
311
  @dataloader.clear_cache
320
312
  }
321
313
  else
@@ -323,15 +315,12 @@ module GraphQL
323
315
  evaluate_selection(
324
316
  result_name, field_ast_nodes_or_ast_node, selections_result
325
317
  )
326
- finished_jobs += 1
327
- if finished_jobs == enqueued_jobs
328
- if target_result
329
- selections_result.merge_into(target_result)
330
- end
331
- end
332
318
  }
333
319
  end
334
320
  end
321
+ if target_result
322
+ selections_result.merge_into(target_result)
323
+ end
335
324
  selections_result
336
325
  end
337
326
  end
@@ -557,7 +546,7 @@ module GraphQL
557
546
  path
558
547
  end
559
548
 
560
- HALT = Object.new
549
+ HALT = Object.new.freeze
561
550
  def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
562
551
  case value
563
552
  when nil
@@ -20,6 +20,11 @@ module GraphQL
20
20
  @finished
21
21
  end
22
22
 
23
+ def freeze
24
+ @scanner = nil
25
+ super
26
+ end
27
+
23
28
  attr_reader :pos, :tokens_count
24
29
 
25
30
  def advance
@@ -242,7 +247,7 @@ module GraphQL
242
247
  :SCALAR,
243
248
  nil,
244
249
  :FRAGMENT
245
- ]
250
+ ].freeze
246
251
 
247
252
  # This produces a unique integer for bytes 2 and 3 of each keyword string
248
253
  # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
@@ -271,7 +276,8 @@ module GraphQL
271
276
  PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
272
277
  punct = Punctuation.const_get(name)
273
278
  arr[punct.ord] = name
274
- }
279
+ }.freeze
280
+
275
281
 
276
282
  QUOTE = '"'
277
283
  UNICODE_DIGIT = /[0-9A-Za-z]/
@@ -321,6 +327,7 @@ module GraphQL
321
327
  punct = Punctuation.const_get(punct_name)
322
328
  FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
323
329
  end
330
+ FIRST_BYTES.freeze
324
331
 
325
332
 
326
333
  # Replace any escaped unicode or whitespace with the _actual_ characters
@@ -83,7 +83,11 @@ module GraphQL
83
83
 
84
84
  def to_query_string(printer: GraphQL::Language::Printer.new)
85
85
  if printer.is_a?(GraphQL::Language::Printer)
86
- @query_string ||= printer.print(self)
86
+ if frozen?
87
+ @query_string || printer.print(self)
88
+ else
89
+ @query_string ||= printer.print(self)
90
+ end
87
91
  else
88
92
  printer.print(self)
89
93
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "strscan"
4
4
  require "graphql/language/nodes"
5
+ require "graphql/tracing/null_trace"
5
6
 
6
7
  module GraphQL
7
8
  module Language
@@ -84,7 +84,7 @@ module GraphQL
84
84
 
85
85
  attr_writer :types
86
86
 
87
- RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
87
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
88
88
  # @!method []=(key, value)
89
89
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
90
90
 
@@ -258,7 +258,9 @@ module GraphQL
258
258
  # We can get these now; we'll have to get late-bound types later
259
259
  if interface_type.is_a?(Module) && type.is_a?(Class)
260
260
  implementers = @possible_types[interface_type] ||= []
261
- implementers << type
261
+ if !implementers.include?(type)
262
+ implementers << type
263
+ end
262
264
  end
263
265
  when String, Schema::LateBoundType
264
266
  interface_type = interface_type_membership
@@ -212,6 +212,11 @@ module GraphQL
212
212
  @statically_coercible = !requires_parent_object
213
213
  end
214
214
 
215
+ def freeze
216
+ statically_coercible?
217
+ super
218
+ end
219
+
215
220
  # Apply the {prepare} configuration to `value`, using methods from `obj`.
216
221
  # Used by the runtime.
217
222
  # @api private
@@ -187,6 +187,10 @@ module GraphQL
187
187
 
188
188
  object_types.each do |t|
189
189
  t.interfaces.each do |int_t|
190
+ if int_t.is_a?(LateBoundType)
191
+ int_t = types[int_t.graphql_name]
192
+ t.implements(int_t)
193
+ end
190
194
  int_t.orphan_types(t)
191
195
  end
192
196
  end
@@ -519,7 +523,7 @@ module GraphQL
519
523
 
520
524
  def define_field_resolve_method(owner, method_name, field_name)
521
525
  owner.define_method(method_name) { |**args|
522
- field_instance = self.class.get_field(field_name)
526
+ field_instance = context.types.field(owner, field_name)
523
527
  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
524
528
  }
525
529
  end
@@ -616,6 +616,14 @@ module GraphQL
616
616
  end
617
617
  end
618
618
 
619
+ def freeze
620
+ type
621
+ owner_type
622
+ arguments_statically_coercible?
623
+ connection?
624
+ super
625
+ end
626
+
619
627
  class MissingReturnTypeError < GraphQL::Error; end
620
628
  attr_writer :type
621
629
 
@@ -8,8 +8,8 @@ module GraphQL
8
8
  new_memberships = []
9
9
  new_interfaces.each do |int|
10
10
  if int.is_a?(Module)
11
- unless int.include?(GraphQL::Schema::Interface)
12
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
11
+ unless int.include?(GraphQL::Schema::Interface) && !int.is_a?(Class)
12
+ raise "#{int.respond_to?(:graphql_name) ? "#{int.graphql_name} (#{int})" : int.inspect} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
13
  end
14
14
 
15
15
  new_memberships << int.type_membership_class.new(int, self, **options)
@@ -12,12 +12,26 @@ module GraphQL
12
12
 
13
13
  # @return [Schema::NonNull] Make a non-null-type representation of this type
14
14
  def to_non_null_type
15
- @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
15
+ @to_non_null_type || begin
16
+ t = GraphQL::Schema::NonNull.new(self)
17
+ if frozen?
18
+ t
19
+ else
20
+ @to_non_null_type = t
21
+ end
22
+ end
16
23
  end
17
24
 
18
25
  # @return [Schema::List] Make a list-type representation of this type
19
26
  def to_list_type
20
- @to_list_type ||= GraphQL::Schema::List.new(self)
27
+ @to_list_type || begin
28
+ t = GraphQL::Schema::List.new(self)
29
+ if frozen?
30
+ t
31
+ else
32
+ @to_list_type = t
33
+ end
34
+ end
21
35
  end
22
36
 
23
37
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -96,17 +96,26 @@ module GraphQL
96
96
  end
97
97
 
98
98
  def build_message(context)
99
- argument_definitions = @validated.arguments(context).values
99
+ argument_definitions = context.types.arguments(@validated)
100
+
100
101
  required_names = @one_of.map do |arg_keyword|
101
102
  if arg_keyword.is_a?(Array)
102
103
  names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
104
+ names.compact! # hidden arguments are `nil`
103
105
  "(" + names.join(" and ") + ")"
104
106
  else
105
107
  arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
106
108
  end
107
109
  end
110
+ required_names.compact! # remove entries for hidden arguments
111
+
108
112
 
109
- if required_names.size == 1
113
+ case required_names.size
114
+ when 0
115
+ # The required definitions were hidden from the client.
116
+ # Another option here would be to raise an error in the application....
117
+ "%{validated} is missing a required argument."
118
+ when 1
110
119
  "%{validated} must include the following argument: #{required_names.first}."
111
120
  else
112
121
  "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
@@ -115,7 +124,7 @@ module GraphQL
115
124
 
116
125
  def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
117
126
  argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
118
- argument_definition.graphql_name
127
+ argument_definition&.graphql_name
119
128
  end
120
129
  end
121
130
  end
@@ -76,10 +76,10 @@ module GraphQL
76
76
  end
77
77
  end
78
78
 
79
- def initialize(context:, schema:, name: nil)
79
+ def initialize(context:, schema:, name: nil, visibility:)
80
80
  @name = name
81
81
  @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
82
- @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
82
+ @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility)
83
83
  if !@skip_error
84
84
  context[:visibility_migration_running] = true
85
85
  warden_ctx_vals = context.to_h.dup
@@ -31,10 +31,37 @@ module GraphQL
31
31
  # @return [Symbol, nil]
32
32
  attr_reader :name
33
33
 
34
- def initialize(name: nil, context:, schema:)
34
+ def freeze
35
+ @cached_visible.default_proc = nil
36
+ @cached_visible_fields.default_proc = nil
37
+ @cached_visible_fields.each do |type, fields|
38
+ fields.default_proc = nil
39
+ end
40
+ @cached_visible_arguments.default_proc = nil
41
+ @cached_visible_arguments.each do |type, fields|
42
+ fields.default_proc = nil
43
+ end
44
+ @cached_parent_fields.default_proc = nil
45
+ @cached_parent_fields.each do |type, fields|
46
+ fields.default_proc = nil
47
+ end
48
+ @cached_parent_arguments.default_proc = nil
49
+ @cached_parent_arguments.each do |type, args|
50
+ args.default_proc = nil
51
+ end
52
+ @cached_possible_types.default_proc = nil
53
+ @cached_enum_values.default_proc = nil
54
+ @cached_fields.default_proc = nil
55
+ @cached_arguments.default_proc = nil
56
+ @loadable_possible_types.default_proc = nil
57
+ super
58
+ end
59
+
60
+ def initialize(name: nil, context:, schema:, visibility:)
35
61
  @name = name
36
62
  @context = context
37
63
  @schema = schema
64
+ @visibility = visibility
38
65
  @all_types = {}
39
66
  @all_types_loaded = false
40
67
  @unvisited_types = []
@@ -122,7 +149,7 @@ module GraphQL
122
149
  end
123
150
 
124
151
  def type(type_name)
125
- t = @schema.visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
152
+ t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
126
153
  if t
127
154
  if t.is_a?(Array)
128
155
  vis_t = nil
@@ -250,8 +277,8 @@ module GraphQL
250
277
  end
251
278
 
252
279
  def directives
253
- @all_directives ||= @schema.visibility.all_directives.select { |dir|
254
- @cached_visible[dir] && @schema.visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
280
+ @all_directives ||= @visibility.all_directives.select { |dir|
281
+ @cached_visible[dir] && @visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
255
282
  }
256
283
  end
257
284
 
@@ -276,6 +303,42 @@ module GraphQL
276
303
  @cached_visible[enum_value]
277
304
  end
278
305
 
306
+ def preload
307
+ load_all_types
308
+ @all_types.each do |type_name, type_defn|
309
+ if type_defn.kind.fields?
310
+ fields(type_defn).each do |f|
311
+ field(type_defn, f.graphql_name)
312
+ arguments(f).each do |arg|
313
+ argument(f, arg.graphql_name)
314
+ end
315
+ end
316
+ @schema.introspection_system.dynamic_fields.each do |f|
317
+ field(type_defn, f.graphql_name)
318
+ end
319
+ elsif type_defn.kind.input_object?
320
+ arguments(type_defn).each do |arg|
321
+ argument(type_defn, arg.graphql_name)
322
+ end
323
+ elsif type_defn.kind.enum?
324
+ enum_values(type_defn)
325
+ end
326
+ # Lots more to do here
327
+ end
328
+ @schema.introspection_system.entry_points.each do |f|
329
+ arguments(f).each do |arg|
330
+ argument(f, arg.graphql_name)
331
+ end
332
+ field(@schema.query, f.graphql_name)
333
+ end
334
+ @schema.introspection_system.dynamic_fields.each do |f|
335
+ arguments(f).each do |arg|
336
+ argument(f, arg.graphql_name)
337
+ end
338
+ end
339
+
340
+ end
341
+
279
342
  private
280
343
 
281
344
  def non_duplicate_items(definitions, visibility_cache)
@@ -322,7 +385,7 @@ module GraphQL
322
385
  end
323
386
 
324
387
  def referenced?(type_defn)
325
- @schema.visibility.all_references[type_defn].any? do |ref|
388
+ @visibility.all_references[type_defn].any? do |ref|
326
389
  case ref
327
390
  when GraphQL::Schema::Argument
328
391
  @cached_visible_arguments[ref.owner][ref]
@@ -340,9 +403,11 @@ module GraphQL
340
403
  case type.kind.name
341
404
  when "INTERFACE"
342
405
  pts = []
343
- @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
344
- if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
345
- pts << impl_type
406
+ @visibility.all_interface_type_memberships[type].each do |impl_type, type_memberships|
407
+ if impl_type.kind.object? && referenced?(impl_type) && @cached_visible[impl_type]
408
+ if type_memberships.any? { |itm| @cached_visible[itm] }
409
+ pts << impl_type
410
+ end
346
411
  end
347
412
  end
348
413
  pts
@@ -18,9 +18,6 @@ module GraphQL
18
18
  ctx.freeze
19
19
  }
20
20
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
- if preload
22
- schema.visibility.preload
23
- end
24
21
  end
25
22
 
26
23
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
@@ -43,6 +40,17 @@ module GraphQL
43
40
  @types = nil
44
41
  @all_references = nil
45
42
  @loaded_all = false
43
+ if preload
44
+ self.preload
45
+ end
46
+ end
47
+
48
+ def freeze
49
+ load_all
50
+ @visit = true
51
+ @interface_type_memberships.default_proc = nil
52
+ @all_references.default_proc = nil
53
+ super
46
54
  end
47
55
 
48
56
  def all_directives
@@ -65,6 +73,8 @@ module GraphQL
65
73
  @types[type_name]
66
74
  end
67
75
 
76
+ attr_accessor :types
77
+
68
78
  def preload?
69
79
  @preload
70
80
  end
@@ -86,7 +96,7 @@ module GraphQL
86
96
  ensure_all_loaded(types_to_visit)
87
97
  @profiles.each do |profile_name, example_ctx|
88
98
  prof = profile_for(example_ctx)
89
- prof.all_types # force loading
99
+ prof.preload
90
100
  end
91
101
  end
92
102
 
@@ -153,12 +163,12 @@ module GraphQL
153
163
  visibility_profile = context[:visibility_profile]
154
164
  if @profiles.include?(visibility_profile)
155
165
  profile_ctx = @profiles[visibility_profile]
156
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema)
166
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema, visibility: self)
157
167
  elsif @dynamic
158
168
  if context.is_a?(Query::NullContext)
159
169
  top_level_profile
160
170
  else
161
- @schema.visibility_profile_class.new(context: context, schema: @schema)
171
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
162
172
  end
163
173
  elsif !context.key?(:visibility_profile)
164
174
  raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration."
@@ -168,7 +178,7 @@ module GraphQL
168
178
  elsif context.is_a?(Query::NullContext)
169
179
  top_level_profile
170
180
  else
171
- @schema.visibility_profile_class.new(context: context, schema: @schema)
181
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
172
182
  end
173
183
  end
174
184
 
@@ -181,7 +191,7 @@ module GraphQL
181
191
  if refresh
182
192
  @top_level_profile = nil
183
193
  end
184
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
185
195
  end
186
196
 
187
197
  private
@@ -202,7 +212,11 @@ module GraphQL
202
212
  def load_all(types: nil)
203
213
  if @visit.nil?
204
214
  # Set up the visit system
205
- @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
215
+ @interface_type_memberships = Hash.new { |h, interface_type|
216
+ h[interface_type] = Hash.new { |h2, obj_type|
217
+ h2[obj_type] = []
218
+ }.compare_by_identity
219
+ }.compare_by_identity
206
220
  @directives = []
207
221
  @types = {} # String => Module
208
222
  @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
@@ -227,7 +241,7 @@ module GraphQL
227
241
  @all_references[itm.abstract_type] << member
228
242
  # `itm.object_type` may not actually be `member` if this implementation
229
243
  # is inherited from a superclass
230
- @interface_type_memberships[itm.abstract_type] << [itm, member]
244
+ @interface_type_memberships[itm.abstract_type][member] << itm
231
245
  end
232
246
  elsif member < GraphQL::Schema::Union
233
247
  @unions_for_references << member
@@ -276,12 +290,11 @@ module GraphQL
276
290
 
277
291
  # TODO: somehow don't iterate over all these,
278
292
  # only the ones that may have been modified
279
- @interface_type_memberships.each do |int_type, type_membership_pairs|
280
- referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
281
- if !referers.empty?
282
- type_membership_pairs.each do |(type_membership, impl_type)|
283
- # Add new items only:
284
- @all_references[impl_type] |= referers
293
+ @interface_type_memberships.each do |int_type, obj_type_memberships|
294
+ referrers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
295
+ if !referrers.empty?
296
+ obj_type_memberships.each_key do |impl_type|
297
+ @all_references[impl_type] |= referrers
285
298
  end
286
299
  end
287
300
  end
@@ -7,6 +7,7 @@ require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
8
  require "graphql/schema/introspection_system"
9
9
  require "graphql/schema/late_bound_type"
10
+ require "graphql/schema/ractor_shareable"
10
11
  require "graphql/schema/timeout"
11
12
  require "graphql/schema/type_expression"
12
13
  require "graphql/schema/unique_within_type"
@@ -148,10 +149,12 @@ module GraphQL
148
149
  end
149
150
 
150
151
  # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
151
- def default_trace_mode(new_mode = nil)
152
- if new_mode
152
+ def default_trace_mode(new_mode = NOT_CONFIGURED)
153
+ if !NOT_CONFIGURED.equal?(new_mode)
153
154
  @default_trace_mode = new_mode
154
- elsif defined?(@default_trace_mode)
155
+ elsif defined?(@default_trace_mode) &&
156
+ !@default_trace_mode.nil? # This `nil?` check seems necessary because of
157
+ # Ractors silently initializing @default_trace_mode somehow
155
158
  @default_trace_mode
156
159
  elsif superclass.respond_to?(:default_trace_mode)
157
160
  superclass.default_trace_mode
@@ -365,7 +368,8 @@ module GraphQL
365
368
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
366
369
  def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
367
370
  if use_visibility_profile
368
- return Visibility::Profile.from_context(context, self).type(type_name)
371
+ profile = Visibility::Profile.from_context(context, self)
372
+ return profile.type(type_name)
369
373
  end
370
374
  local_entry = own_types[type_name]
371
375
  type_defn = case local_entry
@@ -697,7 +701,21 @@ module GraphQL
697
701
  GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
698
702
  end
699
703
 
700
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
704
+ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
705
+ if use_visibility_profile
706
+ profile = Visibility::Profile.from_context(context, self)
707
+ parent_type = case type_or_name
708
+ when String
709
+ profile.type(type_or_name)
710
+ when Module
711
+ type_or_name
712
+ when LateBoundType
713
+ profile.type(type_or_name.name)
714
+ else
715
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
716
+ end
717
+ return profile.field(parent_type, field_name)
718
+ end
701
719
  parent_type = case type_or_name
702
720
  when LateBoundType
703
721
  get_type(type_or_name.name, context)
@@ -1105,20 +1123,21 @@ module GraphQL
1105
1123
  end
1106
1124
  end
1107
1125
 
1108
- NEW_HANDLER_HASH = ->(h, k) {
1109
- h[k] = {
1110
- class: k,
1111
- handler: nil,
1112
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
1113
- }
1114
- }
1115
-
1116
1126
  def error_handlers
1117
- @error_handlers ||= {
1118
- class: nil,
1119
- handler: nil,
1120
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
1121
- }
1127
+ @error_handlers ||= begin
1128
+ new_handler_hash = ->(h, k) {
1129
+ h[k] = {
1130
+ class: k,
1131
+ handler: nil,
1132
+ subclass_handlers: Hash.new(&new_handler_hash),
1133
+ }
1134
+ }
1135
+ {
1136
+ class: nil,
1137
+ handler: nil,
1138
+ subclass_handlers: Hash.new(&new_handler_hash),
1139
+ }
1140
+ end
1122
1141
  end
1123
1142
 
1124
1143
  # @api private
@@ -1695,7 +1714,11 @@ module GraphQL
1695
1714
  # @return [true, false, nil]
1696
1715
  def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
1697
1716
  if NOT_CONFIGURED.equal?(new_value)
1698
- @allow_legacy_invalid_empty_selections_on_union
1717
+ if defined?(@allow_legacy_invalid_empty_selections_on_union)
1718
+ @allow_legacy_invalid_empty_selections_on_union
1719
+ else
1720
+ find_inherited_value(:allow_legacy_invalid_empty_selections_on_union)
1721
+ end
1699
1722
  else
1700
1723
  @allow_legacy_invalid_empty_selections_on_union = new_value
1701
1724
  end
@@ -1726,7 +1749,11 @@ module GraphQL
1726
1749
  # @return [true, false, nil]
1727
1750
  def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
1728
1751
  if NOT_CONFIGURED.equal?(new_value)
1729
- @allow_legacy_invalid_return_type_conflicts
1752
+ if defined?(@allow_legacy_invalid_return_type_conflicts)
1753
+ @allow_legacy_invalid_return_type_conflicts
1754
+ else
1755
+ find_inherited_value(:allow_legacy_invalid_return_type_conflicts)
1756
+ end
1730
1757
  else
1731
1758
  @allow_legacy_invalid_return_type_conflicts = new_value
1732
1759
  end
@@ -1774,7 +1801,11 @@ module GraphQL
1774
1801
  # complexity_cost_calculation_mode(:compare)
1775
1802
  def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
1776
1803
  if NOT_CONFIGURED.equal?(new_mode)
1777
- @complexity_cost_calculation_mode
1804
+ if defined?(@complexity_cost_calculation_mode)
1805
+ @complexity_cost_calculation_mode
1806
+ else
1807
+ find_inherited_value(:complexity_cost_calculation_mode)
1808
+ end
1778
1809
  else
1779
1810
  @complexity_cost_calculation_mode = new_mode
1780
1811
  end
@@ -37,6 +37,6 @@ module GraphQL
37
37
  GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
- ]
40
+ ].freeze
41
41
  end
42
42
  end
@@ -19,13 +19,17 @@ module GraphQL
19
19
  :on_field,
20
20
  ]
21
21
 
22
- DIRECTIVE_NODE_HOOKS.each do |method_name|
23
- define_method(method_name) do |node, parent|
22
+ VALIDATE_DIRECTIVE_LOCATION_ON_NODE = <<~RUBY
23
+ def %{method_name}(node, parent)
24
24
  if !node.directives.empty?
25
25
  validate_directive_location(node)
26
26
  end
27
27
  super(node, parent)
28
28
  end
29
+ RUBY
30
+ DIRECTIVE_NODE_HOOKS.each do |method_name|
31
+ # Can't use `define_method {...}` here because the proc can't be isolated for use in non-main Ractors
32
+ module_eval(VALIDATE_DIRECTIVE_LOCATION_ON_NODE % { method_name: method_name }) # rubocop:disable Development/NoEvalCop
29
33
  end
30
34
 
31
35
  private
@@ -4,6 +4,6 @@ require "graphql/tracing/trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
- NullTrace = Trace.new
7
+ NullTrace = Trace.new.freeze
8
8
  end
9
9
  end
@@ -14,6 +14,7 @@ module GraphQL
14
14
  @leaf = leaf
15
15
  @composite = fields? || abstract?
16
16
  @description = description
17
+ freeze
17
18
  end
18
19
 
19
20
  # Does this TypeKind have multiple possible implementers?
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.5.9"
3
+ VERSION = "2.5.10"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -72,7 +72,7 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
72
72
  GraphQL::Language::Lexer.tokenize(graphql_string)
73
73
  end
74
74
 
75
- NOT_CONFIGURED = Object.new
75
+ NOT_CONFIGURED = Object.new.freeze
76
76
  private_constant :NOT_CONFIGURED
77
77
  module EmptyObjects
78
78
  EMPTY_HASH = {}.freeze
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.9
4
+ version: 2.5.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-06 00:00:00.000000000 Z
10
+ date: 2025-07-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -622,6 +622,7 @@ files:
622
622
  - lib/graphql/schema/non_null.rb
623
623
  - lib/graphql/schema/object.rb
624
624
  - lib/graphql/schema/printer.rb
625
+ - lib/graphql/schema/ractor_shareable.rb
625
626
  - lib/graphql/schema/relay_classic_mutation.rb
626
627
  - lib/graphql/schema/resolver.rb
627
628
  - lib/graphql/schema/resolver/has_payload_type.rb