UNPKG

@graphql-mesh/cli

Version:
179 lines (178 loc) • 6.88 kB
/* eslint-disable import/no-nodejs-modules */ /* eslint-disable dot-notation */ import cluster from 'cluster'; import os from 'os'; import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets'; import open from 'open'; import { process } from '@graphql-mesh/cross-helpers'; import { createMeshHTTPHandler } from '@graphql-mesh/http'; import { handleFatalError } from '../../handleFatalError.js'; import { registerTerminateHandler } from '../../terminateHandler.js'; function portSelectorFn(sources, logger) { const port = sources.find(source => Boolean(source)) || 4000; if (sources.filter(source => Boolean(source)).length > 1) { const activeSources = []; if (sources[0]) { activeSources.push('CLI'); } if (sources[1]) { activeSources.push('serve configuration'); } if (sources[2]) { activeSources.push('environment variable'); } logger.warn(`Multiple ports specified (${activeSources.join(', ')}), using ${port}`); } return port; } export async function serveMesh({ baseDir, argsPort, getBuiltMesh, logger, rawServeConfig = {}, playgroundTitle, }, cliParams) { const { fork: configFork = process.env.NODE_ENV?.toLowerCase() === 'production', port: configPort, hostname = os.platform() === 'win32' || // is WSL? os.release().toLowerCase().includes('microsoft') ? '127.0.0.1' : '0.0.0.0', sslCredentials, endpoint: graphqlPath = '/graphql', browser = process.env.NODE_ENV?.toLowerCase() !== 'production', // TODO // trustProxy = 'loopback', } = rawServeConfig; const port = portSelectorFn([argsPort, parseInt(configPort?.toString()), parseInt(process.env.PORT)], logger); let forkNum; const envFork = process.env.FORK; let defaultForkNum = 0; try { defaultForkNum = os.availableParallelism(); } catch (e) { try { defaultForkNum = os.cpus().length; } catch (e) { // ignore } } if (envFork != null) { if (envFork === 'false' || envFork === '0') { forkNum = 0; } else if (envFork === 'true') { forkNum = defaultForkNum; } else { forkNum = parseInt(envFork); } } else if (configFork != null) { if (typeof configFork === 'boolean') { forkNum = configFork ? defaultForkNum : 0; } else { forkNum = configFork; } } const protocol = sslCredentials ? 'https' : 'http'; const serverUrl = `${protocol}://${hostname}:${port}`; if (!playgroundTitle) { playgroundTitle = rawServeConfig?.playgroundTitle || cliParams.playgroundTitle; } if (!cluster.isWorker && forkNum > 1) { let mainProcessKilled = false; registerTerminateHandler(eventName => { mainProcessKilled = true; }); for (let i = 0; i < forkNum; i++) { const worker = cluster.fork(); registerTerminateHandler(eventName => worker.kill(eventName)); } logger.info(`${cliParams.serveMessage}: ${serverUrl} in ${forkNum} forks`); cluster.on('exit', (worker, code, signal) => { if (!mainProcessKilled) { logger.child(`Worker ${worker.id}`).error(`died with ${signal || code}. Restarting...`); const newWorker = cluster.fork(); registerTerminateHandler(eventName => newWorker.kill(eventName)); } }); } else { if (cluster.isWorker) { logger.addPrefix?.(`Worker ${cluster.worker?.id}`); } logger.info(`Starting GraphQL Mesh...`); const mesh$ = getBuiltMesh() .then(async (mesh) => { if (mesh.schema.getType('BigInt')) { await import('json-bigint-patch'); } logger.info(`${cliParams.serveMessage}: ${serverUrl}`); registerTerminateHandler(eventName => { const eventLogger = logger.child(`${eventName} 💀`); eventLogger.info(`Destroying GraphQL Mesh...`); mesh.destroy(); }); return mesh; }) .catch(e => handleFatalError(e, logger)); let uWebSocketsApp; const meshHTTPHandler = createMeshHTTPHandler({ baseDir, getBuiltMesh: () => mesh$, rawServeConfig, playgroundTitle, }); if (sslCredentials) { const { SSLApp } = await import('uWebSockets.js'); uWebSocketsApp = SSLApp({ key_file_name: sslCredentials.key, cert_file_name: sslCredentials.cert, }); } else { const { App } = await import('uWebSockets.js'); uWebSocketsApp = App(); } uWebSocketsApp.any('/*', meshHTTPHandler); const wsHandler = makeBehavior({ execute: args => args.rootValue.execute(args), subscribe: args => args.rootValue.subscribe(args), onSubscribe: async (ctx, msg) => { const { getEnveloped } = await mesh$; const { schema, execute, subscribe, contextFactory, parse, validate } = getEnveloped(ctx); const args = { schema, operationName: msg.payload.operationName, document: parse(msg.payload.query), variableValues: msg.payload.variables, contextValue: await contextFactory(), rootValue: { execute, subscribe, }, }; const errors = validate(args.schema, args.document); if (errors.length) return errors; return args; }, }); uWebSocketsApp.ws(graphqlPath, wsHandler); uWebSocketsApp.listen(hostname, port, listenSocket => { if (!listenSocket) { logger.error(`Failed to listen to ${serverUrl}`); process.exit(1); } registerTerminateHandler(async (eventName) => { const eventLogger = logger.child(`${eventName} 💀`); eventLogger.debug(`Stopping HTTP Server`); uWebSocketsApp?.close?.(); eventLogger.debug(`HTTP Server has been stopped`); }); if (browser) { open(serverUrl.replace('0.0.0.0', 'localhost'), typeof browser === 'string' ? { app: browser } : undefined).catch(() => { }); } }); return mesh$.then(mesh => ({ mesh, httpServer: uWebSocketsApp, logger, })); } return null; }