lighthouse-graphql 0.4.0 → 0.6.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1
  3. data/README.md +26 -15
  4. data/docs/caching.md +4 -3
  5. data/docs/configuration.md +16 -12
  6. data/docs/directives.md +294 -4
  7. data/docs/generators.md +84 -0
  8. data/docs/getting-started.md +93 -60
  9. data/docs/performance.md +2 -2
  10. data/docs/relationships.md +47 -4
  11. data/docs/relay.md +200 -0
  12. data/docs/resolvers.md +326 -34
  13. data/docs/security.md +73 -8
  14. data/docs/subscriptions.md +16 -9
  15. data/docs/types.md +218 -0
  16. data/docs/uploads.md +107 -0
  17. data/lib/generators/lighthouse/base_classes.rb +27 -49
  18. data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
  19. data/lib/generators/lighthouse/install/install_generator.rb +125 -45
  20. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +10 -8
  21. data/lib/lighthouse/async_mutation_job.rb +17 -0
  22. data/lib/lighthouse/caching.rb +9 -3
  23. data/lib/lighthouse/complexity.rb +5 -4
  24. data/lib/lighthouse/config.rb +12 -18
  25. data/lib/lighthouse/controller.rb +53 -12
  26. data/lib/lighthouse/dataloader_sources/preload_loader.rb +16 -0
  27. data/lib/lighthouse/dataloader_sources/relation_counts_loader.rb +46 -0
  28. data/lib/lighthouse/default_resolve_dispatcher.rb +26 -0
  29. data/lib/lighthouse/directive_resolvers/all_resolver.rb +1 -1
  30. data/lib/lighthouse/directive_resolvers/argument_binding.rb +61 -0
  31. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +220 -0
  32. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +2 -2
  33. data/lib/lighthouse/directive_resolvers/async_resolver.rb +90 -0
  34. data/lib/lighthouse/directive_resolvers/base.rb +63 -14
  35. data/lib/lighthouse/directive_resolvers/builder_applier.rb +89 -19
  36. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
  37. data/lib/lighthouse/directive_resolvers/can_resolvers.rb +1 -1
  38. data/lib/lighthouse/directive_resolvers/convention_resolver.rb +2 -1
  39. data/lib/lighthouse/directive_resolvers/count_resolver.rb +1 -1
  40. data/lib/lighthouse/directive_resolvers/field_resolver.rb +35 -7
  41. data/lib/lighthouse/directive_resolvers/first_resolver.rb +1 -1
  42. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +59 -0
  43. data/lib/lighthouse/directive_resolvers/lazy_load_resolver.rb +61 -0
  44. data/lib/lighthouse/directive_resolvers/limit_resolver.rb +59 -0
  45. data/lib/lighthouse/directive_resolvers/method_resolver.rb +38 -3
  46. data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +1 -1
  47. data/lib/lighthouse/directive_resolvers/namespaced_resolver.rb +31 -0
  48. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +1 -1
  49. data/lib/lighthouse/directive_resolvers/resolution.rb +19 -4
  50. data/lib/lighthouse/directive_resolvers/subscription_resolver.rb +2 -1
  51. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +136 -0
  52. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +87 -0
  53. data/lib/lighthouse/directive_resolvers/with_count_resolver.rb +43 -0
  54. data/lib/lighthouse/directive_resolvers/with_resolver.rb +42 -0
  55. data/lib/lighthouse/directives/arguments/more_filters.rb +215 -0
  56. data/lib/lighthouse/directives/definitions.rb +52 -0
  57. data/lib/lighthouse/directives/model_directive.rb +131 -0
  58. data/lib/lighthouse/directives/node_directive.rb +134 -0
  59. data/lib/lighthouse/directives/pagination_manipulator.rb +1 -1
  60. data/lib/lighthouse/directives/registry.rb +21 -21
  61. data/lib/lighthouse/directives/visibility_directive.rb +71 -0
  62. data/lib/lighthouse/enum_directive.rb +41 -0
  63. data/lib/lighthouse/field_resolver_stack.rb +56 -6
  64. data/lib/lighthouse/generated_resolvers.rb +13 -8
  65. data/lib/lighthouse/global_id.rb +57 -0
  66. data/lib/lighthouse/graphql/version.rb +1 -1
  67. data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
  68. data/lib/lighthouse/multipart_request.rb +43 -0
  69. data/lib/lighthouse/node_registry.rb +68 -0
  70. data/lib/lighthouse/pagination.rb +3 -0
  71. data/lib/lighthouse/rb_lighthouse.rb +34 -0
  72. data/lib/lighthouse/relation_counts.rb +29 -0
  73. data/lib/lighthouse/resolve_info.rb +80 -0
  74. data/lib/lighthouse/resolver_bases.rb +25 -4
  75. data/lib/lighthouse/resolver_dispatch.rb +51 -11
  76. data/lib/lighthouse/root_types.rb +117 -0
  77. data/lib/lighthouse/scalar_coercion.rb +72 -0
  78. data/lib/lighthouse/schema_cache.rb +2 -1
  79. data/lib/lighthouse/schema_generator.rb +13 -11
  80. data/lib/lighthouse/schema_implementation.rb +4 -8
  81. data/lib/lighthouse/support/environment.rb +13 -0
  82. data/lib/lighthouse/support/model_resolver.rb +7 -5
  83. data/lib/lighthouse/support/preload.rb +49 -0
  84. data/lib/lighthouse/type.rb +103 -0
  85. data/lib/lighthouse/type_resolution.rb +150 -0
  86. data/lib/lighthouse/upload.rb +28 -0
  87. data/lib/lighthouse/upload_store.rb +57 -0
  88. data/sig/lighthouse/directives.rbs +3 -2
  89. data/sig/lighthouse.rbs +10 -2
  90. metadata +40 -4
  91. data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -22
  92. data/lib/generators/lighthouse/mutation_generator.rb +0 -31
  93. data/lib/generators/lighthouse/query_generator.rb +0 -30
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Eager loading for @with and @lazyLoad: ActiveRecord's preloader, over records
4
+ # of any classes, with relation names as PHP Lighthouse writes them
5
+ # ("comments.author" is comments, and each comment's author).
6
+ module Lighthouse::Support::Preload
7
+ module_function
8
+
9
+ # ["comments.author", "tags"] -> [{ comments: :author }, :tags]
10
+ def associations(names)
11
+ Array(names).map do |name|
12
+ *path, last = name.to_s.split('.').map(&:to_sym)
13
+ path.reverse.reduce(last) { |nested, segment| { segment => nested } }
14
+ end
15
+ end
16
+
17
+ # Load `associations` into `records`, grouped by class; a class that has no
18
+ # such association is left alone. `scopes` (model scope names) narrow the
19
+ # loaded records, as Laravel's constrained eager loads do.
20
+ def records(records, associations, scopes = [])
21
+ records.grep(::ActiveRecord::Base).group_by(&:class).each do |klass, group|
22
+ own = associations.select { |association| klass.reflect_on_association(root(association)) }
23
+ own.each { |association| preload(klass, group, association, scopes) }
24
+ end
25
+ records
26
+ end
27
+
28
+ def root(association)
29
+ association.is_a?(Hash) ? association.keys.first : association
30
+ end
31
+
32
+ def preload(klass, records, association, scopes)
33
+ scope = scoped(klass, association, scopes)
34
+ preloader = ::ActiveRecord::Associations::Preloader
35
+ if preloader.method_defined?(:call)
36
+ preloader.new(records: records, associations: association, scope: scope).call # Rails >= 7
37
+ else
38
+ preloader.new.preload(records, association, scope) # Rails 6
39
+ end
40
+ end
41
+
42
+ # The scopes applied to the association's model, or nil for none.
43
+ def scoped(klass, association, scopes)
44
+ return if scopes.blank?
45
+
46
+ target = klass.reflect_on_association(root(association)).klass
47
+ scopes.reduce(target.all) { |relation, scope| relation.public_send(scope) }
48
+ end
49
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ # Hands a graphql-ruby type class to Lighthouse, as `include
6
+ # Lighthouse::GraphQL::Subscription` does a subscription. The class keeps
7
+ # graphql-ruby's folder structure and naming (app/graphql/types/post_type.rb,
8
+ # Types::PostType for the SDL's Post type) and base classes, and does for the
9
+ # SDL type of its name what it does in a graphql-ruby schema:
10
+ #
11
+ # class Types::PostType < Types::BaseObject # an object type
12
+ # include Lighthouse::GraphQL::Type
13
+ #
14
+ # def reading_time = { words: object.body.to_s.split.size }
15
+ # end
16
+ #
17
+ # class Types::SearchResultType < Types::BaseUnion # a union
18
+ # include Lighthouse::GraphQL::Type
19
+ #
20
+ # def self.resolve_type(object, context) = object.is_a?(Post) ? 'Post' : 'User'
21
+ # end
22
+ #
23
+ # module Types::NamedType # an interface
24
+ # include Types::BaseInterface
25
+ # include Lighthouse::GraphQL::Type
26
+ #
27
+ # definition_methods do
28
+ # def resolve_type(object, context) = ...
29
+ # end
30
+ # end
31
+ #
32
+ # class Types::DateTimeType < Types::BaseScalar # a scalar
33
+ # include Lighthouse::GraphQL::Type
34
+ #
35
+ # def self.coerce_input(value, context) = Time.iso8601(value)
36
+ # def self.coerce_result(value, context) = value.iso8601
37
+ # end
38
+ #
39
+ # An object type's methods resolve its fields that have no resolving
40
+ # directive, with graphql-ruby's `object` (the parent), `context`, the
41
+ # `dataload*` helpers, and the field's arguments as snake_case keywords. A
42
+ # union's or interface's `resolve_type` picks the object type of a value (a
43
+ # type name, or a type class such as Types::PostType). A scalar's
44
+ # `coerce_input` / `coerce_result` convert its values. The SDL still declares
45
+ # the types and their fields: the class only lends its behaviour, so `field`
46
+ # declarations in it are ignored, and an object type's `authorized?` does not run.
47
+ #
48
+ # The model an object type resolves is the SDL's to say, with @model(class:).
49
+ #
50
+ # A Types::<Name>Type that does not include the concern is not Lighthouse's,
51
+ # and is left alone.
52
+ module Lighthouse::GraphQL::Type
53
+ extend ActiveSupport::Concern
54
+
55
+ # The kinds of graphql-ruby class the concern can be included in.
56
+ KINDS = {
57
+ object: ::GraphQL::Schema::Object,
58
+ union: ::GraphQL::Schema::Union,
59
+ scalar: ::GraphQL::Schema::Scalar,
60
+ interface: ::GraphQL::Schema::Interface
61
+ }.freeze
62
+
63
+ # :object, :union, :scalar or :interface for a graphql-ruby type class or
64
+ # interface module; nil for anything else.
65
+ def self.kind_of(mod)
66
+ return :interface if !mod.is_a?(Class) && mod.is_a?(Module) && mod.include?(KINDS[:interface])
67
+ return unless mod.is_a?(Class)
68
+
69
+ %i[object union scalar].find { |kind| mod < KINDS[kind] }
70
+ end
71
+
72
+ # The class of the SDL type `type_name` (`Types::PostType` for `Post`) in
73
+ # Config.namespaces[:types], when it includes the concern; nil otherwise. The
74
+ # class must be of the same `kind` as the SDL type.
75
+ def self.class_for(type_name, kind)
76
+ klass = Lighthouse::Support::ModelResolver.constantize("#{type_name}Type", namespaces: Lighthouse::Config.namespaces[:types],
77
+ fallback: false)
78
+ return unless klass.is_a?(Module) && klass.include?(self) && kind_of(klass)
79
+ return klass if kind_of(klass) == kind
80
+
81
+ raise Lighthouse::DefinitionError, "#{klass} is a type class of kind #{kind_of(klass)}, but #{type_name} is of kind #{kind} in the schema."
82
+ end
83
+
84
+ # What the SDL's types take from their classes and directives, once the
85
+ # schema is built: every @model names a model (ModelDirective), every @union /
86
+ # @interface class exists and every type class matches its kind and base
87
+ # (TypeResolution), and every scalar with a class reads and writes its values
88
+ # through it (ScalarCoercion). `bases`: the schema's base_types, by kind.
89
+ def self.apply(schema, bases = {})
90
+ Lighthouse::GraphQL::Directives::ModelDirective.validate!(schema)
91
+ Lighthouse::GraphQL::TypeResolution.validate!(schema, bases)
92
+ Lighthouse::GraphQL::ScalarCoercion.apply(schema, bases[:scalar])
93
+ Lighthouse::GraphQL::EnumDirective.apply(schema)
94
+ end
95
+
96
+ included do
97
+ unless Lighthouse::GraphQL::Type.kind_of(self)
98
+ raise Lighthouse::DefinitionError,
99
+ "#{self} includes Lighthouse::GraphQL::Type, which is for graphql-ruby types: a subclass of GraphQL::Schema::Object, " \
100
+ 'Union or Scalar, or a module including GraphQL::Schema::Interface (class Types::PostType < Types::BaseObject).'
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Which object type a value of a union or interface is: graphql-ruby asks the
4
+ # schema (its `resolve_type`) each time a field returns one. In order:
5
+ #
6
+ # 1. `@union(resolveType: "Name")` / `@interface(resolveType: "Name")` on the
7
+ # SDL type names a class, looked up in Config.namespaces[:types] (nested
8
+ # names too) and then as written, with `resolve_type(object, context)` or
9
+ # `call(object, context)`. PHP Lighthouse's directives.
10
+ # 2. Its type class, Types::<Name>Type including Lighthouse::GraphQL::Type: a
11
+ # union's `self.resolve_type(object, context)`, or an interface's
12
+ # `definition_methods { def resolve_type(object, context) }`, as in graphql-ruby.
13
+ # 3. The default, as PHP Lighthouse's: a Hash's `__typename`; else the type
14
+ # named after the object's class, or one of its superclasses (STI); else the
15
+ # type whose @model names the object's model (`type Article @model(class: "Post")`).
16
+ #
17
+ # A class returns a type name, the SDL type, or a type class (Types::PostType),
18
+ # which stands for the SDL type of its graphql_name; or, as graphql-ruby allows,
19
+ # [type, value] to resolve the type's fields on `value`. nil falls through to the
20
+ # next step. A value no step resolves, or one resolved to a type the union or
21
+ # interface does not include, raises Lighthouse::DefinitionError.
22
+ module Lighthouse::GraphQL::TypeResolution
23
+ DEFINITIONS = {
24
+ 'union' => <<~GRAPHQL.chomp,
25
+ """
26
+ The class that picks the object type of this union's values: `resolve_type(object, context)`
27
+ or `call(object, context)`, returning a type name or a type class.
28
+ """
29
+ directive @union(resolveType: String!) on UNION
30
+ GRAPHQL
31
+ 'interface' => <<~GRAPHQL.chomp
32
+ """
33
+ The class that picks the object type of this interface's values: `resolve_type(object, context)`
34
+ or `call(object, context)`, returning a type name or a type class.
35
+ """
36
+ directive @interface(resolveType: String!) on INTERFACE
37
+ GRAPHQL
38
+ }.freeze
39
+
40
+ DIRECTIVES = DEFINITIONS.keys.freeze
41
+
42
+ module_function
43
+
44
+ # The object type, or [object type, value] when a class answered with a
45
+ # value to resolve the fields on instead of `object` (as graphql-ruby allows).
46
+ def resolve(schema, abstract_type, object, context)
47
+ answer = custom(abstract_type, object, context)
48
+ answer, value = answer if answer.is_a?(Array) && answer.size == 2
49
+ type = type_named(schema, answer) || default(schema, abstract_type, object)
50
+ unless type && schema.possible_types(abstract_type).include?(type)
51
+ raise Lighthouse::DefinitionError, unresolved_message(schema, abstract_type, object, type)
52
+ end
53
+
54
+ value.nil? ? type : [type, value]
55
+ end
56
+
57
+ # What the directive's class or the type class says, or nil.
58
+ def custom(abstract_type, object, context)
59
+ resolver = directive_class(abstract_type) || type_class(abstract_type)
60
+ return unless resolver
61
+
62
+ resolver.respond_to?(:resolve_type) ? resolver.resolve_type(object, context) : resolver.call(object, context)
63
+ end
64
+
65
+ def default(schema, abstract_type, object)
66
+ candidates = schema.possible_types(abstract_type)
67
+ by_name = candidates.index_by(&:graphql_name)
68
+
69
+ typename = typename_of(object)
70
+ return by_name[typename] if typename
71
+
72
+ class_chain(object.class).each { |name| return by_name[name] if by_name.key?(name) }
73
+ candidates.find { |type| model_of(type)&.then { |model| object.is_a?(model) } }
74
+ end
75
+
76
+ # Fail at build time, not on the first query: every class a @union /
77
+ # @interface names exists and answers, and every type class matches its kind.
78
+ # `bases`: the schema's base_types, by kind, that type classes inherit from.
79
+ def validate!(schema, bases = {})
80
+ schema.types.each_value do |type|
81
+ next unless type.kind.union? || type.kind.interface?
82
+
83
+ directive_class(type)
84
+ kind = type.kind.union? ? :union : :interface
85
+ klass = Lighthouse::GraphQL::Type.class_for(type.graphql_name, kind)
86
+ Lighthouse::GraphQL::ResolverBases.check!(klass, bases[kind], "the #{type.graphql_name} #{kind}") if klass
87
+ end
88
+ end
89
+
90
+ # The SDL type a resolver's answer stands for: a name, a type, or a type class.
91
+ def type_named(schema, answer)
92
+ return if answer.nil?
93
+
94
+ schema.get_type(answer.respond_to?(:graphql_name) ? answer.graphql_name : answer.to_s)
95
+ end
96
+
97
+ def directive_class(abstract_type)
98
+ directive, name = resolve_type_directive(abstract_type)
99
+ return unless name
100
+
101
+ klass = Lighthouse::Support::ModelResolver.constantize(name, namespaces: Lighthouse::Config.namespaces[:types], nested: true)
102
+ return klass if klass.respond_to?(:resolve_type) || klass.respond_to?(:call)
103
+
104
+ raise Lighthouse::DefinitionError,
105
+ "@#{directive.name}(resolveType: #{name.inspect}) on #{abstract_type.graphql_name} names no class with resolve_type(object, context) " \
106
+ 'or call(object, context).'
107
+ end
108
+
109
+ # The @union / @interface on the type and the class name it gives, or nil.
110
+ def resolve_type_directive(abstract_type)
111
+ directive = (abstract_type.ast_node&.directives || []).find { |d| DIRECTIVES.include?(d.name) }
112
+ [directive, Lighthouse::Support::DirectiveArgs.literal(directive, 'resolveType')] if directive
113
+ end
114
+
115
+ def type_class(abstract_type)
116
+ kind = abstract_type.kind.union? ? :union : :interface
117
+ klass = Lighthouse::GraphQL::Type.class_for(abstract_type.graphql_name, kind)
118
+ klass if klass.respond_to?(:resolve_type)
119
+ end
120
+
121
+ def typename_of(object)
122
+ return unless object.is_a?(Hash)
123
+
124
+ (object['__typename'] || object[:__typename])&.to_s
125
+ end
126
+
127
+ # Admin, User, ApplicationRecord, ... for an Admin < User record.
128
+ def class_chain(klass)
129
+ names = []
130
+ while klass
131
+ names << klass.name.demodulize if klass.name
132
+ klass = klass.superclass
133
+ end
134
+ names
135
+ end
136
+
137
+ def model_of(type)
138
+ Lighthouse::GraphQL::Directives::ModelDirective.declared_model(type)
139
+ end
140
+
141
+ def unresolved_message(schema, abstract_type, object, type)
142
+ possible = schema.possible_types(abstract_type).map(&:graphql_name).join(' | ')
143
+ kind = abstract_type.kind.union? ? 'union' : 'interface'
144
+ return "#{abstract_type.graphql_name} resolved a #{object.class} to #{type.graphql_name}, which is not one of its types (#{possible})." if type
145
+
146
+ "Lighthouse could not tell the type of a #{object.class} in the #{kind} #{abstract_type.graphql_name} (#{possible}): " \
147
+ "name a type after its class, map one to it with @model(class: \"#{object.class}\"), or resolve it in " \
148
+ "Types::#{abstract_type.graphql_name}Type.resolve_type or with @#{kind}(resolveType:)."
149
+ end
150
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Upload` scalar: a file a client sends with a multipart request (the
4
+ # GraphQL multipart request specification, MultipartRequest). `scalar Upload`
5
+ # in the SDL reads its values through it unless the SDL or a type class names
6
+ # another class. The resolver gets the uploaded file (an
7
+ # ActionDispatch::Http::UploadedFile in Rails): attach it to a model
8
+ # (`avatar: Upload` with @create on a model with has_one_attached :avatar), or
9
+ # store it with @upload.
10
+ class Lighthouse::GraphQL::Upload < GraphQL::Schema::Scalar
11
+ graphql_name 'Upload'
12
+
13
+ def self.coerce_input(value, _context)
14
+ return value if file?(value)
15
+
16
+ raise ::GraphQL::CoercionError,
17
+ 'Could not get uploaded file, be sure to conform to GraphQL multipart request specification: ' \
18
+ "https://github.com/jaydenseric/graphql-multipart-request-spec. Instead got: #{value.inspect}."
19
+ end
20
+
21
+ def self.coerce_result(_value, _context)
22
+ raise ::GraphQL::Error, '"Upload" cannot be serialized, it can only be used as an argument.'
23
+ end
24
+
25
+ def self.file?(value)
26
+ value.respond_to?(:read) && (value.respond_to?(:original_filename) || value.respond_to?(:path))
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Where @upload puts a file, as PHP Lighthouse's @upload puts it on one of the
4
+ # app's Laravel disks: an ActiveStorage blob on one of the app's services
5
+ # (config/storage.yml). `disk` names the service (config.active_storage.service
6
+ # without it), `path` prefixes the blob's key. ActiveStorage sets visibility per
7
+ # service (`public: true` in storage.yml), not per file, so `public` must agree
8
+ # with the service's: the schema never says a file is private that anyone can
9
+ # read, or public that no one can. The argument then holds the blob's signed id,
10
+ # which a has_one_attached / has_many_attached attribute attaches (where Laravel
11
+ # hands the stored path, for a model to keep). To store files elsewhere, declare
12
+ # an ActiveStorage service for it, as a Laravel app declares a disk.
13
+ module Lighthouse::GraphQL::UploadStore
14
+ module_function
15
+
16
+ def call(file, argument:, disk: nil, path: nil, public: nil)
17
+ unless defined?(::ActiveStorage::Blob)
18
+ raise Lighthouse::DefinitionError, '@upload stores files with ActiveStorage: install it (bin/rails active_storage:install).'
19
+ end
20
+ unless Lighthouse::GraphQL::Upload.file?(file)
21
+ raise Lighthouse::DefinitionError, "Expected argument `#{argument}` to be an uploaded file (Upload)."
22
+ end
23
+
24
+ service = service(argument, disk, public == true)
25
+ file.rewind if file.respond_to?(:rewind) # a @validator, or another argument the multipart map sent it to, may have read it
26
+ ::ActiveStorage::Blob.create_and_upload!(
27
+ io: file, filename: filename(file), content_type: file.try(:content_type), key: key(path), service_name: service.name
28
+ ).signed_id
29
+ end
30
+
31
+ # The service `disk` names, or the default one, checked to be as public as asked.
32
+ def service(argument, disk, public)
33
+ service = disk ? ::ActiveStorage::Blob.services.fetch(disk) : ::ActiveStorage::Blob.service
34
+ return service if (service.public? == true) == public
35
+
36
+ raise Lighthouse::DefinitionError, visibility_mismatch(argument, service.name, public)
37
+ rescue KeyError => e
38
+ raise Lighthouse::DefinitionError, "@upload on `#{argument}`: #{e.message}"
39
+ end
40
+
41
+ def visibility_mismatch(argument, service_name, public)
42
+ fix = public ? 'name a public one (public: true in config/storage.yml) with disk:' : 'pass public: true, or name a private one with disk:'
43
+ "@upload#{'(public: true)' if public} on `#{argument}` stores on the ActiveStorage service `#{service_name}`, " \
44
+ "which is #{'not ' if public}public: #{fix}."
45
+ end
46
+
47
+ def filename(file)
48
+ file.respond_to?(:original_filename) ? file.original_filename : File.basename(file.path)
49
+ end
50
+
51
+ # `path`'s directory, then a fresh token, as Laravel's `storeAs(path, hashName)`;
52
+ # under the root ("/", the default), ActiveStorage's own key.
53
+ def key(path)
54
+ prefix = path.to_s.delete_prefix('/').delete_suffix('/')
55
+ prefix.empty? ? nil : "#{prefix}/#{::ActiveStorage::Blob.generate_unique_secure_token}"
56
+ end
57
+ end
@@ -104,8 +104,9 @@ module Lighthouse
104
104
  # Install the field's resolver on `target_type_class` (a graphql-ruby type class).
