UNPKG

@graphql-hive/nestjs

Version:
217 lines (214 loc) • 7.55 kB
import { createLoggerFromLogging, MemPubSub, getCacheInstanceFromConfig, getBuiltinPluginsFromConfig, createGatewayRuntime, getGraphQLWSOptions } from '@graphql-hive/gateway'; import { Logger as Logger$1 } from '@graphql-hive/logger'; import { asArray } from '@graphql-tools/utils'; import { Injectable, Logger } from '@nestjs/common'; import { AbstractGraphQLDriver, GqlSubscriptionService } from '@nestjs/graphql'; import { handleMaybePromise } from '@whatwg-node/promise-helpers'; import { lexicographicSortSchema } from 'graphql'; import { __decorate } from 'tslib'; class HiveGatewayDriver extends AbstractGraphQLDriver { _gatewayRuntime; _subscriptionService; async ensureGatewayRuntime({ typeDefs, resolvers, logging, ...options }) { if (this._gatewayRuntime) { return this._gatewayRuntime; } const additionalTypeDefs = []; if (typeDefs) { additionalTypeDefs.push(typeDefs); } const additionalResolvers = []; if (resolvers) { additionalResolvers.push(...asArray(resolvers)); } let log; if (logging != null) { log = createLoggerFromLogging(logging); } else { const nestLog = new Logger("Hive Gateway"); log = new Logger$1({ writers: [ { write(level, attrs, msg) { switch (level) { case "trace": nestLog.verbose(msg, attrs); break; case "info": nestLog.log(msg, attrs); break; default: nestLog[level](msg, attrs); } } } ] }); } const configCtx = { log, cwd: process.cwd(), pubsub: options.pubsub || new MemPubSub() }; const cache = await getCacheInstanceFromConfig(options, configCtx); const builtinPlugins = await getBuiltinPluginsFromConfig(options, { ...configCtx, cache }); this._gatewayRuntime = createGatewayRuntime({ ...options, logging: configCtx.log, cache, graphqlEndpoint: options.path, additionalTypeDefs, additionalResolvers, disableIntrospection: options.introspection === false ? { disableIf: () => options.introspection || false } : void 0, ...options.context || options.transformSchema || options.sortSchema ? { plugins: (ctx) => { const existingPlugins = options.plugins?.(ctx) || []; if (options.context) { const contextPlugin = { onContextBuilding: ({ context, extendContext }) => handleMaybePromise( () => typeof options.context === "function" ? options.context(context) : options.context, extendContext ) }; existingPlugins.push(contextPlugin); } if (options.transformSchema) { const schemaTransformPlugin = { onSchemaChange({ schema, replaceSchema }) { return handleMaybePromise( () => options.transformSchema(schema), replaceSchema ); } }; existingPlugins.push(schemaTransformPlugin); } if (options.sortSchema) { const schemaSortPlugin = { onSchemaChange({ schema, replaceSchema }) { replaceSchema(lexicographicSortSchema(schema)); } }; existingPlugins.push(schemaSortPlugin); } return [...builtinPlugins, ...existingPlugins]; } } : {} }); return this._gatewayRuntime; } async start(options) { const gatewayRuntime = await this.ensureGatewayRuntime(options); const platformName = this.httpAdapterHost.httpAdapter.getType(); if (platformName === "express") { this.registerExpress(); } else if (platformName === "fastify") { this.registerFastify(); } else { throw new Error(`No support for current HttpAdapter: ${platformName}`); } if (options.installSubscriptionHandlers || options.subscriptions) { const subscriptionsOptions = options.subscriptions || { "graphql-ws": {} }; if (subscriptionsOptions["graphql-ws"]) { const gwOptions = getGraphQLWSOptions( gatewayRuntime, (ctx) => ({ req: ctx.extra?.request, socket: ctx.extra?.socket }) ); subscriptionsOptions["graphql-ws"] = { ...gwOptions, ...typeof subscriptionsOptions["graphql-ws"] === "object" ? subscriptionsOptions["graphql-ws"] : {} }; } if (subscriptionsOptions["subscriptions-transport-ws"]) { subscriptionsOptions["subscriptions-transport-ws"] = typeof subscriptionsOptions["subscriptions-transport-ws"] === "object" ? subscriptionsOptions["subscriptions-transport-ws"] : {}; subscriptionsOptions["subscriptions-transport-ws"].onOperation = async (_msg, params, ws) => { const { schema, execute, subscribe, contextFactory, parse, validate } = gatewayRuntime.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(), rootValue: { execute, subscribe } }; const errors = validate(args.schema, args.document); if (errors.length) return errors; return args; }; } this._subscriptionService = new GqlSubscriptionService( { schema: await gatewayRuntime.getSchema(), path: options.path, // @ts-expect-error - We know that execute and subscribe are defined execute: (args) => args.rootValue.execute(args), // @ts-expect-error - We know that execute and subscribe are defined subscribe: (args) => args.rootValue.subscribe(args), ...subscriptionsOptions }, this.httpAdapterHost.httpAdapter?.getHttpServer() ); } } async stop() { await Promise.all([ this._subscriptionService?.stop(), this._gatewayRuntime?.dispose() ]); } async generateSchema(options) { const gatewayRuntime = await this.ensureGatewayRuntime(options); return gatewayRuntime.getSchema(); } registerExpress() { if (!this._gatewayRuntime) { throw new Error("Hive Gateway is not initialized"); } this.httpAdapterHost.httpAdapter.use(this._gatewayRuntime); } registerFastify() { this.httpAdapterHost.httpAdapter.getInstance().all("*", async (req, reply) => { if (!this._gatewayRuntime) { throw new Error("Hive Gateway is not initialized"); } const response = await this._gatewayRuntime.handleNodeRequestAndResponse(req, reply, { req, reply }); response.headers.forEach((value, key) => { reply.header(key, value); }); reply.status(response.status); reply.send(response.body); return reply; }); } } __decorate([Injectable()], HiveGatewayDriver); export { HiveGatewayDriver };