lighthouse-graphql 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +168 -1
- data/README.md +46 -2
- 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/mutations.md +260 -0
- data/docs/performance.md +138 -0
- data/docs/relationships.md +80 -33
- data/docs/resolvers.md +179 -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
- metadata +52 -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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
# Resolves a Query or Mutation field that has no resolving directive by
|
|
6
|
+
# convention, as PHP Lighthouse does: `Query.latestPosts` resolves to
|
|
7
|
+
# `Queries::LatestPosts` (a GraphQL::Schema::Resolver), `Mutation.publishPost`
|
|
8
|
+
# to `Mutations::PublishPost` (a GraphQL::Schema::Mutation), looked up in
|
|
9
|
+
# Config.namespaces[:queries] / [:mutations] at call time so code reloading
|
|
10
|
+
# keeps working. Not a directive: SchemaGenerator installs it.
|
|
11
|
+
class Lighthouse::GraphQL::DirectiveResolvers::ConventionResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
12
|
+
def initialize(field_def, type_def, kind)
|
|
13
|
+
super(field_def, type_def, nil)
|
|
14
|
+
@kind = kind
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def define_resolver(target_type_class)
|
|
18
|
+
me = self
|
|
19
|
+
inner = capture_inner(target_type_class)
|
|
20
|
+
|
|
21
|
+
install(target_type_class, lambda do |obj, args, ctx|
|
|
22
|
+
me.send(:resolve_by_convention, inner, obj, args, ctx)
|
|
23
|
+
end)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ROOT_KINDS = { 'Query' => :queries, 'Mutation' => :mutations }.freeze
|
|
27
|
+
|
|
28
|
+
# :queries / :mutations for the conventional root type names. The
|
|
29
|
+
# SchemaGenerator passes the schema's actual roots as `root_kind:`.
|
|
30
|
+
def self.root_kind_of(type)
|
|
31
|
+
ROOT_KINDS[type.respond_to?(:graphql_name) ? type.graphql_name : type.to_s]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def resolve_by_convention(inner, obj, args, ctx)
|
|
37
|
+
namespaces = Lighthouse::Config.namespaces[@kind]
|
|
38
|
+
klass = Lighthouse::Support::ModelResolver.constantize(class_name, namespaces: namespaces, fallback: false)
|
|
39
|
+
return missing(namespaces, inner, obj, args, ctx) unless klass
|
|
40
|
+
|
|
41
|
+
unless Lighthouse::GraphQL::ResolverDispatch.graphql_resolver?(klass)
|
|
42
|
+
raise Lighthouse::DefinitionError,
|
|
43
|
+
"#{klass} resolves #{location}, so it must inherit from GraphQL::Schema::Resolver " \
|
|
44
|
+
'(or GraphQL::Schema::Mutation).'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Lighthouse::GraphQL::ResolverDispatch.call(klass, @field_def, obj, args, ctx)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# No class: the root object may still answer (a custom root_value);
|
|
51
|
+
# otherwise report the misconfiguration through Config.on_error.
|
|
52
|
+
def missing(namespaces, inner, obj, args, ctx)
|
|
53
|
+
return inner.call(obj, args, ctx) if obj && !obj.is_a?(Hash) && obj.respond_to?(@field_def.name)
|
|
54
|
+
|
|
55
|
+
expected = Array(namespaces).map { |ns| "#{ns}::#{class_name}" }.join(' or ')
|
|
56
|
+
Lighthouse::Config.handle_error(
|
|
57
|
+
Lighthouse::DefinitionError.new("#{location} has no resolver: define #{expected}, or add a resolving directive."),
|
|
58
|
+
ctx
|
|
59
|
+
)
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def class_name
|
|
64
|
+
@class_name ||= @field_def.name.to_s.camelize
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def location
|
|
68
|
+
@location ||= "#{@type_def.graphql_name}.#{@field_def.name}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -3,54 +3,48 @@
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require_relative 'builder_applier'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
relation = DirectiveResolvers::BuilderApplier.apply(relation, field_ast_node, args_h, ctx, resolver_instance)
|
|
40
|
-
relation = resolver_instance.apply_where_filters(relation, args_h)
|
|
41
|
-
relation.count
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
target_type_class.define_singleton_method(method_name, &resolver)
|
|
45
|
-
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def directive_arg(name)
|
|
51
|
-
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
6
|
+
# @count(model: "Model", relation: "assoc") — return the number of records.
|
|
7
|
+
# Counts `Model.all` when `model:` is given, otherwise the named relation on
|
|
8
|
+
# the parent object (or a relation matching the field name). Honors `scopes:`, @builder
|
|
9
|
+
# and where/argument filters before counting.
|
|
10
|
+
class Lighthouse::GraphQL::DirectiveResolvers::CountDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
11
|
+
def self.directive_name = 'count'
|
|
12
|
+
def self.definition = 'directive @count(model: String, relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
13
|
+
|
|
14
|
+
def define_resolver(target_type_class)
|
|
15
|
+
model_name = directive_arg('model')
|
|
16
|
+
relation_name = directive_arg('relation')
|
|
17
|
+
me = self
|
|
18
|
+
|
|
19
|
+
install(target_type_class, lambda do |obj, arguments, ctx|
|
|
20
|
+
me.send(:count_records, me.send(:countable, obj, model_name, relation_name), arguments, ctx)
|
|
21
|
+
end)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def directive_arg(name)
|
|
27
|
+
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def countable(obj, model_name, relation_name)
|
|
31
|
+
field_name = @field_def.name
|
|
32
|
+
if model_name
|
|
33
|
+
constantize_model(model_name)&.all
|
|
34
|
+
elsif relation_name && obj.respond_to?(relation_name)
|
|
35
|
+
obj.public_send(relation_name)
|
|
36
|
+
elsif obj.respond_to?(field_name)
|
|
37
|
+
obj.public_send(field_name)
|
|
54
38
|
end
|
|
55
39
|
end
|
|
40
|
+
|
|
41
|
+
def count_records(relation, arguments, ctx)
|
|
42
|
+
return 0 unless relation.respond_to?(:count)
|
|
43
|
+
|
|
44
|
+
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
45
|
+
relation = apply_scopes(relation)
|
|
46
|
+
relation = Lighthouse::GraphQL::DirectiveResolvers::BuilderApplier.apply(relation, @field_def.ast_node, args_h, ctx, self)
|
|
47
|
+
relation = apply_where_filters(relation, args_h)
|
|
48
|
+
relation.count
|
|
49
|
+
end
|
|
56
50
|
end
|
|
@@ -2,77 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
instance = resolver_class.new
|
|
25
|
-
if instance.respond_to?(:call)
|
|
26
|
-
result = instance.call(obj, args, ctx)
|
|
27
|
-
elsif instance.respond_to?(:resolve)
|
|
28
|
-
result = instance.resolve(obj, args, ctx)
|
|
29
|
-
else
|
|
30
|
-
result = nil
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
result
|
|
35
|
-
rescue StandardError => e
|
|
36
|
-
raise e
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
target_type_class.define_singleton_method(method_name, &resolver)
|
|
41
|
-
|
|
42
|
-
if @field_def.respond_to?(:resolve_proc=)
|
|
43
|
-
@field_def.resolve_proc = resolver
|
|
44
|
-
end
|
|
45
|
-
end
|
|
5
|
+
class Lighthouse::GraphQL::DirectiveResolvers::FieldDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
6
|
+
def self.directive_name = 'field'
|
|
7
|
+
def self.definition = 'directive @field(resolver: String, class: String) on FIELD_DEFINITION'
|
|
8
|
+
|
|
9
|
+
# On Query and Mutation the class is looked up in the queries / mutations
|
|
10
|
+
# namespaces; on any other type, in Config.resolver_namespaces.
|
|
11
|
+
def define_resolver(target_type_class)
|
|
12
|
+
field_def = @field_def
|
|
13
|
+
class_name = extract_directive_arg('resolver') || extract_directive_arg('class')
|
|
14
|
+
kind = root_kind(target_type_class)
|
|
15
|
+
|
|
16
|
+
install(target_type_class, lambda do |obj, args, ctx|
|
|
17
|
+
namespaces = kind ? Lighthouse::Config.namespaces[kind] : Lighthouse::Config.resolver_namespaces
|
|
18
|
+
klass = Lighthouse::Support::ModelResolver.constantize(class_name, namespaces: namespaces)
|
|
19
|
+
next nil unless klass
|
|
20
|
+
|
|
21
|
+
Lighthouse::GraphQL::ResolverDispatch.call(klass, field_def, obj, args, ctx)
|
|
22
|
+
end)
|
|
23
|
+
end
|
|
46
24
|
|
|
47
|
-
|
|
25
|
+
private
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
53
|
-
end
|
|
27
|
+
def extract_directive_arg(name)
|
|
28
|
+
arg = @directive_node.arguments&.find { |a| a.name == name }
|
|
29
|
+
return nil unless arg
|
|
54
30
|
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
32
|
+
end
|
|
57
33
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
elsif resolver_class.respond_to?(:new)
|
|
61
|
-
instance = resolver_class.new
|
|
62
|
-
if instance.respond_to?(:call)
|
|
63
|
-
instance.call(obj, args, ctx)
|
|
64
|
-
elsif instance.respond_to?(:resolve)
|
|
65
|
-
instance.resolve(obj, args, ctx)
|
|
66
|
-
else
|
|
67
|
-
nil
|
|
68
|
-
end
|
|
69
|
-
else
|
|
70
|
-
nil
|
|
71
|
-
end
|
|
72
|
-
rescue StandardError => e
|
|
73
|
-
raise e
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
34
|
+
def root_kind(target_type_class)
|
|
35
|
+
@opts[:root_kind] || Lighthouse::GraphQL::DirectiveResolvers::ConventionResolver.root_kind_of(target_type_class)
|
|
77
36
|
end
|
|
78
37
|
end
|
|
@@ -2,64 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
# @find — the single record the field's argument filters select:
|
|
6
|
+
#
|
|
7
|
+
# user(id: ID! @eq): User @find
|
|
8
|
+
# userByEmail(email: String! @eq): User @find
|
|
9
|
+
#
|
|
10
|
+
# A lookup by `id` alone is batched through the Dataloader, and an `id`
|
|
11
|
+
# argument without a filter directive selects by primary key. More than one
|
|
12
|
+
# match is an error, as in PHP Lighthouse; none returns null.
|
|
13
|
+
class Lighthouse::GraphQL::DirectiveResolvers::FindDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
14
|
+
def self.directive_name = 'find'
|
|
15
|
+
def self.definition = 'directive @find(model: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
16
|
+
|
|
17
|
+
def define_resolver(target_type_class)
|
|
18
|
+
me = self
|
|
19
|
+
install(target_type_class, lambda do |_obj, args, ctx|
|
|
20
|
+
me.send(:find, Lighthouse::Support::Arguments.shallow(args), ctx)
|
|
21
|
+
end)
|
|
22
|
+
end
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::ModelLoader, model_class)
|
|
25
|
-
return loader.load(id)
|
|
26
|
-
end
|
|
24
|
+
protected
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
def find(args, ctx)
|
|
27
|
+
model = model_class
|
|
28
|
+
dataloader = Lighthouse::Support::Batching.dataloader(ctx)
|
|
29
|
+
return dataloader.with(Lighthouse::GraphQL::DataloaderSources::ModelLoader, model).load(args[:id]) if dataloader && batchable?(args)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
args = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
34
|
-
id = args[:id] || args['id']
|
|
31
|
+
records = find_relation(model, args).limit(2).to_a
|
|
32
|
+
raise ::GraphQL::ExecutionError, 'The query returned more than one result.' if records.size > 1
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
nil
|
|
39
|
-
elsif id && ctx&.dataloader
|
|
40
|
-
loader = ctx.dataloader.with(Lighthouse::GraphQL::DataloaderSources::ModelLoader, model_class)
|
|
41
|
-
loader.load(id)
|
|
42
|
-
else
|
|
43
|
-
model_class.find_by(id: id)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
34
|
+
records.first
|
|
35
|
+
end
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
# Only a lookup by `id` alone, with no scopes to apply, can be batched.
|
|
38
|
+
def batchable?(args)
|
|
39
|
+
args.keys == [:id] && !directive_arg('scopes')
|
|
40
|
+
end
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
def find_relation(model, args)
|
|
43
|
+
relation = apply_scopes(model.all)
|
|
44
|
+
relation = relation.where(model.primary_key => args[:id]) if args.key?(:id) && !filtered?('id')
|
|
45
|
+
apply_where_filters(relation, args)
|
|
46
|
+
end
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
48
|
+
# Does the argument carry a filter directive (@eq, @where, ...)? An `id`
|
|
49
|
+
# without one still selects by primary key.
|
|
50
|
+
def filtered?(name)
|
|
51
|
+
argument = (@field_def.ast_node&.arguments || []).find { |a| a.name == name }
|
|
52
|
+
registry = Lighthouse::DirectiveRegistry.default
|
|
53
|
+
argument&.directives&.any? { |d| registry.arg_builder_class(d.name) }
|
|
64
54
|
end
|
|
65
55
|
end
|
|
@@ -3,51 +3,45 @@
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require_relative 'builder_applier'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
relation = DirectiveResolvers::BuilderApplier.apply(relation, field_ast_node, args_h, ctx, resolver_instance)
|
|
29
|
-
relation = resolver_instance.apply_where_filters(relation, args_h)
|
|
30
|
-
relation.first
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
target_type_class.define_singleton_method(method_name, &resolver)
|
|
34
|
-
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def directive_arg(name)
|
|
40
|
-
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def field_type_name
|
|
44
|
-
unwrapped = @field_def.type.respond_to?(:unwrap) ? @field_def.type.unwrap : @field_def.type
|
|
45
|
-
return unwrapped.graphql_name if unwrapped.respond_to?(:graphql_name) && unwrapped.graphql_name
|
|
46
|
-
return unwrapped.name if unwrapped.respond_to?(:name) && unwrapped.name
|
|
47
|
-
|
|
48
|
-
@field_def.type.to_type_signature.to_s.gsub(/[!\[\]]/, '')
|
|
49
|
-
end
|
|
50
|
-
end
|
|
6
|
+
# @first(model: "Model") — return a single record (the first match). Like
|
|
7
|
+
# @all but `.first`, honoring `scopes:`, @builder and where/argument filters so it can
|
|
8
|
+
# be combined with @eq/@where to fetch one record by criteria.
|
|
9
|
+
class Lighthouse::GraphQL::DirectiveResolvers::FirstDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
10
|
+
def self.directive_name = 'first'
|
|
11
|
+
def self.definition = 'directive @first(model: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
12
|
+
|
|
13
|
+
def define_resolver(target_type_class)
|
|
14
|
+
method_name = "resolve_field_#{@field_def.name}"
|
|
15
|
+
model_name_static = directive_arg('model') || field_type_name
|
|
16
|
+
resolver_instance = self
|
|
17
|
+
field_ast_node = @field_def.ast_node
|
|
18
|
+
|
|
19
|
+
resolver = lambda do |_obj, arguments, ctx|
|
|
20
|
+
model_class = resolver_instance.constantize_model(model_name_static)
|
|
21
|
+
next nil unless model_class
|
|
22
|
+
|
|
23
|
+
args_h = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
24
|
+
relation = resolver_instance.apply_scopes(model_class.all)
|
|
25
|
+
relation = Lighthouse::GraphQL::DirectiveResolvers::BuilderApplier.apply(relation, field_ast_node, args_h, ctx, resolver_instance)
|
|
26
|
+
relation = resolver_instance.apply_where_filters(relation, args_h)
|
|
27
|
+
relation.first
|
|
51
28
|
end
|
|
29
|
+
|
|
30
|
+
target_type_class.define_singleton_method(method_name, &resolver)
|
|
31
|
+
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def directive_arg(name)
|
|
37
|
+
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def field_type_name
|
|
41
|
+
unwrapped = @field_def.type.respond_to?(:unwrap) ? @field_def.type.unwrap : @field_def.type
|
|
42
|
+
return unwrapped.graphql_name if unwrapped.respond_to?(:graphql_name) && unwrapped.graphql_name
|
|
43
|
+
return unwrapped.name if unwrapped.respond_to?(:name) && unwrapped.name
|
|
44
|
+
|
|
45
|
+
@field_def.type.to_type_signature.to_s.gsub(/[!\[\]]/, '')
|
|
52
46
|
end
|
|
53
47
|
end
|
|
@@ -2,31 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class GuardDirectiveResolver < BaseDirectiveResolver
|
|
12
|
-
def self.directive_name = 'guard'
|
|
13
|
-
def self.definition = 'directive @guard on FIELD_DEFINITION'
|
|
5
|
+
# @guard — require an authenticated user before the field resolves.
|
|
6
|
+
# Wraps the field's resolver; raises "Unauthenticated." when no user is
|
|
7
|
+
# present (per Config.authenticated_user). Mirrors PHP Lighthouse's @guard.
|
|
8
|
+
class Lighthouse::GraphQL::DirectiveResolvers::GuardDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
9
|
+
def self.directive_name = 'guard'
|
|
10
|
+
def self.definition = 'directive @guard on FIELD_DEFINITION'
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
def define_resolver(target_type_class)
|
|
13
|
+
field_name = @field_def.name
|
|
14
|
+
method_name = "resolve_field_#{field_name}"
|
|
15
|
+
inner = Lighthouse::GraphQL::DirectiveResolvers::Resolution.capture(target_type_class, method_name, @field_def, field_name)
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
wrapper = lambda do |obj, arguments, ctx|
|
|
18
|
+
raise(::GraphQL::ExecutionError, 'Unauthenticated.') unless Lighthouse::Support::Authorization.authenticated?(ctx)
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
target_type_class.define_singleton_method(method_name, &wrapper)
|
|
27
|
-
@field_def.resolve_proc = wrapper if @field_def.respond_to?(:resolve_proc=)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
20
|
+
inner.call(obj, arguments, ctx)
|
|
30
21
|
end
|
|
22
|
+
|
|
23
|
+
target_type_class.define_singleton_method(method_name, &wrapper)
|
|
24
|
+
@field_def.resolve_proc = wrapper if @field_def.respond_to?(:resolve_proc=)
|
|
31
25
|
end
|
|
32
26
|
end
|
|
@@ -2,34 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class MethodDirectiveResolver < BaseDirectiveResolver
|
|
15
|
-
def self.directive_name = 'method'
|
|
16
|
-
def self.definition = 'directive @method(name: String) on FIELD_DEFINITION'
|
|
5
|
+
# @method(name: "someMethod") — resolve the field by calling a method on the
|
|
6
|
+
# parent object. Mirrors PHP Lighthouse's @method. Defaults to the field's
|
|
7
|
+
# own (snake_cased) name when `name` is omitted.
|
|
8
|
+
#
|
|
9
|
+
# labels: [String!]! @method(name: "label_list")
|
|
10
|
+
# canReply: Boolean @method(name: "can_reply?")
|
|
11
|
+
class Lighthouse::GraphQL::DirectiveResolvers::MethodDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
12
|
+
def self.directive_name = 'method'
|
|
13
|
+
def self.definition = 'directive @method(name: String) on FIELD_DEFINITION'
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
def define_resolver(target_type_class)
|
|
16
|
+
resolver_method = "resolve_field_#{@field_def.name}"
|
|
17
|
+
target_method = (Lighthouse::Support::DirectiveArgs.literal(@directive_node, 'name') ||
|
|
18
|
+
Lighthouse::Support::Naming.attribute(@field_def.name)).to_s
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
resolver = lambda do |obj, _arguments, _ctx|
|
|
21
|
+
next nil if obj.nil?
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
target_type_class.define_singleton_method(resolver_method, &resolver)
|
|
30
|
-
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
23
|
+
obj.respond_to?(target_method) ? obj.public_send(target_method) : nil
|
|
33
24
|
end
|
|
25
|
+
|
|
26
|
+
target_type_class.define_singleton_method(resolver_method, &resolver)
|
|
27
|
+
@field_def.resolve_proc = resolver if @field_def.respond_to?(:resolve_proc=)
|
|
34
28
|
end
|
|
35
29
|
end
|