@graphql-yoga/nestjs
Version:
GraphQL Yoga driver for NestJS GraphQL.
253 lines (252 loc) • 11.6 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 {
yoga;
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({ conditionalSchema, ...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 schema = this.mergeConditionalSchema(conditionalSchema, options.schema);
const yoga = (0, graphql_yoga_1.createYoga)({
...options,
schema,
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,
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - TODO: fix types
this.yoga = yoga;
app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res }));
}
registerFastify({ conditionalSchema, ...options }, { preStartHook } = {}) {
const app = this.httpAdapterHost.httpAdapter.getInstance();
preStartHook?.(app);
const schema = this.mergeConditionalSchema(conditionalSchema, options.schema);
const yoga = (0, graphql_yoga_1.createYoga)({
...options,
schema,
graphqlEndpoint: options.path,
// disable logging by default
// however, if `true` use fastify logger
logging: options.logging == null ? false : options.logging ? app.log : options.logging,
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - TODO: fix types
this.yoga = yoga;
app.all(yoga.graphqlEndpoint, async (req, reply) => {
const response = await yoga.handleNodeRequestAndResponse(req, reply, {
req,
reply,
});
for (const [key, value] of response.headers.entries())
reply.header(key, value);
reply.status(response.status);
reply.send(response.body);
return reply;
});
}
mergeConditionalSchema(conditionalSchema, schema) {
let mergedSchema = schema;
if (conditionalSchema) {
mergedSchema = async (request) => {
const schemas = [];
if (schema) {
schemas.push(schema);
}
const conditionalSchemaResult = typeof conditionalSchema === 'function'
? await conditionalSchema(request)
: await conditionalSchema;
if (conditionalSchemaResult) {
schemas.push(conditionalSchemaResult);
}
return (0, graphql_yoga_1.mergeSchemas)({
schemas,
});
};
}
return mergedSchema;
}
subscriptionWithFilter(instanceRef, filterFn,
// disable next error, the original function in @nestjs/graphql is also untyped
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
createSubscribeContext) {
return async (...args) => {
return (0, graphql_yoga_1.pipe)(await createSubscribeContext()(...args), (0, graphql_yoga_1.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))));
};
}
}
exports.AbstractYogaDriver = AbstractYogaDriver;
let YogaDriver = class YogaDriver extends AbstractYogaDriver {
subscriptionService;
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'] : {};
if (options.conditionalSchema) {
throw new Error(`
Conditional schema is not supported with graphql-ws.
`);
}
config['graphql-ws'].onSubscribe = async (ctx, _id, params) => {
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,
});
const args = {
schema,
operationName: params.operationName,
document: parse(params.query),
variableValues: params.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']
: {};
if (options.conditionalSchema) {
throw new Error(`
Conditional schema is not supported with 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,
// 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();
}
};
exports.YogaDriver = YogaDriver;
exports.YogaDriver = YogaDriver = tslib_1.__decorate([
(0, common_1.Injectable)()
], YogaDriver);
;