Skip to content

Commit 39ad97a

Browse files
committed
First commit
0 parents  commit 39ad97a

35 files changed

+8131
-0
lines changed

Diff for: .eslintrc.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
extends: 'eslint-config-gusto',
3+
rules: {
4+
'no-console': 'off',
5+
},
6+
};

Diff for: .gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# yarn
2+
yarn-debug.log*
3+
yarn-error.log*
4+
5+
# Dependency directories
6+
node_modules/

Diff for: .prettierrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('eslint-config-gusto/.prettierrc');

Diff for: Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source "https://rubygems.org"
2+
3+
gemspec

Diff for: Gemfile.lock

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
PATH
2+
remote: .
3+
specs:
4+
apollo-federation (0.1.0)
5+
graphql
6+
7+
GEM
8+
remote: https://rubygems.org/
9+
specs:
10+
byebug (11.0.1)
11+
coderay (1.1.2)
12+
diff-lcs (1.3)
13+
graphql (1.9.6)
14+
method_source (0.9.2)
15+
pry (0.12.2)
16+
coderay (~> 1.1.0)
17+
method_source (~> 0.9.0)
18+
pry-byebug (3.7.0)
19+
byebug (~> 11.0)
20+
pry (~> 0.10)
21+
rack (2.0.7)
22+
rspec (3.8.0)
23+
rspec-core (~> 3.8.0)
24+
rspec-expectations (~> 3.8.0)
25+
rspec-mocks (~> 3.8.0)
26+
rspec-core (3.8.0)
27+
rspec-support (~> 3.8.0)
28+
rspec-expectations (3.8.3)
29+
diff-lcs (>= 1.2.0, < 2.0)
30+
rspec-support (~> 3.8.0)
31+
rspec-mocks (3.8.0)
32+
diff-lcs (>= 1.2.0, < 2.0)
33+
rspec-support (~> 3.8.0)
34+
rspec-support (3.8.0)
35+
36+
PLATFORMS
37+
ruby
38+
39+
DEPENDENCIES
40+
apollo-federation!
41+
pry-byebug
42+
rack
43+
rspec
44+
45+
BUNDLED WITH
46+
2.0.1

Diff for: README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# apollo-federation
2+
3+
This gem extends the graphql gem to add support for creating a federated schema
4+
5+
## Installation
6+
7+
Add this line to your application's Gemfile:
8+
9+
```ruby
10+
gem 'apollo-federation'
11+
```
12+
13+
And then execute:
14+
15+
$ bundle
16+
17+
Or install it yourself as:
18+
19+
$ gem install apollo-federation
20+
21+
## Usage
22+
23+
TODO: Write usage instructions here

Diff for: apollo-federation.gemspec

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
lib = File.expand_path("../lib", __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
require "apollo-federation/version"
5+
6+
Gem::Specification.new do |spec|
7+
spec.name = "apollo-federation"
8+
spec.version = ApolloFederation::VERSION
9+
spec.authors = ["Gusto"]
10+
11+
spec.summary = 'This gem extends the graphql gem to add support for creating a federated schema'
12+
spec.description = spec.summary
13+
spec.homepage = 'https://www.gusto.com'
14+
15+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16+
# to allow pushing to a single host or delete this section to allow pushing to any host.
17+
if spec.respond_to?(:metadata)
18+
spec.metadata['allowed_push_host'] = 'https://gemstash.zp-int.com/private'
19+
else
20+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
21+
end
22+
23+
spec.add_dependency 'graphql'
24+
25+
spec.add_development_dependency 'pry-byebug'
26+
spec.add_development_dependency 'rack'
27+
spec.add_development_dependency 'rspec'
28+
end

Diff for: babel.config.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
presets: [
3+
[
4+
'@babel/preset-env',
5+
{
6+
targets: {
7+
node: 'current',
8+
},
9+
},
10+
],
11+
],
12+
};

Diff for: example/accounts.rb

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require_relative './graphql_server'
2+
3+
# extend type Query {
4+
# me: User
5+
# }
6+
7+
# type User @key(fields: "id") {
8+
# id: ID!
9+
# name: String
10+
# username: String
11+
# }
12+
13+
USERS = [
14+
{
15+
id: "1",
16+
name: "Ada Lovelace",
17+
birthDate: "1815-12-10",
18+
username: "@ada"
19+
},
20+
{
21+
id: "2",
22+
name: "Alan Turing",
23+
birthDate: "1912-06-23",
24+
username: "@complete"
25+
}
26+
];
27+
28+
class User < BaseObject
29+
key fields: 'id'
30+
31+
field :id, ID, null: false
32+
field :name, String, null: true
33+
field :username, String, null: true
34+
35+
def self.resolve_reference(object, _context)
36+
USERS.find { |user| user[:id] == object[:id] }
37+
end
38+
end
39+
40+
class Query < BaseObject
41+
field :me, User, null: true
42+
43+
def me
44+
USERS[0]
45+
end
46+
end
47+
48+
class AccountSchema < GraphQL::Schema
49+
extend ApolloFederation::Schema
50+
51+
query(Query)
52+
end
53+
54+
GraphQLServer.run(AccountSchema, Port: 5001)