105
105
  def define_resolver: (untyped target_type_class) -> untyped
106
106
 
107
- def apply_where_filters: (relation relation, untyped args) -> relation
108
- def apply_arg_builders: (relation relation, arguments args_h) -> relation
107
+ # `ctx`: the query's context, which @builder on an argument receives.
108
+ def apply_where_filters: (relation relation, untyped args, ?context? ctx) -> relation
109
+ def apply_arg_builders: (relation relation, arguments args_h, ?context? ctx) -> relation
109
110
  def extract_relation_name_from_directive: (untyped arg_node) -> String?
110
111
  def constantize_model: (String? name) -> Class?
111
112
  def apply_scopes: (relation relation) -> relation
data/sig/lighthouse.rbs CHANGED
@@ -39,6 +39,12 @@ module Lighthouse
39
39
  def call: (StandardError error, context? context) -> void
40
40
  end
41
41
 
42
+ # Config.global_id: encodes and decodes Relay global ids (@globalId, @node).
43
+ interface _GlobalId
44
+ def encode: (String type, untyped id) -> String
45
+ def decode: (String global_id) -> Array[String]
46
+ end
47
+
42
48
  # Config.authenticated_user and Config.policy_user: the user of a request.
43
49
  interface _UserReader
44
50
  def call: (context? context) -> untyped
