graphql 2.6.5 → 2.6.9

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: 8ece536c0702c8789a3a5c79de82af4bd2ff806a3209d0ac4da88fc968629d19
4
- data.tar.gz: 0063f1ea552e7782029083eb027138a0725af89a65265dc643469f2227184134
3
+ metadata.gz: 6a359d53729477046ffd6b51ef9345099defcdc95521579711fffe5ab79c0b5e
4
+ data.tar.gz: 4c88aecd90fb26e02198f21f4bdf7a73f577ec37ac9ea9eb6c840c8a2957fc7a
5
5
  SHA512:
6
- metadata.gz: 2ee406b8ad164004af45d98e3b5dc70d69396f008a80cc68ef83a4bff212698c0a181644dc2b513e8bd1e71d2baf7d1ba2901ad05f497199556b171a6dfb11ab
7
- data.tar.gz: 2ffd4e01742bd3b0feb207a316b1935d1edff05be8b28afb1c42e5ba47b4df916df802ee15617bb30bd45cdf6fd4f7a59c22b5d73cbabb3d8ee6658f3246d8ef
6
+ metadata.gz: d40bb56d8b5e77ca6f126f1528e2a074bbf679996dfb8ea5a12d646d028d547848dc36dea3a5fb73273fb7252135a5c7e904ec7d0a38790fe05164c5e81701b5
7
+ data.tar.gz: e486b9492693cdc69375f8d3bc662e075a71ff45642f62cb2fd741959b2a2d1f9105fa14718b992ef2d3b3a809fd0f0d94ec66abf509137b3a7af05877527c23
@@ -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(...)
@@ -19,9 +23,12 @@ module GraphQL
19
23
  create_pending_run
20
24
  end
21
25
 
26
+ # @api private
27
+ attr_reader :pending_sources
28
+
22
29
  def create_pending_run
23
30
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
24
- @pending_run = Run.new(total_fiber_limit, jobs_fiber_limit)
31
+ @pending_run = Run.new(self, total_fiber_limit, jobs_fiber_limit)
25
32
  end
26
33
 
27
34
  def yield(source = Fiber[:__graphql_current_dataloader_source])
@@ -29,16 +36,17 @@ module GraphQL
29
36
  run = task.graphql_async_dataloader_run
30
37
  trace = run.trace
31
38
  trace&.dataloader_fiber_yield(source)
32
- run.finished_tasks.push(task)
39
+ run.tasks_channel.push([:paused_task, task])
33
40
  condition = task.graphql_async_dataloader_condition
34
41
  condition.wait
35
- run.started_tasks.push(task)
42
+ run.tasks_channel.push([:resumed_task, task])
36
43
  trace&.dataloader_fiber_resume(source)
37
44
  nil
38
45
  end
39
46
 
40
47
  class Run
41
- def initialize(total_fiber_limit, jobs_fiber_limit)
48
+ def initialize(dataloader, total_fiber_limit, jobs_fiber_limit)
49
+ @dataloader = dataloader
42
50
  @root_task = nil
43
51
  @trace = nil
44
52
  @jobs = []
@@ -47,12 +55,13 @@ module GraphQL
47
55
  @jobs_fiber_limit = jobs_fiber_limit
48
56
  @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
49
57
 
50
- @finished_tasks = nil
51
- @started_tasks = nil
52
- @started_count_task = nil
53
- @finished_count_task = nil
54
- @finished_all_tasks = nil
55
- @finished_first_pass = nil
58
+ @running_tasks = nil
59
+ @tasks_channel = nil
60
+ @tasks_channel_task = nil
61
+ @activity = nil
62
+ @task_error = nil
63
+ @expected_resumes = 0
64
+ @mode = nil
56
65
 
57
66
  @snoozed_jobs_condition = Async::Condition.new
58
67
  @snoozed_sources_condition = Async::Condition.new
@@ -60,74 +69,76 @@ module GraphQL
60
69
 
61
70
  attr_accessor :trace, :root_task
62
71
 
63
- attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :total_fiber_limit, :finished_tasks, :started_tasks, :snoozed_jobs_condition, :snoozed_sources_condition
72
+ attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition, :tasks_channel
64
73
 
65
74
  def jobs_bandwidth?
66
- running_count < jobs_fiber_limit
75
+ running_count < @jobs_fiber_limit
67
76
  end
68
77
 
