Vulcan/packages/vulcan-lib/lib/server/apollo_server.js

233 lines
6 KiB
JavaScript
Raw Normal View History

import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
2017-02-06 14:33:34 +08:00
import bodyParser from 'body-parser';
import express from 'express';
2017-02-12 22:00:13 +08:00
import { makeExecutableSchema } from 'graphql-tools';
2017-02-06 14:33:34 +08:00
import deepmerge from 'deepmerge';
import DataLoader from 'dataloader';
import { formatError } from 'apollo-errors';
import compression from 'compression';
2017-02-06 14:33:34 +08:00
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import { Engine } from 'apollo-engine';
2017-02-06 14:33:34 +08:00
import { GraphQLSchema } from '../modules/graphql.js';
import { Utils } from '../modules/utils.js';
import { webAppConnectHandlersUse } from './meteor_patch.js';
2017-02-06 14:33:34 +08:00
import { getSetting } from '../modules/settings.js';
import { Collections } from '../modules/collections.js';
import findByIds from '../modules/findbyids.js';
import { runCallbacks } from '../modules/callbacks.js';
export let executableSchema;
// see https://github.com/apollographql/apollo-cache-control
const engineApiKey = getSetting('apolloEngine.apiKey');
const engineConfig = {
apiKey: engineApiKey,
// "origins": [
// {
// "http": {
// "url": "http://localhost:3000/graphql"
// }
// }
// ],
2017-12-08 20:54:23 +09:00
"stores": [
{
"name": "vulcanCache",
"inMemory": {
"cacheSize": 20000000
}
}
],
// "sessionAuth": {
// "store": "embeddedCache",
// "header": "Authorization"
// },
// "frontends": [
// {
// "host": "127.0.0.1",
// "port": 3000,
2017-12-08 20:54:23 +09:00
// "endpoint": "/graphql",
// "extensions": {
// "strip": []
// }
// }
// ],
2017-12-08 20:54:23 +09:00
"queryCache": {
"publicFullQueryStore": "vulcanCache",
"privateFullQueryStore": "vulcanCache"
},
// "reporting": {
// "endpointUrl": "https://engine-report.apollographql.com",
// "debugReports": true
// },
// "logging": {
// "level": "DEBUG"
// }
};
let engine;
if (engineApiKey) {
engine = new Engine({ engineConfig });
engine.start();
}
2017-02-12 22:00:13 +08:00
// defaults
2017-02-06 14:33:34 +08:00
const defaultConfig = {
path: '/graphql',
maxAccountsCacheSizeInMB: 1,
2017-02-12 22:00:13 +08:00
graphiql: Meteor.isDevelopment,
graphiqlPath: '/graphiql',
graphiqlOptions: {
passHeader: "'Authorization': localStorage['Meteor.loginToken']", // eslint-disable-line quotes
2017-02-06 14:33:34 +08:00
},
configServer: (graphQLServer) => {},
};
const defaultOptions = {
formatError: e => ({
message: e.message,
locations: e.locations,
2017-02-12 22:00:13 +08:00
path: e.path,
2017-02-06 14:33:34 +08:00
}),
};
2017-02-12 22:00:13 +08:00
if (Meteor.isDevelopment) {
defaultOptions.debug = true;
}
2017-02-06 14:33:34 +08:00
2017-02-12 22:00:13 +08:00
// createApolloServer
const createApolloServer = (givenOptions = {}, givenConfig = {}) => {
const graphiqlOptions = { ...defaultConfig.graphiqlOptions, ...givenConfig.graphiqlOptions };
const config = { ...defaultConfig, ...givenConfig };
2017-02-06 14:33:34 +08:00
config.graphiqlOptions = graphiqlOptions;
const graphQLServer = express();
2017-02-12 22:00:13 +08:00
config.configServer(graphQLServer);
2017-02-06 14:33:34 +08:00
// Use Engine middleware
if (engineApiKey) {
graphQLServer.use(engine.expressMiddleware());
}
// compression
graphQLServer.use(compression());
2017-02-06 14:33:34 +08:00
// GraphQL endpoint
graphQLServer.use(config.path, bodyParser.json(), graphqlExpress(async (req) => {
2017-02-12 22:00:13 +08:00
let options;
let user = null;
2017-02-06 14:33:34 +08:00
2017-02-12 22:00:13 +08:00
if (typeof givenOptions === 'function') {
2017-02-06 14:33:34 +08:00
options = givenOptions(req);
2017-02-12 22:00:13 +08:00
} else {
2017-02-06 14:33:34 +08:00
options = givenOptions;
2017-02-12 22:00:13 +08:00
}
2017-02-06 14:33:34 +08:00
// Merge in the defaults
2017-02-12 22:00:13 +08:00
options = { ...defaultOptions, ...options };
2017-02-06 14:33:34 +08:00
if (options.context) {
// don't mutate the context provided in options
2017-02-12 22:00:13 +08:00
options.context = { ...options.context };
2017-02-06 14:33:34 +08:00
} else {
options.context = {};
}
// enable tracing and caching
options.tracing = true;
options.cacheControl = true;
2017-02-06 14:33:34 +08:00
// Get the token from the header
if (req.headers.authorization) {
const token = req.headers.authorization;
check(token, String);
const hashedToken = Accounts._hashLoginToken(token);
// Get the user from the database
2017-02-12 22:00:13 +08:00
user = await Meteor.users.findOne(
{ 'services.resume.loginTokens.hashedToken': hashedToken },
);
2017-02-06 14:33:34 +08:00
if (user) {
// identify user to any server-side analytics providers
runCallbacks('events.identify', user);
2017-02-12 22:00:13 +08:00
const loginToken = Utils.findWhere(user.services.resume.loginTokens, { hashedToken });
const expiresAt = Accounts._tokenExpiration(loginToken.when);
2017-02-06 14:33:34 +08:00
const isExpired = expiresAt < new Date();
if (!isExpired) {
options.context.userId = user._id;
options.context.currentUser = user;
}
}
}
// merge with custom context
2017-02-06 14:33:34 +08:00
options.context = deepmerge(options.context, GraphQLSchema.context);
// go over context and add Dataloader to each collection
Collections.forEach(collection => {
options.context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, options.context), { cache: true });
});
// add error formatting from apollo-errors
options.formatError = formatError;
2017-02-06 14:33:34 +08:00
return options;
}));
2017-02-06 14:33:34 +08:00
// Start GraphiQL if enabled
if (config.graphiql) {
graphQLServer.use(config.graphiqlPath, graphiqlExpress({ ...config.graphiqlOptions, endpointURL: config.path }));
2017-02-06 14:33:34 +08:00
}
// This binds the specified paths to the Express server running Apollo + GraphiQL
webAppConnectHandlersUse(Meteor.bindEnvironment(graphQLServer), {
2017-02-12 22:00:13 +08:00
name: 'graphQLServerMiddleware_bindEnvironment',
order: 30,
});
2017-02-06 14:33:34 +08:00
};
2017-02-12 22:00:13 +08:00
// createApolloServer when server startup
Meteor.startup(() => {
runCallbacks('graphql.init.before');
2017-02-12 22:00:13 +08:00
// typeDefs
const generateTypeDefs = () => [`
scalar JSON
scalar Date
${GraphQLSchema.getCollectionsSchemas()}
${GraphQLSchema.getAdditionalSchemas()}
type Query {
${GraphQLSchema.queries.join('\n')}
}
${GraphQLSchema.mutations.length > 0 ? `
type Mutation {
${GraphQLSchema.mutations.join('\n')}
}
` : ''}
`];
2017-02-06 14:33:34 +08:00
const typeDefs = generateTypeDefs();
GraphQLSchema.finalSchema = typeDefs;
executableSchema = makeExecutableSchema({
2017-02-06 14:33:34 +08:00
typeDefs,
resolvers: GraphQLSchema.resolvers,
});
createApolloServer({
schema: executableSchema,
2017-02-06 14:33:34 +08:00
});
});