lighthouse-graphql 0.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -1
  3. data/README.md +24 -16
  4. data/docs/caching.md +4 -3
  5. data/docs/configuration.md +1 -21
  6. data/docs/directives.md +14 -7
  7. data/docs/generators.md +41 -39
  8. data/docs/getting-started.md +88 -51
  9. data/docs/performance.md +2 -2
  10. data/docs/relationships.md +5 -4
  11. data/docs/relay.md +63 -0
  12. data/docs/resolvers.md +90 -37
  13. data/docs/security.md +64 -16
  14. data/docs/subscriptions.md +16 -9
  15. data/docs/types.md +16 -17
  16. data/docs/uploads.md +41 -14
  17. data/lib/generators/lighthouse/base_classes.rb +9 -14
  18. data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
  19. data/lib/generators/lighthouse/install/install_generator.rb +112 -91
  20. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +3 -9
  21. data/lib/lighthouse/caching.rb +9 -3
  22. data/lib/lighthouse/config.rb +6 -23
  23. data/lib/lighthouse/controller.rb +28 -5
  24. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +18 -18
  25. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
  26. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +3 -1
  27. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +85 -26
  28. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +12 -6
  29. data/lib/lighthouse/directives/arguments/more_filters.rb +1 -1
  30. data/lib/lighthouse/field_resolver_stack.rb +9 -6
  31. data/lib/lighthouse/global_id.rb +29 -3
  32. data/lib/lighthouse/graphql/version.rb +1 -1
  33. data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
  34. data/lib/lighthouse/rb_lighthouse.rb +1 -0
  35. data/lib/lighthouse/resolver_bases.rb +18 -4
  36. data/lib/lighthouse/resolver_dispatch.rb +16 -1
  37. data/lib/lighthouse/root_types.rb +117 -0
  38. data/lib/lighthouse/schema_generator.rb +4 -3
  39. data/lib/lighthouse/upload.rb +4 -2
  40. data/lib/lighthouse/upload_store.rb +39 -14
  41. data/sig/lighthouse.rbs +0 -10
  42. metadata +3 -4
  43. data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -29
  44. data/lib/generators/lighthouse/mutation_generator.rb +0 -31
  45. data/lib/generators/lighthouse/query_generator.rb +0 -30
@@ -1,32 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
-
5
- # Where @upload puts a file, by default: an ActiveStorage blob, on the service
6
- # `disk` names (the default service without it), its key under `path`. The
7
- # argument then holds the blob's signed id, which a has_one_attached /
8
- # has_many_attached attribute attaches, and which ActiveStorage::Blob.find_signed
9
- # finds. Config.upload_store takes another `call(file, disk:, path:, public:)`.
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.
10
13
  module Lighthouse::GraphQL::UploadStore
11
14
  module_function
12
15
 
13
- def call(file, disk: nil, path: nil, public: false) # rubocop:disable Lint/UnusedMethodArgument -- public is the service's setting
16
+ def call(file, argument:, disk: nil, path: nil, public: nil)
14
17
  unless defined?(::ActiveStorage::Blob)
15
- raise Lighthouse::DefinitionError,
16
- '@upload stores files with ActiveStorage: install it (bin/rails active_storage:install), or set Config.upload_store.'
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)."
17
22
  end
18
23
 
19
- attributes = { io: file, filename: filename(file), content_type: file.try(:content_type), key: key(path) }
20
- attributes[:service_name] = disk if disk
21
- ::ActiveStorage::Blob.create_and_upload!(**attributes.compact).signed_id
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}."
22
45
  end
23
46
 
24
47
  def filename(file)
25
48
  file.respond_to?(:original_filename) ? file.original_filename : File.basename(file.path)
26
49
  end
27
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.
28
53
  def key(path)
29
54
  prefix = path.to_s.delete_prefix('/').delete_suffix('/')
30
- prefix.empty? ? nil : "#{prefix}/#{SecureRandom.base36(28)}"
55
+ prefix.empty? ? nil : "#{prefix}/#{::ActiveStorage::Blob.generate_unique_secure_token}"
31
56
  end
32
57
  end
data/sig/lighthouse.rbs CHANGED
@@ -45,12 +45,6 @@ module Lighthouse
45
45
  def decode: (String global_id) -> Array[String]