@@ -100,8 +106,6 @@ module Lighthouse
100
106
  def self.directive_map: () -> Hash[untyped, untyped]
101
107
 
102
108
  # An ActiveSupport::Cache::Store; Rails.cache by default.
103
- def self.cache_store: () -> untyped
104
- def self.cache_store=: (untyped store) -> untyped
105
109
 
106
110
  def self.transactional_mutations: () -> bool
107
111
  def self.transactional_mutations=: (bool? enabled) -> bool?
@@ -132,6 +136,10 @@ module Lighthouse
132
136
  def self.convert_empty_strings_to_null: () -> bool
133
137
  def self.convert_empty_strings_to_null=: (bool? enabled) -> bool?
134
138
 
139
+ def self.global_id: () -> _GlobalId
140
+ def self.global_id=: (_GlobalId? codec) -> _GlobalId?
141
+
142
+
135
143
  # Whether @broadcast triggers in an ActiveJob (Lighthouse::GraphQL::BroadcastJob)
136
144
  # unless the directive says shouldQueue; and the job's queue (nil: ActiveJob's default).
137
145
  def self.queue_broadcasts: () -> bool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lighthouse-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vaz Innovation
@@ -62,29 +62,32 @@ files:
62
62
  - docs/events.md
63
63
  - docs/federation.md
