lighthouse-graphql 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/docs/authorization.md +109 -0
- data/docs/best-practices.md +75 -0
- data/docs/configuration.md +70 -0
- data/docs/custom-directives.md +129 -0
- data/docs/directives.md +298 -0
- data/docs/federation.md +108 -0
- data/docs/filtering-and-ordering.md +128 -0
- data/docs/getting-started.md +152 -0
- data/docs/relationships.md +95 -0
- data/lib/lighthouse/config.rb +112 -0
- data/lib/lighthouse/contracts/arg_builder.rb +17 -0
- data/lib/lighthouse/contracts/arg_manipulator.rb +17 -0
- data/lib/lighthouse/contracts/directive.rb +17 -0
- data/lib/lighthouse/contracts/field_manipulator.rb +17 -0
- data/lib/lighthouse/contracts/field_middleware.rb +16 -0
- data/lib/lighthouse/contracts/field_resolver.rb +18 -0
- data/lib/lighthouse/dataloader_sources/association_loader.rb +25 -0
- data/lib/lighthouse/dataloader_sources/model_loader.rb +19 -0
- data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +86 -0
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +73 -0
- data/lib/lighthouse/directive_resolvers/auth_resolver.rb +44 -0
- data/lib/lighthouse/directive_resolvers/base.rb +92 -0
- data/lib/lighthouse/directive_resolvers/belongs_to_many_resolver.rb +56 -0
- data/lib/lighthouse/directive_resolvers/belongs_to_resolver.rb +47 -0
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +63 -0
- data/lib/lighthouse/directive_resolvers/can_resolver.rb +86 -0
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +56 -0
- data/lib/lighthouse/directive_resolvers/field_resolver.rb +78 -0
- data/lib/lighthouse/directive_resolvers/find_resolver.rb +65 -0
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +53 -0
- data/lib/lighthouse/directive_resolvers/guard_resolver.rb +32 -0
- data/lib/lighthouse/directive_resolvers/has_many_resolver.rb +109 -0
- data/lib/lighthouse/directive_resolvers/has_one_resolver.rb +43 -0
- data/lib/lighthouse/directive_resolvers/method_resolver.rb +35 -0
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +147 -0
- data/lib/lighthouse/directive_resolvers/rename_resolver.rb +35 -0
- data/lib/lighthouse/directive_resolvers/resolution.rb +35 -0
- data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +169 -0
- data/lib/lighthouse/directives/arguments/base.rb +40 -0
- data/lib/lighthouse/directives/arguments/filters.rb +101 -0
- data/lib/lighthouse/directives/arguments/order_by.rb +48 -0
- data/lib/lighthouse/directives/registry.rb +168 -0
- data/lib/lighthouse/directives/relation_directive.rb +332 -0
- data/lib/lighthouse/directives/where_conditions_directive.rb +208 -0
- data/lib/lighthouse/graphql/version.rb +7 -0
- data/lib/lighthouse/graphql.rb +8 -0
- data/lib/lighthouse/rb_lighthouse.rb +88 -0
- data/lib/lighthouse/reference_resolver.rb +24 -0
- data/lib/lighthouse/schema_factory.rb +46 -0
- data/lib/lighthouse/schema_generator.rb +244 -0
- data/lib/lighthouse/schema_implementation.rb +119 -0
- data/lib/lighthouse/sdl_loader.rb +27 -0
- data/lib/lighthouse/support/authorization.rb +26 -0
- data/lib/lighthouse/support/directive_args.rb +33 -0
- data/lib/lighthouse/support/model_resolver.rb +61 -0
- data/lib/lighthouse/support/naming.rb +71 -0
- data/lib/lighthouse/where_conditions/operator_map.rb +35 -0
- data/lib/lighthouse-graphql.rb +10 -0
- metadata +195 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lighthouse
|
|
4
|
+
# Maps directive names to the classes that implement them, so the
|
|
5
|
+
# SchemaGenerator can dispatch by iterating the registry instead of a
|
|
6
|
+
# hardcoded `case` statement. Adding a directive is "write one class and
|
|
7
|
+
# register it" — no generator edits.
|
|
8
|
+
#
|
|
9
|
+
# Two registration styles are supported during the migration to clean,
|
|
10
|
+
# self-describing directive classes:
|
|
11
|
+
#
|
|
12
|
+
# register('hasMany', SomeLegacyResolverClass) # explicit name
|
|
13
|
+
# register(MyDirectiveClass) # class responds to .directive_name
|
|
14
|
+
#
|
|
15
|
+
# A registered class is classified as a field resolver when it includes
|
|
16
|
+
# Contracts::FieldResolver OR responds to the legacy `#define_resolver`
|
|
17
|
+
# instance method.
|
|
18
|
+
class DirectiveRegistry
|
|
19
|
+
class << self
|
|
20
|
+
def default
|
|
21
|
+
@default ||= new.tap(&:register_defaults)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Allow the host app to extend the default registry from an initializer.
|
|
25
|
+
def register(name_or_klass, klass = nil)
|
|
26
|
+
default.register(name_or_klass, klass)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@resolution = {}
|
|
32
|
+
@arg_builders = {}
|
|
33
|
+
@sdl_manipulators = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def register(name_or_klass, klass = nil)
|
|
37
|
+
if klass.nil? && name_or_klass.respond_to?(:directive_name)
|
|
38
|
+
klass = name_or_klass
|
|
39
|
+
name = klass.directive_name
|
|
40
|
+
else
|
|
41
|
+
name = name_or_klass
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if arg_builder?(klass)
|
|
45
|
+
@arg_builders[name.to_s] = klass
|
|
46
|
+
elsif resolver?(klass)
|
|
47
|
+
@resolution[name.to_s] = klass
|
|
48
|
+
end
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resolution_class(name)
|
|
53
|
+
@resolution[name.to_s]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Application order for wrapping/resolution directives on a field. Lower is
|
|
57
|
+
# applied first (innermost, closest to the data); higher is applied last
|
|
58
|
+
# (outermost, so it executes first). Authentication guards run outermost;
|
|
59
|
+
# @can wraps the resolver to authorize its result.
|
|
60
|
+
def resolution_order(name)
|
|
61
|
+
case name.to_s
|
|
62
|
+
when 'guard', 'auth' then 2
|
|
63
|
+
when 'can' then 1
|
|
64
|
+
else 0
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resolution_names
|
|
69
|
+
@resolution.keys
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Argument-builder directives (@eq, @neq, @in, @notIn, @like, @where,
|
|
73
|
+
# @orderBy) compose constraints onto a relation at resolve time.
|
|
74
|
+
def arg_builder_class(name)
|
|
75
|
+
@arg_builders[name.to_s]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def arg_builder_names
|
|
79
|
+
@arg_builders.keys
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# SDL `directive @name(...) on ...` definitions contributed by registered,
|
|
83
|
+
# self-describing directive classes. The generator injects these so the app
|
|
84
|
+
# does not have to hand-declare every directive it uses.
|
|
85
|
+
# Returns { "name" => "directive @name(...) on ..." }.
|
|
86
|
+
def directive_definitions
|
|
87
|
+
definitions = {}
|
|
88
|
+
(@resolution.values + @arg_builders.values).uniq.each do |klass|
|
|
89
|
+
next unless klass.respond_to?(:directive_name) && klass.respond_to?(:definition)
|
|
90
|
+
|
|
91
|
+
text = safe_definition(klass)
|
|
92
|
+
definitions[klass.directive_name.to_s] = text if text
|
|
93
|
+
end
|
|
94
|
+
definitions
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Register an SDL manipulator: any object responding to `manipulate(sdl)` and
|
|
98
|
+
# returning the (possibly transformed) SDL string. These run before the
|
|
99
|
+
# schema is built to inject Connection/Paginator types, whereConditions
|
|
100
|
+
# inputs, orderBy enums, etc. Order of registration is preserved.
|
|
101
|
+
def register_manipulator(manipulator)
|
|
102
|
+
@sdl_manipulators << manipulator unless @sdl_manipulators.include?(manipulator)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def sdl_manipulators
|
|
107
|
+
@sdl_manipulators
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Fold every registered manipulator over the SDL string.
|
|
111
|
+
def manipulate_sdl(sdl)
|
|
112
|
+
@sdl_manipulators.reduce(sdl) { |acc, manipulator| manipulator.manipulate(acc) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def register_defaults
|
|
116
|
+
dr = Lighthouse::GraphQL::DirectiveResolvers
|
|
117
|
+
{
|
|
118
|
+
'hasMany' => dr::HasManyDirectiveResolver,
|
|
119
|
+
'belongsTo' => dr::BelongsToDirectiveResolver,
|
|
120
|
+
'belongsToMany' => dr::BelongsToManyDirectiveResolver,
|
|
121
|
+
'hasOne' => dr::HasOneDirectiveResolver,
|
|
122
|
+
'all' => dr::AllDirectiveResolver,
|
|
123
|
+
'find' => dr::FindDirectiveResolver,
|
|
124
|
+
'first' => dr::FirstDirectiveResolver,
|
|
125
|
+
'paginate' => dr::PaginateDirectiveResolver,
|
|
126
|
+
'aggregate' => dr::AggregateDirectiveResolver,
|
|
127
|
+
'count' => dr::CountDirectiveResolver,
|
|
128
|
+
'field' => dr::FieldDirectiveResolver,
|
|
129
|
+
'rename' => dr::RenameDirectiveResolver,
|
|
130
|
+
'method' => dr::MethodDirectiveResolver,
|
|
131
|
+
'auth' => dr::AuthDirectiveResolver,
|
|
132
|
+
'guard' => dr::GuardDirectiveResolver,
|
|
133
|
+
'can' => dr::CanDirectiveResolver
|
|
134
|
+
}.each { |name, k| register(name, k) }
|
|
135
|
+
|
|
136
|
+
# Argument-builder filter/sort directives.
|
|
137
|
+
args = Lighthouse::GraphQL::Directives::Arguments
|
|
138
|
+
[args::Eq, args::Neq, args::In, args::NotIn, args::Like, args::Where, args::OrderBy]
|
|
139
|
+
.each { |k| register(k) }
|
|
140
|
+
|
|
141
|
+
# SDL manipulators run in registration order during the manipulation phase.
|
|
142
|
+
register_manipulator(Lighthouse::GraphQL::Directives::RelationDirective)
|
|
143
|
+
register_manipulator(Lighthouse::GraphQL::Directives::WhereConditionsDirective)
|
|
144
|
+
|
|
145
|
+
self
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def safe_definition(klass)
|
|
151
|
+
klass.definition
|
|
152
|
+
rescue StandardError
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def arg_builder?(klass)
|
|
157
|
+
klass.respond_to?(:include?) && klass.include?(Lighthouse::Contracts::ArgBuilder)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def resolver?(klass)
|
|
161
|
+
return false unless klass
|
|
162
|
+
|
|
163
|
+
(klass.respond_to?(:include?) && klass.include?(Lighthouse::Contracts::FieldResolver)) ||
|
|
164
|
+
klass.method_defined?(:define_resolver) ||
|
|
165
|
+
klass.method_defined?(:resolve_field)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lighthouse
|
|
4
|
+
module GraphQL
|
|
5
|
+
module Directives
|
|
6
|
+
# AST transformer that converts list-typed relationship fields annotated
|
|
7
|
+
# with relationship directives (e.g. @hasMany, @paginate) into
|
|
8
|
+
# Connection/Paginator return types and injects pagination arguments.
|
|
9
|
+
class RelationDirective
|
|
10
|
+
DIRECTIVE_NAMES = %w[hasMany belongsToMany hasManyThrough paginate all].freeze
|
|
11
|
+
|
|
12
|
+
def self.manipulate(sdl)
|
|
13
|
+
return sdl unless sdl.is_a?(String) && !sdl.empty?
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
document = ::GraphQL::Language::Parser.parse(sdl)
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
warn("Lighthouse::GraphQL::Directives::RelationDirective: parse failed: #{e.class}: #{e.message}")
|
|
19
|
+
return sdl
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
existing_def_names = document.definitions.filter_map { |d| d.respond_to?(:name) ? d.name : nil }
|
|
23
|
+
produced_inner_types = []
|
|
24
|
+
|
|
25
|
+
document.definitions.each do |definition|
|
|
26
|
+
next unless definition.is_a?(::GraphQL::Language::Nodes::ObjectTypeDefinition)
|
|
27
|
+
|
|
28
|
+
definition.fields.each do |field|
|
|
29
|
+
next unless field.type
|
|
30
|
+
next if field.directives.nil? || field.directives.empty?
|
|
31
|
+
next unless field.directives.any? { |d| DIRECTIVE_NAMES.include?(d.name) }
|
|
32
|
+
|
|
33
|
+
list_inner = list_inner_named_type(field.type)
|
|
34
|
+
next unless list_inner
|
|
35
|
+
|
|
36
|
+
produce_connection, produce_paginator = pagination_kind_for(field.directives)
|
|
37
|
+
next unless produce_connection || produce_paginator
|
|
38
|
+
|
|
39
|
+
produced_inner_types << list_inner unless produced_inner_types.include?(list_inner)
|
|
40
|
+
|
|
41
|
+
# Replace list type with Connection/Paginator return type, preserving outer nullability.
|
|
42
|
+
new_named_type =
|
|
43
|
+
if produce_connection
|
|
44
|
+
"#{list_inner}Connection"
|
|
45
|
+
else
|
|
46
|
+
"#{list_inner}Paginator"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
field.instance_variable_set(:@type, replace_with_named_type(field.type, new_named_type))
|
|
50
|
+
|
|
51
|
+
if produce_connection
|
|
52
|
+
field.instance_variable_set(:@arguments, ensure_connection_arguments(field.arguments || []))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return sdl if produced_inner_types.empty?
|
|
58
|
+
|
|
59
|
+
# Add Connection/Edge/Paginator utility types where missing.
|
|
60
|
+
new_defs = []
|
|
61
|
+
produced_inner_types.each do |inner|
|
|
62
|
+
conn_name = "#{inner}Connection"
|
|
63
|
+
edge_name = "#{inner}Edge"
|
|
64
|
+
paginator_name = "#{inner}Paginator"
|
|
65
|
+
|
|
66
|
+
unless existing_def_names.include?(conn_name)
|
|
67
|
+
new_defs << build_connection_type(conn_name, edge_name, inner)
|
|
68
|
+
existing_def_names << conn_name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless existing_def_names.include?(edge_name)
|
|
72
|
+
new_defs << build_edge_type(edge_name, inner)
|
|
73
|
+
existing_def_names << edge_name
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless existing_def_names.include?(paginator_name)
|
|
77
|
+
new_defs << build_paginator_type(paginator_name, inner)
|
|
78
|
+
existing_def_names << paginator_name
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
unless existing_def_names.include?('PaginatorInfo')
|
|
83
|
+
new_defs << build_paginator_info_type
|
|
84
|
+
existing_def_names << 'PaginatorInfo'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless existing_def_names.include?('PageInfo')
|
|
88
|
+
new_defs << build_page_info_type
|
|
89
|
+
existing_def_names << 'PageInfo'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
modified_sdl = ::GraphQL::Language::Printer.new.print(document)
|
|
93
|
+
modified_sdl + "\n\n" + new_defs.map { |d| ::GraphQL::Language::Printer.new.print(d) }.join("\n\n")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.pagination_kind_for(directives)
|
|
97
|
+
# For relation directives, always use Connection.
|
|
98
|
+
if directives.any? { |d| d.name != 'paginate' && DIRECTIVE_NAMES.include?(d.name) }
|
|
99
|
+
return [true, false]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
paginate = directives.find { |d| d.name == 'paginate' }
|
|
103
|
+
return [false, false] unless paginate
|
|
104
|
+
|
|
105
|
+
type_arg = directive_arg_literal(paginate, 'type')&.to_s&.upcase
|
|
106
|
+
if %w[CONNECTION CURSOR].include?(type_arg)
|
|
107
|
+
[true, false]
|
|
108
|
+
else
|
|
109
|
+
[false, true]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.directive_arg_literal(directive, name)
|
|
114
|
+
arg = directive.arguments&.find { |a| a.name == name }
|
|
115
|
+
return nil unless arg
|
|
116
|
+
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.list_inner_named_type(type_node)
|
|
120
|
+
case type_node
|
|
121
|
+
when ::GraphQL::Language::Nodes::NonNullType
|
|
122
|
+
list_inner_named_type(type_node.of_type)
|
|
123
|
+
when ::GraphQL::Language::Nodes::ListType
|
|
124
|
+
unwrap_named_type(type_node.of_type)
|
|
125
|
+
else
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.unwrap_named_type(type_node)
|
|
131
|
+
case type_node
|
|
132
|
+
when ::GraphQL::Language::Nodes::NonNullType
|
|
133
|
+
unwrap_named_type(type_node.of_type)
|
|
134
|
+
when ::GraphQL::Language::Nodes::TypeName
|
|
135
|
+
type_node.name
|
|
136
|
+
else
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.replace_with_named_type(type_node, new_name)
|
|
142
|
+
new_type = ::GraphQL::Language::Nodes::TypeName.new(name: new_name)
|
|
143
|
+
if type_node.is_a?(::GraphQL::Language::Nodes::NonNullType)
|
|
144
|
+
::GraphQL::Language::Nodes::NonNullType.new(of_type: new_type)
|
|
145
|
+
else
|
|
146
|
+
new_type
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.ensure_connection_arguments(existing_args)
|
|
151
|
+
existing_names = existing_args.map(&:name)
|
|
152
|
+
args = existing_args.map do |arg|
|
|
153
|
+
next arg unless %w[after before first last].include?(arg.name)
|
|
154
|
+
next arg if arg.description && !arg.description.empty?
|
|
155
|
+
|
|
156
|
+
::GraphQL::Language::Nodes::InputValueDefinition.new(
|
|
157
|
+
name: arg.name,
|
|
158
|
+
type: arg.type,
|
|
159
|
+
directives: arg.directives || [],
|
|
160
|
+
default_value: arg.default_value,
|
|
161
|
+
description: connection_arg_description(arg.name)
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
%w[after before first last].each do |name|
|
|
166
|
+
next if existing_names.include?(name)
|
|
167
|
+
|
|
168
|
+
type =
|
|
169
|
+
if %w[first last].include?(name)
|
|
170
|
+
::GraphQL::Language::Nodes::TypeName.new(name: 'Int')
|
|
171
|
+
else
|
|
172
|
+
::GraphQL::Language::Nodes::TypeName.new(name: 'String')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
args << ::GraphQL::Language::Nodes::InputValueDefinition.new(
|
|
176
|
+
name: name,
|
|
177
|
+
type: type,
|
|
178
|
+
directives: [],
|
|
179
|
+
default_value: nil,
|
|
180
|
+
description: connection_arg_description(name)
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
args
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.build_connection_type(conn_name, edge_name, node_name)
|
|
188
|
+
::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
|
|
189
|
+
name: conn_name,
|
|
190
|
+
interfaces: [],
|
|
191
|
+
directives: [],
|
|
192
|
+
description: "The connection type for #{node_name}.",
|
|
193
|
+
fields: [
|
|
194
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
195
|
+
name: 'edges',
|
|
196
|
+
type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: edge_name)),
|
|
197
|
+
arguments: [],
|
|
198
|
+
directives: [],
|
|
199
|
+
description: 'A list of edges.'
|
|
200
|
+
),
|
|
201
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
202
|
+
name: 'nodes',
|
|
203
|
+
type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: node_name)),
|
|
204
|
+
arguments: [],
|
|
205
|
+
directives: [],
|
|
206
|
+
description: 'A list of nodes.'
|
|
207
|
+
),
|
|
208
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
209
|
+
name: 'pageInfo',
|
|
210
|
+
type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'PageInfo')),
|
|
211
|
+
arguments: [],
|
|
212
|
+
directives: [],
|
|
213
|
+
description: 'Information to aid in pagination.'
|
|
214
|
+
)
|
|
215
|
+
]
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.build_edge_type(edge_name, node_name)
|
|
220
|
+
::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
|
|
221
|
+
name: edge_name,
|
|
222
|
+
interfaces: [],
|
|
223
|
+
directives: [],
|
|
224
|
+
description: 'An edge in a connection.',
|
|
225
|
+
fields: [
|
|
226
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
227
|
+
name: 'cursor',
|
|
228
|
+
type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'String')),
|
|
229
|
+
arguments: [],
|
|
230
|
+
directives: [],
|
|
231
|
+
description: 'A cursor for use in pagination.'
|
|
232
|
+
),
|
|
233
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
234
|
+
name: 'node',
|
|
235
|
+
type: ::GraphQL::Language::Nodes::TypeName.new(name: node_name),
|
|
236
|
+
arguments: [],
|
|
237
|
+
directives: [],
|
|
238
|
+
description: 'The item at the end of the edge.'
|
|
239
|
+
)
|
|
240
|
+
]
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self.build_paginator_type(paginator_name, inner_name)
|
|
245
|
+
::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
|
|
246
|
+
name: paginator_name,
|
|
247
|
+
interfaces: [],
|
|
248
|
+
directives: [],
|
|
249
|
+
description: "A paginated list of #{inner_name} items.",
|
|
250
|
+
fields: [
|
|
251
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
252
|
+
name: 'paginatorInfo',
|
|
253
|
+
type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'PaginatorInfo')),
|
|
254
|
+
arguments: [],
|
|
255
|
+
directives: [],
|
|
256
|
+
description: 'Pagination information about the list of items.'
|
|
257
|
+
),
|
|
258
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
259
|
+
name: 'data',
|
|
260
|
+
type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: inner_name)),
|
|
261
|
+
arguments: [],
|
|
262
|
+
directives: [],
|
|
263
|
+
description: "A list of #{inner_name} items."
|
|
264
|
+
)
|
|
265
|
+
]
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def self.build_paginator_info_type
|
|
270
|
+
::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
|
|
271
|
+
name: 'PaginatorInfo',
|
|
272
|
+
interfaces: [],
|
|
273
|
+
directives: [],
|
|
274
|
+
description: 'Information about pagination in a paginator.',
|
|
275
|
+
fields: [
|
|
276
|
+
field_def('count', 'Int', non_null: true),
|
|
277
|
+
field_def('currentPage', 'Int', non_null: true),
|
|
278
|
+
field_def('firstItem', 'Int'),
|
|
279
|
+
field_def('hasMorePages', 'Boolean', non_null: true),
|
|
280
|
+
field_def('lastItem', 'Int'),
|
|
281
|
+
field_def('lastPage', 'Int', non_null: true),
|
|
282
|
+
field_def('perPage', 'Int', non_null: true),
|
|
283
|
+
field_def('total', 'Int', non_null: true)
|
|
284
|
+
]
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def self.build_page_info_type
|
|
289
|
+
# Minimal Relay PageInfo. GraphQL-Ruby can also supply this via Relay,
|
|
290
|
+
# but SDL-first apps may not include it.
|
|
291
|
+
::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
|
|
292
|
+
name: 'PageInfo',
|
|
293
|
+
interfaces: [],
|
|
294
|
+
directives: [],
|
|
295
|
+
description: nil,
|
|
296
|
+
fields: [
|
|
297
|
+
field_def('hasNextPage', 'Boolean', non_null: true),
|
|
298
|
+
field_def('hasPreviousPage', 'Boolean', non_null: true),
|
|
299
|
+
field_def('startCursor', 'String'),
|
|
300
|
+
field_def('endCursor', 'String')
|
|
301
|
+
]
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def self.field_def(name, type_name, non_null: false)
|
|
306
|
+
type = ::GraphQL::Language::Nodes::TypeName.new(name: type_name)
|
|
307
|
+
type = ::GraphQL::Language::Nodes::NonNullType.new(of_type: type) if non_null
|
|
308
|
+
::GraphQL::Language::Nodes::FieldDefinition.new(
|
|
309
|
+
name: name,
|
|
310
|
+
type: type,
|
|
311
|
+
arguments: [],
|
|
312
|
+
directives: [],
|
|
313
|
+
description: nil
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def self.connection_arg_description(name)
|
|
318
|
+
case name
|
|
319
|
+
when 'after'
|
|
320
|
+
'Returns the elements in the list that come after the specified cursor.'
|
|
321
|
+
when 'before'
|
|
322
|
+
'Returns the elements in the list that come before the specified cursor.'
|
|
323
|
+
when 'first'
|
|
324
|
+
'Returns the first _n_ elements from the list.'
|
|
325
|
+
when 'last'
|
|
326
|
+
'Returns the last _n_ elements from the list.'
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|