-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit beb14f2
Showing
8 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
GRAPHCOOL_ENDPOINT=http://localhost:60000/simple/v1/cj9larpve000c0196iathtcl2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
dist | ||
yarn.lock | ||
package-lock.json | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# graphql-gateway-tools | ||
|
||
A set of tools to help you build a GraphQL Gateway using remote schemas and schema stitching. | ||
|
||
## SchemaGenerator | ||
|
||
A higher order helper for creating a merged schema. It makes it easy to compose a schema, by providing methods to add remote schema endpoints, local GraphQL schemas, partial type definitions, and multiple resolver functions. | ||
|
||
``` | ||
const schemaGenerator = new SchemaGenerator() | ||
// Create a remoteExecutableSchema by specifying the endpoint address | ||
await schemaGenerator.registerEndpoint({uri: 'http://myendpointaddress/graphql'}) | ||
// Create a remoteExecutableSchema by specifying endpoint adddress and introspection schema: | ||
await schemaGenerator.registerEndpoint({uri: 'http://myendpointaddress/graphql', introspectionSchema: myIntrospectionSchema}) | ||
// Create a remoteExecutableSchema by passing in an ApolloLink instance | ||
await schemaGenerator.registerEndpoint({link: myApolloLink}) | ||
// Create a remoteExecutableSchema that uses an Authorization bearer token | ||
await schemaGenerator.registerEndpoint({uri: 'http://myendpointaddress/graphql', authenticationToken: 'ey.......'}) | ||
// Add a schema | ||
schemaGenerator.registerSchema(schema: myGraphQLSchema) | ||
// Add a type definition | ||
schemaGenerator.registerTypeDefinition(typeDefs: myTypeDefinitionString) | ||
// Add a resolver function | ||
schemaGenerator.registerResolver(resolverFunction: myResolverFunction) | ||
// Generate the merged schema | ||
const mySchema = schemaGenerator.generateSchema() | ||
``` | ||
|
||
See the [examples](./examples) folder for a complete example (coming soon). | ||
|
||
## addTypeNameField | ||
|
||
If you add an Interface Types to your merged schema, you have to manually add the `__typeName` field to your resolvers. This helper function makes it easy to do so. | ||
|
||
``` | ||
// Assuming you have created a remote schema mySchema with types Car and Boat | ||
const typeDefs = ` | ||
interface Verhicle { | ||
maxSpeed: Float | ||
} | ||
extend type Car implements Vehicle { } | ||
extend type Boat implements Vehicle { } | ||
extend type Query { | ||
allVehicles: [Verhicle] | ||
}` | ||
const schema = mergeSchemas({ | ||
schemas: [mySchema], | ||
resolvers: mergeInfo => ({ | ||
Query: { | ||
allVehicles: { | ||
async resolve(parent, args, context, info){ | ||
const newInfo = addTypeNameField(info) | ||
const cars = mergeInfo.delegate('query', 'allCars', args, context, info) | ||
const boats = mergeInfo.delgate('query', 'allBoats', args, context, info) | ||
return [...cars, ...boats] | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
``` | ||
|
||
## addFields(mergeInfo: MergeInfo, fields: Array<FieldNode | string>) | ||
|
||
A generic helper for adding fields to the resolveInfo, by passing in a fieldName, or a complete FieldNode. | ||
|
||
``` | ||
const myField: FieldNode = { kind: 'Field', name: { kind: 'Name', value: 'myField' } } | ||
const anotherField: 'anotherField' | ||
addFields(mergeInfo, [myField, anotherField]) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { SchemaGenerator } from './src/SchemaGenerator' | ||
export { addTypeNameField, delegateHelper } from './src/delegateHelpers' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "graphql-gateway-tools", | ||
"version": "0.1.0", | ||
"scripts": { | ||
"prepublish": "npm run build", | ||
"build": "rm -rf dist && tsc -d", | ||
"start": "./node_modules/.bin/ts-node ./test/server.ts" | ||
}, | ||
"main": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"license": "GPL-3.0", | ||
"author": "Kim Brandwijk <[email protected]>", | ||
"homepage": "https://github.com/kbrandwijk/graphql-gateway-tools#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/kbrandwijk/graphql-gateway-tools.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/kbrandwijk/graphql-gateway-tools/issues" | ||
}, | ||
"files": [ | ||
"dist", | ||
"package.json", | ||
"README.md" | ||
], | ||
"peerDependencies": { | ||
"graphql": "^0.11.7" | ||
}, | ||
"dependencies": { | ||
"apollo-link": "^1.0.0", | ||
"apollo-link-http": "^1.1.0", | ||
"graphql-tools": "^2.7.2", | ||
"lodash": "^4.17.4", | ||
"node-fetch": "^1.7.3" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^8.0.49", | ||
"@types/zen-observable": "^0.5.3", | ||
"typescript": "^2.6.1", | ||
"ts-node": "^3.3.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { createHttpLink } from 'apollo-link-http' | ||
import { ApolloLink } from 'apollo-link' | ||
import fetch from 'node-fetch' | ||
import { GraphQLSchema } from 'graphql' | ||
import { introspectSchema, makeRemoteExecutableSchema, mergeSchemas } from 'graphql-tools' | ||
import { MergeInfo } from 'graphql-tools/dist/stitching/mergeSchemas' | ||
import { IResolvers } from 'graphql-tools/dist/Interfaces' | ||
import { merge } from 'lodash' | ||
|
||
export class SchemaGenerator { | ||
private _schemas: (GraphQLSchema | string)[] = [] | ||
private _resolvers: ((mergeInfo: MergeInfo) => IResolvers)[] = [] | ||
|
||
async registerEndpoint({uri, introspectionSchema, link, authenticationToken}: { | ||
uri?: string, | ||
introspectionSchema?: GraphQLSchema, | ||
link?: ApolloLink, | ||
authenticationToken?: (context) => string }): Promise<GraphQLSchema> { | ||
|
||
|
||
if (link === undefined) { | ||
const httpLink: ApolloLink = createHttpLink({ uri, fetch }) | ||
|
||
if (authenticationToken !== undefined) { | ||
link = new ApolloLink((operation, forward) => { | ||
operation.setContext((context) => { | ||
|
||
if (context && authenticationToken(context)) { | ||
console.log(authenticationToken(context)) | ||
return { | ||
headers: { | ||
'Authorization': `Bearer ${authenticationToken(context)}`, | ||
} | ||
} | ||
} | ||
else { | ||
return null | ||
} | ||
}) | ||
return forward!(operation); | ||
}).concat(httpLink) | ||
} | ||
else { | ||
link = httpLink | ||
} | ||
} | ||
|
||
if (introspectionSchema === undefined) { | ||
introspectionSchema = await introspectSchema(link) | ||
} | ||
|
||
const executableSchema = makeRemoteExecutableSchema({ schema: introspectionSchema, link }) | ||
this._schemas.push(executableSchema) | ||
|
||
return executableSchema | ||
} | ||
|
||
registerSchema(schema: GraphQLSchema) { | ||
this._schemas.push(schema) | ||
} | ||
|
||
registerTypeDefinition(typeDefs: string) { | ||
this._schemas.push(typeDefs) | ||
} | ||
|
||
registerResolver(resolverFunction: any) { | ||
this._resolvers.push(resolverFunction) | ||
} | ||
|
||
generateSchema() { | ||
const resolvers = mergeInfo => { | ||
const resolverObject = {} | ||
merge(resolverObject, ...this._resolvers.map(r => r(mergeInfo))) | ||
return resolverObject | ||
} | ||
|
||
const finalSchema = mergeSchemas({ | ||
schemas: this._schemas, | ||
resolvers | ||
}) | ||
|
||
return finalSchema | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { DocumentNode, GraphQLResolveInfo, OperationDefinitionNode, parse, FieldNode } from 'graphql' | ||
import { MergeInfo } from 'graphql-tools/dist/stitching/mergeSchemas'; | ||
|
||
/* | ||
mergeInfo.delegate only allows you to pass in an operationName and variables. | ||
It does not allow you to define query fields, because they are inferred from the user query. | ||
If you need to delegate a query that is unrelated to the user query, you need to provide | ||
the fields you need. This helper does that, based on a provided query. | ||
*/ | ||
export const delegateHelper = (mergeInfo: MergeInfo) => ({ | ||
delegateQuery: async ( | ||
query: string, | ||
args: { [key: string]: any }, | ||
context: { [key: string]: any }, | ||
info: GraphQLResolveInfo): Promise<any> => { | ||
const document: DocumentNode = parse(query) | ||
|
||
const operationDefinition: OperationDefinitionNode = document.definitions[0] as OperationDefinitionNode | ||
const operationType: "query" | "mutation" = operationDefinition.operation | ||
const operationName:string = (operationDefinition.selectionSet.selections[0] as any).name.value | ||
const fields: [FieldNode] = (operationDefinition.selectionSet.selections[0] as any).selectionSet.selections | ||
|
||
const newInfo: GraphQLResolveInfo = JSON.parse(JSON.stringify(info)); | ||
newInfo.fieldNodes[0].selectionSet!.selections = fields | ||
|
||
return await mergeInfo.delegate( | ||
operationType, operationName, args, context, newInfo | ||
) | ||
} | ||
}) | ||
|
||
export const addTypeNameField: GraphQLResolveInfo = (info: GraphQLResolveInfo) => { | ||
const field: FieldNode = | ||
{ kind: 'Field', name: { kind: 'Name', value: '__typename' } } | ||
|
||
return addFields(info, [field]) | ||
} | ||
|
||
export const addFields: GraphQLResolveInfo = (info: GraphQLResolveInfo, fields: [FieldNode | string]) => { | ||
const newInfo = JSON.parse(JSON.stringify(info)); | ||
|
||
for (const field of fields) { | ||
if (typeof field === 'string'){ | ||
newInfo.fieldNodes[0].selectionSet.selections.push({ | ||
kind: 'Field', name: { kind: 'Name', value: field } | ||
}) | ||
} | ||
else { | ||
newInfo.fieldNodes[0].selectionSet.selections.push(field) | ||
} | ||
} | ||
|
||
return newInfo | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"declaration": true, | ||
"module": "commonjs", | ||
"strictNullChecks": true, | ||
"target": "es6", | ||
"sourceMap": true, | ||
"outDir": "./dist" | ||
}, | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
} |