UNPKG

@yeskiy/federation-with-subscriptions

Version:
276 lines (275 loc) 14.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createGateway = void 0; const schema_1 = require("@graphql-tools/schema"); const stitch_1 = require("@graphql-tools/stitch"); const stitching_directives_1 = require("@graphql-tools/stitching-directives"); const utils_1 = require("@graphql-tools/utils"); const wrap_1 = require("@graphql-tools/wrap"); const apollo_server_core_1 = require("apollo-server-core"); const express_1 = __importDefault(require("express")); const graphql_1 = require("graphql"); const graphql_tag_1 = __importDefault(require("graphql-tag")); const graphql_ws_1 = require("graphql-ws"); const ws_1 = require("graphql-ws/lib/use/ws"); const http_1 = __importDefault(require("http")); const node_fetch_1 = __importDefault(require("node-fetch")); const ws_2 = __importStar(require("ws")); const extended_apollo_server_1 = require("./extended-apollo-server"); const createSchema = (microservices, buildHttpHeaders, buildSubscriptionHeaders, fallbackReq) => __awaiter(void 0, void 0, void 0, function* () { const { stitchingDirectivesTransformer } = (0, stitching_directives_1.stitchingDirectives)(); const remoteSchemas = yield Promise.all(microservices.map(({ url }) => __awaiter(void 0, void 0, void 0, function* () { var _a; let urlObject; try { urlObject = new URL(url); } catch (error) { urlObject = new URL(`http://${url}`); } const wsUrlObject = new URL(urlObject); wsUrlObject.protocol = urlObject.protocol.split(":")[0].endsWith("s") ? "wss:" : "ws:"; const httpExecutor = ({ document, variables, operationName, extensions, context: contextForHttpExecutor, }) => __awaiter(void 0, void 0, void 0, function* () { var _b, _c, _d; const query = (0, graphql_1.print)(document); const fallback = { req: fallbackReq, res: undefined }; const isSubscriptionContext = (contextForHttpExecutor === null || contextForHttpExecutor === void 0 ? void 0 : contextForHttpExecutor.type) === "subscription"; const fetchResult = yield (0, node_fetch_1.default)(urlObject.href, { method: "POST", headers: Object.assign({ "Content-Type": "application/json" }, (isSubscriptionContext ? yield (buildSubscriptionHeaders === null || buildSubscriptionHeaders === void 0 ? void 0 : buildSubscriptionHeaders((_b = contextForHttpExecutor === null || contextForHttpExecutor === void 0 ? void 0 : contextForHttpExecutor.value) === null || _b === void 0 ? void 0 : _b[0], (_c = contextForHttpExecutor === null || contextForHttpExecutor === void 0 ? void 0 : contextForHttpExecutor.value) === null || _c === void 0 ? void 0 : _c[1], (_d = contextForHttpExecutor === null || contextForHttpExecutor === void 0 ? void 0 : contextForHttpExecutor.value) === null || _d === void 0 ? void 0 : _d[2])) : yield (buildHttpHeaders === null || buildHttpHeaders === void 0 ? void 0 : buildHttpHeaders((contextForHttpExecutor === null || contextForHttpExecutor === void 0 ? void 0 : contextForHttpExecutor.value) || fallback)))), body: JSON.stringify({ query, variables, operationName, extensions }), }); return fetchResult.json(); }); const subscriptionClient = (0, graphql_ws_1.createClient)({ url: wsUrlObject.href, webSocketImpl: ws_2.default, }); const wsExecutor = ({ document, variables, operationName, extensions, context: contextForWsExecutor, }) => __awaiter(void 0, void 0, void 0, function* () { var _e, _f, _g; const subscriptionHeaders = yield (buildSubscriptionHeaders === null || buildSubscriptionHeaders === void 0 ? void 0 : buildSubscriptionHeaders((_e = contextForWsExecutor === null || contextForWsExecutor === void 0 ? void 0 : contextForWsExecutor.value) === null || _e === void 0 ? void 0 : _e[0], (_f = contextForWsExecutor === null || contextForWsExecutor === void 0 ? void 0 : contextForWsExecutor.value) === null || _f === void 0 ? void 0 : _f[1], (_g = contextForWsExecutor === null || contextForWsExecutor === void 0 ? void 0 : contextForWsExecutor.value) === null || _g === void 0 ? void 0 : _g[2])); return (0, utils_1.observableToAsyncIterable)({ subscribe: (observer) => ({ unsubscribe: subscriptionClient.subscribe({ query: (0, graphql_1.print)(document), variables: Object.assign(Object.assign({}, variables), { __headers: subscriptionHeaders }), operationName, extensions, }, { next: (data) => observer.next && observer.next(data), error: (err) => { if (!observer.error) { return; } if (err instanceof Error) { observer.error(err); } else if (err instanceof CloseEvent) { observer.error(new Error(`Socket closed with event ${err.code}`)); } else if (Array.isArray(err)) { // graphQLError[] observer.error(new Error(err.map(({ message }) => message).join(", "))); } }, complete: () => observer.complete && observer.complete(), }), }), }); }); const executor = (args) => __awaiter(void 0, void 0, void 0, function* () { // get the operation node of from the document that should be executed const operation = (0, graphql_1.getOperationAST)(args.document, args.operationName); // subscription operations should be handled by the wsExecutor if ((operation === null || operation === void 0 ? void 0 : operation.operation) === graphql_1.OperationTypeNode.SUBSCRIPTION) { return wsExecutor(args); } // all other operations should be handles by the httpExecutor return httpExecutor(args); }); const sdlResponse = yield httpExecutor({ document: (0, graphql_tag_1.default) ` { _sdl } `, }); const sdl = (_a = sdlResponse === null || sdlResponse === void 0 ? void 0 : sdlResponse.data) === null || _a === void 0 ? void 0 : _a._sdl; if (!sdl) { throw new Error("microservice SDL could not be found!"); } const remoteSchema = (0, wrap_1.wrapSchema)({ schema: (0, schema_1.makeExecutableSchema)({ typeDefs: sdl }), executor, }); return { schema: remoteSchema, }; }))); // build the combined schema const gatewaySchema = (0, stitch_1.stitchSchemas)({ subschemaConfigTransforms: [stitchingDirectivesTransformer], subschemas: remoteSchemas, }); return (0, wrap_1.wrapSchema)({ schema: gatewaySchema, transforms: [ new wrap_1.FilterTypes((type) => { switch (type.name) { case "_Entity": return false; default: return true; } }), new wrap_1.FilterRootFields((operationName, fieldName) => { if (operationName === "Query") { switch (fieldName) { case "_sdl": return false; default: return true; } } return true; }), ], }); }); const createGateway = (params) => __awaiter(void 0, void 0, void 0, function* () { const { microservices, port, host, path, onWebsocketMessage, onWebsocketClose, buildHttpHeaders, buildSubscriptionHeaders, context: userContext } = params, rest = __rest(params, ["microservices", "port", "host", "path", "onWebsocketMessage", "onWebsocketClose", "buildHttpHeaders", "buildSubscriptionHeaders", "context"]); const assumedPort = port || 4000; const assumedPath = path || "/graphql"; const assumedHost = host || "0.0.0.0"; const finalSchema = yield createSchema(microservices, buildHttpHeaders, buildSubscriptionHeaders); const app = (0, express_1.default)(); const httpServer = http_1.default.createServer(app); const apolloServer = new extended_apollo_server_1.ExtendedApolloServer(Object.assign({ schema: finalSchema, plugins: [(0, apollo_server_core_1.ApolloServerPluginDrainHttpServer)({ httpServer })], context: (...data) => __awaiter(void 0, void 0, void 0, function* () { let contextData = {}; if (userContext) { contextData = yield userContext(...data); } return Object.assign({ type: "http", value: data[0] }, contextData); }), schemaCallback: (req) => __awaiter(void 0, void 0, void 0, function* () { return createSchema(microservices, buildHttpHeaders, buildSubscriptionHeaders, req); }) }, rest)); yield apolloServer.start(); apolloServer.applyMiddleware({ app, path: assumedPath, cors: true }); const server = app.listen(assumedPort, assumedHost, () => { let wsServer; const wsServerGraphql = new ws_2.WebSocketServer({ noServer: true, }); if (onWebsocketMessage) { wsServer = new ws_2.WebSocketServer({ noServer: true, }); wsServer.on("connection", (ws) => { let context = {}; ws.on("message", (message) => { try { const messageParsed = JSON.parse(String(message)); if (messageParsed.action === "SET_CONTEXT") { context = messageParsed.context; } } catch (error) { // do nothing } onWebsocketMessage(message, context); }); if (onWebsocketClose) { ws.on("close", () => onWebsocketClose(context)); } }); } server.on("upgrade", (request, socket, head) => { const pathname = request.url; if (pathname === assumedPath) { wsServerGraphql.handleUpgrade(request, socket, head, (ws) => { wsServerGraphql.emit("connection", ws); }); } else if (wsServer) { wsServer.handleUpgrade(request, socket, head, (ws) => { wsServer.emit("connection", ws); }); } else { socket.destroy(); } }); (0, ws_1.useServer)({ schema: finalSchema, context: (...data) => __awaiter(void 0, void 0, void 0, function* () { let contextData = {}; if (userContext) { contextData = yield userContext({ req: data[0].extra.request, res: data[0].extra.socket, }); } return Object.assign({ type: "subscription", value: data }, contextData); }), }, wsServerGraphql); }); return { app, server, url: `http://${assumedHost}:${assumedPort}${assumedPath}`, executableSchema: finalSchema, sdl: (options = {}) => { var _a; return ((_a = options.withDirectives) !== null && _a !== void 0 ? _a : true) ? (0, utils_1.printSchemaWithDirectives)(finalSchema) : (0, graphql_1.printSchema)(finalSchema); }, }; }); exports.createGateway = createGateway;