graphql 1.12.16 → 1.13.2
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/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- 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 +3 -3
- 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/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +93 -37
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- 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 +39 -23
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.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 +10 -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 +2 -6
- 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 +12 -2
- 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 +15 -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/query.rb +4 -0
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -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 +79 -34
- 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 +7 -3
- data/lib/graphql/schema/enum.rb +60 -10
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +140 -42
- data/lib/graphql/schema/field_extension.rb +52 -2
- 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 +13 -14
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +0 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- 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/non_null.rb +3 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +49 -64
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +17 -9
- data/lib/graphql/schema/traversal.rb +1 -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 +8 -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 +13 -2
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +124 -27
- data/lib/graphql/static_validation/base_visitor.rb +8 -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 +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -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 +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +15 -5
- data/lib/graphql/subscriptions/event.rb +66 -13
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- 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 +10 -32
- data/readme.md +1 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 381cd8cc5f2508805ccc69172566e28577652372b04e44b236ceaf903db1622f
|
4
|
+
data.tar.gz: 56c03d754890c85d6a6c7d5df6a50874f3986a1284f2666a256ea51d90028a82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31f7ab242f8b5efa5f2e77a9c79ab3c4a6b3b9d319dcdc4215d676009f472dc59a1fdf3303a3af86ab557c5b7f53ff2610d30dc36a58eb0937962ebe1ce388f9
|
7
|
+
data.tar.gz: 8c6295f3cdae55803aad1bb45abb97ec1f6a5b76d60f2150e449c5e8244619096c9462317358a048b9d5045a0246cfd2354b96d6c4de49968995e86dd6b2aafa
|
@@ -19,7 +19,9 @@ module Graphql
|
|
19
19
|
sentinel = /< GraphQL::Schema\s*\n/m
|
20
20
|
|
21
21
|
in_root do
|
22
|
-
|
22
|
+
if File.exist?(schema_file_path)
|
23
|
+
inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -122,8 +122,15 @@ module Graphql
|
|
122
122
|
if options.api?
|
123
123
|
say("Skipped graphiql, as this rails project is API only")
|
124
124
|
say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app")
|
125
|
-
elsif !options[:skip_graphiql]
|
126
|
-
gem(
|
125
|
+
elsif !options[:skip_graphiql]
|
126
|
+
# `gem(...)` uses `gsub_file(...)` under the hood, which is a no-op for `rails destroy...` (when `behavior == :revoke`).
|
127
|
+
# So handle that case by calling `gsub_file` with `force: true`.
|
128
|
+
if behavior == :invoke && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails")
|
129
|
+
gem("graphiql-rails", group: :development)
|
130
|
+
elsif behavior == :revoke
|
131
|
+
gemfile_pattern = /\n\s*gem ('|")graphiql-rails('|"), :?group(:| =>) :development/
|
132
|
+
gsub_file Rails.root.join("Gemfile"), gemfile_pattern, "", { force: true }
|
133
|
+
end
|
127
134
|
|
128
135
|
# This is a little cheat just to get cleaner shell output:
|
129
136
|
log :route, 'graphiql-rails'
|
@@ -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)
|
40
|
-
elsif argument.definition.type.list?
|
39
|
+
extract_deprecated_arguments(argument.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
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)
|
data/lib/graphql/base_type.rb
CHANGED
@@ -41,7 +41,9 @@ module GraphQL
|
|
41
41
|
alias :graphql_name :name
|
42
42
|
# Future-compatible alias
|
43
43
|
# @see {GraphQL::SchemaMember}
|
44
|
-
|
44
|
+
def graphql_definition(silence_deprecation_warning: false)
|
45
|
+
itself
|
46
|
+
end
|
45
47
|
|
46
48
|
def type_class
|
47
49
|
metadata[:type_class]
|
@@ -194,7 +196,7 @@ module GraphQL
|
|
194
196
|
resolve_related_type(Object.const_get(type_arg))
|
195
197
|
else
|
196
198
|
if type_arg.respond_to?(:graphql_definition)
|
197
|
-
type_arg.graphql_definition
|
199
|
+
type_arg.graphql_definition(silence_deprecation_warning: true)
|
198
200
|
else
|
199
201
|
type_arg
|
200
202
|
end
|
data/lib/graphql/boolean_type.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
GraphQL::BOOLEAN_TYPE = GraphQL::Types::Boolean.graphql_definition
|
2
|
+
GraphQL::BOOLEAN_TYPE = GraphQL::Types::Boolean.graphql_definition(silence_deprecation_warning: true)
|
@@ -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,31 +68,68 @@ 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
|
|
113
|
+
# These arguments are given to `dataloader.with(source_class, ...)`. The object
|
114
|
+
# returned from this method is used to de-duplicate batch loads under the hood
|
115
|
+
# by using it as a Hash key.
|
116
|
+
#
|
117
|
+
# By default, the arguments are all put in an Array. To customize how this source's
|
118
|
+
# batches are merged, override this method to return something else.
|
119
|
+
#
|
120
|
+
# For example, if you pass `ActiveRecord::Relation`s to `.with(...)`, you could override
|
121
|
+
# this method to call `.to_sql` on them, thus merging `.load(...)` calls when they apply
|
122
|
+
# to equivalent relations.
|
123
|
+
#
|
124
|
+
# @param batch_args [Array<Object>]
|
125
|
+
# @param batch_kwargs [Hash]
|
126
|
+
# @return [Object]
|
127
|
+
def self.batch_key_for(*batch_args, **batch_kwargs)
|
128
|
+
[*batch_args, **batch_kwargs]
|
129
|
+
end
|
130
|
+
|
131
|
+
attr_reader :pending_keys
|
132
|
+
|
92
133
|
private
|
93
134
|
|
94
135
|
# Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
|
@@ -96,6 +137,13 @@ module GraphQL
|
|
96
137
|
# @return [Object] The result from {#fetch} for `key`.
|
97
138
|
# @api private
|
98
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
|
99
147
|
result = @results[key]
|
100
148
|
|
101
149
|
raise result if result.class <= StandardError
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -23,23 +23,42 @@ module GraphQL
|
|
23
23
|
# end
|
24
24
|
#
|
25
25
|
class Dataloader
|
26
|
-
|
27
|
-
|
26
|
+
class << self
|
27
|
+
attr_accessor :default_nonblocking
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
38
|
+
end
|
39
|
+
|
40
|
+
# Call the block with a Dataloader instance,
|
41
|
+
# then run all enqueued jobs and return the result of the block.
|
42
|
+
def self.with_dataloading(&block)
|
43
|
+
dataloader = self.new
|
44
|
+
result = nil
|
45
|
+
dataloader.append_job {
|
46
|
+
result = block.call(dataloader)
|
41
47
|
}
|
48
|
+
dataloader.run
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(nonblocking: self.class.default_nonblocking)
|
53
|
+
@source_cache = Hash.new { |h, k| h[k] = {} }
|
42
54
|
@pending_jobs = []
|
55
|
+
if !nonblocking.nil?
|
56
|
+
@nonblocking = nonblocking
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def nonblocking?
|
61
|
+
@nonblocking
|
43
62
|
end
|
44
63
|
|
45
64
|
# Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
|
@@ -48,17 +67,25 @@ module GraphQL
|
|
48
67
|
# @param batch_parameters [Array<Object>]
|
49
68
|
# @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
|
50
69
|
# and cached for the lifetime of this {Multiplex}.
|
51
|
-
if RUBY_VERSION < "3"
|
52
|
-
def with(source_class, *
|
53
|
-
|
70
|
+
if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below
|
71
|
+
def with(source_class, *batch_args)
|
72
|
+
batch_key = source_class.batch_key_for(*batch_args)
|
73
|
+
@source_cache[source_class][batch_key] ||= begin
|
74
|
+
source = source_class.new(*batch_args)
|
75
|
+
source.setup(self)
|
76
|
+
source
|
77
|
+
end
|
54
78
|
end
|
55
79
|
else
|
56
80
|
def with(source_class, *batch_args, **batch_kwargs)
|
57
|
-
|
58
|
-
@source_cache[source_class][
|
81
|
+
batch_key = source_class.batch_key_for(*batch_args, **batch_kwargs)
|
82
|
+
@source_cache[source_class][batch_key] ||= begin
|
83
|
+
source = source_class.new(*batch_args, **batch_kwargs)
|
84
|
+
source.setup(self)
|
85
|
+
source
|
86
|
+
end
|
59
87
|
end
|
60
88
|
end
|
61
|
-
|
62
89
|
# Tell the dataloader that this fiber is waiting for data.
|
63
90
|
#
|
64
91
|
# Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
|
@@ -80,6 +107,16 @@ module GraphQL
|
|
80
107
|
# Use a self-contained queue for the work in the block.
|
81
108
|
def run_isolated
|
82
109
|
prev_queue = @pending_jobs
|
110
|
+
prev_pending_keys = {}
|
111
|
+
@source_cache.each do |source_class, batched_sources|
|
112
|
+
batched_sources.each do |batch_args, batched_source_instance|
|
113
|
+
if batched_source_instance.pending?
|
114
|
+
prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
|
115
|
+
batched_source_instance.pending_keys.clear
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
83
120
|
@pending_jobs = []
|
84
121
|
res = nil
|
85
122
|
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
@@ -90,10 +127,16 @@ module GraphQL
|
|
90
127
|
res
|
91
128
|
ensure
|
92
129
|
@pending_jobs = prev_queue
|
130
|
+
prev_pending_keys.each do |source_instance, pending_keys|
|
131
|
+
source_instance.pending_keys.concat(pending_keys)
|
132
|
+
end
|
93
133
|
end
|
94
134
|
|
95
135
|
# @api private Move along, move along
|
96
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
|
97
140
|
# At a high level, the algorithm is:
|
98
141
|
#
|
99
142
|
# A) Inside Fibers, run jobs from the queue one-by-one
|
@@ -114,6 +157,8 @@ module GraphQL
|
|
114
157
|
#
|
115
158
|
pending_fibers = []
|
116
159
|
next_fibers = []
|
160
|
+
pending_source_fibers = []
|
161
|
+
next_source_fibers = []
|
117
162
|
first_pass = true
|
118
163
|
|
119
164
|
while first_pass || (f = pending_fibers.shift)
|
@@ -151,31 +196,27 @@ module GraphQL
|
|
151
196
|
# This is where an evented approach would be even better -- can we tell which
|
152
197
|
# fibers are ready to continue, and continue execution there?
|
153
198
|
#
|
154
|
-
|
155
|
-
|
156
|
-
else
|
157
|
-
nil
|
199
|
+
if (first_source_fiber = create_source_fiber)
|
200
|
+
pending_source_fibers << first_source_fiber
|
158
201
|
end
|
159
202
|
|
160
|
-
|
161
|
-
while (outer_source_fiber =
|
203
|
+
while pending_source_fibers.any?
|
204
|
+
while (outer_source_fiber = pending_source_fibers.pop)
|
162
205
|
resume(outer_source_fiber)
|
163
|
-
|
164
|
-
# If this source caused more sources to become pending, run those before running this one again:
|
165
|
-
next_source_fiber = create_source_fiber
|
166
|
-
if next_source_fiber
|
167
|
-
source_fiber_queue << next_source_fiber
|
168
|
-
end
|
169
|
-
|
170
206
|
if outer_source_fiber.alive?
|
171
|
-
|
207
|
+
next_source_fibers << outer_source_fiber
|
208
|
+
end
|
209
|
+
if (next_source_fiber = create_source_fiber)
|
210
|
+
pending_source_fibers << next_source_fiber
|
172
211
|
end
|
173
212
|
end
|
213
|
+
join_queues(pending_source_fibers, next_source_fibers)
|
214
|
+
next_source_fibers.clear
|
174
215
|
end
|
175
216
|
# Move newly-enqueued Fibers on to the list to be resumed.
|
176
217
|
# Clear out the list of next-round Fibers, so that
|
177
218
|
# any Fibers that pause can be put on it.
|
178
|
-
pending_fibers
|
219
|
+
join_queues(pending_fibers, next_fibers)
|
179
220
|
next_fibers.clear
|
180
221
|
end
|
181
222
|
end
|
@@ -190,6 +231,14 @@ module GraphQL
|
|
190
231
|
nil
|
191
232
|
end
|
192
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
|
+
|
193
242
|
private
|
194
243
|
|
195
244
|
# If there are pending sources, return a fiber for running them.
|
@@ -243,9 +292,16 @@ module GraphQL
|
|
243
292
|
fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
|
244
293
|
end
|
245
294
|
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
249
305
|
end
|
250
306
|
end
|
251
307
|
end
|
@@ -76,7 +76,7 @@ ERR
|
|
76
76
|
# Apply definition from `define(...)` kwargs
|
77
77
|
defn.define_keywords.each do |keyword, value|
|
78
78
|
# Don't splat string hashes, which blows up on Rubies before 2.7
|
79
|
-
if value.is_a?(Hash) && value.each_key.all? { |k| k.is_a?(Symbol) }
|
79
|
+
if value.is_a?(Hash) && !value.empty? && value.each_key.all? { |k| k.is_a?(Symbol) }
|
80
80
|
defn_proxy.public_send(keyword, **value)
|
81
81
|
else
|
82
82
|
defn_proxy.public_send(keyword, value)
|
@@ -38,9 +38,17 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
|
42
|
+
TYPE_CLASSES.each do |type_class|
|
43
|
+
refine type_class.singleton_class do
|
44
|
+
import_methods Methods
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
TYPE_CLASSES.each do |type_class|
|
49
|
+
refine type_class.singleton_class do
|
50
|
+
include Methods
|
51
|
+
end
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
data/lib/graphql/deprecation.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
GraphQL::Directive::DeprecatedDirective = GraphQL::Schema::Directive::Deprecated.graphql_definition
|
2
|
+
GraphQL::Directive::DeprecatedDirective = GraphQL::Schema::Directive::Deprecated.graphql_definition(silence_deprecation_warning: true)
|
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
GraphQL::Directive::IncludeDirective = GraphQL::Schema::Directive::Include.graphql_definition
|
2
|
+
GraphQL::Directive::IncludeDirective = GraphQL::Schema::Directive::Include.graphql_definition(silence_deprecation_warning: true)
|
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
GraphQL::Directive::SkipDirective = GraphQL::Schema::Directive::Skip.graphql_definition
|
2
|
+
GraphQL::Directive::SkipDirective = GraphQL::Schema::Directive::Skip.graphql_definition(silence_deprecation_warning: true)
|