@graphql-yoga/nestjs
Version:
GraphQL Yoga driver for NestJS GraphQL.
207 lines (206 loc) • 9.57 kB
JavaScript
import { __decorate } from "tslib";
import { printSchema } from 'graphql';
import { createYoga, filter, pipe } from 'graphql-yoga';
import { Injectable, Logger } from '@nestjs/common';
import { AbstractGraphQLDriver, GqlSubscriptionService, } from '@nestjs/graphql';
export class AbstractYogaDriver extends AbstractGraphQLDriver {
async start(options) {
const platformName = this.httpAdapterHost.httpAdapter.getType();
options = {
...options,
// disable error masking by default
maskedErrors: options.maskedErrors == null ? false : options.maskedErrors,
// disable graphiql in production
graphiql: options.graphiql == null ? process.env.NODE_ENV !== 'production' : options.graphiql,
};
if (platformName === 'express') {
return this.registerExpress(options);
}
if (platformName === 'fastify') {
return this.registerFastify(options);
}
throw new Error(`Provided HttpAdapter "${platformName}" not supported`);
}
async stop() {
// noop
}
registerExpress(options, { preStartHook } = {}) {
const app = this.httpAdapterHost.httpAdapter.getInstance();
preStartHook?.(app);
// nest's logger doesnt have the info method
class LoggerWithInfo extends Logger {
constructor(context) {
super(context);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
info(message, ...args) {
this.log(message, ...args);
}
}
const yoga = createYoga({
...options,
graphqlEndpoint: options.path,
// disable logging by default
// however, if `true` use nest logger
logging: options.logging == null
? false
: options.logging
? new LoggerWithInfo('YogaDriver')
: options.logging,
});
this.yoga = yoga;
app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res }));
}
registerFastify(options, { preStartHook } = {}) {
const app = this.httpAdapterHost.httpAdapter.getInstance();
preStartHook?.(app);
const yoga = createYoga({
...options,
graphqlEndpoint: options.path,
// disable logging by default
// however, if `true` use fastify logger
logging: options.logging == null ? false : options.logging ? app.log : options.logging,
});
this.yoga = yoga;
app.all(yoga.graphqlEndpoint, async (req, reply) => {
const response = await yoga.handleNodeRequest(req, {
req,
reply,
});
for (const [key, value] of response.headers.entries())
reply.header(key, value);
reply.status(response.status);
reply.send(response.body);
return reply;
});
}
subscriptionWithFilter(instanceRef, filterFn,
// disable next error, the original function in @nestjs/graphql is also untyped
// eslint-disable-next-line @typescript-eslint/ban-types
createSubscribeContext) {
return async (...args) => {
return pipe(await createSubscribeContext()(...args), filter((payload) =>
// typecast the spread sliced args to avoid error TS 2556, see https://github.com/microsoft/TypeScript/issues/49802
filterFn.call(instanceRef, payload, ...args.slice(1))));
};
}
}
export let YogaDriver = class YogaDriver extends AbstractYogaDriver {
async start(options) {
if (options.definitions?.path) {
if (!options.schema) {
throw new Error('Schema is required when generating definitions');
}
await this.graphQlFactory.generateDefinitions(printSchema(options.schema), options);
}
await super.start(options);
if (options.subscriptions) {
if (!options.schema) {
throw new Error('Schema is required when using subscriptions');
}
const config = options.subscriptions === true
? {
'graphql-ws': true,
}
: options.subscriptions;
if (config['graphql-ws']) {
config['graphql-ws'] = typeof config['graphql-ws'] === 'object' ? config['graphql-ws'] : {};
config['graphql-ws'].onSubscribe = async (ctx, msg) => {
const { schema, execute, subscribe, contextFactory, parse, validate } = this.yoga.getEnveloped({
...ctx,
// @ts-expect-error context extra is from graphql-ws/lib/use/ws
req: ctx.extra.request,
// @ts-expect-error context extra is from graphql-ws/lib/use/ws
socket: ctx.extra.socket,
params: msg.payload,
});
const args = {
schema,
operationName: msg.payload.operationName,
document: parse(msg.payload.query),
variableValues: msg.payload.variables,
contextValue: await contextFactory({ execute, subscribe }),
};
const errors = validate(args.schema, args.document);
if (errors.length)
return errors;
return args;
};
}
if (config['subscriptions-transport-ws']) {
config['subscriptions-transport-ws'] =
typeof config['subscriptions-transport-ws'] === 'object'
? config['subscriptions-transport-ws']
: {};
config['subscriptions-transport-ws'].onOperation = async (_msg, params, ws) => {
const { schema, execute, subscribe, contextFactory, parse, validate } = this.yoga.getEnveloped({
...params.context,
req:
// @ts-expect-error upgradeReq does exist but is untyped
ws.upgradeReq,
socket: ws,
params,
});
const args = {
schema,
operationName: params.operationName,
document: typeof params.query === 'string' ? parse(params.query) : params.query,
variables: params.variables,
context: await contextFactory({ execute, subscribe }),
};
const errors = validate(args.schema, args.document);
if (errors.length)
return errors;
return args;
};
}
this.subscriptionService = new GqlSubscriptionService({
schema: options.schema,
path: options.path,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
execute: (...args) => {
const contextValue =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
args[0].contextValue ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
args[3];
if (!contextValue) {
throw new Error('Execution arguments are missing the context value');
}
return (contextValue
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
.execute(...args));
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
subscribe: (...args) => {
const contextValue =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
args[0].contextValue ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
args?.[3];
if (!contextValue) {
throw new Error('Subscribe arguments are missing the context value');
}
return (contextValue
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- because we test both graphql v15 and v16
// @ts-ignore
.subscribe(...args));
},
...config,
}, this.httpAdapterHost.httpAdapter.getHttpServer());
}
}
async stop() {
await this.subscriptionService?.stop();
}
};
YogaDriver = __decorate([
Injectable()
], YogaDriver);