Last active
November 3, 2021 22:48
-
-
Save zhenwenc/09c9e78faefa4c35bdff6280a010ab81 to your computer and use it in GitHub Desktop.
Generate Apollo API based on remote GraphQL schema merging with schemas for local state
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
#!/usr/bin/env node | |
const fs = require('fs'); | |
const path = require('path'); | |
const https = require('https'); | |
const yargs = require('yargs'); | |
const fetch = require('node-fetch'); | |
const { parse, execute, buildSchema } = require('graphql'); | |
const { makeExecutableSchema } = require('graphql-tools'); | |
const { | |
fileLoader, | |
mergeTypes, | |
mergeResolvers, | |
} = require('merge-graphql-schemas'); | |
const { | |
printSchema, | |
printIntrospectionSchema, | |
buildClientSchema, | |
introspectionQuery, | |
} = require('graphql/utilities'); | |
// Make sure unhandled errors in async code are propagated correctly | |
process.on('uncaughtException', error => { | |
console.error(error); | |
process.exit(1); | |
}); | |
process.on('unhandledRejection', error => { | |
throw error; | |
}); | |
async function fetchRemoteSchema(url, insecure) { | |
const agent = | |
/^https:\/\//i.test(url) && insecure | |
? new https.Agent({ rejectUnauthorized: false }) | |
: undefined; | |
const body = JSON.stringify({ query: introspectionQuery }); | |
const method = 'POST'; | |
const headers = { | |
Accept: 'application/json', | |
'Content-Type': 'application/json', | |
}; | |
try { | |
const response = await fetch(url, { | |
agent, | |
method, | |
headers, | |
body, | |
}); | |
const result = await response.json(); | |
if (result.errors) { | |
throw new Error(`Errors in introspection query result: ${result.errors}`); | |
} | |
if (!result.data) { | |
throw new Error( | |
`No introspection query result data from: ${JSON.stringify(result)}` | |
); | |
} | |
return buildClientSchema(result.data); | |
} catch (e) { | |
throw new Error(`Error while fetching introspection query: ${e.message}`); | |
} | |
} | |
// Copy from graphql-js library, will be released in new version | |
// https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionFromSchema.js | |
async function introspectionFromSchema(schema /* GraphQLSchema */) { | |
const queryAST = parse(introspectionQuery); | |
const result = await execute(schema, queryAST); | |
return result.data; /* IntrospectionQuery */ | |
} | |
async function introspectSchema(remoteURL, clientURL, output, insecure) { | |
return fetchRemoteSchema(remoteURL, true) | |
.then(schema => { | |
const clientSchemas = fileLoader(clientURL); | |
const remoteSchema = printSchema(schema); | |
const typeDefs = mergeTypes([...clientSchemas, remoteSchema], { | |
all: true, | |
}); | |
return makeExecutableSchema({ typeDefs }); | |
}) | |
.then(schema => { | |
return introspectionFromSchema(schema); | |
}) | |
.then(introspection => { | |
const json = JSON.stringify(introspection, null, 2); | |
fs.writeFileSync(output, json); | |
}) | |
.catch(err => { | |
console.error('Error while generating types for schema:', err); | |
}); | |
} | |
const remoteSchema = 'https://<Your magic URL>/graphql'; | |
const clientSchema = path.resolve('<Root Dir>', 'src/**/*.graphql'); | |
const output = path.resolve('<Root Dir>', 'src/schema.json'); | |
// Generate an introspection JSON format from remote GraphQL server merging | |
// with any local GraphQL schemas | |
introspectSchema(remoteSchema, clientSchema, output, true); |
This was a life saver. Thank you very much for this!
Thanks so much. Ran this through my linter and removed some unused vars
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const https = require('https');
const fetch = require('node-fetch');
const { parse, execute } = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const { fileLoader, mergeTypes } = require('merge-graphql-schemas');
const { printSchema, buildClientSchema, introspectionQuery } = require('graphql/utilities');
// Make sure unhandled errors in async code are propagated correctly
process.on('uncaughtException', error => {
console.error(error);
process.exit(1);
});
process.on('unhandledRejection', error => {
throw error;
});
async function fetchRemoteSchema(url, insecure) {
const agent =
/^https:\/\//i.test(url) && insecure
? new https.Agent({ rejectUnauthorized: false })
: undefined;
const body = JSON.stringify({ query: introspectionQuery });
const method = 'POST';
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
};
try {
const response = await fetch(url, {
agent,
method,
headers,
body
});
const result = await response.json();
if (result.errors) {
throw new Error(`Errors in introspection query result: ${result.errors}`);
}
if (!result.data) {
throw new Error(`No introspection query result data from: ${JSON.stringify(result)}`);
}
return buildClientSchema(result.data);
} catch (e) {
throw new Error(`Error while fetching introspection query: ${e.message}`);
}
}
// Copy from graphql-js library, will be released in new version
// https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionFromSchema.js
async function introspectionFromSchema(schema /* GraphQLSchema */) {
const queryAST = parse(introspectionQuery);
const result = await execute(schema, queryAST);
return result.data; /* IntrospectionQuery */
}
async function introspectSchema(remoteURL, clientURL, output) {
return fetchRemoteSchema(remoteURL, true)
.then(schema => {
const clientSchemas = fileLoader(clientURL);
const remoteSchema = printSchema(schema);
const typeDefs = mergeTypes([...clientSchemas, remoteSchema], {
all: true
});
return makeExecutableSchema({ typeDefs });
})
.then(schema => {
return introspectionFromSchema(schema);
})
.then(introspection => {
const json = JSON.stringify(introspection, null, 2);
fs.writeFileSync(output, json);
})
.catch(err => {
console.error('Error while generating types for schema:', err);
});
}
const remoteSchema = 'http://localhost:8000/graphql';
const clientSchema = path.resolve('<Root Dir>', 'src/**/*.graphql');
const output = path.resolve('<Root Dir>', 'schema.json');
// Generate an introspection JSON format from remote GraphQL server merging
// with any local GraphQL schemas
introspectSchema(remoteSchema, clientSchema, output, true);
Just a note if anyone is getting the error:
Error while generating types for schema: TypeError: Cannot convert undefined or null to object
at Function.from (native)
at toConsumableArray (..\node_modules\merge-graphql-schemas\dist\index.cjs.js:4241:18)
I am working with TypeScript, and my local queries are like this:
import gql from 'graphql-tag';
export const GET_IN_FLIGHT_REQUESTS = gql`
query GetInFlightRequests {
network @client {
inFlightRequests
}
}
`;
To fix the error I had to change line 78 in the original gist to:
const clientSchemas = fileLoader(clientURL)
.map(module => Object.values(module))
.reduce((flattened, schemas) => flattened.concat(schemas), []);
to convert the array of object hashes into an array of schemas.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was incredibly helpful, thank you!