64
64
  - docs/filtering-and-ordering.md
65
+ - docs/generators.md
65
66
  - docs/getting-started.md
66
67
  - docs/motivation.md
67
68
  - docs/mutations.md
68
69
  - docs/performance.md
69
70
  - docs/relationships.md
71
+ - docs/relay.md
70
72
  - docs/resolvers.md
71
73
  - docs/security.md
72
74
  - docs/subscriptions.md
75
+ - docs/types.md
76
+ - docs/uploads.md
73
77
  - docs/validation.md
74
78
  - lib/generators/lighthouse/base_classes.rb
75
79
  - lib/generators/lighthouse/builder_generator.rb
76
80
  - lib/generators/lighthouse/class_generator.rb
81
+ - lib/generators/lighthouse/install/graphql_ruby_install.rb
77
82
  - lib/generators/lighthouse/install/install_generator.rb
78
83
  - lib/generators/lighthouse/install/templates/graphql.config.yml.tt
79
84
  - lib/generators/lighthouse/install/templates/graphql_controller.rb.tt
80
85
  - lib/generators/lighthouse/install/templates/initializer.rb.tt
81
86
  - lib/generators/lighthouse/install/templates/schema.graphql.tt
82
- - lib/generators/lighthouse/install/templates/schema.rb.tt
83
- - lib/generators/lighthouse/mutation_generator.rb
84
- - lib/generators/lighthouse/query_generator.rb
85
87
  - lib/generators/lighthouse/subscription_generator.rb
