@graphql-yoga/nestjs
Version:
GraphQL Yoga driver for NestJS GraphQL.
196 lines (195 loc) • 8.46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.YogaDriver = exports.AbstractYogaDriver = void 0;
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const graphql_yoga_1 = require("graphql-yoga");
const common_1 = require("@nestjs/common");
const graphql_2 = require("@nestjs/graphql");
class AbstractYogaDriver extends graphql_2.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 common_1.Logger {
constructor(context) {
super(context);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
info(message, ...args) {
this.log(message, ...args);
}
}
const yoga = (0, graphql_yoga_1.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 = (0, graphql_yoga_1.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;
});
}
}
exports.AbstractYogaDriver = AbstractYogaDriver;
let YogaDriver = exports.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((0, graphql_1.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 graphql_2.GqlSubscriptionService({
schema: options.schema,
path: options.path,
execute: (...args) => {
const contextValue = args[0].contextValue ||
// @ts-expect-error args can be inlined with graphql-js@<=15
args[3];
if (!contextValue) {
throw new Error('Execution arguments are missing the context value');
}
return (contextValue
// @ts-expect-error execute method will be available, see above
.execute(...args));
},
subscribe: (...args) => {
const contextValue = args[0].contextValue ||
// @ts-expect-error args can be inlined with graphql-js@<=15
args?.[3];
if (!contextValue) {
throw new Error('Subscribe arguments are missing the context value');
}
return (contextValue
// @ts-expect-error execute method will be available, see above
.subscribe(...args));
},
...config,
}, this.httpAdapterHost.httpAdapter.getHttpServer());
}
}
async stop() {
await this.subscriptionService?.stop();
}
};
exports.YogaDriver = YogaDriver = tslib_1.__decorate([
(0, common_1.Injectable)()
], YogaDriver);