graphql 2.6.6 → 2.6.8

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: 3366525e188ac8b81b4dc7bdc8e34bb1839153fbec23d8e2a5faf07c02b56aa6
4
- data.tar.gz: 414d8f17cee8d40adf8c5923427e83d187efdbd08871c7b9d8bbd4fa13b0e550
3
+ metadata.gz: 12ae3a5cc9e399639228a8ccfc1654c7bcaf73a6c81339ad72f7ce90b159eb9d
4
+ data.tar.gz: a998bd1576174777857e365cbe67ffc7b68273bbaa17ad0cd1e609193254417f
5
5
  SHA512:
6
- metadata.gz: f4423d53187dcf97e67ae1ba93cc9b5135c7db35cab04abc5533b96e97e3abd5d2c4bbbe683a64abc0117a022743a53df804d170e5fbd0fd7430a3987466531b
7
- data.tar.gz: c6093a9a53e554aed992da96b80007bbe10df7ac9552c4a1dc0cce19ae8a3f33fd3e2cb0858c117c1e061c4b996a4af8a7b161b31b4f3806ff3a30a84dfaddb2
6
+ metadata.gz: 2c95b6f2c0aec486d42170e970f59272ece87e9433c5ba8e3fff4838da802993f1de9fcaf3b2c2807fb3365674a7a915265c248470987bf0a9f47d13b90699d1
7
+ data.tar.gz: 03eb4c0fb7cddfd2cebce9a50ce3a9264f6473d4ca037e8a60ddb5d14b3f880a54c6b249958b2b58100dca9ea9b3ad9de04ad25cd333f0d00e620fd724ff7ff6
@@ -26,7 +26,8 @@ class <%= schema_name %> < GraphQL::Schema
26
26
  raise(GraphQL::RequiredImplementationMissingError)
27
27
  end
28
28
 
29
- # Limit the size of incoming queries:
29
+ # Limit the depth and size of incoming queries:
30
+ max_depth(15)
30
31
  max_query_string_tokens(5000)
31
32
 
32
33
  # Stop validating when it encounters this many errors:
@@ -22,7 +22,11 @@ module Graphql
22
22
 
23
23
  def schema_class
24
24
  @schema_class ||= begin
25
- schema_param = request.query_parameters["schema"] || params[:schema]
25
+ configured_schemas = Array(request.path_parameters[:schema] || request.path_parameters["schema"])
26
+ schema_param = request.query_parameters["schema"]
27
+ schema_param = configured_schemas.find { |schema| schema.to_s == schema_param } if schema_param
28
+ schema_param ||= configured_schemas.first
29
+
26
30
  case schema_param
27
31
  when Class
28
32
  schema_param
@@ -10,6 +10,9 @@ module Graphql
10
10
  # @example Mounting the Dashboard in your app
11
11
  # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema"
12
12
  #
13
+ # Pass an array to allow selecting from multiple schemas with the `schema` query parameter.
14
+ # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: ["MySchema", "OtherSchema"]
15
+ #
13
16
  # @example Authenticating the Dashboard with HTTP Basic Auth
14
17
  # # config/initializers/graphql_dashboard.rb
15
18
  # GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password|
@@ -4,6 +4,11 @@ module GraphQL
4
4
  class Dataloader
5
5
  class AsyncDataloader < Dataloader
6
6
  def self.use(...)
7
+ install_graphql_methods
8
+ super
9
+ end
10
+
11
+ def self.install_graphql_methods
7
12
  if !Async::Task.method_defined?(:cancel)
8
13
  Async::Task.alias_method(:cancel, :stop)
9
14
  end
@@ -11,7 +16,6 @@ module GraphQL
11
16
  Async::Task.attr_accessor(:graphql_async_dataloader_run)
12
17
  Async::Task.attr_accessor(:graphql_async_dataloader_condition)
13
18
  end
14
- super
15
19
  end
16
20
 
17
21
  def initialize(...)
@@ -54,7 +58,10 @@ module GraphQL
54
58
  @running_tasks = nil
55
59
  @tasks_channel = nil
56
60
  @tasks_channel_task = nil
57
- @finished_all_tasks = nil
61
+ @activity = nil
62
+ @task_error = nil
63
+ @expected_resumes = 0
64
+ @mode = nil
58
65
 
59
66
  @snoozed_jobs_condition = Async::Condition.new
60
67
  @snoozed_sources_condition = Async::Condition.new
@@ -77,20 +84,41 @@ module GraphQL
77
84
  @tasks_channel_task.cancel
78
85
  end