86
88
  - lib/generators/lighthouse/validator_generator.rb
87
89
  - lib/lighthouse-graphql.rb
90
+ - lib/lighthouse/async_mutation_job.rb
88
91
  - lib/lighthouse/broadcast_job.rb
89
92
  - lib/lighthouse/broadcasting.rb
90
93
  - lib/lighthouse/cache_control.rb
@@ -101,11 +104,17 @@ files:
101
104
  - lib/lighthouse/controller.rb
102
105
  - lib/lighthouse/dataloader_sources/association_loader.rb
103
106
  - lib/lighthouse/dataloader_sources/model_loader.rb
107
+ - lib/lighthouse/dataloader_sources/preload_loader.rb
104
108
  - lib/lighthouse/dataloader_sources/relation_count_loader.rb
109
+ - lib/lighthouse/dataloader_sources/relation_counts_loader.rb
105
110
  - lib/lighthouse/dataloader_sources/relation_page_loader.rb
111
+ - lib/lighthouse/default_resolve_dispatcher.rb
106
112
  - lib/lighthouse/directive_resolvers/aggregate_resolver.rb
107
113
  - lib/lighthouse/directive_resolvers/all_resolver.rb
114
+ - lib/lighthouse/directive_resolvers/argument_binding.rb
115
+ - lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb
108
116
  - lib/lighthouse/directive_resolvers/association_resolvers.rb
