graphql-stitching 1.3.0 → 1.4.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/README.md +57 -7
- data/docs/resolver.md +101 -0
- data/lib/graphql/stitching/client.rb +5 -1
- data/lib/graphql/stitching/composer/resolver_config.rb +17 -12
- data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
- data/lib/graphql/stitching/composer/validate_resolvers.rb +23 -22
- data/lib/graphql/stitching/composer.rb +77 -83
- data/lib/graphql/stitching/executor/resolver_source.rb +25 -26
- data/lib/graphql/stitching/plan.rb +2 -3
- data/lib/graphql/stitching/planner.rb +10 -21
- data/lib/graphql/stitching/planner_step.rb +1 -1
- data/lib/graphql/stitching/resolver/arguments.rb +284 -0
- data/lib/graphql/stitching/resolver/keys.rb +206 -0
- data/lib/graphql/stitching/resolver.rb +44 -23
- data/lib/graphql/stitching/shaper.rb +3 -3
- data/lib/graphql/stitching/skip_include.rb +1 -1
- data/lib/graphql/stitching/supergraph/key_directive.rb +13 -0
- data/lib/graphql/stitching/supergraph/resolver_directive.rb +4 -5
- data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
- data/lib/graphql/stitching/supergraph.rb +13 -128
- data/lib/graphql/stitching/util.rb +28 -0
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +2 -1
- metadata +7 -3
- data/lib/graphql/stitching/export_selection.rb +0 -42
| @@ -0,0 +1,165 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            require_relative "./key_directive"
         | 
| 3 | 
            +
            require_relative "./resolver_directive"
         | 
| 4 | 
            +
            require_relative "./source_directive"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module GraphQL::Stitching
         | 
| 7 | 
            +
              class Supergraph
         | 
| 8 | 
            +
                class << self
         | 
| 9 | 
            +
                  def validate_executable!(location, executable)
         | 
| 10 | 
            +
                    return true if executable.is_a?(Class) && executable <= GraphQL::Schema
         | 
| 11 | 
            +
                    return true if executable && executable.respond_to?(:call)
         | 
| 12 | 
            +
                    raise StitchingError, "Invalid executable provided for location `#{location}`."
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def from_definition(schema, executables:)
         | 
| 16 | 
            +
                    schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
         | 
| 17 | 
            +
                    field_map = {}
         | 
| 18 | 
            +
                    resolver_map = {}
         | 
| 19 | 
            +
                    possible_locations = {}
         | 
| 20 | 
            +
                    introspection_types = schema.introspection_system.types.keys
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    schema.types.each do |type_name, type|
         | 
| 23 | 
            +
                      next if introspection_types.include?(type_name)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      # Collect/build key definitions for each type
         | 
| 26 | 
            +
                      locations_by_key = type.directives.each_with_object({}) do |directive, memo|
         | 
| 27 | 
            +
                        next unless directive.graphql_name == KeyDirective.graphql_name
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        kwargs = directive.arguments.keyword_arguments
         | 
| 30 | 
            +
                        memo[kwargs[:key]] ||= []
         | 
| 31 | 
            +
                        memo[kwargs[:key]] << kwargs[:location]
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      key_definitions = locations_by_key.each_with_object({}) do |(key, locations), memo|
         | 
| 35 | 
            +
                        memo[key] = Resolver.parse_key(key, locations)
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      # Collect/build resolver definitions for each type
         | 
| 39 | 
            +
                      type.directives.each do |directive|
         | 
| 40 | 
            +
                        next unless directive.graphql_name == ResolverDirective.graphql_name
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        kwargs = directive.arguments.keyword_arguments
         | 
| 43 | 
            +
                        resolver_map[type_name] ||= []
         | 
| 44 | 
            +
                        resolver_map[type_name] << Resolver.new(
         | 
| 45 | 
            +
                          location: kwargs[:location],
         | 
| 46 | 
            +
                          type_name: kwargs.fetch(:type_name, type_name),
         | 
| 47 | 
            +
                          field: kwargs[:field],
         | 
| 48 | 
            +
                          list: kwargs[:list] || false,
         | 
| 49 | 
            +
                          key: key_definitions[kwargs[:key]],
         | 
| 50 | 
            +
                          arguments: Resolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
         | 
| 51 | 
            +
                        )
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      next unless type.kind.fields?
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      type.fields.each do |field_name, field|
         | 
