graphql 1.12.18 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +2 -2
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +1 -1
- data/lib/graphql/dataloader/source.rb +30 -2
- data/lib/graphql/dataloader.rb +55 -22
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
- data/lib/graphql/execution/interpreter/runtime.rb +20 -12
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +4 -4
- data/lib/graphql/introspection/schema_type.rb +2 -2
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +0 -4
- data/lib/graphql/language/document_from_schema_definition.rb +4 -2
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/connections.rb +35 -16
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +5 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +37 -28
- data/lib/graphql/schema/argument.rb +6 -6
- data/lib/graphql/schema/build_from_definition.rb +5 -5
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +2 -2
- data/lib/graphql/schema/enum.rb +57 -9
- data/lib/graphql/schema/enum_value.rb +4 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +92 -17
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +6 -5
- data/lib/graphql/schema/interface.rb +8 -19
- data/lib/graphql/schema/member/accepts_definition.rb +8 -1
- data/lib/graphql/schema/member/build_type.rb +0 -4
- data/lib/graphql/schema/member/has_arguments.rb +62 -14
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/object.rb +7 -74
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +29 -5
- data/lib/graphql/schema/subscription.rb +11 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +6 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +8 -1
- data/lib/graphql/schema/validator.rb +36 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +87 -15
- data/lib/graphql/static_validation/base_visitor.rb +5 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +41 -22
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
- data/lib/graphql/static_validation/validation_context.rb +2 -1
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +6 -4
- data/lib/graphql/subscriptions/event.rb +65 -13
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +9 -31
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 853fa0482ef0c3bafe04dce77dff9bb685633a1e4c273fd234169f98d595352c
|
4
|
+
data.tar.gz: 6cf800af8dfc469f4d7a375ee22f6d98e03207799f813f10176916f09a0a4064
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bcd2b94fa64ab65fb47995a4896b08629bfc9b66f7797dfd4959ad8ccdb5332bf63ae49f5139c7d1766b3d72cb500da257c611e628dc5db36ae26b472c5d51c
|
7
|
+
data.tar.gz: 51a5c6791429c17940d8d0a5407efb852fd4ddbe7a5eaf706dd8fb074a6654f17b32b08f21956e4e450dff6cff05fb0020a7ff8a141fe38f3e09b8433ca1b4ea
|
@@ -17,7 +17,7 @@ module Graphql
|
|
17
17
|
|
18
18
|
argument :name, type: :string
|
19
19
|
|
20
|
-
def initialize(args, *options)
|
20
|
+
def initialize(args, *options) # :nodoc:
|
21
21
|
# Unfreeze name in case it's given as a frozen string
|
22
22
|
args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
|
23
23
|
super
|
@@ -12,7 +12,8 @@ module Graphql
|
|
12
12
|
#
|
13
13
|
# Add the Node interface with `--node`.
|
14
14
|
class ObjectGenerator < TypeGeneratorBase
|
15
|
-
desc "Create a GraphQL::ObjectType with the given name and fields"
|
15
|
+
desc "Create a GraphQL::ObjectType with the given name and fields." \
|
16
|
+
"If the given type name matches an existing ActiveRecord model, the generated type will automatically include fields for the models database columns."
|
16
17
|
source_root File.expand_path('../templates', __FILE__)
|
17
18
|
|
18
19
|
argument :custom_fields,
|
@@ -32,20 +32,28 @@ module Graphql
|
|
32
32
|
|
33
33
|
# Return a string UUID for `object`
|
34
34
|
def self.id_from_object(object, type_definition, query_ctx)
|
35
|
-
#
|
36
|
-
|
37
|
-
#
|
38
|
-
|
35
|
+
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
|
36
|
+
object_id = object.to_global_id.to_s
|
37
|
+
# Remove this redundant prefix to make IDs shorter:
|
38
|
+
object_id = object_id.sub("gid://\#{GlobalID.app}/", "")
|
39
|
+
encoded_id = Base64.urlsafe_encode64(object_id)
|
40
|
+
# Remove the "=" padding
|
41
|
+
encoded_id = encoded_id.sub(/=+/, "")
|
42
|
+
# Add a type hint
|
43
|
+
type_hint = type_definition.graphql_name.first
|
44
|
+
"\#{type_hint}_\#{encoded_id}"
|
39
45
|
end
|
40
46
|
|
41
47
|
# Given a string UUID, find the object
|
42
|
-
def self.object_from_id(
|
43
|
-
# For example,
|
44
|
-
#
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
#
|
48
|
+
def self.object_from_id(encoded_id_with_hint, query_ctx)
|
49
|
+
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
|
50
|
+
# Split off the type hint
|
51
|
+
_type_hint, encoded_id = encoded_id_with_hint.split("_", 2)
|
52
|
+
# Decode the ID
|
53
|
+
id = Base64.urlsafe_decode64(encoded_id)
|
54
|
+
# Rebuild it for Rails then find the object:
|
55
|
+
full_global_id = "gid://\#{GlobalID.app}/\#{id}"
|
56
|
+
GlobalID::Locator.locate(full_global_id)
|
49
57
|
end
|
50
58
|
RUBY
|
51
59
|
inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false
|
@@ -4,11 +4,23 @@ class <%= schema_name %> < GraphQL::Schema
|
|
4
4
|
<% if options[:batch] %>
|
5
5
|
# GraphQL::Batch setup:
|
6
6
|
use GraphQL::Batch
|
7
|
+
<% else %>
|
8
|
+
# For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
|
9
|
+
use GraphQL::Dataloader
|
7
10
|
<% end %>
|
11
|
+
# GraphQL-Ruby calls this when something goes wrong while running a query:
|
12
|
+
def self.type_error(err, context)
|
13
|
+
# if err.is_a?(GraphQL::InvalidNullError)
|
14
|
+
# # report to your bug tracker here
|
15
|
+
# return nil
|
16
|
+
# end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
8
20
|
# Union and Interface Resolution
|
9
21
|
def self.resolve_type(abstract_type, obj, ctx)
|
10
|
-
# TODO: Implement this
|
11
|
-
# to return the correct object type for `obj`
|
22
|
+
# TODO: Implement this method
|
23
|
+
# to return the correct GraphQL object type for `obj`
|
12
24
|
raise(GraphQL::RequiredImplementationMissingError)
|
13
25
|
end
|
14
26
|
end
|
@@ -36,12 +36,12 @@ module GraphQL
|
|
36
36
|
end
|
37
37
|
|
38
38
|
if argument.definition.type.kind.input_object?
|
39
|
-
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
39
|
+
extract_deprecated_arguments(argument.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
40
40
|
elsif argument.definition.type.list? && !argument.value.nil?
|
41
41
|
argument
|
42
42
|
.value
|
43
43
|
.select { |value| value.respond_to?(:arguments) }
|
44
|
-
.each { |value| extract_deprecated_arguments(value.arguments.argument_values) }
|
44
|
+
.each { |value| extract_deprecated_arguments(value.arguments.argument_values) } # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -23,18 +23,22 @@ module GraphQL
|
|
23
23
|
|
24
24
|
attr_reader :field_definition, :response_path, :query
|
25
25
|
|
26
|
-
# @param
|
26
|
+
# @param parent_type [Class] The owner of `field_definition`
|
27
27
|
# @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
|
28
28
|
# @param query [GraphQL::Query] Used for `query.possible_types`
|
29
29
|
# @param response_path [Array<String>] The path to the response key for the field
|
30
|
-
def initialize(
|
31
|
-
@
|
30
|
+
def initialize(parent_type, field_definition, query, response_path)
|
31
|
+
@parent_type = parent_type
|
32
32
|
@field_definition = field_definition
|
33
33
|
@query = query
|
34
34
|
@response_path = response_path
|
35
35
|
@scoped_children = nil
|
36
|
+
@nodes = []
|
36
37
|
end
|
37
38
|
|
39
|
+
# @return [Array<GraphQL::Language::Nodes::Field>]
|
40
|
+
attr_reader :nodes
|
41
|
+
|
38
42
|
# Returns true if this field has no selections, ie, it's a scalar.
|
39
43
|
# We need a quick way to check whether we should continue traversing.
|
40
44
|
def terminal?
|
@@ -50,16 +54,7 @@ module GraphQL
|
|
50
54
|
end
|
51
55
|
|
52
56
|
def own_complexity(child_complexity)
|
53
|
-
|
54
|
-
case defined_complexity
|
55
|
-
when Proc
|
56
|
-
arguments = @query.arguments_for(@node, @field_definition)
|
57
|
-
defined_complexity.call(@query.context, arguments.keyword_arguments, child_complexity)
|
58
|
-
when Numeric
|
59
|
-
defined_complexity + child_complexity
|
60
|
-
else
|
61
|
-
raise("Invalid complexity: #{defined_complexity.inspect} on #{@field_definition.name}")
|
62
|
-
end
|
57
|
+
@field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
|
63
58
|
end
|
64
59
|
end
|
65
60
|
|
@@ -79,7 +74,8 @@ module GraphQL
|
|
79
74
|
# then the query would have been rejected as invalid.
|
80
75
|
complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
|
81
76
|
|
82
|
-
complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(
|
77
|
+
complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
|
78
|
+
complexity.nodes.push(node)
|
83
79
|
# Push it on the stack.
|
84
80
|
complexities_on_type.push(complexity)
|
85
81
|
end
|
@@ -100,7 +100,7 @@ module GraphQL
|
|
100
100
|
def on_field(node, parent)
|
101
101
|
@response_path.push(node.alias || node.name)
|
102
102
|
parent_type = @object_types.last
|
103
|
-
field_definition = @schema.get_field(parent_type, node.name)
|
103
|
+
field_definition = @schema.get_field(parent_type, node.name, @query.context)
|
104
104
|
@field_definitions.push(field_definition)
|
105
105
|
if !field_definition.nil?
|
106
106
|
next_object_type = field_definition.type.unwrap
|
@@ -138,14 +138,14 @@ module GraphQL
|
|
138
138
|
argument_defn = if (arg = @argument_definitions.last)
|
139
139
|
arg_type = arg.type.unwrap
|
140
140
|
if arg_type.kind.input_object?
|
141
|
-
arg_type.
|
141
|
+
arg_type.get_argument(node.name, @query.context)
|
142
142
|
else
|
143
143
|
nil
|
144
144
|
end
|
145
145
|
elsif (directive_defn = @directive_definitions.last)
|
146
|
-
directive_defn.
|
146
|
+
directive_defn.get_argument(node.name, @query.context)
|
147
147
|
elsif (field_defn = @field_definitions.last)
|
148
|
-
field_defn.
|
148
|
+
field_defn.get_argument(node.name, @query.context)
|
149
149
|
else
|
150
150
|
nil
|
151
151
|
end
|
@@ -89,7 +89,7 @@ module GraphQL
|
|
89
89
|
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
90
90
|
"#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
|
91
91
|
"#{context_entry.object.object.inspect}",
|
92
|
-
context_entry.arguments.to_h.inspect,
|
92
|
+
context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method
|
93
93
|
Backtrace::InspectResult.inspect_result(value),
|
94
94
|
]
|
95
95
|
if (parent = context_entry.parent_frame)
|
@@ -6,7 +6,11 @@ module GraphQL
|
|
6
6
|
# Called by {Dataloader} to prepare the {Source}'s internal state
|
7
7
|
# @api private
|
8
8
|
def setup(dataloader)
|
9
|
+
# These keys have been requested but haven't been fetched yet
|
9
10
|
@pending_keys = []
|
11
|
+
# These keys have been passed to `fetch` but haven't been finished yet
|
12
|
+
@fetching_keys = []
|
13
|
+
# { key => result }
|
10
14
|
@results = {}
|
11
15
|
@dataloader = dataloader
|
12
16
|
end
|
@@ -64,29 +68,46 @@ module GraphQL
|
|
64
68
|
# Then run the batch and update the cache.
|
65
69
|
# @return [void]
|
66
70
|
def sync
|
71
|
+
pending_keys = @pending_keys.dup
|
67
72
|
@dataloader.yield
|
73
|
+
iterations = 0
|
74
|
+
while pending_keys.any? { |k| !@results.key?(k) }
|
75
|
+
iterations += 1
|
76
|
+
if iterations > 1000
|
77
|
+
raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
|
78
|
+
end
|
79
|
+
@dataloader.yield
|
80
|
+
end
|
81
|
+
nil
|
68
82
|
end
|
69
83
|
|
70
84
|
# @return [Boolean] True if this source has any pending requests for data.
|
71
85
|
def pending?
|
72
|
-
|
86
|
+
!@pending_keys.empty?
|
73
87
|
end
|
74
88
|
|
75
89
|
# Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
|
76
90
|
# @api private
|
77
91
|
# @return [void]
|
78
92
|
def run_pending_keys
|
93
|
+
if !@fetching_keys.empty?
|
94
|
+
@pending_keys -= @fetching_keys
|
95
|
+
end
|
79
96
|
return if @pending_keys.empty?
|
80
97
|
fetch_keys = @pending_keys.uniq
|
98
|
+
@fetching_keys.concat(fetch_keys)
|
81
99
|
@pending_keys = []
|
82
100
|
results = fetch(fetch_keys)
|
83
101
|
fetch_keys.each_with_index do |key, idx|
|
84
102
|
@results[key] = results[idx]
|
85
103
|
end
|
104
|
+
nil
|
86
105
|
rescue StandardError => error
|
87
106
|
fetch_keys.each { |key| @results[key] = error }
|
88
107
|
ensure
|
89
|
-
|
108
|
+
if fetch_keys
|
109
|
+
@fetching_keys -= fetch_keys
|
110
|
+
end
|
90
111
|
end
|
91
112
|
|
92
113
|
# These arguments are given to `dataloader.with(source_class, ...)`. The object
|
@@ -116,6 +137,13 @@ module GraphQL
|
|
116
137
|
# @return [Object] The result from {#fetch} for `key`.
|
117
138
|
# @api private
|
118
139
|
def result_for(key)
|
140
|
+
if !@results.key?(key)
|
141
|
+
raise <<-ERR
|
142
|
+
Invariant: fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
|
143
|
+
|
144
|
+
This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
|
145
|
+
ERR
|
146
|
+
end
|
119
147
|
result = @results[key]
|
120
148
|
|
121
149
|
raise result if result.class <= StandardError
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -23,8 +23,18 @@ module GraphQL
|
|
23
23
|
# end
|
24
24
|
#
|
25
25
|
class Dataloader
|
26
|
-
|
27
|
-
|
26
|
+
class << self
|
27
|
+
attr_accessor :default_nonblocking
|
28
|
+
end
|
29
|
+
|
30
|
+
AsyncDataloader = Class.new(self) { self.default_nonblocking = true }
|
31
|
+
|
32
|
+
def self.use(schema, nonblocking: nil)
|
33
|
+
schema.dataloader_class = if nonblocking
|
34
|
+
AsyncDataloader
|
35
|
+
else
|
36
|
+
self
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
30
40
|
# Call the block with a Dataloader instance,
|
@@ -39,9 +49,16 @@ module GraphQL
|
|
39
49
|
result
|
40
50
|
end
|
41
51
|
|
42
|
-
def initialize
|
52
|
+
def initialize(nonblocking: self.class.default_nonblocking)
|
43
53
|
@source_cache = Hash.new { |h, k| h[k] = {} }
|
44
54
|
@pending_jobs = []
|
55
|
+
if !nonblocking.nil?
|
56
|
+
@nonblocking = nonblocking
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def nonblocking?
|
61
|
+
@nonblocking
|
45
62
|
end
|
46
63
|
|
47
64
|
# Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
|
@@ -50,7 +67,7 @@ module GraphQL
|
|
50
67
|
# @param batch_parameters [Array<Object>]
|
51
68
|
# @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
|
52
69
|
# and cached for the lifetime of this {Multiplex}.
|
53
|
-
if RUBY_VERSION < "3"
|
70
|
+
if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below
|
54
71
|
def with(source_class, *batch_args)
|
55
72
|
batch_key = source_class.batch_key_for(*batch_args)
|
56
73
|
@source_cache[source_class][batch_key] ||= begin
|
@@ -117,6 +134,9 @@ module GraphQL
|
|
117
134
|
|
118
135
|
# @api private Move along, move along
|
119
136
|
def run
|
137
|
+
if @nonblocking && !Fiber.scheduler
|
138
|
+
raise "`nonblocking: true` requires `Fiber.scheduler`, assign one with `Fiber.set_scheduler(...)` before executing GraphQL."
|
139
|
+
end
|
120
140
|
# At a high level, the algorithm is:
|
121
141
|
#
|
122
142
|
# A) Inside Fibers, run jobs from the queue one-by-one
|
@@ -137,6 +157,8 @@ module GraphQL
|
|
137
157
|
#
|
138
158
|
pending_fibers = []
|
139
159
|
next_fibers = []
|
160
|
+
pending_source_fibers = []
|
161
|
+
next_source_fibers = []
|
140
162
|
first_pass = true
|
141
163
|
|
142
164
|
while first_pass || (f = pending_fibers.shift)
|
@@ -174,31 +196,27 @@ module GraphQL
|
|
174
196
|
# This is where an evented approach would be even better -- can we tell which
|
175
197
|
# fibers are ready to continue, and continue execution there?
|
176
198
|
#
|
177
|
-
|
178
|
-
|
179
|
-
else
|
180
|
-
nil
|
199
|
+
if (first_source_fiber = create_source_fiber)
|
200
|
+
pending_source_fibers << first_source_fiber
|
181
201
|
end
|
182
202
|
|
183
|
-
|
184
|
-
while (outer_source_fiber =
|
203
|
+
while pending_source_fibers.any?
|
204
|
+
while (outer_source_fiber = pending_source_fibers.pop)
|
185
205
|
resume(outer_source_fiber)
|
186
|
-
|
187
|
-
# If this source caused more sources to become pending, run those before running this one again:
|
188
|
-
next_source_fiber = create_source_fiber
|
189
|
-
if next_source_fiber
|
190
|
-
source_fiber_queue << next_source_fiber
|
191
|
-
end
|
192
|
-
|
193
206
|
if outer_source_fiber.alive?
|
194
|
-
|
207
|
+
next_source_fibers << outer_source_fiber
|
208
|
+
end
|
209
|
+
if (next_source_fiber = create_source_fiber)
|
210
|
+
pending_source_fibers << next_source_fiber
|
195
211
|
end
|
196
212
|
end
|
213
|
+
join_queues(pending_source_fibers, next_source_fibers)
|
214
|
+
next_source_fibers.clear
|
197
215
|
end
|
198
216
|
# Move newly-enqueued Fibers on to the list to be resumed.
|
199
217
|
# Clear out the list of next-round Fibers, so that
|
200
218
|
# any Fibers that pause can be put on it.
|
201
|
-
pending_fibers
|
219
|
+
join_queues(pending_fibers, next_fibers)
|
202
220
|
next_fibers.clear
|
203
221
|
end
|
204
222
|
end
|
@@ -213,6 +231,14 @@ module GraphQL
|
|
213
231
|
nil
|
214
232
|
end
|
215
233
|
|
234
|
+
def join_queues(previous_queue, next_queue)
|
235
|
+
if @nonblocking
|
236
|
+
Fiber.scheduler.run
|
237
|
+
next_queue.select!(&:alive?)
|
238
|
+
end
|
239
|
+
previous_queue.concat(next_queue)
|
240
|
+
end
|
241
|
+
|
216
242
|
private
|
217
243
|
|
218
244
|
# If there are pending sources, return a fiber for running them.
|
@@ -266,9 +292,16 @@ module GraphQL
|
|
266
292
|
fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
|
267
293
|
end
|
268
294
|
|
269
|
-
|
270
|
-
|
271
|
-
|
295
|
+
if @nonblocking
|
296
|
+
Fiber.new(blocking: false) do
|
297
|
+
fiber_locals.each { |k, v| Thread.current[k] = v }
|
298
|
+
yield
|
299
|
+
end
|
300
|
+
else
|
301
|
+
Fiber.new do
|
302
|
+
fiber_locals.each { |k, v| Thread.current[k] = v }
|
303
|
+
yield
|
304
|
+
end
|
272
305
|
end
|
273
306
|
end
|
274
307
|
end
|
data/lib/graphql/deprecation.rb
CHANGED
data/lib/graphql/directive.rb
CHANGED
data/lib/graphql/enum_type.rb
CHANGED
@@ -34,10 +34,14 @@ module GraphQL
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# @return [Hash<String => EnumValue>] `{name => value}` pairs contained in this type
|
37
|
-
def values
|
37
|
+
def values(_context = nil)
|
38
38
|
@values_by_name
|
39
39
|
end
|
40
40
|
|
41
|
+
def enum_values(_context = nil)
|
42
|
+
values.values
|
43
|
+
end
|
44
|
+
|
41
45
|
def kind
|
42
46
|
GraphQL::TypeKinds::ENUM
|
43
47
|
end
|
@@ -111,6 +111,7 @@ module GraphQL
|
|
111
111
|
runtime_info = ctx.namespace(:interpreter) || {}
|
112
112
|
obj = runtime_info[:current_object]
|
113
113
|
args = runtime_info[:current_arguments]
|
114
|
+
args = args && args.keyword_arguments
|
114
115
|
field = runtime_info[:current_field]
|
115
116
|
if obj.is_a?(GraphQL::Schema::Object)
|
116
117
|
obj = obj.object
|
@@ -59,7 +59,7 @@ module GraphQL
|
|
59
59
|
@empty
|
60
60
|
end
|
61
61
|
|
62
|
-
def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
|
62
|
+
def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values, :size, :to_h
|
63
63
|
def_delegators :argument_values, :each_value
|
64
64
|
|
65
65
|
def inspect
|
@@ -71,11 +71,11 @@ module GraphQL
|
|
71
71
|
when Array
|
72
72
|
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(query, v) }
|
73
73
|
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
74
|
-
if ast_arg_or_hash_or_value.arguments.empty?
|
74
|
+
if ast_arg_or_hash_or_value.arguments.empty? # rubocop:disable Development/ContextIsPassedCop -- AST-related
|
75
75
|
return NO_ARGUMENTS
|
76
76
|
end
|
77
77
|
args_hash = {}
|
78
|
-
ast_arg_or_hash_or_value.arguments.each do |arg|
|
78
|
+
ast_arg_or_hash_or_value.arguments.each do |arg| # rubocop:disable Development/ContextIsPassedCop -- AST-related
|
79
79
|
v = prepare_args_hash(query, arg.value)
|
80
80
|
if v != NO_VALUE_GIVEN
|
81
81
|
args_hash[arg.name] = v
|
@@ -314,7 +314,7 @@ module GraphQL
|
|
314
314
|
case node
|
315
315
|
when GraphQL::Language::Nodes::InlineFragment
|
316
316
|
if node.type
|
317
|
-
type_defn = schema.get_type(node.type.name)
|
317
|
+
type_defn = schema.get_type(node.type.name, context)
|
318
318
|
|
319
319
|
# Faster than .map{}.include?()
|
320
320
|
query.warden.possible_types(type_defn).each do |t|
|
@@ -329,7 +329,7 @@ module GraphQL
|
|
329
329
|
end
|
330
330
|
when GraphQL::Language::Nodes::FragmentSpread
|
331
331
|
fragment_def = query.fragments[node.name]
|
332
|
-
type_defn =
|
332
|
+
type_defn = query.get_type(fragment_def.type.name)
|
333
333
|
possible_types = query.warden.possible_types(type_defn)
|
334
334
|
possible_types.each do |t|
|
335
335
|
if t == owner_type
|
@@ -384,7 +384,9 @@ module GraphQL
|
|
384
384
|
ast_node = field_ast_nodes_or_ast_node
|
385
385
|
end
|
386
386
|
field_name = ast_node.name
|
387
|
-
|
387
|
+
# This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
|
388
|
+
# because of how `is_introspection` is used to call `.authorized_new` later on.
|
389
|
+
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
|
388
390
|
is_introspection = false
|
389
391
|
if field_defn.nil?
|
390
392
|
field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
@@ -419,10 +421,10 @@ module GraphQL
|
|
419
421
|
object = authorized_new(field_defn.owner, object, context)
|
420
422
|
end
|
421
423
|
|
422
|
-
total_args_count = field_defn.arguments.size
|
424
|
+
total_args_count = field_defn.arguments(context).size
|
423
425
|
if total_args_count == 0
|
424
|
-
|
425
|
-
evaluate_selection_with_args(
|
426
|
+
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
427
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
426
428
|
else
|
427
429
|
# TODO remove all arguments(...) usages?
|
428
430
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
@@ -431,10 +433,10 @@ module GraphQL
|
|
431
433
|
end
|
432
434
|
end
|
433
435
|
|
434
|
-
def evaluate_selection_with_args(
|
436
|
+
def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
435
437
|
context.scoped_context = scoped_context
|
436
438
|
return_type = field_defn.type
|
437
|
-
after_lazy(
|
439
|
+
after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
438
440
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
439
441
|
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
440
442
|
next
|
@@ -485,7 +487,7 @@ module GraphQL
|
|
485
487
|
resolved_arguments.keyword_arguments
|
486
488
|
end
|
487
489
|
|
488
|
-
set_all_interpreter_context(nil, nil,
|
490
|
+
set_all_interpreter_context(nil, nil, resolved_arguments, nil)
|
489
491
|
|
490
492
|
# Optimize for the case that field is selected only once
|
491
493
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
@@ -511,7 +513,7 @@ module GraphQL
|
|
511
513
|
rescue GraphQL::ExecutionError => err
|
512
514
|
err
|
513
515
|
end
|
514
|
-
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments:
|
516
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
515
517
|
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
516
518
|
if HALT != continue_value
|
517
519
|
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments, result_name, selection_result)
|
@@ -637,14 +639,20 @@ module GraphQL
|
|
637
639
|
when Array
|
638
640
|
# It's an array full of execution errors; add them all.
|
639
641
|
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
642
|
+
list_type_at_all = (field && (field.type.list?))
|
640
643
|
if selection_result.nil? || !dead_result?(selection_result)
|
641
644
|
value.each_with_index do |error, index|
|
642
645
|
error.ast_node ||= ast_node
|
643
|
-
error.path ||= path + (
|
646
|
+
error.path ||= path + (list_type_at_all ? [index] : [])
|
644
647
|
context.errors << error
|
645
648
|
end
|
646
649
|
if selection_result
|
647
|
-
|
650
|
+
if list_type_at_all
|
651
|
+
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
652
|
+
set_result(selection_result, result_name, result_without_errors)
|
653
|
+
else
|
654
|
+
set_result(selection_result, result_name, nil)
|
655
|
+
end
|
648
656
|
end
|
649
657
|
end
|
650
658
|
HALT
|
@@ -254,14 +254,14 @@ module GraphQL
|
|
254
254
|
subselections_on_type = selections_on_type
|
255
255
|
if (t = ast_selection.type)
|
256
256
|
# Assuming this is valid, that `t` will be found.
|
257
|
-
on_type = @query.
|
257
|
+
on_type = @query.get_type(t.name).type_class
|
258
258
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
259
259
|
end
|
260
260
|
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
|
261
261
|
when GraphQL::Language::Nodes::FragmentSpread
|
262
262
|
frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
|
263
263
|
# Again, assuming a valid AST
|
264
|
-
on_type = @query.
|
264
|
+
on_type = @query.get_type(frag_defn.type.name).type_class
|
265
265
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
266
266
|
find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments)
|
267
267
|
else
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
35
35
|
@queries = queries
|
36
36
|
@queries.each { |q| q.multiplex = self }
|
37
37
|
@context = context
|
38
|
-
@
|
38
|
+
@dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
|
39
39
|
@tracers = schema.tracers + (context[:tracers] || [])
|
40
40
|
# Support `context: {backtrace: true}`
|
41
41
|
if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
|