lighthouse-graphql 0.1.1 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +175 -1
- data/README.md +47 -3
- data/docs/authorization.md +116 -57
- data/docs/best-practices.md +16 -3
- data/docs/caching.md +161 -0
- data/docs/configuration.md +83 -4
- data/docs/custom-directives.md +5 -1
- data/docs/directives.md +117 -38
- data/docs/getting-started.md +84 -0
- data/docs/motivation.md +184 -0
- data/docs/mutations.md +260 -0
- data/docs/performance.md +138 -0
- data/docs/relationships.md +80 -33
- data/docs/resolvers.md +189 -0
- data/docs/validation.md +132 -0
- data/lib/generators/lighthouse/builder_generator.rb +30 -0
- data/lib/generators/lighthouse/class_generator.rb +61 -0
- data/lib/generators/lighthouse/install/install_generator.rb +127 -0
- data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +8 -0
- data/lib/generators/lighthouse/install/templates/graphql_controller.rb.tt +57 -0
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +67 -0
- data/lib/generators/lighthouse/install/templates/schema.graphql.tt +19 -0
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +10 -0
- data/lib/generators/lighthouse/mutation_generator.rb +30 -0
- data/lib/generators/lighthouse/query_generator.rb +29 -0
- data/lib/generators/lighthouse/validator_generator.rb +36 -0
- data/lib/lighthouse/cache_control.rb +126 -0
- data/lib/lighthouse/caching.rb +68 -0
- data/lib/lighthouse/config.rb +85 -19
- data/lib/lighthouse/contracts/arg_builder.rb +8 -12
- data/lib/lighthouse/contracts/arg_manipulator.rb +8 -12
- data/lib/lighthouse/contracts/directive.rb +10 -14
- data/lib/lighthouse/contracts/field_manipulator.rb +8 -12
- data/lib/lighthouse/contracts/field_middleware.rb +7 -11
- data/lib/lighthouse/contracts/field_resolver.rb +9 -13
- data/lib/lighthouse/dataloader_sources/association_loader.rb +28 -19
- data/lib/lighthouse/dataloader_sources/model_loader.rb +10 -14
- data/lib/lighthouse/dataloader_sources/relation_count_loader.rb +16 -0
- data/lib/lighthouse/dataloader_sources/relation_page_loader.rb +18 -0
- data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +57 -69
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +26 -57
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +161 -0
- data/lib/lighthouse/directive_resolvers/auth_resolver.rb +41 -38
- data/lib/lighthouse/directive_resolvers/base.rb +142 -86
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +58 -56
- data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +133 -0
- data/lib/lighthouse/directive_resolvers/can_resolvers.rb +177 -0
- data/lib/lighthouse/directive_resolvers/convention_resolver.rb +70 -0
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +42 -48
- data/lib/lighthouse/directive_resolvers/field_resolver.rb +27 -68
- data/lib/lighthouse/directive_resolvers/find_resolver.rb +42 -52
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +39 -45
- data/lib/lighthouse/directive_resolvers/guard_resolver.rb +16 -22
- data/lib/lighthouse/directive_resolvers/method_resolver.rb +19 -25
- data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +219 -0
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +70 -128
- data/lib/lighthouse/directive_resolvers/rename_resolver.rb +18 -24
- data/lib/lighthouse/directive_resolvers/resolution.rb +31 -27
- data/lib/lighthouse/directive_resolvers/validator_resolver.rb +193 -0
- data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +164 -163
- data/lib/lighthouse/directives/arguments/base.rb +23 -31
- data/lib/lighthouse/directives/arguments/filters.rb +113 -93
- data/lib/lighthouse/directives/arguments/order_by.rb +41 -41
- data/lib/lighthouse/directives/pagination_info_types.rb +69 -0
- data/lib/lighthouse/directives/pagination_manipulator.rb +176 -0
- data/lib/lighthouse/directives/registry.rb +210 -141
- data/lib/lighthouse/directives/where_conditions_directive.rb +236 -200
- data/lib/lighthouse/errors.rb +27 -0
- data/lib/lighthouse/federation_directives.rb +53 -0
- data/lib/lighthouse/field_resolver_stack.rb +56 -0
- data/lib/lighthouse/generated_resolvers.rb +82 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/graphql_ruby_patches.rb +18 -0
- data/lib/lighthouse/ide_helper.rb +98 -0
- data/lib/lighthouse/namespaces.rb +21 -0
- data/lib/lighthouse/nested_mutations/belongs_to_operations.rb +50 -0
- data/lib/lighthouse/nested_mutations/executor.rb +149 -0
- data/lib/lighthouse/nested_mutations/has_operations.rb +66 -0
- data/lib/lighthouse/nested_mutations/many_to_many_operations.rb +44 -0
- data/lib/lighthouse/nested_mutations/partitioner.rb +125 -0
- data/lib/lighthouse/nested_mutations.rb +210 -0
- data/lib/lighthouse/pagination.rb +221 -0
- data/lib/lighthouse/persisted_queries.rb +167 -0
- data/lib/lighthouse/railtie.rb +26 -0
- data/lib/lighthouse/rb_lighthouse.rb +50 -29
- data/lib/lighthouse/reference_resolver.rb +13 -15
- data/lib/lighthouse/relation_batch.rb +156 -0
- data/lib/lighthouse/resolver_dispatch.rb +26 -0
- data/lib/lighthouse/schema_cache.rb +120 -0
- data/lib/lighthouse/schema_factory.rb +27 -37
- data/lib/lighthouse/schema_generator.rb +196 -239
- data/lib/lighthouse/schema_implementation.rb +51 -48
- data/lib/lighthouse/sdl_loader.rb +17 -20
- data/lib/lighthouse/support/arguments.rb +119 -0
- data/lib/lighthouse/support/authorization.rb +18 -19
- data/lib/lighthouse/support/batching.rb +16 -0
- data/lib/lighthouse/support/directive_args.rb +26 -24
- data/lib/lighthouse/support/model_resolver.rb +56 -56
- data/lib/lighthouse/support/naming.rb +55 -59
- data/lib/lighthouse/tasks/lighthouse.rake +33 -0
- data/lib/lighthouse/where_conditions/operator_map.rb +25 -29
- data/sig/lighthouse/directives.rbs +158 -0
- data/sig/lighthouse/graphql.rbs +78 -0
- data/sig/lighthouse.rbs +142 -0
- metadata +56 -64
- data/lib/lighthouse/directive_resolvers/belongs_to_many_resolver.rb +0 -56
- data/lib/lighthouse/directive_resolvers/belongs_to_resolver.rb +0 -47
- data/lib/lighthouse/directive_resolvers/can_resolver.rb +0 -86
- data/lib/lighthouse/directive_resolvers/has_many_resolver.rb +0 -109
- data/lib/lighthouse/directive_resolvers/has_one_resolver.rb +0 -43
- data/lib/lighthouse/directives/relation_directive.rb +0 -332
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Lighthouse
|
|
6
|
-
module GraphQL
|
|
7
|
-
module DirectiveResolvers
|
|
8
|
-
# @can — authorize access to a field using a policy ability (Pundit by
|
|
9
|
-
# default; configurable via Config.authorizer). Mirrors PHP Lighthouse's
|
|
10
|
-
# @can* family. Repeatable.
|
|
11
|
-
#
|
|
12
|
-
# contact(id: ID!): Contact @field(resolver: "...") @can(ability: "show")
|
|
13
|
-
#
|
|
14
|
-
# Modes:
|
|
15
|
-
# * default — resolve the field, then authorize the resolved
|
|
16
|
-
# record (skips nil; passes collections through —
|
|
17
|
-
# use scoping for lists).
|
|
18
|
-
# * root: true — authorize the parent object before resolving.
|
|
19
|
-
# * find / model — look the model up by an argument and authorize it
|
|
20
|
-
# before resolving.
|
|
21
|
-
class CanDirectiveResolver < BaseDirectiveResolver
|
|
22
|
-
def self.directive_name = 'can'
|
|
23
|
-
def self.definition
|
|
24
|
-
'directive @can(ability: String!, find: String, model: String, root: Boolean) repeatable on FIELD_DEFINITION'
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def define_resolver(target_type_class)
|
|
28
|
-
field_name = @field_def.name
|
|
29
|
-
method_name = "resolve_field_#{field_name}"
|
|
30
|
-
ability = directive_arg('ability')
|
|
31
|
-
return if ability.nil?
|
|
32
|
-
|
|
33
|
-
find_arg = directive_arg('find')
|
|
34
|
-
model_name = directive_arg('model')
|
|
35
|
-
root = directive_arg('root')
|
|
36
|
-
inner = Resolution.capture(target_type_class, method_name, @field_def, field_name)
|
|
37
|
-
resolver_instance = self
|
|
38
|
-
|
|
39
|
-
wrapper = lambda do |obj, arguments, ctx|
|
|
40
|
-
if root
|
|
41
|
-
resolver_instance.send(:authorize_or_raise, ctx, obj, ability)
|
|
42
|
-
next inner.call(obj, arguments, ctx)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
if find_arg && model_name
|
|
46
|
-
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
47
|
-
id = Lighthouse::Support::Naming.fetch_arg(args_h, find_arg)
|
|
48
|
-
model_class = resolver_instance.send(:constantize_model, model_name)
|
|
49
|
-
record = model_class&.find_by(id: id)
|
|
50
|
-
resolver_instance.send(:authorize_or_raise, ctx, record, ability)
|
|
51
|
-
next inner.call(obj, arguments, ctx)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
result = inner.call(obj, arguments, ctx)
|
|
55
|
-
resolver_instance.send(:authorize_resolved, ctx, result, ability)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
target_type_class.define_singleton_method(method_name, &wrapper)
|
|
59
|
-
@field_def.resolve_proc = wrapper if @field_def.respond_to?(:resolve_proc=)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def directive_arg(name)
|
|
65
|
-
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def authorize_resolved(ctx, result, ability)
|
|
69
|
-
return result if result.nil?
|
|
70
|
-
# Collections are filtered with scoping, not item-by-item authorization.
|
|
71
|
-
return result if result.respond_to?(:each) && !result.respond_to?(:id)
|
|
72
|
-
|
|
73
|
-
authorize_or_raise(ctx, result, ability)
|
|
74
|
-
result
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def authorize_or_raise(ctx, record, ability)
|
|
78
|
-
return if record.nil?
|
|
79
|
-
|
|
80
|
-
authorized = Lighthouse::Support::Authorization.authorized?(ctx, record, ability)
|
|
81
|
-
raise(::GraphQL::ExecutionError, 'You are not authorized to perform this action.') unless authorized
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Lighthouse
|
|
6
|
-
module GraphQL
|
|
7
|
-
module DirectiveResolvers
|
|
8
|
-
class HasManyDirectiveResolver < BaseDirectiveResolver
|
|
9
|
-
def define_resolver(target_type_class)
|
|
10
|
-
field_name = @field_def.name
|
|
11
|
-
assoc_name = extract_relation_name || field_name
|
|
12
|
-
is_connection = @field_def.respond_to?(:connection?) ? @field_def.connection? : false
|
|
13
|
-
|
|
14
|
-
resolver_instance = self
|
|
15
|
-
|
|
16
|
-
# Keep compatibility with SchemaImplementation-based resolution by
|
|
17
|
-
# defining a singleton method on the owner type (e.g. Inbox.resolve_field_conversations).
|
|
18
|
-
method_name = "resolve_field_#{field_name}"
|
|
19
|
-
|
|
20
|
-
target_type_class.define_singleton_method(method_name) do |*args|
|
|
21
|
-
# Support both batch and single resolution modes.
|
|
22
|
-
# Batch mode signature: (objects_array, ctx)
|
|
23
|
-
# Single mode signature: (obj, arguments, ctx)
|
|
24
|
-
if args.length == 2 && args[0].is_a?(Array)
|
|
25
|
-
parents, ctx = args
|
|
26
|
-
|
|
27
|
-
if ctx && ctx.respond_to?(:dataloader) && ctx.dataloader
|
|
28
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::AssociationLoader, parents.first.class, assoc_name)
|
|
29
|
-
parents.map do |p|
|
|
30
|
-
promise = loader.load(p)
|
|
31
|
-
if is_connection
|
|
32
|
-
promise.then { |value| ::GraphQL::Pagination::ArrayConnection.new(value, context: ctx, parent: nil, field: nil, arguments: {}) }
|
|
33
|
-
else
|
|
34
|
-
promise
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
parents.map do |p|
|
|
39
|
-
value = p.respond_to?(assoc_name) ? p.public_send(assoc_name) : nil
|
|
40
|
-
is_connection && value ? ::GraphQL::Pagination::ArrayConnection.new(value, context: ctx, parent: nil, field: nil, arguments: {}) : value
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
else
|
|
44
|
-
obj, arguments, ctx = args
|
|
45
|
-
|
|
46
|
-
# Use dataloader when available
|
|
47
|
-
if ctx && ctx.respond_to?(:dataloader) && ctx.dataloader
|
|
48
|
-
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
49
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::AssociationLoader, obj.class, assoc_name)
|
|
50
|
-
value = loader.load(obj)
|
|
51
|
-
if is_connection
|
|
52
|
-
value.then do |v|
|
|
53
|
-
filtered = resolver_instance.apply_where_filters(v, args_h)
|
|
54
|
-
::GraphQL::Pagination::ArrayConnection.new(filtered, context: ctx, parent: nil, field: nil, arguments: {})
|
|
55
|
-
end
|
|
56
|
-
else
|
|
57
|
-
value.then { |v| resolver_instance.apply_where_filters(v, args_h) }
|
|
58
|
-
end
|
|
59
|
-
elsif obj.respond_to?(assoc_name)
|
|
60
|
-
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
61
|
-
value = obj.public_send(assoc_name)
|
|
62
|
-
value = resolver_instance.apply_where_filters(value, args_h)
|
|
63
|
-
is_connection && value ? ::GraphQL::Pagination::ArrayConnection.new(value, context: ctx, parent: nil, field: nil, arguments: {}) : value
|
|
64
|
-
else
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Also attach a resolve proc where supported by the GraphQL runtime.
|
|
71
|
-
if @field_def.respond_to?(:resolve_proc=)
|
|
72
|
-
@field_def.resolve_proc = ->(obj, arguments, ctx) do
|
|
73
|
-
if ctx && ctx.respond_to?(:dataloader) && ctx.dataloader
|
|
74
|
-
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
75
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::AssociationLoader, obj.class, assoc_name)
|
|
76
|
-
result = loader.load(obj)
|
|
77
|
-
if is_connection
|
|
78
|
-
result.then do |value|
|
|
79
|
-
filtered = resolver_instance.apply_where_filters(value, args_h)
|
|
80
|
-
::GraphQL::Pagination::ArrayConnection.new(filtered, context: ctx, parent: nil, field: nil, arguments: {})
|
|
81
|
-
end
|
|
82
|
-
else
|
|
83
|
-
result.then { |value| resolver_instance.apply_where_filters(value, args_h) }
|
|
84
|
-
end
|
|
85
|
-
elsif obj.respond_to?(assoc_name)
|
|
86
|
-
args = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
87
|
-
value = obj.public_send(assoc_name)
|
|
88
|
-
value = resolver_instance.apply_where_filters(value, args)
|
|
89
|
-
is_connection && value ? ::GraphQL::Pagination::ArrayConnection.new(value, context: ctx, parent: nil, field: nil, arguments: {}) : value
|
|
90
|
-
else
|
|
91
|
-
nil
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
private
|
|
98
|
-
|
|
99
|
-
def extract_relation_name
|
|
100
|
-
# directive syntax: @hasMany(relation: "name")
|
|
101
|
-
arg = @directive_node.arguments&.find { |a| a.name == 'relation' }
|
|
102
|
-
return nil unless arg
|
|
103
|
-
# value is AST literal node; use its value method if present
|
|
104
|
-
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Lighthouse
|
|
6
|
-
module GraphQL
|
|
7
|
-
module DirectiveResolvers
|
|
8
|
-
# @hasOne(relation: "name") — resolve a one-to-one association on the parent
|
|
9
|
-
# object. Mirrors @belongsTo (single record) and batch-loads through the
|
|
10
|
-
# AssociationLoader when a dataloader is available.
|
|
11
|
-
class HasOneDirectiveResolver < BaseDirectiveResolver
|
|
12
|
-
def self.directive_name = 'hasOne'
|
|
13
|
-
def self.definition = 'directive @hasOne(relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
14
|
-
|
|
15
|
-
def define_resolver(target_type_class)
|
|
16
|
-
field_name = @field_def.name
|
|
17
|
-
assoc_name = directive_arg('relation') || field_name
|
|
18
|
-
method_name = "resolve_field_#{field_name}"
|
|
19
|
-
|
|
20
|
-
resolver = lambda do |obj, _arguments, ctx|
|
|
21
|
-
next nil if obj.nil?
|
|
22
|
-
|
|
23
|
-
if ctx && ctx.respond_to?(:dataloader) && ctx.dataloader
|
|
24
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::AssociationLoader, obj.class, assoc_name)
|
|
25
|
-
loader.load(obj)
|
|
26
|
-
elsif obj.respond_to?(assoc_name)
|
|
27
|
-
obj.public_send(assoc_name)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
target_type_class.define_singleton_method(method_name, &resolver)
|
|
32
|
-
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def directive_arg(name)
|
|
38
|
-
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,332 +0,0 @@
|
|
|
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
|