| 57 | 
            +
                        # Collection locations for each field definition
         | 
| 58 | 
            +
                        field.directives.each do |d|
         | 
| 59 | 
            +
                          next unless d.graphql_name == SourceDirective.graphql_name
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                          location = d.arguments.keyword_arguments[:location]
         | 
| 62 | 
            +
                          field_map[type_name] ||= {}
         | 
| 63 | 
            +
                          field_map[type_name][field_name] ||= []
         | 
| 64 | 
            +
                          field_map[type_name][field_name] << location
         | 
| 65 | 
            +
                          possible_locations[location] = true
         | 
| 66 | 
            +
                        end
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    executables = possible_locations.keys.each_with_object({}) do |location, memo|
         | 
| 71 | 
            +
                      executable = executables[location] || executables[location.to_sym]
         | 
| 72 | 
            +
                      if validate_executable!(location, executable)
         | 
| 73 | 
            +
                        memo[location] = executable
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    new(
         | 
| 78 | 
            +
                      schema: schema,
         | 
| 79 | 
            +
                      fields: field_map,
         | 
| 80 | 
            +
                      resolvers: resolver_map,
         | 
| 81 | 
            +
                      executables: executables,
         | 
| 82 | 
            +
                    )
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def to_definition
         | 
| 87 | 
            +
                  if @schema.directives[KeyDirective.graphql_name].nil?
         | 
| 88 | 
            +
                    @schema.directive(KeyDirective)
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                  if @schema.directives[ResolverDirective.graphql_name].nil?
         | 
| 91 | 
            +
                    @schema.directive(ResolverDirective)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                  if @schema.directives[SourceDirective.graphql_name].nil?
         | 
| 94 | 
            +
                    @schema.directive(SourceDirective)
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  @schema.types.each do |type_name, type|
         | 
| 98 | 
            +
                    if resolvers_for_type = @resolvers.dig(type_name)
         | 
| 99 | 
            +
                      # Apply key directives for each unique type/key/location
         | 
| 100 | 
            +
                      # (this allows keys to be composite selections and/or omitted from the supergraph schema)
         | 
| 101 | 
            +
                      keys_for_type = resolvers_for_type.each_with_object({}) do |resolver, memo|
         | 
| 102 | 
            +
                        memo[resolver.key.to_definition] ||= Set.new
         | 
| 103 | 
            +
                        memo[resolver.key.to_definition].merge(resolver.key.locations)
         | 
| 104 | 
            +
                      end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                      keys_for_type.each do |key, locations|
         | 
| 107 | 
            +
                        locations.each do |location|
         | 
| 108 | 
            +
                          params = { key: key, location: location }
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                          unless has_directive?(type, KeyDirective.graphql_name, params)
         | 
| 111 | 
            +
                            type.directive(KeyDirective, **params)
         | 
| 112 | 
            +
                          end
         | 
| 113 | 
            +
                        end
         | 
| 114 | 
            +
                      end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                      # Apply resolver directives for each unique query resolver
         | 
| 117 | 
            +
                      resolvers_for_type.each do |resolver|
         | 
| 118 | 
            +
                        params = {
         | 
| 119 | 
            +
                          location: resolver.location,
         | 
| 120 | 
            +
                          field: resolver.field,
         | 
| 121 | 
            +
                          list: resolver.list? || nil,
         | 
| 122 | 
            +
                          key: resolver.key.to_definition,
         | 
| 123 | 
            +
                          arguments: resolver.arguments.map(&:to_definition).join(", "),
         | 
| 124 | 
            +
                          argument_types: resolver.arguments.map(&:to_type_definition).join(", "),
         | 
| 125 | 
            +
                          type_name: (resolver.type_name if resolver.type_name != type_name),
         | 
| 126 | 
            +
                        }
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                        unless has_directive?(type, ResolverDirective.graphql_name, params)
         | 
| 129 | 
            +
                          type.directive(ResolverDirective, **params.tap(&:compact!))
         | 
