graphql 1.9.6 → 1.9.7
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 +5 -5
- data/lib/generators/graphql/install_generator.rb +2 -1
- data/lib/generators/graphql/templates/base_field.erb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +5 -2
- data/lib/graphql/execution/multiplex.rb +5 -1
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/relay/array_connection.rb +1 -1
- data/lib/graphql/schema.rb +15 -1
- data/lib/graphql/schema/argument.rb +5 -1
- data/lib/graphql/schema/input_object.rb +21 -12
- data/lib/graphql/schema/introspection_system.rb +6 -1
- data/lib/graphql/schema/member/has_arguments.rb +4 -2
- data/lib/graphql/schema/resolver.rb +8 -3
- data/lib/graphql/schema/subscription.rb +22 -0
- data/lib/graphql/schema/timeout.rb +109 -0
- data/lib/graphql/types/relay/base_edge.rb +0 -3
- data/lib/graphql/upgrader/member.rb +148 -111
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/fixtures/upgrader/mutation.original.rb +28 -0
- data/spec/fixtures/upgrader/mutation.transformed.rb +28 -0
- data/spec/graphql/analysis/ast_spec.rb +27 -0
- data/spec/graphql/execution/instrumentation_spec.rb +34 -6
- data/spec/graphql/execution/multiplex_spec.rb +11 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +6 -1
- data/spec/graphql/schema/input_object_spec.rb +56 -7
- data/spec/graphql/schema/introspection_system_spec.rb +24 -0
- data/spec/graphql/schema/subscription_spec.rb +65 -0
- data/spec/graphql/schema/timeout_spec.rb +206 -0
- data/spec/integration/mongoid/star_trek/schema.rb +1 -2
- data/spec/integration/rails/graphql/input_object_spec.rb +19 -0
- data/spec/integration/rails/graphql/relay/array_connection_spec.rb +47 -28
- data/spec/integration/rails/graphql/schema_spec.rb +18 -0
- data/spec/integration/tmp/app/graphql/types/date_type.rb +14 -0
- data/spec/integration/tmp/dummy/Gemfile +50 -0
- data/spec/integration/tmp/dummy/README.md +24 -0
- data/spec/integration/tmp/dummy/Rakefile +6 -0
- data/spec/integration/tmp/dummy/app/assets/config/manifest.js +3 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/channel.rb +5 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/connection.rb +5 -0
- data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +44 -0
- data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +3 -0
- data/spec/integration/tmp/dummy/app/jobs/application_job.rb +3 -0
- data/spec/integration/tmp/dummy/app/mailers/application_mailer.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/dummy_schema.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/mutations/update_name.rb +15 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_enum.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_input_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_interface.rb +6 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_scalar.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_union.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/mutation_type.rb +12 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/query_type.rb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/integration/tmp/dummy/bin/bundle +3 -0
- data/spec/integration/tmp/dummy/bin/rails +4 -0
- data/spec/integration/tmp/dummy/bin/rake +4 -0
- data/spec/integration/tmp/dummy/bin/setup +34 -0
- data/spec/integration/tmp/dummy/bin/update +29 -0
- data/spec/integration/tmp/dummy/config.ru +5 -0
- data/spec/integration/tmp/dummy/config/application.rb +26 -0
- data/spec/integration/tmp/dummy/config/boot.rb +4 -0
- data/spec/integration/tmp/dummy/config/cable.yml +9 -0
- data/spec/integration/tmp/dummy/config/environment.rb +6 -0
- data/spec/integration/tmp/dummy/config/environments/development.rb +52 -0
- data/spec/integration/tmp/dummy/config/environments/production.rb +84 -0
- data/spec/integration/tmp/dummy/config/environments/test.rb +43 -0
- data/spec/integration/tmp/dummy/config/initializers/application_controller_renderer.rb +9 -0
- data/spec/integration/tmp/dummy/config/initializers/assets.rb +12 -0
- data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +6 -0
- data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/inflections.rb +17 -0
- data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/spec/integration/tmp/dummy/config/initializers/session_store.rb +4 -0
- data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
- data/spec/integration/tmp/dummy/config/puma.rb +48 -0
- data/spec/integration/tmp/dummy/config/routes.rb +9 -0
- data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
- data/spec/integration/tmp/dummy/db/seeds.rb +8 -0
- data/spec/integration/tmp/dummy/log/test.log +0 -0
- data/spec/integration/tmp/dummy/public/404.html +67 -0
- data/spec/integration/tmp/dummy/public/422.html +67 -0
- data/spec/integration/tmp/dummy/public/500.html +66 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon.png +0 -0
- data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
- data/spec/integration/tmp/dummy/public/robots.txt +5 -0
- data/spec/integration/tmp/dummy/test/test_helper.rb +8 -0
- data/spec/support/jazz.rb +6 -0
- data/spec/support/star_wars/schema.rb +1 -2
- metadata +171 -6
- data/spec/integration/tmp/app/graphql/types/bird_type.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bc1010a9a70360cc6b3d6dcb13db9d35c207179686f503d01d5904c2655f9ae2
|
|
4
|
+
data.tar.gz: 510f939a5c9dd9b1a66610e7d526aa84e9ae2b494f76a9a07973aef0b24e6cde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bebd9ccec78e0ba8e2d2a13d351a33e9a169261faf954bc50710f80c4ad6d555e495761681e0691213a9ed409b3057f77118f91e73e577fdd988759e4c7eb743
|
|
7
|
+
data.tar.gz: d458a432047fa847b775f8ff5558084eb2cc845a5277d4f47643483de5f9049d2b19c8c7831058c79b28bc962000d8f13c5201a7396169fa81bf080be8f12e18
|
|
@@ -13,6 +13,7 @@ module Graphql
|
|
|
13
13
|
# - graphql/
|
|
14
14
|
# - resolvers/
|
|
15
15
|
# - types/
|
|
16
|
+
# - base_field.rb
|
|
16
17
|
# - base_enum.rb
|
|
17
18
|
# - base_input_object.rb
|
|
18
19
|
# - base_interface.rb
|
|
@@ -93,7 +94,7 @@ module Graphql
|
|
|
93
94
|
create_dir("#{options[:directory]}/types")
|
|
94
95
|
template("schema.erb", schema_file_path)
|
|
95
96
|
|
|
96
|
-
["base_object", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type|
|
|
97
|
+
["base_object", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type|
|
|
97
98
|
template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb")
|
|
98
99
|
end
|
|
99
100
|
|
|
@@ -220,8 +220,11 @@ module GraphQL
|
|
|
220
220
|
|
|
221
221
|
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
|
|
222
222
|
def argument_definition
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
@argument_definitions.last
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# @return [GraphQL::Argument, nil] The previous GraphQL argument
|
|
227
|
+
def previous_argument_definition
|
|
225
228
|
@argument_definitions[-2]
|
|
226
229
|
end
|
|
227
230
|
|
|
@@ -175,7 +175,11 @@ module GraphQL
|
|
|
175
175
|
schema = multiplex.schema
|
|
176
176
|
multiplex_analyzers = schema.multiplex_analyzers
|
|
177
177
|
if multiplex.max_complexity
|
|
178
|
-
multiplex_analyzers +=
|
|
178
|
+
multiplex_analyzers += if schema.using_ast_analysis?
|
|
179
|
+
[GraphQL::Analysis::AST::MaxQueryComplexity]
|
|
180
|
+
else
|
|
181
|
+
[GraphQL::Analysis::MaxQueryComplexity.new(multiplex.max_complexity)]
|
|
182
|
+
end
|
|
179
183
|
end
|
|
180
184
|
|
|
181
185
|
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
data/lib/graphql/query.rb
CHANGED
|
@@ -44,7 +44,12 @@ module GraphQL
|
|
|
44
44
|
|
|
45
45
|
# @return [GraphQL::Language::Nodes::Document]
|
|
46
46
|
def document
|
|
47
|
-
|
|
47
|
+
# It's ok if this hasn't been assigned yet
|
|
48
|
+
if @query_string || @document
|
|
49
|
+
with_prepared_ast { @document }
|
|
50
|
+
else
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def inspect
|
|
@@ -13,7 +13,7 @@ module GraphQL
|
|
|
13
13
|
sliced_nodes.count > first
|
|
14
14
|
elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && before
|
|
15
15
|
# The original array is longer than the `before` index
|
|
16
|
-
index_from_cursor(before) < nodes.length
|
|
16
|
+
index_from_cursor(before) < nodes.length + 1
|
|
17
17
|
else
|
|
18
18
|
false
|
|
19
19
|
end
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -11,6 +11,7 @@ require "graphql/schema/middleware_chain"
|
|
|
11
11
|
require "graphql/schema/null_mask"
|
|
12
12
|
require "graphql/schema/possible_types"
|
|
13
13
|
require "graphql/schema/rescue_middleware"
|
|
14
|
+
require "graphql/schema/timeout"
|
|
14
15
|
require "graphql/schema/timeout_middleware"
|
|
15
16
|
require "graphql/schema/traversal"
|
|
16
17
|
require "graphql/schema/type_expression"
|
|
@@ -90,6 +91,7 @@ module GraphQL
|
|
|
90
91
|
:object_from_id, :id_from_object,
|
|
91
92
|
:default_mask,
|
|
92
93
|
:cursor_encoder,
|
|
94
|
+
disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
|
|
93
95
|
directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } },
|
|
94
96
|
directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
|
|
95
97
|
instrument: ->(schema, type, instrumenter, after_built_ins: false) {
|
|
@@ -110,6 +112,8 @@ module GraphQL
|
|
|
110
112
|
rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block) },
|
|
111
113
|
tracer: ->(schema, tracer) { schema.tracers.push(tracer) }
|
|
112
114
|
|
|
115
|
+
ensure_defined :introspection_system
|
|
116
|
+
|
|
113
117
|
attr_accessor \
|
|
114
118
|
:query, :mutation, :subscription,
|
|
115
119
|
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
|
|
@@ -140,6 +144,9 @@ module GraphQL
|
|
|
140
144
|
# @return [Class] Instantiated for each query
|
|
141
145
|
attr_accessor :context_class
|
|
142
146
|
|
|
147
|
+
# [Boolean] True if this object disables the introspection entry point fields
|
|
148
|
+
attr_accessor :disable_introspection_entry_points
|
|
149
|
+
|
|
143
150
|
class << self
|
|
144
151
|
attr_writer :default_execution_strategy
|
|
145
152
|
end
|
|
@@ -186,6 +193,7 @@ module GraphQL
|
|
|
186
193
|
@introspection_system = nil
|
|
187
194
|
@interpreter = false
|
|
188
195
|
@error_bubbling = false
|
|
196
|
+
@disable_introspection_entry_points = false
|
|
189
197
|
end
|
|
190
198
|
|
|
191
199
|
# @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
|
|
@@ -712,7 +720,8 @@ module GraphQL
|
|
|
712
720
|
:subscriptions,
|
|
713
721
|
:union_memberships,
|
|
714
722
|
:get_field, :root_types, :references_to, :type_from_ast,
|
|
715
|
-
:possible_types
|
|
723
|
+
:possible_types,
|
|
724
|
+
:disable_introspection_entry_points=
|
|
716
725
|
|
|
717
726
|
def graphql_definition
|
|
718
727
|
@graphql_definition ||= to_graphql
|
|
@@ -737,6 +746,7 @@ module GraphQL
|
|
|
737
746
|
schema_defn.max_depth = max_depth
|
|
738
747
|
schema_defn.default_max_page_size = default_max_page_size
|
|
739
748
|
schema_defn.orphan_types = orphan_types
|
|
749
|
+
schema_defn.disable_introspection_entry_points = @disable_introspection_entry_points
|
|
740
750
|
|
|
741
751
|
prepped_dirs = {}
|
|
742
752
|
directives.each { |k, v| prepped_dirs[k] = v.graphql_definition}
|
|
@@ -887,6 +897,10 @@ module GraphQL
|
|
|
887
897
|
end
|
|
888
898
|
end
|
|
889
899
|
|
|
900
|
+
def disable_introspection_entry_points
|
|
901
|
+
@disable_introspection_entry_points = true
|
|
902
|
+
end
|
|
903
|
+
|
|
890
904
|
def orphan_types(*new_orphan_types)
|
|
891
905
|
if new_orphan_types.any?
|
|
892
906
|
@orphan_types = new_orphan_types.flatten
|
|
@@ -21,6 +21,9 @@ module GraphQL
|
|
|
21
21
|
# @return [Symbol] This argument's name in Ruby keyword arguments
|
|
22
22
|
attr_reader :keyword
|
|
23
23
|
|
|
24
|
+
# @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load
|
|
25
|
+
attr_reader :loads
|
|
26
|
+
|
|
24
27
|
# @param arg_name [Symbol]
|
|
25
28
|
# @param type_expr
|
|
26
29
|
# @param desc [String]
|
|
@@ -30,7 +33,7 @@ module GraphQL
|
|
|
30
33
|
# @param as [Symbol] Override the keyword name when passed to a method
|
|
31
34
|
# @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution
|
|
32
35
|
# @param camelize [Boolean] if true, the name will be camelized when building the schema
|
|
33
|
-
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, description: nil, default_value: NO_DEFAULT, as: nil, camelize: true, prepare: nil, owner:, &definition_block)
|
|
36
|
+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, default_value: NO_DEFAULT, as: nil, camelize: true, prepare: nil, owner:, &definition_block)
|
|
34
37
|
arg_name ||= name
|
|
35
38
|
name_str = camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s
|
|
36
39
|
@name = name_str.freeze
|
|
@@ -40,6 +43,7 @@ module GraphQL
|
|
|
40
43
|
@default_value = default_value
|
|
41
44
|
@owner = owner
|
|
42
45
|
@as = as
|
|
46
|
+
@loads = loads
|
|
43
47
|
@keyword = as || Schema::Member::BuildType.underscore(@name).to_sym
|
|
44
48
|
@prepare = prepare
|
|
45
49
|
|
|
@@ -21,6 +21,17 @@ module GraphQL
|
|
|
21
21
|
self.class.arguments.each do |name, arg_defn|
|
|
22
22
|
@arguments_by_keyword[arg_defn.keyword] = arg_defn
|
|
23
23
|
ruby_kwargs_key = arg_defn.keyword
|
|
24
|
+
loads = arg_defn.loads
|
|
25
|
+
|
|
26
|
+
if @ruby_style_hash.key?(ruby_kwargs_key) && loads
|
|
27
|
+
value = @ruby_style_hash[ruby_kwargs_key]
|
|
28
|
+
@ruby_style_hash[ruby_kwargs_key] = if arg_defn.type.list?
|
|
29
|
+
GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(arg_defn, loads, val) })
|
|
30
|
+
else
|
|
31
|
+
load_application_object(arg_defn, loads, value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
24
35
|
if @ruby_style_hash.key?(ruby_kwargs_key) && arg_defn.prepare
|
|
25
36
|
@ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
|
|
26
37
|
end
|
|
@@ -42,6 +53,10 @@ module GraphQL
|
|
|
42
53
|
end
|
|
43
54
|
end
|
|
44
55
|
|
|
56
|
+
def to_hash
|
|
57
|
+
to_h
|
|
58
|
+
end
|
|
59
|
+
|
|
45
60
|
def unwrap_value(value)
|
|
46
61
|
case value
|
|
47
62
|
when Array
|
|
@@ -83,20 +98,14 @@ module GraphQL
|
|
|
83
98
|
# @return [Class<GraphQL::Arguments>]
|
|
84
99
|
attr_accessor :arguments_class
|
|
85
100
|
|
|
86
|
-
def argument(
|
|
87
|
-
|
|
101
|
+
def argument(*args, **kwargs, &block)
|
|
102
|
+
# Translate `loads:` to `as:` if needed`
|
|
103
|
+
*args, kwargs = argument_with_loads(*args, **kwargs, &block)
|
|
104
|
+
argument_defn = super(*args, **kwargs, &block)
|
|
88
105
|
# Add a method access
|
|
89
106
|
method_name = argument_defn.keyword
|
|
90
107
|
define_method(method_name) do
|
|
91
|
-
|
|
92
|
-
argument = @arguments_by_keyword[method_name]
|
|
93
|
-
if loads && argument_defn.type.list?
|
|
94
|
-
GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(argument, loads, val) })
|
|
95
|
-
elsif loads
|
|
96
|
-
load_application_object(argument, loads, value)
|
|
97
|
-
else
|
|
98
|
-
value
|
|
99
|
-
end
|
|
108
|
+
self[method_name]
|
|
100
109
|
end
|
|
101
110
|
end
|
|
102
111
|
|
|
@@ -122,4 +131,4 @@ module GraphQL
|
|
|
122
131
|
end
|
|
123
132
|
end
|
|
124
133
|
end
|
|
125
|
-
end
|
|
134
|
+
end
|
|
@@ -18,7 +18,12 @@ module GraphQL
|
|
|
18
18
|
@input_value_type = load_constant(:InputValueType).to_graphql
|
|
19
19
|
@type_kind_enum = load_constant(:TypeKindEnum).to_graphql
|
|
20
20
|
@directive_location_enum = load_constant(:DirectiveLocationEnum).to_graphql
|
|
21
|
-
@entry_point_fields =
|
|
21
|
+
@entry_point_fields =
|
|
22
|
+
if schema.disable_introspection_entry_points
|
|
23
|
+
{}
|
|
24
|
+
else
|
|
25
|
+
get_fields_from_class(class_sym: :EntryPoints)
|
|
26
|
+
end
|
|
22
27
|
@dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
|
|
23
28
|
end
|
|
24
29
|
|
|
@@ -13,8 +13,10 @@ module GraphQL
|
|
|
13
13
|
cls.include(ArgumentObjectLoader)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def argument_with_loads(
|
|
16
|
+
def argument_with_loads(*args, **kwargs)
|
|
17
|
+
loads = kwargs[:loads]
|
|
17
18
|
if loads
|
|
19
|
+
name = args[0]
|
|
18
20
|
name_as_string = name.to_s
|
|
19
21
|
|
|
20
22
|
inferred_arg_name = case name_as_string
|
|
@@ -31,7 +33,7 @@ module GraphQL
|
|
|
31
33
|
kwargs[:as] ||= inferred_arg_name
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
return [
|
|
36
|
+
return [*args, **kwargs]
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
# @see {GraphQL::Schema::Argument#initialize} for parameters
|
|
@@ -255,8 +255,11 @@ module GraphQL
|
|
|
255
255
|
# also add some preparation hook methods which will be used for this argument
|
|
256
256
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
|
257
257
|
def argument(name, type, *rest, loads: nil, **kwargs, &block)
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
*args, kwargs = argument_with_loads(name, type, *rest, loads: loads, **kwargs, &block)
|
|
259
|
+
# Short-circuit the InputObject's own `loads:` implementation
|
|
260
|
+
# so that we can support `#load_{x}` methods below.
|
|
261
|
+
kwargs.delete(:loads)
|
|
262
|
+
arg_defn = super(*args, **kwargs)
|
|
260
263
|
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
|
261
264
|
|
|
262
265
|
if loads && arg_defn.type.list?
|
|
@@ -264,7 +267,9 @@ module GraphQL
|
|
|
264
267
|
def load_#{arg_defn.keyword}(values)
|
|
265
268
|
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
|
266
269
|
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
|
267
|
-
|
|
270
|
+
context.schema.after_lazy(values) do |values2|
|
|
271
|
+
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value) })
|
|
272
|
+
end
|
|
268
273
|
end
|
|
269
274
|
RUBY
|
|
270
275
|
elsif loads
|
|
@@ -92,6 +92,28 @@ module GraphQL
|
|
|
92
92
|
def unsubscribe
|
|
93
93
|
raise UnsubscribedError
|
|
94
94
|
end
|
|
95
|
+
|
|
96
|
+
# Call this method to provide a new subscription_scope; OR
|
|
97
|
+
# call it without an argument to get the subscription_scope
|
|
98
|
+
# @param new_scope [Symbol]
|
|
99
|
+
# @return [Symbol]
|
|
100
|
+
READING_SCOPE = ::Object.new
|
|
101
|
+
def self.subscription_scope(new_scope = READING_SCOPE)
|
|
102
|
+
if new_scope != READING_SCOPE
|
|
103
|
+
@subscription_scope = new_scope
|
|
104
|
+
elsif defined?(@subscription_scope)
|
|
105
|
+
@subscription_scope
|
|
106
|
+
else
|
|
107
|
+
find_inherited_method(:subscription_scope, nil)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Overriding Resolver#field_options to include subscription_scope
|
|
112
|
+
def self.field_options
|
|
113
|
+
super.merge(
|
|
114
|
+
subscription_scope: subscription_scope
|
|
115
|
+
)
|
|
116
|
+
end
|
|
95
117
|
end
|
|
96
118
|
end
|
|
97
119
|
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
class Schema
|
|
5
|
+
# This plugin will stop resolving new fields after `max_seconds` have elapsed.
|
|
6
|
+
# After the time has passed, any remaining fields will be `nil`, with errors added
|
|
7
|
+
# to the `errors` key. Any already-resolved fields will be in the `data` key, so
|
|
8
|
+
# you'll get a partial response.
|
|
9
|
+
#
|
|
10
|
+
# You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method
|
|
11
|
+
# to provide custom logic when a timeout error occurs.
|
|
12
|
+
#
|
|
13
|
+
# Note that this will stop a query _in between_ field resolutions, but
|
|
14
|
+
# it doesn't interrupt long-running `resolve` functions. Be sure to use
|
|
15
|
+
# timeout options for external connections. For more info, see
|
|
16
|
+
# www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/
|
|
17
|
+
#
|
|
18
|
+
# @example Stop resolving fields after 2 seconds
|
|
19
|
+
# class MySchema < GraphQL::Schema
|
|
20
|
+
# use GraphQL::Schema::Timeout, max_seconds: 2
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Notifying Bugsnag and logging a timeout
|
|
24
|
+
# class MyTimeout < GraphQL::Schema::Timeout
|
|
25
|
+
# def handle_timeout(error, query)
|
|
26
|
+
# Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}")
|
|
27
|
+
# Bugsnag.notify(error, {query_string: query.query_string})
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# class MySchema < GraphQL::Schema
|
|
32
|
+
# use MyTimeout, max_seconds: 2
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
class Timeout
|
|
36
|
+
attr_reader :max_seconds
|
|
37
|
+
|
|
38
|
+
def self.use(schema, **options)
|
|
39
|
+
tracer = new(**options)
|
|
40
|
+
schema.tracer(tracer)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
|
44
|
+
def initialize(max_seconds:)
|
|
45
|
+
@max_seconds = max_seconds
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def trace(key, data)
|
|
49
|
+
case key
|
|
50
|
+
when 'execute_multiplex'
|
|
51
|
+
timeout_state = {
|
|
52
|
+
timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
|
|
53
|
+
timed_out: false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
data.fetch(:multiplex).queries.each do |query|
|
|
57
|
+
query.context.namespace(self.class)[:state] = timeout_state
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
yield
|
|
61
|
+
when 'execute_field', 'execute_field_lazy'
|
|
62
|
+
query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
|
|
63
|
+
timeout_state = query.context.namespace(self.class).fetch(:state)
|
|
64
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
|
65
|
+
error = if data[:context]
|
|
66
|
+
context = data.fetch(:context)
|
|
67
|
+
GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
|
|
68
|
+
else
|
|
69
|
+
field = data.fetch(:field)
|
|
70
|
+
GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Only invoke the timeout callback for the first timeout
|
|
74
|
+
unless timeout_state[:timed_out]
|
|
75
|
+
timeout_state[:timed_out] = true
|
|
76
|
+
handle_timeout(error, query)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
error
|
|
80
|
+
else
|
|
81
|
+
yield
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
yield
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Invoked when a query times out.
|
|
89
|
+
# @param error [GraphQL::Schema::Timeout::TimeoutError]
|
|
90
|
+
# @param query [GraphQL::Error]
|
|
91
|
+
def handle_timeout(error, query)
|
|
92
|
+
# override to do something interesting
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# This error is raised when a query exceeds `max_seconds`.
|
|
96
|
+
# Since it's a child of {GraphQL::ExecutionError},
|
|
97
|
+
# its message will be added to the response's `errors` key.
|
|
98
|
+
#
|
|
99
|
+
# To raise an error that will stop query resolution, use a custom block
|
|
100
|
+
# to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError},
|
|
101
|
+
# such as `RuntimeError`.
|
|
102
|
+
class TimeoutError < GraphQL::ExecutionError
|
|
103
|
+
def initialize(parent_type, field)
|
|
104
|
+
super("Timeout on #{parent_type.graphql_name}.#{field.graphql_name}")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|