Diff for: example/gateway.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const { ApolloServer } = require('apollo-server');
2+
const { ApolloGateway } = require('@apollo/gateway');
3+
4+
const gateway = new ApolloGateway({
5+
serviceList: [
6+
{ name: 'accounts', url: 'http://localhost:5001/graphql' },
7+
{ name: 'reviews', url: 'http://localhost:5002/graphql' },
8+
{ name: 'products', url: 'http://localhost:5003/graphql' },
9+
{ name: 'inventory', url: 'http://localhost:5004/graphql' },
10+
],
11+
debug: true,
12+
});
13+
14+
(async () => {
15+
const { schema, executor } = await gateway.load();
16+
17+
const server = new ApolloServer({ schema, executor });
18+
19+
server.listen({ port: 5000 }).then(({ url }) => {
20+
console.log(`🚀 Server ready at ${url}`);
21+
});
22+
})();

Diff for: example/graphql_server.rb

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require 'rack'
2+
require 'json'
3+
require 'graphql'
4+
require 'pry-byebug'
5+
require 'apollo-federation'
6+
7+
class BaseField < GraphQL::Schema::Field
8+
include ApolloFederation::Field
9+
end
10+
11+
class BaseObject < GraphQL::Schema::Object
12+
extend ApolloFederation::Object
13+
14+
field_class BaseField
15+
end
16+
17+
class GraphQLServer
18+
def self.run(schema, options = {})
19+
# TODO: Should this code be shared with the integration tests? If so, we should probably add
20+
# a command line arg to run in test mode
21+
Rack::Handler::WEBrick.run(
22+
GraphQLServer.new(schema),
23+
options.merge!(
24+
Logger: ::WEBrick::Log.new($stderr, ::WEBrick::Log::ERROR),
25+
AccessLog: [],
26+
),
27+
) do
28+
$stdout.puts '_READY_'
29+
$stdout.flush
30+
end
31+
end
32+
33+
def initialize(schema)
34+
self.schema = schema
35+
end
36+
37+
def call(env)
38+
req = Rack::Request.new(env)
39+
req_vars = JSON.parse(req.body.read)
40+
query = req_vars['query']
41+
operationName = req_vars["operationName"]
42+
vars = req_vars["variables"] || {}
43+
44+
graphql_debugging = {
45+
query: query,
46+
operationName: operationName,
47+
vars: vars,
48+
schema: schema,
49+
}
50+
puts graphql_debugging.inspect
51+
52+
result = schema.execute(
53+
query,
54+
operation_name: operationName,
55+
variables: vars
56+
)
57+
['200', {'Content-Type' => 'application/json'}, [JSON.dump(result.to_h)]]
58+
end
59+
60+
private
61+
62+
attr_accessor :schema
63+
end

Diff for: example/inventory.rb

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require_relative './graphql_server'
2+
3+
# extend type Product @key(fields: "upc") {
4+
# upc: String! @external
5+
# weight: Int @external
6+
# price: Int @external
7+
# inStock: Boolean
8+
# shippingEstimate: Int @requires(fields: "price weight")
9+
# }
10+
11+
INVENTORY = [
12+
{ upc: "1", in_stock: true },
13+
{ upc: "2", in_stock: false },
14+
{ upc: "3", in_stock: true }
15+
]
16+
17+
class Product < BaseObject
18+
extend_type
19+
key fields: 'upc'
20+
21+
field :upc, String, null: false, external: true
22+
field :weight, Int, null: true, external: true
23+
field :price, Int, null: true, external: true
24+
field :in_stock, Boolean, null: true
25+
field :shipping_estimate, Int, null: true, requires: { fields: "price weight"}
26+
27+
def self.resolve_reference(reference, context)
28+
reference.merge(INVENTORY.find { |product| product[:upc] == reference[:upc] })
29+
end
30+
31+
def shipping_estimate
32+
# free for expensive items
33+
if object[:price] > 1000
34+
0
35+
else
36+
# estimate is based on weight
37+
object[:weight] * 0.5
38+
end
39+
end
40+
end
41+
42+
class InventorySchema < GraphQL::Schema
43+
extend ApolloFederation::Schema
44+
45+
orphan_types Product
46+
end
47+
48+
GraphQLServer.run(InventorySchema, Port: 5004)

Diff for: example/products.rb

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
require_relative './graphql_server'
2+
3+
# extend type Query {
4+
# topProducts(first: Int = 5): [Product]
5+
# }
6+
7+
# type Product @key(fields: "upc") {
8+
# upc: String!
9+
# name: String
10+
# price: Int
11+
# weight: Int
12+
# }
13+
14+
PRODUCTS = [
15+
{
16+
upc: "1",
17+
name: "Table",
18+
price: 899,
19+
weight: 100
20+
},
21+
{
22+
upc: "2",
23+
name: "Couch",
24+
price: 1299,
25+
weight: 1000
26+
},
27+
{
28+
upc: "3",
29+
name: "Chair",
30+
price: 54,
31+
weight: 50
32+
}
33+
]
34+
35+
class Product < BaseObject
36+
key fields: 'upc'
37+
38+
field :upc, String, null: false
39+
field :name, String, null: true
40+
field :price, Int, null: true
41+
field :weight, Int, null: true
42+
43+
def self.resolve_reference(reference, context)
44+
PRODUCTS.find { |product| product[:upc] === reference[:upc] }
45+
end
46+
end
47+
48+
class Query < BaseObject
49+
field :top_products, [Product], null: false do
50+
argument :first, Int, required: false, default_value: 5
51+
end
52+
53+
def top_products(first:)
54+
PRODUCTS.slice(0, first)
55+
end
56+
end
57+
58+
class ProductSchema < GraphQL::Schema
59+
extend ApolloFederation::Schema
60+
61+
query(Query)
62+
end
63+
64+
GraphQLServer.run(ProductSchema, Port: 5003)

0 commit comments

Comments
 (0)