79
86
 
80
- def wait_for_queues
81
- @finished_all_tasks.wait
82
- @finished_all_tasks = Async::Promise.new
87
+ def wait_for_activity
88
+ @activity.wait
89
+ end
90
+
91
+ def quiesced?
92
+ @running_tasks.empty? && @tasks_channel.empty? && @expected_resumes == 0
93
+ end
94
+
95
+ def has_pending_work?
96
+ @mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
97
+ end
98
+
99
+ def has_bandwidth?
100
+ @mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
101
+ end
102
+
103
+ # Signalled tasks don't appear in any accounting until their first slice
104
+ # pushes `:resumed_task`, so they have to be counted at signal time:
105
+ def expect_resumes(count)
106
+ @expected_resumes = count
83
107
  end
84
108
 
85
- def wait_for_no_running_tasks
86
- @no_running_tasks.wait
87
- @no_running_tasks = Async::Promise.new
109
+ def check_error!
110
+ if (err = @task_error)
111
+ @task_error = nil
112
+ raise err
113
+ end
88
114
  end
89
115
 
90
116
  def new_queues(mode)
117
+ @mode = mode
91
118
  @tasks_channel = Async::Queue.new(parent: @root_task)
92
- @no_running_tasks = Async::Promise.new
93
- @finished_all_tasks = Async::Promise.new
119
+ @activity = Async::Condition.new
120
+ @task_error = nil
121
+ @expected_resumes = 0
94
122
  @running_tasks = []
95
123
  @tasks_channel_task = @root_task.async do |_t|
96
124
  while ((msg, data) = @tasks_channel.wait)
@@ -99,23 +127,18 @@ module GraphQL
99
127
  @running_tasks.push(data)
100
128
  data.run
101
129
  when :resumed_task
130
+ if @expected_resumes > 0
131
+ @expected_resumes -= 1
132
+ end
102
133
  @running_tasks.push(data)
103
134
  when :finished_task, :paused_task
104
135
  @running_tasks.delete(data)
105
- has_pending_work = mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
106
- if @running_tasks.empty?
107
- @no_running_tasks.resolve(true)
108
- has_bandwidth = mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
109
- if (!has_pending_work) || (!has_bandwidth)
110
- @finished_all_tasks.resolve(true)
111
- end
112
- end
113
136
  when :task_error
114
- @no_running_tasks.resolve(true)
115
- @finished_all_tasks.reject(data)
137
+ @task_error ||= data
116
138
  else
117
139
  raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
118
140
  end
141
+ @activity.signal
119
142
  end
120
143
  end
121
144
  end
@@ -240,19 +263,20 @@ module GraphQL
240
263
  private
241
264
 
242
265
  def run_queue(run, condition, mode)
243
- should_wait_for_all_tasks = false
266
+ opened_queues = false
244
267
 
245
- if (unsnoozed = condition.waiting?)
246
- should_wait_for_all_tasks = true
268
+ if condition.waiting?
269
+ opened_queues = true
247
270
  run.new_queues(mode)
271
+ run.expect_resumes(condition.instance_variable_get(:@ready).num_waiting)
248
272
  condition.signal
249
273
  end
250
274
 
251
- while (pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)) || unsnoozed
252
- unsnoozed = false
275
+ loop do
276
+ pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)
253
277
  if pending_work
254
- if should_wait_for_all_tasks == false
255
- should_wait_for_all_tasks = true
278
+ if opened_queues == false
279
+ opened_queues = true
256
280
  run.new_queues(mode)
257
281
  end
258
282
  num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
@@ -262,14 +286,23 @@ module GraphQL
262
286
  spawn_tasks(run, mode, condition, pending_work, num_tasks)
263
287
  end
264
288
 
265
- run.wait_for_no_running_tasks
266
- end
289
+ if !opened_queues
290
+ break
291
+ end
292
+
293
+ run.check_error!
267
294
 
268
- if should_wait_for_all_tasks
269
- run.wait_for_queues
295
+ if run.quiesced?
296
+ if !run.has_pending_work? || !run.has_bandwidth?
297
+ break
298
+ end
299
+ # Quiesced, but more work appeared - loop around to drain it.
300
+ else
301
+ run.wait_for_activity
302
+ end
270
303
  end
271
304
  ensure
272
- if should_wait_for_all_tasks
305
+ if opened_queues
273
306
  run.close_queues
274
307
  end
275
308
  end
@@ -130,7 +130,7 @@ module GraphQL
130
130
  highest_nulled_depth = path.size
131
131
  highest_list_depth = nil