117
+ - lib/lighthouse/directive_resolvers/async_resolver.rb
109
118
  - lib/lighthouse/directive_resolvers/auth_resolver.rb
110
119
  - lib/lighthouse/directive_resolvers/base.rb
111
120
  - lib/lighthouse/directive_resolvers/broadcast_resolver.rb
@@ -119,25 +128,39 @@ files:
119
128
  - lib/lighthouse/directive_resolvers/field_resolver.rb
120
129
  - lib/lighthouse/directive_resolvers/find_resolver.rb
121
130
  - lib/lighthouse/directive_resolvers/first_resolver.rb
131
+ - lib/lighthouse/directive_resolvers/global_id_resolver.rb
122
132
  - lib/lighthouse/directive_resolvers/guard_resolver.rb
133
+ - lib/lighthouse/directive_resolvers/lazy_load_resolver.rb
134
+ - lib/lighthouse/directive_resolvers/limit_resolver.rb
123
135
  - lib/lighthouse/directive_resolvers/method_resolver.rb
124
136
  - lib/lighthouse/directive_resolvers/model_mutation_resolver.rb
137
+ - lib/lighthouse/directive_resolvers/namespaced_resolver.rb
125
138
  - lib/lighthouse/directive_resolvers/paginate_resolver.rb
126
139
  - lib/lighthouse/directive_resolvers/rename_resolver.rb