69
- def allowed_sources_tasks
70
- within_limit = total_fiber_limit - running_count
71
- if within_limit < 1
72
- 1
73
- else
74
- within_limit
75
- end
78
+ def sources_bandwidth?
79
+ running_count < current_sources_fiber_limit
76
80
  end
77
81
 
78
82
  def close_queues
79
- @finished_tasks.close
80
- @finished_count_task.cancel
83
+ @tasks_channel.close
84
+ @tasks_channel_task.cancel
85
+ end
81
86
 
82
- @started_tasks.close
83
- @started_count_task.cancel
87
+ def wait_for_activity
88
+ @activity.wait
84
89
  end
85
90
 
86
- def running_count
87
- @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
88
- @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
89
- @started_count +
90
- @started_tasks.size -
91
- @finished_count
91
+ def quiesced?
92
+ @running_tasks.empty? && @tasks_channel.empty? && @expected_resumes == 0
92
93
  end
93
94
 
94
- def wait_for_queues
95
- if !@finished_first_pass.resolved?
96
- @finished_first_pass.resolve(true)
97
- end
95
+ def has_pending_work?
96
+ @mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
97
+ end
98
98
 
99
- @finished_all_tasks.wait
100
- @finished_all_tasks = Async::Promise.new
99
+ def has_bandwidth?
100
+ @mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
101
101
  end
102
102
 
103
- def new_queues
104
- @finished_tasks = Async::Queue.new
105
- @finished_count = 0
106
- @started_tasks = Async::Queue.new
107
- @started_count = 0
108
- @finished_first_pass = Async::Promise.new
109
- @finished_all_tasks = Async::Promise.new
110
-
111
- @started_count_task = @root_task.async do |task|
112
- @finished_first_pass.wait
113
- while task = @started_tasks.wait
114
- @started_count += 1
115
- if task.status == :initialized # could also be resumed after waiting
116
- task.run
117
- end
118
- end
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
107
+ end
108
+
109
+ def check_error!
110
+ if (err = @task_error)
111
+ @task_error = nil
112
+ raise err
119
113
  end
114
+ end
120
115
 
121
- @finished_count_task = @root_task.async do |task|
122
- while t_or_err = @finished_tasks.wait
123
- if t_or_err.is_a?(StandardError)
124
- @finished_all_tasks.reject(t_or_err)
125
- else
126
- @finished_count += 1
127
- if @finished_count == @started_count
128
- @finished_all_tasks.resolve(true)
116
+ def new_queues(mode)
117
+ @mode = mode
118
+ @tasks_channel = Async::Queue.new(parent: @root_task)
119
+ @activity = Async::Condition.new
120
+ @task_error = nil
121
+ @expected_resumes = 0
122
+ @running_tasks = []
123
+ @tasks_channel_task = @root_task.async do |_t|
124
+ while ((msg, data) = @tasks_channel.wait)
125
+ case msg
126
+ when :started_task
127
+ @running_tasks.push(data)
128
+ data.run
129
+ when :resumed_task
130
+ if @expected_resumes > 0
131
+ @expected_resumes -= 1
129
132
  end
133
+ @running_tasks.push(data)
134
+ when :finished_task, :paused_task
135
+ @running_tasks.delete(data)
136
+ when :task_error
137
+ @task_error ||= data
138
+ else
139
+ raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
130
140
  end
141
+ @activity.signal
131
142
  end
132
143
  end
133
144
  end
@@ -135,6 +146,23 @@ module GraphQL
135
146
  def running?
136
147
  @snoozed_jobs_condition.waiting? || @snoozed_sources_condition.waiting?
137
148
  end
149
+
150
+ def current_sources_fiber_limit
151
+ within_limit = @total_fiber_limit - running_count
152
+ if within_limit < 1
153
+ 1
154
+ else
155
+ within_limit
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def running_count
162
+ @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
163
+ @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
164
+ (@running_tasks&.size || 0)
165
+ end
138
166
  end
139
167
 
140
168
  def append_job(callable = nil, &block)
@@ -177,9 +205,10 @@ module GraphQL
177
205
  end
178
206
  prev_pending_keys.each do |source_instance, pending|
179
207
  pending.each do |key, value|
180
- if !source_instance.results.key?(key)
181
- source_instance.pending[key] = value
182
- end
208
+ next if source_instance.results.key?(key)
209
+
210
+ queue_pending_source(source_instance) if source_instance.pending.empty?
211
+ source_instance.pending[key] = value
183
212
  end
184
213
  end
185
214
  end
@@ -203,13 +232,14 @@ module GraphQL
203
232
 