46
46
  end
47
47
 
48
- # Config.upload_store: stores the file an @upload argument carries, and returns
49
- # what the argument holds instead (an ActiveStorage blob's signed id by default).
50
- interface _UploadStore
51
- def call: (untyped file, ?disk: String?, ?path: String?, ?public: bool?) -> untyped
52
- end
53
-
54
48
  # Config.authenticated_user and Config.policy_user: the user of a request.
55
49
  interface _UserReader
56
50
  def call: (context? context) -> untyped
@@ -112,8 +106,6 @@ module Lighthouse
112
106
  def self.directive_map: () -> Hash[untyped, untyped]
113
107
 
114
108
  # An ActiveSupport::Cache::Store; Rails.cache by default.
115
- def self.cache_store: () -> untyped
116
- def self.cache_store=: (untyped store) -> untyped
117
109
 
118
110
  def self.transactional_mutations: () -> bool
119
111
  def self.transactional_mutations=: (bool? enabled) -> bool?
@@ -147,8 +139,6 @@ module Lighthouse
147
139
  def self.global_id: () -> _GlobalId
148
140
  def self.global_id=: (_GlobalId? codec) -> _GlobalId?
149
141
 
150
- def self.upload_store: () -> _UploadStore
151
- def self.upload_store=: (_UploadStore? store) -> _UploadStore?
152
142
 
153
143
  # Whether @broadcast triggers in an ActiveJob (Lighthouse::GraphQL::BroadcastJob)
154
144
  # unless the directive says shouldQueue; and the job's queue (nil: ActiveJob's default).
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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vaz Innovation
@@ -78,14 +78,12 @@ files:
78
78
  - lib/generators/lighthouse/base_classes.rb
79
79
  - lib/generators/lighthouse/builder_generator.rb
80
80
  - lib/generators/lighthouse/class_generator.rb
81
+ - lib/generators/lighthouse/install/graphql_ruby_install.rb
81
82
  - lib/generators/lighthouse/install/install_generator.rb
82
83
  - lib/generators/lighthouse/install/templates/graphql.config.yml.tt
83
84
  - lib/generators/lighthouse/install/templates/graphql_controller.rb.tt
84
85
  - lib/generators/lighthouse/install/templates/initializer.rb.tt
85
86
  - lib/generators/lighthouse/install/templates/schema.graphql.tt
86
- - lib/generators/lighthouse/install/templates/schema.rb.tt
87
- - lib/generators/lighthouse/mutation_generator.rb
88
- - lib/generators/lighthouse/query_generator.rb
89
87
  - lib/generators/lighthouse/subscription_generator.rb
90
88
  - lib/generators/lighthouse/validator_generator.rb
91
89
  - lib/lighthouse-graphql.rb
@@ -197,6 +195,7 @@ files:
197
195
  - lib/lighthouse/resolve_info.rb
198
196
  - lib/lighthouse/resolver_bases.rb
199
197
  - lib/lighthouse/resolver_dispatch.rb
198
+ - lib/lighthouse/root_types.rb
200
199
  - lib/lighthouse/scalar_coercion.rb
201
200
  - lib/lighthouse/schema_cache.rb
202
201
  - lib/lighthouse/schema_factory.rb
@@ -1,29 +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. graphql-ruby builds the
8
- # SDL's types from the others (written by `rails g graphql:install`), and type
9
- # classes (Types::PostType, Types::DateTimeType, ...) inherit from them.
10
- <%= schema_name %> = Lighthouse::GraphQL::RbLightHouse.get_schema(
11
- options: {
12
- using: { GraphQL::Dataloader => {} },
13
- base_types: {
14
- resolver: <%= base_class_constant(:queries) %>,
15
- mutation: <%= base_class_constant(:mutations) %>,
16
- subscription: <%= base_class_constant(:subscriptions) %>,
17
- object: Types::BaseObject,
18
- interface: Types::BaseInterface,
19
- union: Types::BaseUnion,
20
- scalar: Types::BaseScalar,
21
- enum: Types::BaseEnum,
22
- input_object: Types::BaseInputObject
23
- }
24
- }
25
- )
26
-
27
- # Refuse expensive queries before they run (see the gem's docs/security.md):
28
- # <%= schema_name %>.max_complexity(300)
29
- # <%= 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