Skip to content

Commit cc8ffef

Browse files
jakedohmhjvedvik
authored andcommitted
feat(graphql): initial GraphQL source plugin (gridsome#482)
Closes gridsome#80
1 parent d44b5ac commit cc8ffef

File tree

6 files changed

+296
-1
lines changed

6 files changed

+296
-1
lines changed

packages/source-graphql/CHANGELOG.md

Whitespace-only changes.

packages/source-graphql/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# @gridsome/source-graphql
2+
3+
> Pull a remote GraphQL schema in locally
4+
5+
## Install
6+
7+
- `yarn add @gridsome/source-graphql`
8+
- `npm install @gridsome/source-graphql`
9+
10+
## Usage
11+
12+
```js
13+
module.exports = {
14+
plugins: [
15+
{
16+
use: '@gridsome/source-graphql',
17+
options: {
18+
url: 'https://example.com/api',
19+
fieldName: 'puppies',
20+
typeName: 'puppyTypes',
21+
22+
headers: {
23+
Authorization: `Bearer ${process.env.AUTH_TOKEN}`,
24+
},
25+
},
26+
},
27+
],
28+
}
29+
```
30+
31+
## Options
32+
33+
#### url
34+
35+
- Type: `string` _required_
36+
37+
The URL of a GraphQL API endpoint to request your schema from.
38+
39+
#### fieldName
40+
41+
- Type: `string` _required_
42+
43+
The name that should be used to namespace your remote schema when it's merged in, so that it doesn't conflict with any local data.
44+
45+
For instance, if you put "puppies" your remote schema's data will be available by querying like so:
46+
47+
```
48+
query {
49+
puppies {
50+
helloWorld
51+
}
52+
}
53+
```
54+
55+
#### typeName
56+
57+
- Type: `string`
58+
- Defaults: `fieldName`
59+
60+
The prefix to be used for your imported schema's field types.
61+
62+
#### headers
63+
64+
- Type: `object`
65+
66+
An object of headers to be passed along with your request to the API endpoint. This will generally be used to authenticate your request.
67+
68+
**Note**: For safety, you should pass any sensitive tokens/passwords as environmental variables. To learn more, see the [Gridsome Docs on Environmental Variables](https://gridsome.org/docs/environment-variables/).

packages/source-graphql/index.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const { setContext } = require('apollo-link-context')
2+
const { HttpLink } = require('apollo-link-http')
3+
const {
4+
introspectSchema,
5+
makeRemoteExecutableSchema,
6+
transformSchema,
7+
RenameTypes
8+
} = require('graphql-tools')
9+
const fetch = require('node-fetch')
10+
11+
const {
12+
NamespaceUnderFieldTransform,
13+
StripNonQueryTransform
14+
} = require(`./transforms`)
15+
16+
class GraphQLSource {
17+
static defaultOptions () {
18+
return {
19+
url: undefined,
20+
fieldName: undefined,
21+
typeName: undefined,
22+
headers: {}
23+
}
24+
}
25+
26+
constructor (api, options) {
27+
this.api = api
28+
const { url, fieldName, headers } = options
29+
let typeName = options.typeName
30+
31+
// Make sure all required props are passed
32+
33+
if (!url) {
34+
throw new Error(`Missing url option.`)
35+
}
36+
37+
if (!fieldName) {
38+
throw new Error(`Missing fieldName option.`)
39+
}
40+
41+
// If typeName isn't defined, default to fieldName
42+
43+
if (!typeName) {
44+
typeName = fieldName
45+
}
46+
47+
// Fetch schema, namespace it, and merge it into local schema
48+
api.createSchema(async ({ addSchema, graphql }) => {
49+
const remoteSchema = await this.getRemoteExecutableSchema(url, headers)
50+
const namespacedSchema = await this.namespaceSchema(
51+
remoteSchema,
52+
fieldName,
53+
typeName,
54+
graphql
55+
)
56+
57+
return namespacedSchema
58+
})
59+
}
60+
61+
async getRemoteExecutableSchema (url, headers) {
62+
const http = new HttpLink({
63+
uri: url,
64+
fetch
65+
})
66+
const link = setContext((request, previousContext) => ({ headers })).concat(
67+
http
68+
)
69+
const remoteSchema = await introspectSchema(link)
70+
const remoteExecutableSchema = await makeRemoteExecutableSchema({
71+
schema: remoteSchema,
72+
link
73+
})
74+
75+
return remoteExecutableSchema
76+
}
77+
78+
async namespaceSchema (schema, fieldName, typeName, graphql) {
79+
const namespacedSchema = transformSchema(schema, [
80+
new StripNonQueryTransform(),
81+
new RenameTypes(name => `${typeName}_${name}`),
82+
new NamespaceUnderFieldTransform({
83+
typeName,
84+
fieldName,
85+
graphql
86+
})
87+
])
88+
89+
return namespacedSchema
90+
}
91+
}
92+
93+
module.exports = GraphQLSource
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"version": "0.0.0",
3+
"name": "@gridsome/source-graphql",
4+
"description": "GraphQL source for Gridsome",
5+
"homepage": "https://github.com/gridsome/gridsome/tree/master/packages/source-graphql#readme",
6+
"repository": "https://github.com/gridsome/gridsome/tree/master/packages/source-graphql",
7+
"main": "index.js",
8+
"keywords": [
9+
"gridsome",
10+
"gridsome-plugin",
11+
"gridsome-source"
12+
],
13+
"dependencies": {
14+
"apollo-link-context": "^1.0.17",
15+
"apollo-link-http": "^1.5.14",
16+
"graphql-tools": "^4.0.4",
17+
"node-fetch": "^2.6.0"
18+
},
19+
"publishConfig": {
20+
"access": "public"
21+
},
22+
"engines": {
23+
"node": ">=8"
24+
},
25+
"devDependencies": {}
26+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const {
2+
visitSchema,
3+
VisitSchemaKind
4+
} = require(`graphql-tools/dist/transforms/visitSchema`)
5+
const {
6+
createResolveType,
7+
fieldMapToFieldConfigMap
8+
} = require(`graphql-tools/dist/stitching/schemaRecreation`)
9+
10+
class NamespaceUnderFieldTransform {
11+
constructor ({ typeName, fieldName, graphql }) {
12+
this.typeName = typeName
13+
this.fieldName = fieldName
14+
this.graphql = graphql
15+
}
16+
17+
transformSchema (schema) {
18+
const { GraphQLObjectType, GraphQLSchema } = this.graphql
19+
20+
const query = schema.getQueryType()
21+
22+
let newQuery // eslint-disable-line
23+
const nestedType = new GraphQLObjectType({
24+
name: this.typeName,
25+
fields: () =>
26+
fieldMapToFieldConfigMap(
27+
query.getFields(),
28+
createResolveType(typeName => {
29+
if (typeName === query.name) {
30+
return newQuery
31+
} else {
32+
return schema.getType(typeName)
33+
}
34+
}),
35+
true
36+
)
37+
})
38+
newQuery = new GraphQLObjectType({
39+
name: query.name,
40+
fields: {
41+
[this.fieldName]: {
42+
type: nestedType,
43+
resolve: (parent, args, context, info) => {
44+
return {}
45+
}
46+
}
47+
}
48+
})
49+
const typeMap = schema.getTypeMap()
50+
const allTypes = Object.keys(typeMap)
51+
.filter(name => name !== query.name)
52+
.map(key => typeMap[key])
53+
54+
return new GraphQLSchema({
55+
query: newQuery,
56+
types: allTypes
57+
})
58+
}
59+
}
60+
61+
class StripNonQueryTransform {
62+
transformSchema (schema) {
63+
return visitSchema(schema, {
64+
[VisitSchemaKind.MUTATION] () {
65+
return null
66+
},
67+
[VisitSchemaKind.SUBSCRIPTION] () {
68+
return null
69+
}
70+
})
71+
}
72+
}
73+
74+
module.exports = {
75+
NamespaceUnderFieldTransform,
76+
StripNonQueryTransform
77+
}

yarn.lock

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2206,7 +2206,33 @@ anymatch@^2.0.0:
22062206
micromatch "^3.1.4"
22072207
normalize-path "^2.1.1"
22082208

2209-
apollo-link@^1.2.3:
2209+
apollo-link-context@^1.0.17:
2210+
version "1.0.17"
2211+
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.17.tgz#439272cfb43ec1891506dd175ed907845b7de36c"
2212+
integrity sha512-W5UUfHcrrlP5uqJs5X1zbf84AMXhPZGAqX/7AQDgR6wY/7//sMGfJvm36KDkpIeSOElztGtM9z6zdPN1NbT41Q==
2213+
dependencies:
2214+
apollo-link "^1.2.11"
2215+
tslib "^1.9.3"
2216+
2217+
apollo-link-http-common@^0.2.13:
2218+
version "0.2.13"
2219+
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
2220+
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
2221+
dependencies:
2222+
apollo-link "^1.2.11"
2223+
ts-invariant "^0.3.2"
2224+
tslib "^1.9.3"
2225+
2226+
apollo-link-http@^1.5.14:
2227+
version "1.5.14"
2228+
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.14.tgz#ed6292248d1819ccd16523e346d35203a1b31109"
2229+
integrity sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==
2230+
dependencies:
2231+
apollo-link "^1.2.11"
2232+
apollo-link-http-common "^0.2.13"
2233+
tslib "^1.9.3"
2234+
2235+
apollo-link@^1.2.11, apollo-link@^1.2.3:
22102236
version "1.2.11"
22112237
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
22122238
integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
@@ -9283,6 +9309,11 @@ node-fetch@^2.3.0:
92839309
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.5.0.tgz#8028c49fc1191bba56a07adc6e2a954644a48501"
92849310
integrity sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==
92859311

9312+
node-fetch@^2.6.0:
9313+
version "2.6.0"
9314+
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
9315+
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
9316+
92869317
node-gyp@^3.8.0:
92879318
version "3.8.0"
92889319
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"

0 commit comments

Comments
 (0)