204
233
  while first_pass || run.running? || !jobs.empty?
205
234
  first_pass = false
206
- run_pending_steps(run)
207
- run_sources(run)
235
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
236
+ run_queue(run, run.snoozed_sources_condition, :sources)
208
237
 
209
238
  if !run.lazies_at_depth.empty?
210
239
  with_trace_query_lazy(trace_query_lazy) do
211
- run_next_pending_lazies(run)
212
- run_pending_steps(run)
240
+ if enqueue_next_pending_lazies(run.lazies_at_depth)
241
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
242
+ end
213
243
  end
214
244
  end
215
245
  end
@@ -232,129 +262,88 @@ module GraphQL
232
262
 
233
263
  private
234
264
 
235
- def run_pending_steps(run)
236
- run.new_queues
265
+ def run_queue(run, condition, mode)
266
+ opened_queues = false
237
267
 
238
- if (unsnoozed = run.snoozed_jobs_condition.waiting?)
239
- run.snoozed_jobs_condition.signal
268
+ if condition.waiting?
269
+ opened_queues = true
270
+ run.new_queues(mode)
271
+ run.expect_resumes(condition.instance_variable_get(:@ready).num_waiting)
272
+ condition.signal
240
273
  end
241
- pending_jobs = run.jobs
242
- while (!pending_jobs.empty? && (has_limit = run.jobs_bandwidth?)) || (unsnoozed)
243
- unsnoozed = false
244
- if has_limit
245
- spawn_job_task(run)
246
- end
247
- run.wait_for_queues
248
- end
249
- ensure
250
- run.close_queues
251
- end
252
274
 
253
- def spawn_job_task(run)
254
- pending_jobs = run.jobs
255
- if !pending_jobs.empty?
256
- fiber_vars = get_fiber_variables
257
- new_task = Async::Task.new(run.root_task) do |task|
258
- run.trace&.dataloader_spawn_execution_fiber(pending_jobs)
259
- task.graphql_async_dataloader_run = run
260
- task.graphql_async_dataloader_condition = run.snoozed_jobs_condition
261
- set_fiber_variables(fiber_vars)
262
- while job = pending_jobs.shift
263
- job.call
275
+ loop do
276
+ pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)
277
+ if pending_work
278
+ if opened_queues == false
279
+ opened_queues = true
280
+ run.new_queues(mode)
264
281
  end
265
- ensure
266
- cleanup_fiber
267
- run.finished_tasks.push($! || task)
268
- run.trace&.dataloader_fiber_exit
282
+ num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
283
+ if num_tasks > pending_work.size
284
+ num_tasks = pending_work.size
285
+ end
286
+ spawn_tasks(run, mode, condition, pending_work, num_tasks)
269
287
  end
270
- run.started_tasks.push(new_task)
271
- new_task
272
- end
273
- end
274
-
275
- def run_sources(run)
276
- run.new_queues
277
288
 
278
- if (unsnoozed = run.snoozed_sources_condition.waiting?)
279
- run.snoozed_sources_condition.signal
280
- end
281
-
282
- allowed_tasks = run.allowed_sources_tasks
283
- while (has_pending = @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } ) || unsnoozed
284
- unsnoozed = false
285
- if has_pending
286
- spawn_source_task(run, allowed_tasks)
289
+ if !opened_queues
290
+ break
287
291
  end
288
- run.wait_for_queues
289
- end
290
- ensure
291
- run.close_queues
292
- end
293
292
 
294
- #### TODO DRY Had to duplicate to remove spawn_job_fiber
295
- def run_next_pending_lazies(run)
296
- smallest_depth = nil
297
- run.lazies_at_depth.each_key do |depth_key|
298
- smallest_depth ||= depth_key
299
- if depth_key < smallest_depth
300
- smallest_depth = depth_key
301
- end
302
- end
293
+ run.check_error!
303
294
 
304
- if smallest_depth
305
- lazies = run.lazies_at_depth.delete(smallest_depth)
306
- if !lazies.empty?
307
- begin
308
- run.new_queues
309
- lazies.each_with_index do |l, idx|
310
- append_job { l.value }
311
- end
312
- spawn_job_task(run) # Todo what was the last `true` condition?
313
- run.wait_for_queues
314
- ensure
315
- run.close_queues
295
+ if run.quiesced?
296
+ if !run.has_pending_work? || !run.has_bandwidth?
297
+ break
316
298
  end
299
+ # Quiesced, but more work appeared - loop around to drain it.
300
+ else
301
+ run.wait_for_activity
317
302
  end