| 130 | 
            +
                        end
         | 
| 131 | 
            +
                      end
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    next unless type.kind.fields?
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    type.fields.each do |field_name, field|
         | 
| 137 | 
            +
                      locations_for_field = @locations_by_type_and_field.dig(type_name, field_name)
         | 
| 138 | 
            +
                      next if locations_for_field.nil?
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      # Apply source directives to annotate the possible locations of each field
         | 
| 141 | 
            +
                      locations_for_field.each do |location|
         | 
| 142 | 
            +
                        params = { location: location }
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                        unless has_directive?(field, SourceDirective.graphql_name, params)
         | 
| 145 | 
            +
                          field.directive(SourceDirective, **params)
         | 
| 146 | 
            +
                        end
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                    end
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  @schema.to_definition
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                private
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                def has_directive?(element, directive_name, params)
         | 
| 157 | 
            +
                  existing = element.directives.find do |d|
         | 
| 158 | 
            +
                    kwargs = d.arguments.keyword_arguments
         | 
| 159 | 
            +
                    d.graphql_name == directive_name && params.all? { |k, v| kwargs[k] == v }
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  !existing.nil?
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
              end
         | 
| 165 | 
            +
            end
         | 
| @@ -1,78 +1,12 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative "./supergraph/ | 
| 4 | 
            -
            require_relative "./supergraph/source_directive"
         | 
| 3 | 
            +
            require_relative "./supergraph/to_definition"
         | 
| 5 4 |  | 
| 6 5 | 
             
            module GraphQL
         | 
| 7 6 | 
             
              module Stitching
         | 
| 8 7 | 
             
                class Supergraph
         | 
| 9 8 | 
             
                  SUPERGRAPH_LOCATION = "__super"
         | 
| 10 9 |  | 
| 11 | 
            -
                  class << self
         | 
| 12 | 
            -
                    def validate_executable!(location, executable)
         | 
| 13 | 
            -
                      return true if executable.is_a?(Class) && executable <= GraphQL::Schema
         | 
| 14 | 
            -
                      return true if executable && executable.respond_to?(:call)
         | 
| 15 | 
            -
                      raise StitchingError, "Invalid executable provided for location `#{location}`."
         | 
| 16 | 
            -
                    end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                    def from_definition(schema, executables:)
         | 
| 19 | 
            -
                      schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
         | 
| 20 | 
            -
                      field_map = {}
         | 
| 21 | 
            -
                      resolver_map = {}
         | 
| 22 | 
            -
                      possible_locations = {}
         | 
| 23 | 
            -
                      introspection_types = schema.introspection_system.types.keys
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                      schema.types.each do |type_name, type|
         | 
| 26 | 
            -
                        next if introspection_types.include?(type_name)
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                        type.directives.each do |directive|
         | 
| 29 | 
            -
                          next unless directive.graphql_name == ResolverDirective.graphql_name
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                          kwargs = directive.arguments.keyword_arguments
         | 
| 32 | 
            -
                          resolver_map[type_name] ||= []
         | 
| 33 | 
            -
                          resolver_map[type_name] << Resolver.new(
         | 
| 34 | 
            -
                            type_name: kwargs.fetch(:type_name, type_name),
         | 
| 35 | 
            -
                            location: kwargs[:location],
         | 
| 36 | 
            -
                            key: kwargs[:key],
         | 
| 37 | 
            -
                            field: kwargs[:field],
         | 
| 38 | 
            -
                            list: kwargs[:list] || false,
         | 
| 39 | 
            -
                            arg: kwargs[:arg],
         | 
| 40 | 
            -
                            arg_type_name: kwargs[:arg_type_name],
         | 
| 41 | 
            -
                            representations: kwargs[:representations] || false,
         | 
| 42 | 
            -
                          )
         | 
| 43 | 
            -
                        end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                        next unless type.kind.fields?
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                        type.fields.each do |field_name, field|
         | 
| 48 | 
            -
                          field.directives.each do |d|
         | 
| 49 | 
            -
                            next unless d.graphql_name == SourceDirective.graphql_name
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                            location = d.arguments.keyword_arguments[:location]
         | 