127
140
  - lib/lighthouse/directive_resolvers/resolution.rb
128
141
  - lib/lighthouse/directive_resolvers/soft_delete_resolvers.rb
129
142
  - lib/lighthouse/directive_resolvers/subscription_resolver.rb
143
+ - lib/lighthouse/directive_resolvers/throttle_resolver.rb
144
+ - lib/lighthouse/directive_resolvers/type_method_resolver.rb
130
145
  - lib/lighthouse/directive_resolvers/validator_resolver.rb
131
146
  - lib/lighthouse/directive_resolvers/where_conditions_applier.rb
147
+ - lib/lighthouse/directive_resolvers/with_count_resolver.rb
148
+ - lib/lighthouse/directive_resolvers/with_resolver.rb
132
149
  - lib/lighthouse/directives/arguments/base.rb
133
150
  - lib/lighthouse/directives/arguments/filters.rb
151
+ - lib/lighthouse/directives/arguments/more_filters.rb
134
152
  - lib/lighthouse/directives/arguments/order_by.rb
135
153
  - lib/lighthouse/directives/arguments/soft_deletes.rb
154
+ - lib/lighthouse/directives/definitions.rb
155
+ - lib/lighthouse/directives/model_directive.rb
156
+ - lib/lighthouse/directives/node_directive.rb
136
157
  - lib/lighthouse/directives/pagination_info_types.rb
137
158
  - lib/lighthouse/directives/pagination_manipulator.rb
138
159
  - lib/lighthouse/directives/registry.rb
139
160
  - lib/lighthouse/directives/soft_deletes_directive.rb
161
+ - lib/lighthouse/directives/visibility_directive.rb
140
162
  - lib/lighthouse/directives/where_conditions_directive.rb
163
+ - lib/lighthouse/enum_directive.rb
141
164
  - lib/lighthouse/errors.rb
142
165
  - lib/lighthouse/events.rb
143
166
  - lib/lighthouse/events/execution_trace.rb
@@ -147,10 +170,12 @@ files:
147
170
  - lib/lighthouse/federation_directives.rb
148
171
  - lib/lighthouse/field_resolver_stack.rb
149
172
  - lib/lighthouse/generated_resolvers.rb
173
+ - lib/lighthouse/global_id.rb
150
174
  - lib/lighthouse/graphql.rb
151
175
  - lib/lighthouse/graphql/version.rb
152
176
  - lib/lighthouse/graphql_ruby_patches.rb
153
177
  - lib/lighthouse/ide_helper.rb
178
+ - lib/lighthouse/multipart_request.rb
154
179
  - lib/lighthouse/namespaces.rb
155
180
  - lib/lighthouse/nested_mutations.rb
156
181
  - lib/lighthouse/nested_mutations/belongs_to_operations.rb
@@ -158,6 +183,7 @@ files:
158
183
  - lib/lighthouse/nested_mutations/has_operations.rb
159
184
  - lib/lighthouse/nested_mutations/many_to_many_operations.rb
160
185
  - lib/lighthouse/nested_mutations/partitioner.rb
186
+ - lib/lighthouse/node_registry.rb
161
187
  - lib/lighthouse/pagination.rb
162
188
  - lib/lighthouse/persisted_queries.rb
163
189
  - lib/lighthouse/persisted_queries/batch.rb