318
303
  end
319
- end
320
-
321
- def spawn_source_task(run, num_tasks)
322
- pending_sources = nil
323
- @source_cache.each_value do |source_by_batch_params|
324
- source_by_batch_params.each_value do |source|
325
- if source.pending?
326
- pending_sources ||= []
327
- pending_sources << source
328
- end
329
- end
304
+ ensure
305
+ if opened_queues
306
+ run.close_queues
330
307
  end
308
+ end
331
309
 
332
- if pending_sources
333
- if num_tasks == Float::INFINITY
334
- num_tasks = pending_sources.size
335
- end
336
- fiber_vars = get_fiber_variables
337
- trace = run.trace
338
- num_tasks.times do
339
- new_task = Async::Task.new(run.root_task) do |task|
340
- task.graphql_async_dataloader_run = run
341
- task.graphql_async_dataloader_condition = run.snoozed_sources_condition
342
- trace&.dataloader_spawn_source_fiber(pending_sources)
343
- set_fiber_variables(fiber_vars)
344
- while (source = pending_sources.shift)
310
+ # Use a separate method for this so that the outer loop's reassignment of `pending_work`
311
+ # doesn't affect already-running tasks which (would) close over that variable
312
+ def spawn_tasks(run, mode, condition, pending_work, num_tasks)
313
+ fiber_vars = get_fiber_variables
314
+ trace = run.trace
315
+ num_tasks.times do
316
+ new_task = Async::Task.new(run.root_task) do |task|
317
+ task.graphql_async_dataloader_run = run
318
+ task.graphql_async_dataloader_condition = condition
319
+ set_fiber_variables(fiber_vars)
320
+ case mode
321
+ when :jobs
322
+ trace&.dataloader_spawn_execution_fiber(pending_work)
323
+ while job = pending_work.shift
324
+ job.call
325
+ end
326
+ when :sources
327
+ trace&.dataloader_spawn_source_fiber(pending_work)
328
+ while (source = pending_work.shift)
329
+ Fiber[:__graphql_current_dataloader_source] = source
345
330
  trace&.begin_dataloader_source(source)
346
331
  source.run_pending_keys
347
332
  trace&.end_dataloader_source(source)
348
333
  end
349
- nil
350
- ensure
351
- run.finished_tasks.push($! || task)
352
- cleanup_fiber
353
- trace&.dataloader_fiber_exit
334
+ else
335
+ raise ArgumentError, "Unknown mode: #{mode.inspect}"
354
336
  end
355
- run.started_tasks.push(new_task)
356
- new_task
337
+ nil
338
+ rescue StandardError => err
339
+ run.tasks_channel.push([:task_error, err])
340
+ else
341
+ run.tasks_channel.push([:finished_task, task])
342
+ ensure
343
+ cleanup_fiber
344
+ trace&.dataloader_fiber_exit
357
345
  end
346
+ run.tasks_channel.push([:started_task, new_task])
358
347
  end
359
348
  end
360
349
  end
@@ -20,9 +20,7 @@ module GraphQL
20
20
  # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
- if !@results.key?(res_key)
24
- @pending[res_key] ||= normalize_fetch_key(value)
25
- end
23
+ add_pending_key(res_key, value)
26
24
  Dataloader::Request.new(self, value)
27
25
  end
28
26
 
@@ -51,9 +49,7 @@ module GraphQL
51
49
  def request_all(values)
52
50
  values.each do |v|
53
51
  res_key = result_key_for(v)
54
- if !@results.key?(res_key)
55
- @pending[res_key] ||= normalize_fetch_key(v)
56
- end
52
+ add_pending_key(res_key, v)
57
53
  end
58
54
  Dataloader::RequestAll.new(self, values)
59
55
  end
@@ -65,7 +61,7 @@ module GraphQL
65
61
  if @results.key?(result_key)
66
62
  result_for(result_key)
67
63
  else
68
- @pending[result_key] ||= normalize_fetch_key(value)
64
+ add_pending_key(result_key, value)
69
65
  sync([result_key])
70
66
  result_for(result_key)
71
67
  end
@@ -79,8 +75,7 @@ module GraphQL
79
75
  values.each { |v|
80
76
  k = result_key_for(v)
81
77
  result_keys << k
82
- if !@results.key?(k)
83
- @pending[k] ||= normalize_fetch_key(v)
78
+ if add_pending_key(k, v)
84
79
  pending_keys << k
85
80
  end
86
81
  }