| 52 | 
            -
                            field_map[type_name] ||= {}
         | 
| 53 | 
            -
                            field_map[type_name][field_name] ||= []
         | 
| 54 | 
            -
                            field_map[type_name][field_name] << location
         | 
| 55 | 
            -
                            possible_locations[location] = true
         | 
| 56 | 
            -
                          end
         | 
| 57 | 
            -
                        end
         | 
| 58 | 
            -
                      end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                      executables = possible_locations.keys.each_with_object({}) do |location, memo|
         | 
| 61 | 
            -
                        executable = executables[location] || executables[location.to_sym]
         | 
| 62 | 
            -
                        if validate_executable!(location, executable)
         | 
| 63 | 
            -
                          memo[location] = executable
         | 
| 64 | 
            -
                        end
         | 
| 65 | 
            -
                      end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                      new(
         | 
| 68 | 
            -
                        schema: schema,
         | 
| 69 | 
            -
                        fields: field_map,
         | 
| 70 | 
            -
                        resolvers: resolver_map,
         | 
| 71 | 
            -
                        executables: executables,
         | 
| 72 | 
            -
                      )
         | 
| 73 | 
            -
                    end
         | 
| 74 | 
            -
                  end
         | 
| 75 | 
            -
             | 
| 76 10 | 
             
                  # @return [GraphQL::Schema] the composed schema for the supergraph.
         | 
| 77 11 | 
             
                  attr_reader :schema
         | 
| 78 12 |  | 
| @@ -86,6 +20,7 @@ module GraphQL | |
| 86 20 | 
             
                    @schema.use(GraphQL::Schema::AlwaysVisible)
         | 
| 87 21 |  | 
| 88 22 | 
             
                    @resolvers = resolvers
         | 
| 23 | 
            +
                    @resolvers_by_version = nil
         | 
| 89 24 | 
             
                    @fields_by_type_and_location = nil
         | 
| 90 25 | 
             
                    @locations_by_type = nil
         | 
| 91 26 | 
             
                    @memoized_introspection_types = nil
         | 
| @@ -112,66 +47,17 @@ module GraphQL | |
| 112 47 | 
             
                    end.freeze
         | 
| 113 48 | 
             
                  end
         | 
| 114 49 |  | 
| 115 | 
            -
                  def to_definition
         | 
| 116 | 
            -
                    if @schema.directives[ResolverDirective.graphql_name].nil?
         | 
| 117 | 
            -
                      @schema.directive(ResolverDirective)
         | 
| 118 | 
            -
                    end
         | 
| 119 | 
            -
                    if @schema.directives[SourceDirective.graphql_name].nil?
         | 
| 120 | 
            -
                      @schema.directive(SourceDirective)
         | 
| 121 | 
            -
                    end
         | 
| 122 | 
            -
             | 
| 123 | 
            -
                    @schema.types.each do |type_name, type|
         | 
| 124 | 
            -
                      if resolvers_for_type = @resolvers.dig(type_name)
         | 
| 125 | 
            -
                        resolvers_for_type.each do |resolver|
         | 
| 126 | 
            -
                          existing = type.directives.find do |d|
         | 
| 127 | 
            -
                            kwargs = d.arguments.keyword_arguments
         | 
| 128 | 
            -
                            d.graphql_name == ResolverDirective.graphql_name &&
         | 
| 129 | 
            -
                              kwargs[:location] == resolver.location &&
         | 
| 130 | 
            -
                              kwargs[:key] == resolver.key &&
         | 
| 131 | 
            -
                              kwargs[:field] == resolver.field &&
         | 
| 132 | 
            -
                              kwargs[:arg] == resolver.arg &&
         | 
| 133 | 
            -
                              kwargs.fetch(:list, false) == resolver.list &&
         | 
| 134 | 
            -
                              kwargs.fetch(:representations, false) == resolver.representations
         | 
