initial subscription support

This commit is contained in:
Wyatt Johnson
2018-06-20 15:56:15 -06:00
parent 1917a17929
commit a3e3d93607
19 changed files with 422 additions and 145 deletions
@@ -0,0 +1,52 @@
import {
graphqlExpress,
ExpressGraphQLOptionsFunction,
GraphQLOptions,
} from 'apollo-server-express';
import { GraphQLError, FieldDefinitionNode, ValidationContext } from 'graphql';
import { resolveGraphqlOptions } from 'apollo-server-core';
import { Config } from 'talk-server/config';
// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57
const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQLError(
'GraphQL introspection is not allowed in production, but the query contained __schema or __type.',
[node]
)
);
}
},
});
export const graphqlMiddleware = (
config: Config,
baseOptions: GraphQLOptions | ExpressGraphQLOptionsFunction
) => {
// Generate the validation rules.
const validationRules: Array<(context: ValidationContext) => any> = [];
if (config.get('env') !== 'production') {
// Disable introspection in production.
validationRules.push(NoIntrospection);
}
// Generate the actual middleware.
return graphqlExpress(async (req, res) => {
// Resolve the base options.
const requestOptions = await resolveGraphqlOptions(
baseOptions,
req,
res
);
// Apply the validators, sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L104-L107
requestOptions.validationRules = requestOptions.validationRules
? requestOptions.validationRules.concat(validationRules)
: validationRules;
return requestOptions;
});
};
@@ -0,0 +1,15 @@
import { addResolveFunctionsToSchema, IResolvers } from 'graphql-tools';
import { getGraphQLProjectConfig } from 'graphql-config';
export default function loadSchema(projectName: string, resolvers: IResolvers) {
// Load the configuration from the provided `.graphqlconfig` file.
const config = getGraphQLProjectConfig(__dirname, projectName);
// Get the GraphQLSchema from the configuration.
const schema = config.getSchema();
// Attach the resolvers to the schema.
addResolveFunctionsToSchema({ schema, resolvers });
return schema;
}
@@ -0,0 +1,29 @@
import http from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { GraphQLSchema, execute, subscribe } from 'graphql';
export interface SubscriptionMiddlewareOptions {
schema: GraphQLSchema;
path: string;
}
export function handleSubscriptions(
server: http.Server,
{ schema, path }: SubscriptionMiddlewareOptions
): SubscriptionServer {
// Configure some options for the subscription system.
const options = {
schema,
execute,
subscribe,
};
// Configure the socket options for the websocket server. It needs to handle
// upgrade requests on that route.
const socketOption = {
server,
path,
};
return new SubscriptionServer(options, socketOption);
}
@@ -0,0 +1,15 @@
import { RedisPubSub } from 'graphql-redis-subscriptions';
import { createRedisClient } from 'talk-server/services/redis';
import { Config } from 'talk-server/config';
export async function createPubSub(config: Config): Promise<RedisPubSub> {
// Create the Redis clients for the PubSub server.
const publisher = await createRedisClient(config);
const subscriber = await createRedisClient(config);
// Create the new PubSub manager.
return new RedisPubSub({
publisher,
subscriber,
});
}
+11 -10
View File
@@ -1,12 +1,13 @@
import { graphqlExpress } from 'apollo-server-express';
import schema from './schema';
import Context from './context';
import { Db } from 'mongodb';
import { GraphQLSchema } from 'graphql';
export default (db: Db) =>
graphqlExpress(async req => {
return {
schema,
context: new Context({ db }),
};
});
import { graphqlMiddleware } from 'talk-server/graph/common/middleware';
import { Config } from 'talk-server/config';
import Context from './context';
export default (schema: GraphQLSchema, config: Config, db: Db) =>
graphqlMiddleware(config, async () => ({
schema,
context: new Context({ db }),
}));
@@ -1,15 +1,6 @@
import { addResolveFunctionsToSchema } from 'graphql-tools';
import { getGraphQLProjectConfig } from 'graphql-config';
import loadSchema from 'talk-server/graph/common/schema';
import resolvers from 'talk-server/graph/management/resolvers';
import resolvers from '../resolvers';
// Load the configuration from the provided `.graphqlconfig` file.
const config = getGraphQLProjectConfig(__dirname, 'management');
// Get the GraphQLSchema from the configuration.
const schema = config.getSchema();
// Attach the resolvers to the schema.
addResolveFunctionsToSchema({ schema, resolvers });
export default schema;
export default function getManagementSchema() {
return loadSchema('management', resolvers);
}
+6
View File
@@ -0,0 +1,6 @@
import { GraphQLSchema } from 'graphql';
export interface Schemas {
management: GraphQLSchema;
tenant: GraphQLSchema;
}
+18 -7
View File
@@ -1,13 +1,24 @@
import { graphqlExpress } from 'apollo-server-express';
import schema from './schema';
import TenantContext from './context';
import { Db } from 'mongodb';
import { Tenant } from 'talk-server/models/tenant';
import { GraphQLSchema } from 'graphql';
import { retrieveByDomain } from 'talk-server/models/tenant';
import { createPubSub } from 'talk-server/graph/common/subscriptions/pubsub';
import { Config } from 'talk-server/config';
import { graphqlMiddleware } from 'talk-server/graph/common/middleware';
import TenantContext from './context';
export default async (schema: GraphQLSchema, config: Config, db: Db) => {
// Configure the PubSub broker.
const pubsub = await createPubSub(config);
return graphqlMiddleware(config, async req => {
// TODO: replace with shared synced cache instead of direct db access.
const tenant = await retrieveByDomain(db, req.hostname);
export default (db: Db) =>
graphqlExpress(async req => {
return {
schema,
context: new TenantContext({ db, tenant: { id: '1' } as Tenant }),
context: new TenantContext({ db, tenant }),
};
});
};
@@ -1,12 +1,13 @@
import Context from 'talk-server/graph/tenant/context';
import TenantContext from 'talk-server/graph/tenant/context';
import { Asset } from 'talk-server/models/asset';
export default {
asset: async (
_: any,
{ id, url }: { id?: string; url: string },
ctx: Context
ctx: TenantContext
): Promise<Asset> => {
return ctx.loaders.Assets.asset.load(id);
},
settings: async (parent: any, args: any, ctx: TenantContext) => ctx.tenant,
};
+5 -14
View File
@@ -1,15 +1,6 @@
import { addResolveFunctionsToSchema } from 'graphql-tools';
import { getGraphQLProjectConfig } from 'graphql-config';
import loadSchema from 'talk-server/graph/common/schema';
import resolvers from 'talk-server/graph/tenant/resolvers';
import resolvers from '../resolvers';
// Load the configuration from the provided `.graphqlconfig` file.
const config = getGraphQLProjectConfig(__dirname, 'tenant');
// Get the GraphQLSchema from the configuration.
const schema = config.getSchema();
// Attach the resolvers to the schema.
addResolveFunctionsToSchema({ schema, resolvers });
export default schema;
export default function getTenantSchema() {
return loadSchema('tenant', resolvers);
}