@@ -106,6 +101,7 @@ module GraphQL
106
101
  # Then run the batch and update the cache.
107
102
  # @return [void]
108
103
  def sync(pending_result_keys)
104
+ @dataloader.queue_pending_source(self) if pending?
109
105
  @dataloader.yield(self)
110
106
  iterations = 0
111
107
  while pending_result_keys.any? { |key| !@results.key?(key) }
@@ -193,6 +189,15 @@ module GraphQL
193
189
 
194
190
  private
195
191
 
192
+ def add_pending_key(result_key, value)
193
+ return false if @results.key?(result_key)
194
+
195
+ was_empty = @pending.empty?
196
+ @pending[result_key] ||= normalize_fetch_key(value)
197
+ @dataloader.queue_pending_source(self) if was_empty
198
+ true
199
+ end
200
+
196
201
  # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
197
202
  # @param key [Object] key passed to {#load} or {#load_all}
198
203
  # @return [Object] The result from {#fetch} for `key`.
@@ -59,6 +59,8 @@ module GraphQL
59
59
 
60
60
  def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
61
61
  @source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity
62
+ @pending_source_set = Set.new.compare_by_identity
63
+ @pending_sources = []
62
64
  @pending_jobs = []
63
65
  if !nonblocking.nil?
64
66
  @nonblocking = nonblocking
@@ -108,7 +110,7 @@ module GraphQL
108
110
  # @param batch_parameters [Array<Object>]
109
111
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
110
112
  # and cached for the lifetime of this {Multiplex}.