| 135 | 
            -
                          end
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                          type.directive(ResolverDirective, **{
         | 
| 138 | 
            -
                            type_name: (resolver.type_name if resolver.type_name != type_name),
         | 
| 139 | 
            -
                            location: resolver.location,
         | 
| 140 | 
            -
                            key: resolver.key,
         | 
| 141 | 
            -
                            field: resolver.field,
         | 
| 142 | 
            -
                            list: resolver.list || nil,
         | 
| 143 | 
            -
                            arg: resolver.arg,
         | 
| 144 | 
            -
                            arg_type_name: resolver.arg_type_name,
         | 
| 145 | 
            -
                            representations: resolver.representations || nil,
         | 
| 146 | 
            -
                          }.tap(&:compact!)) if existing.nil?
         | 
| 147 | 
            -
                        end
         | 
| 148 | 
            -
                      end
         | 
| 149 | 
            -
             | 
| 150 | 
            -
                      next unless type.kind.fields?
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                      type.fields.each do |field_name, field|
         | 
| 153 | 
            -
                        locations_for_field = @locations_by_type_and_field.dig(type_name, field_name)
         | 
| 154 | 
            -
                        next if locations_for_field.nil?
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                        locations_for_field.each do |location|
         | 
| 157 | 
            -
                          existing = field.directives.find do |d|
         | 
| 158 | 
            -
                            d.graphql_name == SourceDirective.graphql_name &&
         | 
| 159 | 
            -
                              d.arguments.keyword_arguments[:location] == location
         | 
| 160 | 
            -
                          end
         | 
| 161 | 
            -
             | 
| 162 | 
            -
                          field.directive(SourceDirective, location: location) if existing.nil?
         | 
| 163 | 
            -
                        end
         | 
| 164 | 
            -
                      end
         | 
| 165 | 
            -
                    end
         | 
| 166 | 
            -
             | 
| 167 | 
            -
                    @schema.to_definition
         | 
| 168 | 
            -
                  end
         | 
| 169 | 
            -
             | 
| 170 50 | 
             
                  # @return [GraphQL::StaticValidation::Validator] static validator for the supergraph schema.
         | 
| 171 51 | 
             
                  def static_validator
         | 
| 172 52 | 
             
                    @static_validator ||= @schema.static_validator
         | 
| 173 53 | 
             
                  end
         | 
| 174 54 |  | 
| 55 | 
            +
                  def resolvers_by_version
         | 
| 56 | 
            +
                    @resolvers_by_version ||= resolvers.values.tap(&:flatten!).each_with_object({}) do |resolver, memo|
         | 
| 57 | 
            +
                      memo[resolver.version] = resolver
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 175 61 | 
             
                  def fields
         | 
| 176 62 | 
             
                    @locations_by_type_and_field.reject { |k, _v| memoized_introspection_types[k] }
         | 
| 177 63 | 
             
                  end
         | 
| @@ -245,24 +131,23 @@ module GraphQL | |
| 245 131 | 
             
                  end
         | 
| 246 132 |  | 
| 247 133 | 
             
                  # collects all possible resolver keys for a given type
         | 
| 248 | 
            -
                  # ("Type") => ["id", ...]
         | 
| 134 | 
            +
                  # ("Type") => [Key("id"), ...]
         | 
| 249 135 | 
             
                  def possible_keys_for_type(type_name)
         | 
| 250 136 | 
             
                    @possible_keys_by_type[type_name] ||= begin
         | 
| 251 137 | 
             
                      if type_name == @schema.query.graphql_name
         | 
| 252 138 | 
             
                        GraphQL::Stitching::EMPTY_ARRAY
         | 
| 253 139 | 
             
                      else
         | 
| 254 | 
            -
                        @resolvers[type_name].map(&:key). | 
| 140 | 
            +
                        @resolvers[type_name].map(&:key).uniq(&:to_definition)
         | 
| 255 141 | 
             
                      end
         | 
| 256 142 | 
             
                    end
         | 
| 257 143 | 
             
                  end
         | 
| 258 144 |  | 
| 259 145 | 
             
                  # collects possible resolver keys for a given type and location
         | 
| 260 | 
            -
                  # ("Type", "location") => ["id", ...]
         | 
| 146 | 
            +
                  # ("Type", "location") => [Key("id"), ...]
         | 
| 261 147 | 
             
                  def possible_keys_for_type_and_location(type_name, location)
         | 
