graphql-stitching 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +27 -0
- data/.gitignore +59 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +21 -0
- data/Procfile +3 -0
- data/README.md +329 -0
- data/Rakefile +12 -0
- data/docs/README.md +14 -0
- data/docs/composer.md +69 -0
- data/docs/document.md +15 -0
- data/docs/executor.md +29 -0
- data/docs/gateway.md +106 -0
- data/docs/images/library.png +0 -0
- data/docs/images/merging.png +0 -0
- data/docs/images/stitching.png +0 -0
- data/docs/planner.md +43 -0
- data/docs/shaper.md +20 -0
- data/docs/supergraph.md +65 -0
- data/example/gateway.rb +50 -0
- data/example/graphiql.html +153 -0
- data/example/remote1.rb +26 -0
- data/example/remote2.rb +26 -0
- data/graphql-stitching.gemspec +34 -0
- data/lib/graphql/stitching/composer/base_validator.rb +11 -0
- data/lib/graphql/stitching/composer/validate_boundaries.rb +80 -0
- data/lib/graphql/stitching/composer/validate_interfaces.rb +24 -0
- data/lib/graphql/stitching/composer.rb +442 -0
- data/lib/graphql/stitching/document.rb +59 -0
- data/lib/graphql/stitching/executor.rb +254 -0
- data/lib/graphql/stitching/gateway.rb +120 -0
- data/lib/graphql/stitching/planner.rb +323 -0
- data/lib/graphql/stitching/planner_operation.rb +59 -0
- data/lib/graphql/stitching/remote_client.rb +25 -0
- data/lib/graphql/stitching/shaper.rb +92 -0
- data/lib/graphql/stitching/supergraph.rb +171 -0
- data/lib/graphql/stitching/util.rb +63 -0
- data/lib/graphql/stitching/version.rb +7 -0
- data/lib/graphql/stitching.rb +30 -0
- metadata +142 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Stitching
|
5
|
+
class PlannerOperation
|
6
|
+
attr_reader :key, :location, :parent_type, :type_condition, :operation_type, :insertion_path
|
7
|
+
attr_accessor :after_key, :selections, :variables, :boundary
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
key:,
|
11
|
+
location:,
|
12
|
+
parent_type:,
|
13
|
+
operation_type: "query",
|
14
|
+
insertion_path: [],
|
15
|
+
type_condition: nil,
|
16
|
+
after_key: nil,
|
17
|
+
selections: [],
|
18
|
+
variables: [],
|
19
|
+
boundary: nil
|
20
|
+
)
|
21
|
+
@key = key
|
22
|
+
@after_key = after_key
|
23
|
+
@location = location
|
24
|
+
@parent_type = parent_type
|
25
|
+
@operation_type = operation_type
|
26
|
+
@insertion_path = insertion_path
|
27
|
+
@type_condition = type_condition
|
28
|
+
@selections = selections
|
29
|
+
@variables = variables
|
30
|
+
@boundary = boundary
|
31
|
+
end
|
32
|
+
|
33
|
+
def selection_set
|
34
|
+
op = GraphQL::Language::Nodes::OperationDefinition.new(selections: @selections)
|
35
|
+
GraphQL::Language::Printer.new.print(op).gsub!(/\s+/, " ").strip!
|
36
|
+
end
|
37
|
+
|
38
|
+
def variable_set
|
39
|
+
@variables.each_with_object({}) do |(variable_name, value_type), memo|
|
40
|
+
memo[variable_name] = GraphQL::Language::Printer.new.print(value_type)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
{
|
46
|
+
"key" => @key,
|
47
|
+
"after_key" => @after_key,
|
48
|
+
"location" => @location,
|
49
|
+
"operation_type" => @operation_type,
|
50
|
+
"insertion_path" => @insertion_path,
|
51
|
+
"type_condition" => @type_condition,
|
52
|
+
"selections" => selection_set,
|
53
|
+
"variables" => variable_set,
|
54
|
+
"boundary" => @boundary,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "uri"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module GraphQL
|
8
|
+
module Stitching
|
9
|
+
class RemoteClient
|
10
|
+
def initialize(url:, headers:{})
|
11
|
+
@url = url
|
12
|
+
@headers = headers
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(location, document, variables)
|
16
|
+
response = Net::HTTP.post(
|
17
|
+
URI(@url),
|
18
|
+
{ "query" => document, "variables" => variables }.to_json,
|
19
|
+
{ "Content-Type" => "application/json" }.merge!(@headers)
|
20
|
+
)
|
21
|
+
JSON.parse(response.body)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Stitching
|
6
|
+
class Shaper
|
7
|
+
def initialize(supergraph:, document:, raw:)
|
8
|
+
@supergraph = supergraph
|
9
|
+
@document = document
|
10
|
+
@result = raw
|
11
|
+
@errors = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform!
|
15
|
+
if @result.key?("data") && ! @result["data"].empty?
|
16
|
+
munge_entry(@result["data"], @document.operation.selections, @supergraph.schema.query)
|
17
|
+
# hate doing a second pass, but cannot remove _STITCH_ fields until the fragements are processed
|
18
|
+
clean_entry(@result["data"])
|
19
|
+
end
|
20
|
+
|
21
|
+
if @errors.length > 0
|
22
|
+
@result = [] unless @result.key?("errors")
|
23
|
+
@result["errors"] += @errors
|
24
|
+
end
|
25
|
+
|
26
|
+
@result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def munge_entry(entry, selections, parent_type)
|
32
|
+
selections.each do |node|
|
33
|
+
case node
|
34
|
+
when GraphQL::Language::Nodes::Field
|
35
|
+
next if node.respond_to?(:name) && node&.name == "__typename"
|
36
|
+
|
37
|
+
munge_field(entry, node, parent_type)
|
38
|
+
|
39
|
+
when GraphQL::Language::Nodes::InlineFragment
|
40
|
+
next unless entry["_STITCH_typename"] == node.type.name
|
41
|
+
fragment_type = @supergraph.schema.types[node.type.name]
|
42
|
+
munge_entry(entry, node.selections, fragment_type)
|
43
|
+
|
44
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
45
|
+
next unless entry["_STITCH_typename"] == node.name
|
46
|
+
fragment = @document.fragment_definitions[node.name]
|
47
|
+
fragment_type = @supergraph.schema.types[node.name]
|
48
|
+
munge_entry(entry, fragment.selections, fragment_type)
|
49
|
+
else
|
50
|
+
raise "Unexpected node of type #{node.class.name} in selection set."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def munge_field(entry, node, parent_type)
|
56
|
+
field_identifier = (node.alias || node.name)
|
57
|
+
node_type = Util.get_named_type_for_field_node(@supergraph.schema, parent_type, node)
|
58
|
+
|
59
|
+
if entry.nil?
|
60
|
+
return # TODO bubble up error if not nullable
|
61
|
+
end
|
62
|
+
|
63
|
+
child_entry = entry[field_identifier]
|
64
|
+
if child_entry.nil?
|
65
|
+
entry[field_identifier] = nil
|
66
|
+
elsif child_entry.is_a? Array
|
67
|
+
child_entry.each do |raw_item|
|
68
|
+
munge_entry(raw_item, node.selections, node_type)
|
69
|
+
end
|
70
|
+
elsif ! Util.is_leaf_type?(node_type)
|
71
|
+
munge_entry(child_entry, node.selections, node_type)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def clean_entry(entry)
|
76
|
+
return if entry.nil?
|
77
|
+
|
78
|
+
entry.each do |key, value|
|
79
|
+
if key.start_with? "_STITCH_"
|
80
|
+
entry.delete(key)
|
81
|
+
elsif value.is_a?(Array)
|
82
|
+
value.each do |item|
|
83
|
+
clean_entry(item)
|
84
|
+
end
|
85
|
+
elsif value.is_a?(Hash)
|
86
|
+
clean_entry(value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Stitching
|
5
|
+
class Supergraph
|
6
|
+
LOCATION = "__super"
|
7
|
+
INTROSPECTION_TYPES = [
|
8
|
+
"__Schema",
|
9
|
+
"__Type",
|
10
|
+
"__Field",
|
11
|
+
"__Directive",
|
12
|
+
"__EnumValue",
|
13
|
+
"__InputValue",
|
14
|
+
"__TypeKind",
|
15
|
+
"__DirectiveLocation",
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
attr_reader :schema, :boundaries, :locations_by_type_and_field, :executables
|
19
|
+
|
20
|
+
def initialize(schema:, fields:, boundaries:, executables: {})
|
21
|
+
@schema = schema
|
22
|
+
@boundaries = boundaries
|
23
|
+
@locations_by_type_and_field = INTROSPECTION_TYPES.each_with_object(fields) do |type_name, memo|
|
24
|
+
introspection_type = schema.get_type(type_name)
|
25
|
+
next unless introspection_type.kind.fields?
|
26
|
+
|
27
|
+
memo[type_name] = introspection_type.fields.keys.each_with_object({}) do |field_name, m|
|
28
|
+
m[field_name] = [LOCATION]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@possible_keys_by_type_and_location = {}
|
33
|
+
@executables = { LOCATION => @schema }.merge!(executables)
|
34
|
+
end
|
35
|
+
|
36
|
+
def fields
|
37
|
+
@locations_by_type_and_field.reject { |k, _v| INTROSPECTION_TYPES.include?(k) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def export
|
41
|
+
return GraphQL::Schema::Printer.print_schema(@schema), {
|
42
|
+
"fields" => fields,
|
43
|
+
"boundaries" => @boundaries,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.from_export(schema, delegation_map, executables: {})
|
48
|
+
schema = GraphQL::Schema.from_definition(schema) if schema.is_a?(String)
|
49
|
+
new(
|
50
|
+
schema: schema,
|
51
|
+
fields: delegation_map["fields"],
|
52
|
+
boundaries: delegation_map["boundaries"],
|
53
|
+
executables: executables,
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def assign_executable(location, executable = nil, &block)
|
58
|
+
executable ||= block
|
59
|
+
unless executable.is_a?(Class) && executable <= GraphQL::Schema
|
60
|
+
raise "A client or block handler must be provided." unless executable
|
61
|
+
raise "A client must be callable" unless executable.respond_to?(:call)
|
62
|
+
end
|
63
|
+
@executables[location] = executable
|
64
|
+
end
|
65
|
+
|
66
|
+
def execute_at_location(location, query, variables)
|
67
|
+
executable = executables[location]
|
68
|
+
|
69
|
+
if executable.nil?
|
70
|
+
raise "No executable assigned for #{location} location."
|
71
|
+
elsif executable.is_a?(Class) && executable <= GraphQL::Schema
|
72
|
+
executable.execute(
|
73
|
+
query: query,
|
74
|
+
variables: variables,
|
75
|
+
validate: false,
|
76
|
+
)
|
77
|
+
elsif executable.respond_to?(:call)
|
78
|
+
executable.call(location, query, variables)
|
79
|
+
else
|
80
|
+
raise "Missing valid executable for #{location} location."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# inverts fields map to provide fields for a type/location
|
85
|
+
def fields_by_type_and_location
|
86
|
+
@fields_by_type_and_location ||= @locations_by_type_and_field.each_with_object({}) do |(type_name, fields), memo|
|
87
|
+
memo[type_name] = fields.each_with_object({}) do |(field_name, locations), memo|
|
88
|
+
locations.each do |location|
|
89
|
+
memo[location] ||= []
|
90
|
+
memo[location] << field_name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def locations_by_type
|
97
|
+
@locations_by_type ||= @locations_by_type_and_field.each_with_object({}) do |(type_name, fields), memo|
|
98
|
+
memo[type_name] = fields.values.flatten.uniq
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def possible_keys_for_type_and_location(type_name, location)
|
103
|
+
possible_keys_by_type = @possible_keys_by_type_and_location[type_name] ||= {}
|
104
|
+
possible_keys_by_type[location] ||= begin
|
105
|
+
location_fields = fields_by_type_and_location[type_name][location] || []
|
106
|
+
location_fields & @boundaries[type_name].map { _1["selection"] }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# For a given type, route from one origin service to one or more remote locations.
|
111
|
+
# Tunes a-star search to favor paths with fewest joining locations, ie:
|
112
|
+
# favor longer paths through target locations over shorter paths with additional locations.
|
113
|
+
def route_type_to_locations(type_name, start_location, goal_locations)
|
114
|
+
results = {}
|
115
|
+
costs = {}
|
116
|
+
|
117
|
+
paths = possible_keys_for_type_and_location(type_name, start_location).map do |possible_key|
|
118
|
+
[{ location: start_location, selection: possible_key, cost: 0 }]
|
119
|
+
end
|
120
|
+
|
121
|
+
while paths.any?
|
122
|
+
path = paths.pop
|
123
|
+
current_location = path.last[:location]
|
124
|
+
current_selection = path.last[:selection]
|
125
|
+
current_cost = path.last[:cost]
|
126
|
+
|
127
|
+
@boundaries[type_name].each do |boundary|
|
128
|
+
forward_location = boundary["location"]
|
129
|
+
next if current_selection != boundary["selection"]
|
130
|
+
next if path.any? { _1[:location] == forward_location }
|
131
|
+
|
132
|
+
best_cost = costs[forward_location] || Float::INFINITY
|
133
|
+
next if best_cost < current_cost
|
134
|
+
|
135
|
+
path.pop
|
136
|
+
path << {
|
137
|
+
location: current_location,
|
138
|
+
selection: current_selection,
|
139
|
+
cost: current_cost,
|
140
|
+
boundary: boundary,
|
141
|
+
}
|
142
|
+
|
143
|
+
if goal_locations.include?(forward_location)
|
144
|
+
current_result = results[forward_location]
|
145
|
+
if current_result.nil? || current_cost < best_cost || (current_cost == best_cost && path.length < current_result.length)
|
146
|
+
results[forward_location] = path.map { _1[:boundary] }
|
147
|
+
end
|
148
|
+
else
|
149
|
+
path.last[:cost] += 1
|
150
|
+
end
|
151
|
+
|
152
|
+
forward_cost = path.last[:cost]
|
153
|
+
costs[forward_location] = forward_cost if forward_cost < best_cost
|
154
|
+
|
155
|
+
possible_keys_for_type_and_location(type_name, forward_location).each do |possible_key|
|
156
|
+
paths << [*path, { location: forward_location, selection: possible_key, cost: forward_cost }]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
paths.sort! do |a, b|
|
161
|
+
cost_diff = a.last[:cost] - b.last[:cost]
|
162
|
+
next cost_diff unless cost_diff.zero?
|
163
|
+
a.length - b.length
|
164
|
+
end.reverse!
|
165
|
+
end
|
166
|
+
|
167
|
+
results
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Stitching
|
5
|
+
class Util
|
6
|
+
|
7
|
+
# gets the named type at the bottom of a non-null/list wrapper chain
|
8
|
+
def self.get_named_type(type)
|
9
|
+
while type.respond_to?(:of_type)
|
10
|
+
type = type.of_type
|
11
|
+
end
|
12
|
+
type
|
13
|
+
end
|
14
|
+
|
15
|
+
# gets a deep structural description of a list value type
|
16
|
+
def self.get_list_structure(type)
|
17
|
+
structure = []
|
18
|
+
previous = nil
|
19
|
+
while type.respond_to?(:of_type)
|
20
|
+
if type.is_a?(GraphQL::Schema::List)
|
21
|
+
structure.push(previous.is_a?(GraphQL::Schema::NonNull) ? "non_null_list" : "list")
|
22
|
+
end
|
23
|
+
if structure.any?
|
24
|
+
previous = type
|
25
|
+
if !type.of_type.respond_to?(:of_type)
|
26
|
+
structure.push(previous.is_a?(GraphQL::Schema::NonNull) ? "non_null_element" : "element")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
type = type.of_type
|
30
|
+
end
|
31
|
+
structure
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets all objects and interfaces that implement a given interface
|
35
|
+
def self.get_possible_types(schema, parent_type)
|
36
|
+
return [parent_type] unless parent_type.kind.abstract?
|
37
|
+
return parent_type.possible_types if parent_type.kind.union?
|
38
|
+
|
39
|
+
result = []
|
40
|
+
schema.types.values.each do |type|
|
41
|
+
next unless type <= GraphQL::Schema::Interface && type != parent_type
|
42
|
+
next unless type.interfaces.include?(parent_type)
|
43
|
+
result << type
|
44
|
+
result.push(*get_possible_types(schema, type)) if type.kind.interface?
|
45
|
+
end
|
46
|
+
result.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
# Specifies if a type is a leaf node (no children)
|
50
|
+
def self.is_leaf_type?(type)
|
51
|
+
type.kind.scalar? || type.kind.enum?
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.get_named_type_for_field_node(schema, parent_type, node)
|
55
|
+
if node.name == "__schema" && parent_type == schema.query
|
56
|
+
schema.types["__Schema"] # type mapped to phantom introspection field
|
57
|
+
else
|
58
|
+
Util.get_named_type(parent_type.fields[node.name].type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Stitching
|
7
|
+
class StitchingError < StandardError; end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def stitch_directive
|
12
|
+
@stitch_directive ||= "stitch"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_writer :stitch_directive
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require_relative "stitching/gateway"
|
21
|
+
require_relative "stitching/supergraph"
|
22
|
+
require_relative "stitching/composer"
|
23
|
+
require_relative "stitching/document"
|
24
|
+
require_relative "stitching/executor"
|
25
|
+
require_relative "stitching/planner_operation"
|
26
|
+
require_relative "stitching/planner"
|
27
|
+
require_relative "stitching/remote_client"
|
28
|
+
require_relative "stitching/shaper"
|
29
|
+
require_relative "stitching/util"
|
30
|
+
require_relative "stitching/version"
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql-stitching
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg MacWilliam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.16
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.16
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.12'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.12'
|
69
|
+
description: GraphQL schema stitching for Ruby
|
70
|
+
email:
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".github/workflows/ci.yml"
|
76
|
+
- ".gitignore"
|
77
|
+
- ".ruby-version"
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- LICENSE
|
81
|
+
- Procfile
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- docs/README.md
|
85
|
+
- docs/composer.md
|
86
|
+
- docs/document.md
|
87
|
+
- docs/executor.md
|
88
|
+
- docs/gateway.md
|
89
|
+
- docs/images/library.png
|
90
|
+
- docs/images/merging.png
|
91
|
+
- docs/images/stitching.png
|
92
|
+
- docs/planner.md
|
93
|
+
- docs/shaper.md
|
94
|
+
- docs/supergraph.md
|
95
|
+
- example/gateway.rb
|
96
|
+
- example/graphiql.html
|
97
|
+
- example/remote1.rb
|
98
|
+
- example/remote2.rb
|
99
|
+
- graphql-stitching.gemspec
|
100
|
+
- lib/graphql/stitching.rb
|
101
|
+
- lib/graphql/stitching/composer.rb
|
102
|
+
- lib/graphql/stitching/composer/base_validator.rb
|
103
|
+
- lib/graphql/stitching/composer/validate_boundaries.rb
|
104
|
+
- lib/graphql/stitching/composer/validate_interfaces.rb
|
105
|
+
- lib/graphql/stitching/document.rb
|
106
|
+
- lib/graphql/stitching/executor.rb
|
107
|
+
- lib/graphql/stitching/gateway.rb
|
108
|
+
- lib/graphql/stitching/planner.rb
|
109
|
+
- lib/graphql/stitching/planner_operation.rb
|
110
|
+
- lib/graphql/stitching/remote_client.rb
|
111
|
+
- lib/graphql/stitching/shaper.rb
|
112
|
+
- lib/graphql/stitching/supergraph.rb
|
113
|
+
- lib/graphql/stitching/util.rb
|
114
|
+
- lib/graphql/stitching/version.rb
|
115
|
+
homepage: https://github.com/gmac/graphql-stitching-ruby
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata:
|
119
|
+
homepage_uri: https://github.com/gmac/graphql-stitching-ruby
|
120
|
+
changelog_uri: https://github.com/gmac/graphql-stitching-ruby/releases
|
121
|
+
source_code_uri: https://github.com/gmac/graphql-stitching-ruby
|
122
|
+
bug_tracker_uri: https://github.com/gmac/graphql-stitching-ruby/issues
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.1.1
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubygems_version: 3.3.7
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: GraphQL schema stitching for Ruby
|
142
|
+
test_files: []
|