Class: GraphQL::Stitching::Supergraph
- Inherits:
-
Object
- Object
- GraphQL::Stitching::Supergraph
- Defined in:
- lib/graphql/stitching/supergraph.rb
Defined Under Namespace
Classes: PathNode
Constant Summary collapse
- LOCATION =
"__super"
Instance Attribute Summary collapse
-
#boundaries ⇒ Object
readonly
Returns the value of attribute boundaries.
-
#executables ⇒ Object
readonly
Returns the value of attribute executables.
-
#locations_by_type_and_field ⇒ Object
readonly
Returns the value of attribute locations_by_type_and_field.
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Class Method Summary collapse
- .from_export(schema:, delegation_map:, executables:) ⇒ Object
- .validate_executable!(location, executable) ⇒ Object
Instance Method Summary collapse
- #execute_at_location(location, source, variables, context) ⇒ Object
- #export ⇒ Object
- #fields ⇒ Object
-
#fields_by_type_and_location ⇒ Object
inverts fields map to provide fields for a type/location “Type” => “location” => [“field1”, “field2”, …].
-
#initialize(schema:, fields:, boundaries:, executables:) ⇒ Supergraph
constructor
A new instance of Supergraph.
- #locations ⇒ Object
-
#locations_by_type ⇒ Object
“Type” => [“location1”, “location2”, …].
- #memoized_introspection_types ⇒ Object
- #memoized_schema_fields(type_name) ⇒ Object
- #memoized_schema_possible_types(type_name) ⇒ Object
- #memoized_schema_types ⇒ Object
-
#possible_keys_for_type(type_name) ⇒ Object
collects all possible boundary keys for a given type (“Type”) => [“id”, …].
-
#possible_keys_for_type_and_location(type_name, location) ⇒ Object
collects possible boundary keys for a given type and location (“Type”, “location”) => [“id”, …].
-
#route_type_to_locations(type_name, start_location, goal_locations) ⇒ Object
For a given type, route from one origin location to one or more remote locations used to connect a partial type across locations via boundary queries.
Constructor Details
#initialize(schema:, fields:, boundaries:, executables:) ⇒ Supergraph
Returns a new instance of Supergraph.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/graphql/stitching/supergraph.rb', line 38 def initialize(schema:, fields:, boundaries:, executables:) @schema = schema @boundaries = boundaries @possible_keys_by_type = {} @possible_keys_by_type_and_location = {} @memoized_schema_possible_types = {} @memoized_schema_fields = {} # add introspection types into the fields mapping @locations_by_type_and_field = memoized_introspection_types.each_with_object(fields) do |(type_name, type), memo| next unless type.kind.fields? memo[type_name] = type.fields.keys.each_with_object({}) do |field_name, m| m[field_name] = [LOCATION] end end.freeze # validate and normalize executable references @executables = executables.each_with_object({ LOCATION => @schema }) do |(location, executable), memo| if self.class.validate_executable!(location, executable) memo[location.to_s] = executable end end.freeze end |
Instance Attribute Details
#boundaries ⇒ Object (readonly)
Returns the value of attribute boundaries.
36 37 38 |
# File 'lib/graphql/stitching/supergraph.rb', line 36 def boundaries @boundaries end |
#executables ⇒ Object (readonly)
Returns the value of attribute executables.
36 37 38 |
# File 'lib/graphql/stitching/supergraph.rb', line 36 def executables @executables end |
#locations_by_type_and_field ⇒ Object (readonly)
Returns the value of attribute locations_by_type_and_field.
36 37 38 |
# File 'lib/graphql/stitching/supergraph.rb', line 36 def locations_by_type_and_field @locations_by_type_and_field end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
36 37 38 |
# File 'lib/graphql/stitching/supergraph.rb', line 36 def schema @schema end |
Class Method Details
.from_export(schema:, delegation_map:, executables:) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/graphql/stitching/supergraph.rb', line 14 def self.from_export(schema:, delegation_map:, executables:) schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String) executables = delegation_map["locations"].each_with_object({}) do |location, memo| executable = executables[location] || executables[location.to_sym] if validate_executable!(location, executable) memo[location] = executable end end boundaries = delegation_map["boundaries"].map do |k, b| [k, b.map { Boundary.new(**_1) }] end new( schema: schema, fields: delegation_map["fields"], boundaries: boundaries.to_h, executables: executables, ) end |
.validate_executable!(location, executable) ⇒ Object
8 9 10 11 12 |
# File 'lib/graphql/stitching/supergraph.rb', line 8 def self.validate_executable!(location, executable) return true if executable.is_a?(Class) && executable <= GraphQL::Schema return true if executable && executable.respond_to?(:call) raise StitchingError, "Invalid executable provided for location `#{location}`." end |
Instance Method Details
#execute_at_location(location, source, variables, context) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/graphql/stitching/supergraph.rb', line 108 def execute_at_location(location, source, variables, context) executable = executables[location] if executable.nil? raise StitchingError, "No executable assigned for #{location} location." elsif executable.is_a?(Class) && executable <= GraphQL::Schema executable.execute( query: source, variables: variables, context: context.frozen? ? context.dup : context, validate: false, ) elsif executable.respond_to?(:call) executable.call(location, source, variables, context) else raise StitchingError, "Missing valid executable for #{location} location." end end |
#export ⇒ Object
71 72 73 74 75 76 77 |
# File 'lib/graphql/stitching/supergraph.rb', line 71 def export return GraphQL::Schema::Printer.print_schema(@schema), { "locations" => locations, "fields" => fields, "boundaries" => @boundaries.map { |k, b| [k, b.map(&:as_json)] }.to_h, } end |
#fields ⇒ Object
63 64 65 |
# File 'lib/graphql/stitching/supergraph.rb', line 63 def fields @locations_by_type_and_field.reject { |k, _v| memoized_introspection_types[k] } end |
#fields_by_type_and_location ⇒ Object
inverts fields map to provide fields for a type/location “Type” => “location” => [“field1”, “field2”, …]
129 130 131 132 133 134 135 136 137 138 |
# File 'lib/graphql/stitching/supergraph.rb', line 129 def fields_by_type_and_location @fields_by_type_and_location ||= @locations_by_type_and_field.each_with_object({}) do |(type_name, fields), memo| memo[type_name] = fields.each_with_object({}) do |(field_name, locations), memo| locations.each do |location| memo[location] ||= [] memo[location] << field_name end end end end |
#locations ⇒ Object
67 68 69 |
# File 'lib/graphql/stitching/supergraph.rb', line 67 def locations @executables.keys.reject { _1 == LOCATION } end |
#locations_by_type ⇒ Object
“Type” => [“location1”, “location2”, …]
141 142 143 144 145 |
# File 'lib/graphql/stitching/supergraph.rb', line 141 def locations_by_type @locations_by_type ||= @locations_by_type_and_field.each_with_object({}) do |(type_name, fields), memo| memo[type_name] = fields.values.flatten.uniq end end |
#memoized_introspection_types ⇒ Object
79 80 81 |
# File 'lib/graphql/stitching/supergraph.rb', line 79 def memoized_introspection_types @memoized_introspection_types ||= schema.introspection_system.types end |
#memoized_schema_fields(type_name) ⇒ Object
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/graphql/stitching/supergraph.rb', line 91 def memoized_schema_fields(type_name) @memoized_schema_fields[type_name] ||= begin fields = memoized_schema_types[type_name].fields @schema.introspection_system.dynamic_fields.each do |field| fields[field.name] ||= field # adds __typename end if type_name == @schema.query.graphql_name @schema.introspection_system.entry_points.each do |field| fields[field.name] ||= field # adds __schema, __type end end fields end end |
#memoized_schema_possible_types(type_name) ⇒ Object
87 88 89 |
# File 'lib/graphql/stitching/supergraph.rb', line 87 def memoized_schema_possible_types(type_name) @memoized_schema_possible_types[type_name] ||= @schema.possible_types(memoized_schema_types[type_name]) end |
#memoized_schema_types ⇒ Object
83 84 85 |
# File 'lib/graphql/stitching/supergraph.rb', line 83 def memoized_schema_types @memoized_schema_types ||= @schema.types end |
#possible_keys_for_type(type_name) ⇒ Object
collects all possible boundary keys for a given type (“Type”) => [“id”, …]
149 150 151 152 153 |
# File 'lib/graphql/stitching/supergraph.rb', line 149 def possible_keys_for_type(type_name) @possible_keys_by_type[type_name] ||= begin @boundaries[type_name].map(&:key).tap(&:uniq!) end end |
#possible_keys_for_type_and_location(type_name, location) ⇒ Object
collects possible boundary keys for a given type and location (“Type”, “location”) => [“id”, …]
157 158 159 160 161 162 163 |
# File 'lib/graphql/stitching/supergraph.rb', line 157 def possible_keys_for_type_and_location(type_name, location) possible_keys_by_type = @possible_keys_by_type_and_location[type_name] ||= {} possible_keys_by_type[location] ||= begin location_fields = fields_by_type_and_location[type_name][location] || [] location_fields & possible_keys_for_type(type_name) end end |
#route_type_to_locations(type_name, start_location, goal_locations) ⇒ Object
For a given type, route from one origin location to one or more remote locations used to connect a partial type across locations via boundary queries
167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/graphql/stitching/supergraph.rb', line 167 def route_type_to_locations(type_name, start_location, goal_locations) if possible_keys_for_type(type_name).length > 1 # multiple keys use an A* search to traverse intermediary locations return route_type_to_locations_via_search(type_name, start_location, goal_locations) end # types with a single key attribute must all be within a single hop of each other, # so can use a simple match to collect boundaries for the goal locations. @boundaries[type_name].each_with_object({}) do |boundary, memo| if goal_locations.include?(boundary.location) memo[boundary.location] = [boundary] end end end |