| 262 148 | 
             
                    possible_keys_by_type = @possible_keys_by_type_and_location[type_name] ||= {}
         | 
| 263 | 
            -
                    possible_keys_by_type[location] ||=  | 
| 264 | 
            -
                       | 
| 265 | 
            -
                      location_fields & possible_keys_for_type(type_name)
         | 
| 149 | 
            +
                    possible_keys_by_type[location] ||= possible_keys_for_type(type_name).select do |key|
         | 
| 150 | 
            +
                      key.locations.include?(location)
         | 
| 266 151 | 
             
                    end
         | 
| 267 152 | 
             
                  end
         | 
| 268 153 |  | 
| @@ -47,6 +47,34 @@ module GraphQL | |
| 47 47 | 
             
                      structure
         | 
| 48 48 | 
             
                    end
         | 
| 49 49 |  | 
| 50 | 
            +
                    # builds a single-dimensional representation of a wrapped type structure from AST
         | 
| 51 | 
            +
                    def flatten_ast_type_structure(ast, structure: [])
         | 
| 52 | 
            +
                      null = true
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      while ast.is_a?(GraphQL::Language::Nodes::NonNullType)
         | 
| 55 | 
            +
                        ast = ast.of_type
         | 
| 56 | 
            +
                        null = false
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      if ast.is_a?(GraphQL::Language::Nodes::ListType)
         | 
| 60 | 
            +
                        structure << TypeStructure.new(
         | 
| 61 | 
            +
                          list: true,
         | 
| 62 | 
            +
                          null: null,
         | 
| 63 | 
            +
                          name: nil,
         | 
| 64 | 
            +
                        )
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                        flatten_ast_type_structure(ast.of_type, structure: structure)
         | 
| 67 | 
            +
                      else
         | 
| 68 | 
            +
                        structure << TypeStructure.new(
         | 
| 69 | 
            +
                          list: false,
         | 
| 70 | 
            +
                          null: null,
         | 
| 71 | 
            +
                          name: ast.name,
         | 
| 72 | 
            +
                        )
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      structure
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
             | 
| 50 78 | 
             
                    # expands interfaces and unions to an array of their memberships
         | 
| 51 79 | 
             
                    # like `schema.possible_types`, but includes child interfaces
         | 
| 52 80 | 
             
                    def expand_abstract_type(schema, parent_type)
         | 
    
        data/lib/graphql/stitching.rb
    CHANGED
    
    | @@ -8,6 +8,8 @@ module GraphQL | |
| 8 8 | 
             
                EMPTY_ARRAY = [].freeze
         | 
| 9 9 |  | 
| 10 10 | 
             
                class StitchingError < StandardError; end
         | 
| 11 | 
            +
                class CompositionError < StitchingError; end
         | 
| 12 | 
            +
                class ValidationError < CompositionError; end
         | 
| 11 13 |  | 
| 12 14 | 
             
                class << self
         | 
| 13 15 | 
             
                  def stitch_directive
         | 
| @@ -28,7 +30,6 @@ require_relative "stitching/resolver" | |
| 28 30 | 
             
            require_relative "stitching/client"
         | 
| 29 31 | 
             
            require_relative "stitching/composer"
         | 
| 30 32 | 
             
            require_relative "stitching/executor"
         | 
| 31 | 
            -
            require_relative "stitching/export_selection"
         | 
| 32 33 | 
             
            require_relative "stitching/http_executable"
         | 
| 33 34 | 
             
            require_relative "stitching/plan"
         | 
| 34 35 | 
             
            require_relative "stitching/planner_step"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: graphql-stitching
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Greg MacWilliam
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-07-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: graphql
         | 
| @@ -89,6 +89,7 @@ files: | |
| 89 89 | 
             
            - docs/images/stitching.png
         | 
| 90 90 | 
             
            - docs/mechanics.md
         | 
| 91 91 | 
             
            - docs/request.md
         | 
| 92 | 
            +
            - docs/resolver.md
         | 
| 92 93 | 
             
            - docs/supergraph.md
         | 
| 93 94 | 
             
            - examples/file_uploads/Gemfile
         | 