132
132
  current_field_step = self
133
- while current_field_step
133
+ while current_field_step && current_field_step.field_definition
134
134
  return_type = current_field_step.field_definition.type
135
135
  if propagating_null && return_type.non_null?
136
136
  highest_nulled_depth = current_field_step.path.size
@@ -147,7 +147,7 @@ module GraphQL
147
147
 
148
148
  if highest_list_depth.nil? || highest_nulled_depth <= highest_list_depth
149
149
  kill_field_step = self
150
- while kill_field_step && highest_nulled_depth <= kill_field_step.path.size
150
+ while kill_field_step && kill_field_step.field_definition && highest_nulled_depth <= kill_field_step.path.size
151
151
  kill_field_step.selections_step.killed = true
152
152
  kill_field_step = kill_field_step.selections_step.field_resolve_step
153
153
  end
@@ -119,6 +119,12 @@ module GraphQL
119
119
  end
120
120
 
121
121
  results
122
+ rescue SystemStackError => err
123
+ queries.map do |query|
124
+ schema.query_stack_error(query, err)
125
+ query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
126
+ query.result
127
+ end
122
128
  rescue Exception
123
129
  # TODO rescue at a higher level so it will catch errors in analysis, too
124
130
  # Assign values here so that the query's `@executed` becomes true
@@ -5,7 +5,6 @@ module GraphQL
5
5
  def initialize(multiplex)
6
6
  @multiplex = multiplex
7
7
  @schema = multiplex.schema
8
- @steps_queue = []
9
8
  @runtime_type_at = {}.compare_by_identity
10
9
  @static_type_at = {}.compare_by_identity
11
10
  @finalizers = nil
@@ -53,7 +52,7 @@ module GraphQL
53
52
  @dataloader.append_job(step)
54
53
  end
55
54
 
56
- attr_reader :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
55
+ attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
57
56
 
58
57
  # @return [void]
59
58
  def add_finalizer(query, result_value, key, finalizer)
@@ -83,12 +83,12 @@ module GraphQL
83
83
  end
84
84
  end
85
85
 
86
- continue_selections.each do |frs|
87
- @runner.add_step(frs)
88
- end
89
-
90
86
  i += 2
91
87
  end
88
+
89
+ continue_selections.each do |frs|
90
+ @runner.add_step(frs)
91
+ end
92
92
  end
93
93
  end
94
94
  end
@@ -21,17 +21,12 @@ module GraphQL
21
21
  next
22
22
  end
23
23
  line_length = line.size
24
- line_indent = if line.match?(/\A [^ ]/)
25
- 2
26
- elsif line.match?(/\A [^ ]/)
27
- 4
28
- elsif line.match?(/\A[^ ]/)
29
- 0
30
- else
31
- line[/\A */].size
24
+ leading = 0
25
+ while leading < line_length && line.getbyte(leading) == 32
26
+ leading += 1
32
27
  end
33
- if line_indent < line_length && (common_indent.nil? || line_indent < common_indent)
34
- common_indent = line_indent
28
+ if leading < line_length && (common_indent.nil? || leading < common_indent)
29
+ common_indent = leading
35
30
  end
36
31
  end
37
32
 
@@ -41,7 +36,7 @@ module GraphQL
41
36
  if idx == 0
42
37
  next
43
38
  else
44
- line.slice!(0, common_indent)
39
+ line[0, common_indent] = ""
45
40
  end
46
41
  end
47
42
  end
@@ -175,11 +175,14 @@ module GraphQL
175
175
  end
176
176
 
177
177
  def line_number
178
- @scanner.string[0..@pos].count("\n") + 1
178
+ @scanner.string.byteslice(0, @pos).b.count("\n".b) + 1
179
179
  end
180
180
 
181
181
  def column_number
182
- @scanner.string[0..@pos].split("\n").last.length
182
+ line_prefix = @scanner.string.byteslice(0, @pos)
183
+ line_prefix = line_prefix.b unless line_prefix.valid_encoding?
184
+ newline_index = line_prefix.rindex("\n")
185
+ newline_index ? line_prefix.length - newline_index : line_prefix.length + 1
183
186
  end
184
187
 
185
188
  def raise_parse_error(message, line = line_number, col = column_number)
@@ -304,7 +304,8 @@ module GraphQL
304
304
  children_method_names.map { |m| "#{m}: NO_CHILDREN" } +
305
305
  DEFAULT_INITIALIZE_OPTIONS
306
306
 
