graphql 1.11.10 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +5 -5
- data/lib/generators/graphql/relay_generator.rb +63 -0
- data/lib/generators/graphql/templates/base_connection.erb +8 -0
- data/lib/generators/graphql/templates/base_edge.erb +8 -0
- data/lib/generators/graphql/templates/node_type.erb +9 -0
- data/lib/generators/graphql/templates/object.erb +1 -1
- data/lib/generators/graphql/templates/query_type.erb +1 -3
- data/lib/generators/graphql/templates/schema.erb +8 -35
- data/lib/graphql/analysis/analyze_query.rb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +9 -1
- data/lib/graphql/analysis/ast.rb +11 -2
- data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
- data/lib/graphql/backtrace/table.rb +22 -2
- data/lib/graphql/backtrace/tracer.rb +40 -9
- data/lib/graphql/backtrace.rb +28 -19
- data/lib/graphql/backwards_compatibility.rb +1 -0
- data/lib/graphql/compatibility/execution_specification.rb +1 -0
- data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
- data/lib/graphql/dataloader/null_dataloader.rb +21 -0
- data/lib/graphql/dataloader/request.rb +24 -0
- data/lib/graphql/dataloader/request_all.rb +22 -0
- data/lib/graphql/dataloader/source.rb +93 -0
- data/lib/graphql/dataloader.rb +197 -0
- data/lib/graphql/define/assign_global_id_field.rb +1 -1
- data/lib/graphql/define/instance_definable.rb +32 -2
- data/lib/graphql/define/type_definer.rb +5 -5
- data/lib/graphql/deprecated_dsl.rb +5 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/errors.rb +4 -0
- data/lib/graphql/execution/execute.rb +7 -0
- data/lib/graphql/execution/interpreter/arguments.rb +51 -14
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
- data/lib/graphql/execution/interpreter/runtime.rb +210 -124
- data/lib/graphql/execution/interpreter.rb +10 -6
- data/lib/graphql/execution/multiplex.rb +20 -6
- data/lib/graphql/function.rb +4 -0
- data/lib/graphql/input_object_type.rb +2 -0
- data/lib/graphql/interface_type.rb +3 -1
- data/lib/graphql/language/document_from_schema_definition.rb +50 -23
- data/lib/graphql/object_type.rb +2 -0
- data/lib/graphql/pagination/connection.rb +5 -1
- data/lib/graphql/pagination/connections.rb +6 -16
- data/lib/graphql/query/context.rb +4 -0
- data/lib/graphql/query/serial_execution.rb +1 -0
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query.rb +2 -0
- data/lib/graphql/relay/base_connection.rb +7 -0
- data/lib/graphql/relay/connection_instrumentation.rb +4 -4
- data/lib/graphql/relay/connection_type.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -0
- data/lib/graphql/relay/node.rb +3 -0
- data/lib/graphql/relay/type_extensions.rb +2 -0
- data/lib/graphql/scalar_type.rb +2 -0
- data/lib/graphql/schema/argument.rb +25 -7
- data/lib/graphql/schema/build_from_definition.rb +139 -51
- data/lib/graphql/schema/directive/flagged.rb +57 -0
- data/lib/graphql/schema/directive.rb +76 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +12 -6
- data/lib/graphql/schema/field/connection_extension.rb +3 -2
- data/lib/graphql/schema/field.rb +28 -9
- data/lib/graphql/schema/input_object.rb +33 -22
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +3 -3
- data/lib/graphql/schema/member/has_arguments.rb +24 -6
- data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
- data/lib/graphql/schema/member/has_directives.rb +98 -0
- data/lib/graphql/schema/member/has_validators.rb +31 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member.rb +4 -0
- data/lib/graphql/schema/object.rb +11 -0
- data/lib/graphql/schema/printer.rb +5 -4
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
- data/lib/graphql/schema/resolver.rb +7 -0
- data/lib/graphql/schema/subscription.rb +19 -1
- data/lib/graphql/schema/timeout_middleware.rb +2 -0
- data/lib/graphql/schema/validation.rb +2 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
- data/lib/graphql/schema/validator/format_validator.rb +49 -0
- data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
- data/lib/graphql/schema/validator/length_validator.rb +57 -0
- data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
- data/lib/graphql/schema/validator/required_validator.rb +68 -0
- data/lib/graphql/schema/validator.rb +163 -0
- data/lib/graphql/schema.rb +72 -49
- data/lib/graphql/static_validation/base_visitor.rb +0 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +1 -6
- data/lib/graphql/static_validation/validator.rb +12 -14
- data/lib/graphql/subscriptions.rb +17 -20
- data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
- data/lib/graphql/tracing/platform_tracing.rb +3 -1
- data/lib/graphql/tracing/skylight_tracing.rb +1 -1
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/types/relay/base_connection.rb +2 -92
- data/lib/graphql/types/relay/base_edge.rb +2 -35
- data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
- data/lib/graphql/types/relay/default_relay.rb +27 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
- data/lib/graphql/types/relay/has_node_field.rb +41 -0
- data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
- data/lib/graphql/types/relay/node.rb +2 -4
- data/lib/graphql/types/relay/node_behaviors.rb +15 -0
- data/lib/graphql/types/relay/node_field.rb +1 -19
- data/lib/graphql/types/relay/nodes_field.rb +1 -19
- data/lib/graphql/types/relay/page_info.rb +2 -14
- data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
- data/lib/graphql/types/relay.rb +11 -3
- data/lib/graphql/union_type.rb +2 -0
- data/lib/graphql/upgrader/member.rb +1 -0
- data/lib/graphql/upgrader/schema.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +38 -4
- metadata +31 -6
- data/lib/graphql/types/relay/base_field.rb +0 -22
- data/lib/graphql/types/relay/base_interface.rb +0 -29
- data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -193,26 +193,26 @@ module GraphQL
|
|
193
193
|
if node1.name != node2.name
|
194
194
|
errored_nodes = [node1.name, node2.name].sort.join(" or ")
|
195
195
|
msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
|
196
|
-
|
196
|
+
context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
|
197
197
|
msg,
|
198
198
|
nodes: [node1, node2],
|
199
199
|
path: [],
|
200
200
|
field_name: response_key,
|
201
201
|
conflicts: errored_nodes
|
202
|
-
)
|
202
|
+
)
|
203
203
|
end
|
204
204
|
|
205
205
|
if !same_arguments?(node1, node2)
|
206
206
|
args = [serialize_field_args(node1), serialize_field_args(node2)]
|
207
207
|
conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
|
208
208
|
msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
|
209
|
-
|
209
|
+
context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
|
210
210
|
msg,
|
211
211
|
nodes: [node1, node2],
|
212
212
|
path: [],
|
213
213
|
field_name: response_key,
|
214
214
|
conflicts: conflicts
|
215
|
-
)
|
215
|
+
)
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
@@ -7,12 +7,12 @@ module GraphQL
|
|
7
7
|
dependency_map = context.dependencies
|
8
8
|
dependency_map.cyclical_definitions.each do |defn|
|
9
9
|
if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
10
|
-
|
10
|
+
context.errors << GraphQL::StaticValidation::FragmentsAreFiniteError.new(
|
11
11
|
"Fragment #{defn.name} contains an infinite loop",
|
12
12
|
nodes: defn.node,
|
13
13
|
path: defn.path,
|
14
14
|
name: defn.name
|
15
|
-
)
|
15
|
+
)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -19,11 +19,10 @@ module GraphQL
|
|
19
19
|
|
20
20
|
def_delegators :@query, :schema, :document, :fragments, :operations, :warden
|
21
21
|
|
22
|
-
def initialize(query, visitor_class
|
22
|
+
def initialize(query, visitor_class)
|
23
23
|
@query = query
|
24
24
|
@literal_validator = LiteralValidator.new(context: query.context)
|
25
25
|
@errors = []
|
26
|
-
@max_errors = max_errors || Float::INFINITY
|
27
26
|
@on_dependency_resolve_handlers = []
|
28
27
|
@visitor = visitor_class.new(document, self)
|
29
28
|
end
|
@@ -39,10 +38,6 @@ module GraphQL
|
|
39
38
|
def validate_literal(ast_value, type)
|
40
39
|
@literal_validator.validate(ast_value, type)
|
41
40
|
end
|
42
|
-
|
43
|
-
def too_many_errors?
|
44
|
-
@errors.length >= @max_errors
|
45
|
-
end
|
46
41
|
end
|
47
42
|
end
|
48
43
|
end
|
@@ -22,9 +22,8 @@ module GraphQL
|
|
22
22
|
# @param query [GraphQL::Query]
|
23
23
|
# @param validate [Boolean]
|
24
24
|
# @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
|
25
|
-
# @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
|
26
25
|
# @return [Array<Hash>]
|
27
|
-
def validate(query, validate: true, timeout: nil
|
26
|
+
def validate(query, validate: true, timeout: nil)
|
28
27
|
query.trace("validate", { validate: validate, query: query }) do
|
29
28
|
can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
|
30
29
|
errors = if validate == false && can_skip_rewrite
|
@@ -33,26 +32,25 @@ module GraphQL
|
|
33
32
|
rules_to_use = validate ? @rules : []
|
34
33
|
visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
|
35
34
|
|
36
|
-
context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class
|
35
|
+
context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
|
37
36
|
|
38
37
|
begin
|
39
38
|
# CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
|
40
39
|
# A timeout value of 0 or nil will execute the block without any timeout.
|
41
40
|
Timeout::timeout(timeout) do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
41
|
+
# Attach legacy-style rules.
|
42
|
+
# Only loop through rules if it has legacy-style rules
|
43
|
+
unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
|
44
|
+
legacy_rules.each do |rule_class_or_module|
|
45
|
+
if rule_class_or_module.method_defined?(:validate)
|
46
|
+
warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
|
47
|
+
warn " -> Legacy validator: #{rule_class_or_module}"
|
48
|
+
rule_class_or_module.new.validate(context)
|
51
49
|
end
|
52
50
|
end
|
53
|
-
|
54
|
-
context.visitor.visit
|
55
51
|
end
|
52
|
+
|
53
|
+
context.visitor.visit
|
56
54
|
end
|
57
55
|
rescue Timeout::Error
|
58
56
|
handle_timeout(query, context)
|
@@ -26,7 +26,9 @@ module GraphQL
|
|
26
26
|
|
27
27
|
instrumentation = Subscriptions::Instrumentation.new(schema: schema)
|
28
28
|
defn.instrument(:query, instrumentation)
|
29
|
-
|
29
|
+
if !schema.is_a?(Class)
|
30
|
+
defn.instrument(:field, instrumentation)
|
31
|
+
end
|
30
32
|
options[:schema] = schema
|
31
33
|
schema.subscriptions = self.new(**options)
|
32
34
|
schema.add_subscription_extension_if_necessary
|
@@ -107,31 +109,26 @@ module GraphQL
|
|
107
109
|
variables = query_data.fetch(:variables)
|
108
110
|
context = query_data.fetch(:context)
|
109
111
|
operation_name = query_data.fetch(:operation_name)
|
110
|
-
result =
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
context: context,
|
122
|
-
subscription_topic: event.topic,
|
123
|
-
operation_name: operation_name,
|
124
|
-
variables: variables,
|
125
|
-
root_value: object,
|
126
|
-
)
|
127
|
-
end
|
128
|
-
unsubscribed = false
|
112
|
+
result = @schema.execute(
|
113
|
+
query: query_string,
|
114
|
+
context: context,
|
115
|
+
subscription_topic: event.topic,
|
116
|
+
operation_name: operation_name,
|
117
|
+
variables: variables,
|
118
|
+
root_value: object,
|
119
|
+
)
|
120
|
+
subscriptions_context = result.context.namespace(:subscriptions)
|
121
|
+
if subscriptions_context[:no_update]
|
122
|
+
result = nil
|
129
123
|
end
|
130
124
|
|
125
|
+
unsubscribed = subscriptions_context[:unsubscribed]
|
126
|
+
|
131
127
|
if unsubscribed
|
132
128
|
# `unsubscribe` was called, clean up on our side
|
133
129
|
# TODO also send `{more: false}` to client?
|
134
130
|
delete_subscription(subscription_id)
|
131
|
+
result = nil
|
135
132
|
end
|
136
133
|
|
137
134
|
result
|
@@ -57,7 +57,7 @@ module GraphQL
|
|
57
57
|
def platform_field_key(type, field)
|
58
58
|
"graphql.#{type.graphql_name}.#{field.graphql_name}"
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
def platform_authorized_key(type)
|
62
62
|
"graphql.authorized.#{type.graphql_name}"
|
63
63
|
end
|
@@ -112,6 +112,8 @@ module GraphQL
|
|
112
112
|
graphql_query_string(data[key])
|
113
113
|
when :multiplex
|
114
114
|
graphql_multiplex(data[key])
|
115
|
+
when :path
|
116
|
+
[key, data[key].join(".")]
|
115
117
|
else
|
116
118
|
[key, data[key]]
|
117
119
|
end
|
@@ -96,7 +96,9 @@ module GraphQL
|
|
96
96
|
|
97
97
|
def self.use(schema_defn, options = {})
|
98
98
|
tracer = self.new(**options)
|
99
|
-
schema_defn.
|
99
|
+
if !schema_defn.is_a?(Class)
|
100
|
+
schema_defn.instrument(:field, tracer)
|
101
|
+
end
|
100
102
|
schema_defn.tracer(tracer)
|
101
103
|
end
|
102
104
|
|
@@ -18,7 +18,7 @@ module GraphQL
|
|
18
18
|
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
|
19
19
|
# It can also be specified per-query with `context[:set_skylight_endpoint_name]`.
|
20
20
|
def initialize(options = {})
|
21
|
-
warn("GraphQL::Tracing::SkylightTracing is deprecated, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.")
|
21
|
+
warn("GraphQL::Tracing::SkylightTracing is deprecated and will be removed in GraphQL-Ruby 2.0, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.")
|
22
22
|
@set_endpoint_name = options.fetch(:set_endpoint_name, false)
|
23
23
|
super
|
24
24
|
end
|
data/lib/graphql/tracing.rb
CHANGED
@@ -42,8 +42,8 @@ module GraphQL
|
|
42
42
|
# execute_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }`
|
43
43
|
# execute_query | `{ query: GraphQL::Query }`
|
44
44
|
# execute_query_lazy | `{ query: GraphQL::Query?, multiplex: GraphQL::Execution::Multiplex? }`
|
45
|
-
# execute_field | `{
|
46
|
-
# execute_field_lazy | `{
|
45
|
+
# execute_field | `{ owner: Class, field: GraphQL::Schema::Field, query: GraphQL::Query, path: Array<String, Integer>, ast_node: GraphQL::Language::Nodes::Field}`
|
46
|
+
# execute_field_lazy | `{ owner: Class, field: GraphQL::Schema::Field, query: GraphQL::Query, path: Array<String, Integer>, ast_node: GraphQL::Language::Nodes::Field}`
|
47
47
|
# authorized | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
|
48
48
|
# authorized_lazy | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
|
49
49
|
# resolve_type | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
|
@@ -27,98 +27,8 @@ module GraphQL
|
|
27
27
|
# end
|
28
28
|
#
|
29
29
|
# @see Relay::BaseEdge for edge types
|
30
|
-
class BaseConnection <
|
31
|
-
|
32
|
-
def_delegators :@object, :cursor_from_node, :parent
|
33
|
-
|
34
|
-
class << self
|
35
|
-
# @return [Class]
|
36
|
-
attr_reader :node_type
|
37
|
-
|
38
|
-
# @return [Class]
|
39
|
-
attr_reader :edge_class
|
40
|
-
|
41
|
-
# Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
|
42
|
-
#
|
43
|
-
# This method will use the inputs to create:
|
44
|
-
# - `edges` field
|
45
|
-
# - `nodes` field
|
46
|
-
# - description
|
47
|
-
#
|
48
|
-
# It's called when you subclass this base connection, trying to use the
|
49
|
-
# class name to set defaults. You can call it again in the class definition
|
50
|
-
# to override the default (or provide a value, if the default lookup failed).
|
51
|
-
def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: true)
|
52
|
-
# Set this connection's graphql name
|
53
|
-
node_type_name = node_type.graphql_name
|
54
|
-
|
55
|
-
@node_type = node_type
|
56
|
-
@edge_type = edge_type_class
|
57
|
-
@edge_class = edge_class
|
58
|
-
|
59
|
-
field :edges, [edge_type_class, null: true],
|
60
|
-
null: true,
|
61
|
-
description: "A list of edges.",
|
62
|
-
edge_class: edge_class
|
63
|
-
|
64
|
-
define_nodes_field(node_nullable) if nodes_field
|
65
|
-
|
66
|
-
description("The connection type for #{node_type_name}.")
|
67
|
-
end
|
68
|
-
|
69
|
-
# Filter this list according to the way its node type would scope them
|
70
|
-
def scope_items(items, context)
|
71
|
-
node_type.scope_items(items, context)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Add the shortcut `nodes` field to this connection and its subclasses
|
75
|
-
def nodes_field
|
76
|
-
define_nodes_field
|
77
|
-
end
|
78
|
-
|
79
|
-
def authorized?(obj, ctx)
|
80
|
-
true # Let nodes be filtered out
|
81
|
-
end
|
82
|
-
|
83
|
-
def accessible?(ctx)
|
84
|
-
node_type.accessible?(ctx)
|
85
|
-
end
|
86
|
-
|
87
|
-
def visible?(ctx)
|
88
|
-
node_type.visible?(ctx)
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def define_nodes_field(nullable = true)
|
94
|
-
type = nullable ? [@node_type, null: true] : [@node_type]
|
95
|
-
field :nodes, type,
|
96
|
-
null: nullable,
|
97
|
-
description: "A list of nodes.",
|
98
|
-
connection: false
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
|
103
|
-
|
104
|
-
# By default this calls through to the ConnectionWrapper's edge nodes method,
|
105
|
-
# but sometimes you need to override it to support the `nodes` field
|
106
|
-
def nodes
|
107
|
-
@object.edge_nodes
|
108
|
-
end
|
109
|
-
|
110
|
-
def edges
|
111
|
-
if @object.is_a?(GraphQL::Pagination::Connection)
|
112
|
-
@object.edges
|
113
|
-
elsif context.interpreter?
|
114
|
-
context.schema.after_lazy(object.edge_nodes) do |nodes|
|
115
|
-
nodes.map { |n| self.class.edge_class.new(n, object) }
|
116
|
-
end
|
117
|
-
else
|
118
|
-
# This is done by edges_instrumentation
|
119
|
-
@object.edge_nodes
|
120
|
-
end
|
121
|
-
end
|
30
|
+
class BaseConnection < Schema::Object
|
31
|
+
include ConnectionBehaviors
|
122
32
|
end
|
123
33
|
end
|
124
34
|
end
|
@@ -21,41 +21,8 @@ module GraphQL
|
|
21
21
|
# end
|
22
22
|
#
|
23
23
|
# @see {Relay::BaseConnection} for connection types
|
24
|
-
class BaseEdge <
|
25
|
-
|
26
|
-
|
27
|
-
class << self
|
28
|
-
# Get or set the Object type that this edge wraps.
|
29
|
-
#
|
30
|
-
# @param node_type [Class] A `Schema::Object` subclass
|
31
|
-
# @param null [Boolean]
|
32
|
-
def node_type(node_type = nil, null: true)
|
33
|
-
if node_type
|
34
|
-
@node_type = node_type
|
35
|
-
# Add a default `node` field
|
36
|
-
field :node, node_type, null: null, description: "The item at the end of the edge.",
|
37
|
-
connection: false
|
38
|
-
end
|
39
|
-
@node_type
|
40
|
-
end
|
41
|
-
|
42
|
-
def authorized?(obj, ctx)
|
43
|
-
true
|
44
|
-
end
|
45
|
-
|
46
|
-
def accessible?(ctx)
|
47
|
-
node_type.accessible?(ctx)
|
48
|
-
end
|
49
|
-
|
50
|
-
def visible?(ctx)
|
51
|
-
node_type.visible?(ctx)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
field :cursor, String,
|
57
|
-
null: false,
|
58
|
-
description: "A cursor for use in pagination."
|
24
|
+
class BaseEdge < GraphQL::Schema::Object
|
25
|
+
include Types::Relay::EdgeBehaviors
|
59
26
|
end
|
60
27
|
end
|
61
28
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module ConnectionBehaviors
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@object, :cursor_from_node, :parent
|
9
|
+
|
10
|
+
def self.included(child_class)
|
11
|
+
child_class.extend(ClassMethods)
|
12
|
+
child_class.extend(Relay::DefaultRelay)
|
13
|
+
child_class.default_relay(true)
|
14
|
+
child_class.node_nullable(true)
|
15
|
+
add_page_info_field(child_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# @return [Class]
|
20
|
+
attr_reader :node_type
|
21
|
+
|
22
|
+
# @return [Class]
|
23
|
+
attr_reader :edge_class
|
24
|
+
|
25
|
+
# Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
|
26
|
+
#
|
27
|
+
# This method will use the inputs to create:
|
28
|
+
# - `edges` field
|
29
|
+
# - `nodes` field
|
30
|
+
# - description
|
31
|
+
#
|
32
|
+
# It's called when you subclass this base connection, trying to use the
|
33
|
+
# class name to set defaults. You can call it again in the class definition
|
34
|
+
# to override the default (or provide a value, if the default lookup failed).
|
35
|
+
def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: self.node_nullable)
|
36
|
+
# Set this connection's graphql name
|
37
|
+
node_type_name = node_type.graphql_name
|
38
|
+
|
39
|
+
@node_type = node_type
|
40
|
+
@edge_type = edge_type_class
|
41
|
+
@edge_class = edge_class
|
42
|
+
|
43
|
+
field :edges, [edge_type_class, null: true],
|
44
|
+
null: true,
|
45
|
+
description: "A list of edges.",
|
46
|
+
legacy_edge_class: edge_class, # This is used by the old runtime only, for EdgesInstrumentation
|
47
|
+
connection: false
|
48
|
+
|
49
|
+
define_nodes_field(node_nullable) if nodes_field
|
50
|
+
|
51
|
+
description("The connection type for #{node_type_name}.")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Filter this list according to the way its node type would scope them
|
55
|
+
def scope_items(items, context)
|
56
|
+
node_type.scope_items(items, context)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add the shortcut `nodes` field to this connection and its subclasses
|
60
|
+
def nodes_field(node_nullable: self.node_nullable)
|
61
|
+
define_nodes_field(node_nullable)
|
62
|
+
end
|
63
|
+
|
64
|
+
def authorized?(obj, ctx)
|
65
|
+
true # Let nodes be filtered out
|
66
|
+
end
|
67
|
+
|
68
|
+
def accessible?(ctx)
|
69
|
+
node_type.accessible?(ctx)
|
70
|
+
end
|
71
|
+
|
72
|
+
def visible?(ctx)
|
73
|
+
node_type.visible?(ctx)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
|
77
|
+
# Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields.
|
78
|
+
def node_nullable(new_value = nil)
|
79
|
+
if new_value.nil?
|
80
|
+
@node_nullable || superclass.node_nullable
|
81
|
+
else
|
82
|
+
@node_nullable ||= new_value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def define_nodes_field(nullable)
|
89
|
+
field :nodes, [@node_type, null: nullable],
|
90
|
+
null: nullable,
|
91
|
+
description: "A list of nodes.",
|
92
|
+
connection: false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class << self
|
97
|
+
def add_page_info_field(obj_type)
|
98
|
+
obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# By default this calls through to the ConnectionWrapper's edge nodes method,
|
103
|
+
# but sometimes you need to override it to support the `nodes` field
|
104
|
+
def nodes
|
105
|
+
@object.edge_nodes
|
106
|
+
end
|
107
|
+
|
108
|
+
def edges
|
109
|
+
if @object.is_a?(GraphQL::Pagination::Connection)
|
110
|
+
@object.edges
|
111
|
+
elsif context.interpreter?
|
112
|
+
context.schema.after_lazy(object.edge_nodes) do |nodes|
|
113
|
+
nodes.map { |n| self.class.edge_class.new(n, object) }
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# This is done by edges_instrumentation
|
117
|
+
@object.edge_nodes
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module DefaultRelay
|
7
|
+
def self.extended(child_class)
|
8
|
+
child_class.default_relay(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_relay(new_value)
|
12
|
+
@default_relay = new_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_relay?
|
16
|
+
!!@default_relay
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_graphql
|
20
|
+
type_defn = super
|
21
|
+
type_defn.default_relay = default_relay?
|
22
|
+
type_defn
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module EdgeBehaviors
|
7
|
+
def self.included(child_class)
|
8
|
+
child_class.description("An edge in a connection.")
|
9
|
+
child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
|
10
|
+
child_class.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Get or set the Object type that this edge wraps.
|
15
|
+
#
|
16
|
+
# @param node_type [Class] A `Schema::Object` subclass
|
17
|
+
# @param null [Boolean]
|
18
|
+
def node_type(node_type = nil, null: true)
|
19
|
+
if node_type
|
20
|
+
@node_type = node_type
|
21
|
+
# Add a default `node` field
|
22
|
+
field :node, node_type, null: null, description: "The item at the end of the edge.", connection: false
|
23
|
+
end
|
24
|
+
@node_type
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorized?(obj, ctx)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def accessible?(ctx)
|
32
|
+
node_type.accessible?(ctx)
|
33
|
+
end
|
34
|
+
|
35
|
+
def visible?(ctx)
|
36
|
+
node_type.visible?(ctx)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module HasNodeField
|
7
|
+
def self.included(child_class)
|
8
|
+
child_class.field(**field_options, &field_block)
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def field_options
|
13
|
+
{
|
14
|
+
name: "node",
|
15
|
+
owner: nil,
|
16
|
+
type: GraphQL::Types::Relay::Node,
|
17
|
+
null: true,
|
18
|
+
description: "Fetches an object given its ID.",
|
19
|
+
relay_node_field: true,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def field_block
|
24
|
+
Proc.new {
|
25
|
+
argument :id, "ID!", required: true,
|
26
|
+
description: "ID of the object."
|
27
|
+
|
28
|
+
def resolve(obj, args, ctx)
|
29
|
+
ctx.schema.object_from_id(args[:id], ctx)
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolve_field(obj, args, ctx)
|
33
|
+
resolve(obj, args, ctx)
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module HasNodesField
|
7
|
+
def self.included(child_class)
|
8
|
+
child_class.field(**field_options, &field_block)
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def field_options
|
13
|
+
{
|
14
|
+
name: "nodes",
|
15
|
+
owner: nil,
|
16
|
+
type: [GraphQL::Types::Relay::Node, null: true],
|
17
|
+
null: false,
|
18
|
+
description: "Fetches a list of objects given a list of IDs.",
|
19
|
+
relay_nodes_field: true,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def field_block
|
24
|
+
Proc.new {
|
25
|
+
argument :ids, "[ID!]!", required: true,
|
26
|
+
description: "IDs of the objects."
|
27
|
+
|
28
|
+
def resolve(obj, args, ctx)
|
29
|
+
args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolve_field(obj, args, ctx)
|
33
|
+
resolve(obj, args, ctx)
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -7,10 +7,8 @@ module GraphQL
|
|
7
7
|
# or you can take it as inspiration for your own implementation
|
8
8
|
# of the `Node` interface.
|
9
9
|
module Node
|
10
|
-
include
|
11
|
-
|
12
|
-
description "An object with an ID."
|
13
|
-
field(:id, ID, null: false, description: "ID of the object.")
|
10
|
+
include GraphQL::Schema::Interface
|
11
|
+
include Types::Relay::NodeBehaviors
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|