| 94 95 | 
             
            - examples/file_uploads/Procfile
         | 
| @@ -119,18 +120,21 @@ files: | |
| 119 120 | 
             
            - lib/graphql/stitching/executor.rb
         | 
| 120 121 | 
             
            - lib/graphql/stitching/executor/resolver_source.rb
         | 
| 121 122 | 
             
            - lib/graphql/stitching/executor/root_source.rb
         | 
| 122 | 
            -
            - lib/graphql/stitching/export_selection.rb
         | 
| 123 123 | 
             
            - lib/graphql/stitching/http_executable.rb
         | 
| 124 124 | 
             
            - lib/graphql/stitching/plan.rb
         | 
| 125 125 | 
             
            - lib/graphql/stitching/planner.rb
         | 
| 126 126 | 
             
            - lib/graphql/stitching/planner_step.rb
         | 
| 127 127 | 
             
            - lib/graphql/stitching/request.rb
         | 
| 128 128 | 
             
            - lib/graphql/stitching/resolver.rb
         | 
| 129 | 
            +
            - lib/graphql/stitching/resolver/arguments.rb
         | 
| 130 | 
            +
            - lib/graphql/stitching/resolver/keys.rb
         | 
| 129 131 | 
             
            - lib/graphql/stitching/shaper.rb
         | 
| 130 132 | 
             
            - lib/graphql/stitching/skip_include.rb
         | 
| 131 133 | 
             
            - lib/graphql/stitching/supergraph.rb
         | 
| 134 | 
            +
            - lib/graphql/stitching/supergraph/key_directive.rb
         | 
| 132 135 | 
             
            - lib/graphql/stitching/supergraph/resolver_directive.rb
         | 
| 133 136 | 
             
            - lib/graphql/stitching/supergraph/source_directive.rb
         | 
| 137 | 
            +
            - lib/graphql/stitching/supergraph/to_definition.rb
         | 
| 134 138 | 
             
            - lib/graphql/stitching/util.rb
         | 
| 135 139 | 
             
            - lib/graphql/stitching/version.rb
         | 
| 136 140 | 
             
            homepage: https://github.com/gmac/graphql-stitching-ruby
         | 
| @@ -1,42 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module GraphQL
         | 
| 4 | 
            -
              module Stitching
         | 
| 5 | 
            -
                # Builds hidden selection fields added by stitiching code,
         | 
| 6 | 
            -
                # used to request operational data about resolved objects.
         | 
| 7 | 
            -
                class ExportSelection
         | 
| 8 | 
            -
                  EXPORT_PREFIX = "_export_"
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                  class << self
         | 
| 11 | 
            -
                    @typename_node = nil
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                    def key?(name)
         | 
| 14 | 
            -
                      return false unless name
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                      name.start_with?(EXPORT_PREFIX)
         | 
| 17 | 
            -
                    end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                    def key(name)
         | 
| 20 | 
            -
                      "#{EXPORT_PREFIX}#{name}"
         | 
| 21 | 
            -
                    end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                    # The argument assigning Field.alias changed from
         | 
| 24 | 
            -
                    # a generic `alias` hash key to a structured `field_alias` kwarg.
         | 
| 25 | 
            -
                    # See https://github.com/rmosolgo/graphql-ruby/pull/4718
         | 
| 26 | 
            -
                    FIELD_ALIAS_KWARG = !GraphQL::Language::Nodes::Field.new(field_alias: "a").alias.nil?
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                    def key_node(field_name)
         | 
| 29 | 
            -
                      if FIELD_ALIAS_KWARG
         | 
| 30 | 
            -
                        GraphQL::Language::Nodes::Field.new(field_alias: key(field_name), name: field_name)
         | 
| 31 | 
            -
                      else
         | 
| 32 | 
            -
                        GraphQL::Language::Nodes::Field.new(alias: key(field_name), name: field_name)
         | 
| 33 | 
            -
                      end
         | 
| 34 | 
            -
                    end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                    def typename_node
         | 
| 37 | 
            -
                      @typename_node ||= key_node("__typename")
         | 
| 38 | 
            -
                    end
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
              end
         | 
| 42 | 
            -
            end
         |