307
- assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} +
307
+ # Intern descriptions so identical strings across SDL documents share one frozen object.
308
+ assignments = scalar_method_names.map { |m| m == :description ? "@#{m} = #{m} && -#{m}" : "@#{m} = #{m}" } +
308
309
  children_method_names.map { |m| "@#{m} = #{m}.freeze" }
309
310
 
310
311
  if name.end_with?("Definition") && name != "FragmentDefinition"
@@ -31,7 +31,7 @@ module GraphQL
31
31
 
32
32
  "[#{serialized_array}]"
33
33
  else
34
- JSON.generate(value, quirks_mode: true)
34
+ JSON.generate(value)
35
35
  end
36
36
  rescue JSON::GeneratorError
37
37
  if Float::INFINITY == value
@@ -82,6 +82,7 @@ module GraphQL
82
82
  replace_late_bound_types_with_built_in(types)
83
83
 
84
84
  schema_extensions = nil
85
+ definitions_by_name = nil
85
86
  document.definitions.each do |definition|
86
87
  case definition
87
88
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
@@ -101,9 +102,16 @@ module GraphQL
101
102
  if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
102
103
  if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
103
104
  interface_names = definition.interfaces.map(&:name)
104
- transitive_names = interface_names.map { |n| document.definitions.find { |d| d.respond_to?(:name) && d.name == n }&.interfaces&.map(&:name) }
105
- transitive_names.flatten!
106
- transitive_names.compact!
105
+ if !interface_names.empty?
106
+ definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
107
+ by_name[d.name] ||= d if d.respond_to?(:name)
108
+ end
109
+ transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
110
+ transitive_names.flatten!
111
+ transitive_names.compact!
112
+ else
113
+ transitive_names = interface_names
114
+ end
107
115
  if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
108
116
  raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
109
117
  end
@@ -172,12 +172,12 @@ module GraphQL
172
172
  types = ctx.types
173
173
 
174
174
  if input.is_a?(Array)
175
- return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
175
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
176
176
  end
177
177
 
178
178
  if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
179
179
  # We're not sure it'll act like a hash, so reject it:
180
- return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
180
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
181
181
  end
182
182
 
183
183
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "date"
2
3
  require "set"
3
4
  module GraphQL
4
5
  class Subscriptions
@@ -10,6 +11,7 @@ module GraphQL
10
11
  SYMBOL_KEYS_KEY = "__sym_keys__"
11
12
  TIMESTAMP_KEY = "__timestamp__"
12
13
  TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
14
+ TIMESTAMP_CLASS_NAMES = ["Date", "DateTime", "Time"].freeze
13
15
  OPEN_STRUCT_KEY = "__ostruct__"
14
16
 
15
17
  module_function
@@ -24,7 +26,7 @@ module GraphQL
24
26
  # @param obj [Object] Some subscription-related data to dump
25
27
  # @return [String] The stringified object
26
28
  def dump(obj)
27
- JSON.generate(dump_value(obj), quirks_mode: true)
29
+ JSON.generate(dump_value(obj))
28
30
  end
29
31
 
30
32
  # This is for turning objects into subscription scopes.
@@ -72,15 +74,19 @@ module GraphQL
72
74
  value[SYMBOL_KEY].to_sym
73
75
  when TIMESTAMP_KEY
74
76
  timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY]
75
- timestamp_class = Object.const_get(timestamp_class_name)
76
- if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone
77
+ timestamp_class = if TIMESTAMP_CLASS_NAMES.include?(timestamp_class_name)
78
+ Object.const_get(timestamp_class_name, false)
79
+ end
80
+ if defined?(ActiveSupport::TimeWithZone) && timestamp_class_name == ActiveSupport::TimeWithZone.name
77
81
  zone_name, timestamp_s = timestamp_args
78
82
  zone = ActiveSupport::TimeZone[zone_name]
79
83
  raise "Zone #{zone_name} not found, unable to deserialize" unless zone
80
84
  zone.strptime(timestamp_s, TIMESTAMP_FORMAT)
81
- else
85
+ elsif timestamp_class
82
86
  timestamp_s = timestamp_args.first
83
87
  timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
88
+ else
89
+ raise ArgumentError, "Unsupported timestamp class: #{timestamp_class_name.inspect}"
84
90
  end
85
91
  when OPEN_STRUCT_KEY
86
92
  ostruct_values = load_value(value[OPEN_STRUCT_KEY])
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.6.6"
3
+ VERSION = "2.6.8"
4
4
  end
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.6.6
4
+ version: 2.6.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-21 00:00:00.000000000 Z
10
+ date: 2026-08-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64