@@ -165,8 +191,12 @@ files:
165
191
  - lib/lighthouse/rb_lighthouse.rb
166
192
  - lib/lighthouse/reference_resolver.rb
167
193
  - lib/lighthouse/relation_batch.rb
194
+ - lib/lighthouse/relation_counts.rb
195
+ - lib/lighthouse/resolve_info.rb
168
196
  - lib/lighthouse/resolver_bases.rb
169
197
  - lib/lighthouse/resolver_dispatch.rb
198
+ - lib/lighthouse/root_types.rb
199
+ - lib/lighthouse/scalar_coercion.rb
170
200
  - lib/lighthouse/schema_cache.rb
171
201
  - lib/lighthouse/schema_factory.rb
172
202
  - lib/lighthouse/schema_generator.rb
@@ -177,10 +207,16 @@ files:
177
207
  - lib/lighthouse/support/authorization.rb
178
208
  - lib/lighthouse/support/batching.rb
179
209
  - lib/lighthouse/support/directive_args.rb
210
+ - lib/lighthouse/support/environment.rb
180
211
  - lib/lighthouse/support/model_resolver.rb
181
212
  - lib/lighthouse/support/naming.rb
213
+ - lib/lighthouse/support/preload.rb
182
214
  - lib/lighthouse/support/soft_deletes.rb
183
215
  - lib/lighthouse/tasks/lighthouse.rake
216
+ - lib/lighthouse/type.rb
217
+ - lib/lighthouse/type_resolution.rb
218
+ - lib/lighthouse/upload.rb
219
+ - lib/lighthouse/upload_store.rb
184
220
  - lib/lighthouse/where_conditions/operator_map.rb
185
221
  - sig/lighthouse.rbs
186
222
  - sig/lighthouse/controller.rbs
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Executable schema built by Lighthouse from the SDL in <%= sdl_folder %>/. The
4
- # Dataloader batches relationship loads (@hasMany, @belongsTo, ...) to avoid N+1.
5
- # base_types names the base classes: every class a Query, Mutation or Subscription
6
- # field resolves to must inherit from its kind's, and a Subscription field with no
7
- # class of its own resolves with the subscription one.
8
- <%= schema_name %> = Lighthouse::GraphQL::RbLightHouse.get_schema(
9
- options: {
10
- using: { GraphQL::Dataloader => {} },
11
- base_types: {
12
- resolver: <%= base_class_constant(:queries) %>,
13
- mutation: <%= base_class_constant(:mutations) %>,
14
- subscription: <%= base_class_constant(:subscriptions) %>
15
- # object: Types::BaseObject, ... # graphql-ruby's base classes of the SDL's types
16
- }
17
- }
18
- )
19
-
20
- # Refuse expensive queries before they run (see the gem's docs/security.md):
21
- # <%= schema_name %>.max_complexity(300)
22
- # <%= schema_name %>.max_depth(12)
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'class_generator'
4
-
5
- # `rails generate lighthouse:mutation PublishPost` — the class (a
6
- # Mutations::BaseMutation, so a GraphQL::Schema::Mutation) that `Mutation.publishPost`
7
- # resolves to by convention.
8
- class Lighthouse::Generators::MutationGenerator < Lighthouse::Generators::ClassGenerator
9
- desc 'Generate the class of a Mutation field (a Mutations::BaseMutation) in app/graphql/mutations.'
10
-
11
- private
12
-
13
- def kind = :mutations
14
-
15
- def body
16
- <<~RUBY
17
- # Resolves Mutation.#{class_basename.camelize(:lower)}. Keyword arguments are the field's
18
- # arguments in snake_case; `object` and `context` are available. Raise
19
- # GraphQL::ExecutionError for failures the client should see.
20
- class #{class_basename} < #{superclass_name}
21
- def resolve(**args)
22
- raise NotImplementedError
23
- end
24
- end
25
- RUBY
26
- end
27
-
28
- def usage
29
- "Mutation.#{class_basename.camelize(:lower)} resolves to #{full_class_name} — add the field to your SDL without a resolving directive."
30
- end
31
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'class_generator'
4
-
5
- # `rails generate lighthouse:query LatestPosts` — the resolver (a
6
- # Resolvers::BaseResolver, so a GraphQL::Schema::Resolver) that `Query.latestPosts`
7
- # resolves to by convention.
8
- class Lighthouse::Generators::QueryGenerator < Lighthouse::Generators::ClassGenerator
9
- desc 'Generate the resolver of a Query field (a Resolvers::BaseResolver) in app/graphql/resolvers.'
10
-
11
- private
12
-
13
- def kind = :queries
14
-
15
- def body
16
- <<~RUBY
17
- # Resolves Query.#{class_basename.camelize(:lower)}. Keyword arguments are the field's
18
- # arguments in snake_case; `object` and `context` are available.
19
- class #{class_basename} < #{superclass_name}
20
- def resolve(**args)
21
- raise NotImplementedError
22
- end
23
- end
24
- RUBY
25
- end
26
-
27
- def usage
28
- "Query.#{class_basename.camelize(:lower)} resolves to #{full_class_name} — add the field to your SDL without a resolving directive."
29
- end
30
- end