111
- if (RUBY_ENGINE == "ruby" && RUBY_ENGINE < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
113
+ if (RUBY_ENGINE == "ruby" && RUBY_VERSION < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
112
114
  def with(source_class, *batch_args)
113
115
  batch_key = source_class.batch_key_for(*batch_args)
114
116
  @source_cache[source_class][batch_key] ||= begin
@@ -148,6 +150,14 @@ module GraphQL
148
150
  nil
149
151
  end
150
152
 
153
+ # @api private
154
+ def queue_pending_source(source)
155
+ if @pending_source_set.add?(source)
156
+ @pending_sources << source
157
+ end
158
+ nil
159
+ end
160
+
151
161
  # Clear any already-loaded objects from {Source} caches
152
162
  # @return [void]
153
163
  def clear_cache
@@ -187,9 +197,10 @@ module GraphQL
187
197
  @lazies_at_depth = prev_lazies_at_depth
188
198
  prev_pending_keys.each do |source_instance, pending|
189
199
  pending.each do |key, value|
190
- if !source_instance.results.key?(key)
191
- source_instance.pending[key] = value
192
- end
200
+ next if source_instance.results.key?(key)
201
+
202
+ queue_pending_source(source_instance) if source_instance.pending.empty?
203
+ source_instance.pending[key] = value
193
204
  end
194
205
  end
195
206
  end
@@ -212,8 +223,10 @@ module GraphQL
212
223
 
213
224
  if !@lazies_at_depth.empty?
214
225
  with_trace_query_lazy(trace_query_lazy) do
215
- run_next_pending_lazies(job_fibers, trace)
216
- run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
226
+ if enqueue_next_pending_lazies(@lazies_at_depth)
227
+ job_fibers.unshift(spawn_job_fiber(trace))
228
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
229
+ end
217
230
  end
218
231
  end
219
232
  end
@@ -274,24 +287,19 @@ module GraphQL
274
287
 
275
288
  private
276
289
 
277
- def run_next_pending_lazies(job_fibers, trace)
278
- smallest_depth = nil
279
- @lazies_at_depth.each_key do |depth_key|
280
- smallest_depth ||= depth_key
281
- if depth_key < smallest_depth
282
- smallest_depth = depth_key
283
- end
284
- end
290
+ # Returns true if anything was actually enqueued
291
+ def enqueue_next_pending_lazies(lazies_at_depth)
292
+ smallest_depth = lazies_at_depth.each_key.min
293
+ return false if smallest_depth.nil?
285
294
 
286
- if smallest_depth
287
- lazies = @lazies_at_depth.delete(smallest_depth)
288
- if !lazies.empty?
289
- lazies.each_with_index do |l, idx|
290
- append_job { l.value }
291
- end
292
- job_fibers.unshift(spawn_job_fiber(trace))
293
- end
295
+ lazies = lazies_at_depth.delete(smallest_depth)
296
+ return false if lazies.empty?
297
+
298
+ lazies.each do |lazy|
299
+ append_job { lazy.value }
294
300
  end
301
+
302
+ true
295
303
  end
296
304
 
297
305
  def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
@@ -305,7 +313,7 @@ module GraphQL
305
313
  end
306
314
  join_queues(job_fibers, next_job_fibers)
307
315
 
308
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
316
+ while (!source_fibers.empty? || !@pending_sources.empty?)
309
317
  while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
310
318
  if f.alive?
311
319
  finished = run_fiber(f)
@@ -356,21 +364,29 @@ module GraphQL
356
364
  end
357
365
  end
358
366
 
359
- def spawn_source_fiber(trace)
360
- pending_sources = nil
361
- @source_cache.each_value do |source_by_batch_params|
362
- source_by_batch_params.each_value do |source|
363
- if source.pending?
364
- pending_sources ||= []
365
- pending_sources << source
366
- end
367
- end
367
+ def drain_pending_sources
368
+ pending_sources = @pending_sources
369
+ @pending_sources = []
370
+ @pending_source_set.clear
371
+
372
+ pending_sources.select!(&:pending?)
373
+ pending_sources.empty? ? nil : pending_sources
374
+ end
375
+
376
+ def dequeue_pending_source
377
+ while (source = @pending_sources.shift)
378
+ @pending_source_set.delete(source)
379
+ return source if source.pending?
368
380
  end
381
+ end
369
382
 
370
- if pending_sources
383
+ def spawn_source_fiber(trace)
384
+ if !@pending_sources.empty?
371
385
  spawn_fiber do
372
- trace&.dataloader_spawn_source_fiber(pending_sources)
373
- pending_sources.each do |source|
386
+ trace&.dataloader_spawn_source_fiber(@pending_sources)
387
+ # This will find sources which were enqueued during `#fetch`:
388
+ while (source = dequeue_pending_source)
389
+ next if !source.pending?
374
390
  Fiber[:__graphql_current_dataloader_source] = source
375
391
  trace&.begin_dataloader_source(source)
376
392
  source.run_pending_keys
@@ -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
@@ -765,8 +765,7 @@ module GraphQL
765
765
 
766
766
  response_list
767
767
  rescue NoMethodError => err
768
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
769
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
768
+ if err.name == :each && err.receiver == value
770
769
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
771
770
  raise ListResultFailedError.new(value: value, field: field, path: current_path)
772
771
  else
@@ -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)
@@ -281,8 +280,8 @@ module GraphQL
281
280
  end
282
281
  result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
282
  if result.is_a?(Finalizer)
284
- result.path = path
285
- add_finalizer(query, result, nil, data)
283
+ result.path = beginning_path
284
+ add_finalizer(query, data, nil, result)
286
285
  if result.is_a?(HaltExecution)
287
286
  continue_execution = false
288
287
  break
@@ -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
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'graphql/version'
4
4
  require 'digest/sha2'
5
+ require 'openssl'
6
+ require 'securerandom'
7
+ require 'tempfile'
5
8
 
6
9
  module GraphQL
7
10
  module Language
@@ -9,7 +12,11 @@ module GraphQL
9
12
  #
10
13
  # With Rails, parser caching may enabled by setting `config.graphql.parser_cache = true` in your Rails application.
11
14
  #
12
- # The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new("some_dir")`.
15
+ # The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new(Pathname.new("some_dir"), secret: ENV.fetch("GRAPHQL_CACHE_SECRET"))`.
16
+ # The `secret` should be a stable value stored outside of the cache directory.
17
+ # When it isn't provided, a process-local secret is generated and cache entries
18
+ # are rebuilt after the process restarts. Pass `secret: nil` to disable cache
19
+ # signing. This should only be used when the cache directory is trusted.
13
20
  # This will create a directory (`tmp/cache/graphql` by default) that stores a cache of parsed files.
14
21
  #
15
22
  # Much like [bootsnap](https://github.com/Shopify/bootsnap), the parser cache needs to be cleaned up manually.
@@ -18,32 +25,95 @@ module GraphQL
18
25
  #
19
26
  # @see GraphQL::Railtie for simple Rails integration
20
27
  class Cache
21
- def initialize(path)
28
+ # @param path [Pathname] The directory where cache entries are stored.
29
+ # @param secret [String, nil] A stable secret for verifying cache entries. When omitted,
30
+ # a process-local secret is generated. Pass `nil` to disable cache signing.
31
+ def initialize(path, secret: SecureRandom.random_bytes(32))
22
32
  @path = path
33
+ @secret = secret
23
34
  end
24
35
 
25
36
  DIGEST = Digest::SHA256.new << GraphQL::VERSION
37
+ HMAC_SIZE = OpenSSL::Digest::SHA256.new.digest_length
38
+ InvalidCache = Class.new(StandardError)
39
+ private_constant :InvalidCache
26
40
 
27
41
  def fetch(filename)
28
- hash = DIGEST.dup << filename
42
+ cache_key = cache_key_for(filename)
43
+ return yield unless cache_key
44
+
45
+ cache_path = @path.join(cache_key)
46
+
47
+ begin
48
+ return load_cache(cache_path, cache_key) if cache_path.file?
49
+ rescue InvalidCache, SystemCallError
50
+ # Rebuild caches created by older versions or with an invalid signature.
51
+ end
52
+
53
+ payload = yield
29
54
  begin
30
- hash << File.mtime(filename).to_i.to_s
55
+ write_cache(cache_path, cache_key, payload)
31
56
  rescue SystemCallError
32
- return yield
57
+ # Parser caching is best-effort; return the parsed payload if the cache cannot be written.
33
58
  end
34
- cache_path = @path.join(hash.to_s)
59
+ payload
60
+ end
61
+
62
+ private
63
+
64
+ def cache_key_for(filename)
65
+ content_digest = Digest::SHA256.file(filename).hexdigest
66
+ (DIGEST.dup << filename << content_digest).to_s
67
+ rescue SystemCallError
68
+ nil
69
+ end
70
+
71
+ def load_cache(cache_path, cache_key)
72
+ cache_data = cache_path.binread
73
+ return Marshal.load(cache_data) unless @secret
74
+
75
+ signature = cache_data.byteslice(0, HMAC_SIZE)
76
+ payload = cache_data.byteslice(HMAC_SIZE..-1)
77
+ raise InvalidCache unless signature && payload
35
78
 
36
- if cache_path.exist?
37
- Marshal.load(cache_path.read)
79
+ expected_signature = signature_for(cache_key, payload)
80
+ unless secure_compare(signature, expected_signature)
81
+ raise InvalidCache
82
+ end
83
+ Marshal.load(payload)
84
+ end
85
+
86
+ def write_cache(cache_path, cache_key, payload)
87
+ @path.mkpath
88
+ serialized_payload = Marshal.dump(payload)
89
+ cache_data = if @secret
90
+ signature_for(cache_key, serialized_payload) + serialized_payload
38
91
  else
39
- payload = yield
40
- tmp_path = "#{cache_path}.#{rand}"
92
+ serialized_payload
93
+ end
94
+
95
+ Tempfile.create(['graphql-cache-', '.tmp'], @path.to_s) do |tempfile|
96
+ tempfile.binmode
97
+ tempfile.write(cache_data)
98
+ tempfile.flush
99
+ tempfile.fsync
100
+ tempfile.close
101
+ File.rename(tempfile.path, cache_path.to_s)
102
+ end
103
+ end
104
+
105
+ def signature_for(cache_key, payload)
106
+ OpenSSL::HMAC.digest('SHA256', @secret, cache_key + payload)
107
+ end
108
+
109
+ def secure_compare(left, right)
110
+ return false unless left.bytesize == right.bytesize
41
111
 
42
- @path.mkpath
43
- File.binwrite(tmp_path, Marshal.dump(payload))
44
- File.rename(tmp_path, cache_path.to_s)
45
- payload
112
+ result = 0
113
+ left.bytes.each_with_index do |byte, index|
114
+ result |= byte ^ right.getbyte(index)
46
115
  end
116
+ result.zero?
47
117
  end
48
118
  end
49
119
  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
@@ -19,7 +19,7 @@ module GraphQL
19
19
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
20
20
  if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
21
21
  add_max_errors_reached_message
22
- break
22
+ break memo
23
23
  end
24
24
  # Find the right value for this variable:
25
25
  # - First, use the value provided at runtime
@@ -15,7 +15,8 @@ module GraphQL
15
15
  initializer("graphql.cache") do |app|
16
16
  if config.graphql.parser_cache
17
17
  Language::Parser.cache ||= Language::Cache.new(
18
- app.root.join("tmp/cache/graphql")
18
+ app.root.join("tmp/cache/graphql"),
19
+ secret: app.secret_key_base
19
20
  )
20
21
  end
21
22
  end
@@ -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
@@ -168,6 +168,9 @@ module GraphQL
168
168
  # Let validation handle this
169
169
  value
170
170
  end
171
+ elsif arg_defn.default_value?
172
+ value = arg_defn.default_value
173
+ graphql_value = arg_type.coerce_isolated_result(value) unless value.nil?
171
174
  else
172
175
  value = graphql_value = nil
173
176
  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
 
@@ -83,7 +83,7 @@ module GraphQL
83
83
  is_authed, new_return_value = authorized?(**@prepared_arguments)
84
84
  rescue GraphQL::UnauthorizedError => err
85
85
  new_return_value = q.schema.unauthorized_object(err)
86
- is_authed = true # the error was handled
86
+ is_authed = false
87
87
  end
88
88
  end
89
89
 
@@ -25,6 +25,10 @@ module GraphQL
25
25
  def ==(other)
26
26
  self.class == other.class && of_type == other.of_type
27
27
  end
28
+
29
+ def deconstruct_keys(_keys)
30
+ { of_type: of_type }
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -665,8 +665,8 @@ module GraphQL
665
665
  inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
666
666
  own_um + inherited_um
667
667
  else
668
- joined_um = own_union_memberships.dup
669
- find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
668
+ joined_um = own_union_memberships.transform_values(&:dup)
669
+ find_inherited_value(:union_memberships, EMPTY_HASH).each do |k, v|
670
670
  um = joined_um[k] ||= []
671
671
  um.concat(v)
672
672
  end
@@ -860,7 +860,7 @@ module GraphQL
860
860
  # @return [Array<GraphQL::StaticValidation::Error >]
861
861
  def validate(string_or_document, rules: nil, context: nil)
862
862
  doc = if string_or_document.is_a?(String)
863
- GraphQL.parse(string_or_document)
863
+ GraphQL.parse(string_or_document, max_tokens: max_query_string_tokens)
864
864
  else
865
865
  string_or_document
866
866
  end
@@ -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.5"
3
+ VERSION = "2.6.9"
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.5
4
+ version: 2.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-06 00:00:00.000000000 Z
10
+ date: 2026-08-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -261,34 +261,6 @@ dependencies:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
- - !ruby/object:Gem::Dependency
265
- name: jekyll
266
- requirement: !ruby/object:Gem::Requirement
267
- requirements:
268
- - - ">="
269
- - !ruby/object:Gem::Version
270
- version: '0'
271
- type: :development
272
- prerelease: false
273
- version_requirements: !ruby/object:Gem::Requirement
274
- requirements:
275
- - - ">="
276
- - !ruby/object:Gem::Version
277
- version: '0'
278
- - !ruby/object:Gem::Dependency
279
- name: jekyll-sass-converter
280
- requirement: !ruby/object:Gem::Requirement
281
- requirements:
282
- - - "~>"
283
- - !ruby/object:Gem::Version
284
- version: '2.2'
285
- type: :development
286
- prerelease: false
287
- version_requirements: !ruby/object:Gem::Requirement
288
- requirements:
289
- - - "~>"
290
- - !ruby/object:Gem::Version
291
- version: '2.2'
292
264
  - !ruby/object:Gem::Dependency
293
265
  name: yard
294
266
  requirement: !ruby/object:Gem::Requirement
@@ -303,34 +275,6 @@ dependencies:
303
275
  - - ">="
304
276
  - !ruby/object:Gem::Version
305
277
  version: '0'
306
- - !ruby/object:Gem::Dependency
307
- name: jekyll-algolia
308
- requirement: !ruby/object:Gem::Requirement
309
- requirements:
310
- - - ">="
311
- - !ruby/object:Gem::Version
312
- version: '0'
313
- type: :development
314
- prerelease: false
315
- version_requirements: !ruby/object:Gem::Requirement
316
- requirements:
317
- - - ">="
318
- - !ruby/object:Gem::Version
319
- version: '0'
320
- - !ruby/object:Gem::Dependency
321
- name: jekyll-redirect-from
322
- requirement: !ruby/object:Gem::Requirement
323
- requirements:
324
- - - ">="
325
- - !ruby/object:Gem::Version
326
- version: '0'
327
- type: :development
328
- prerelease: false
329
- version_requirements: !ruby/object:Gem::Requirement
330
- requirements:
331
- - - ">="
332
- - !ruby/object:Gem::Version
333
- version: '0'
334
278
  - !ruby/object:Gem::Dependency
335
279
  name: m
336
280
  requirement: !ruby/object:Gem::Requirement