@yeskiy/federation-with-subscriptions
Version:
Apollo Federation with subscriptions support
276 lines (275 loc) • 14.7 kB
JavaScript
;
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;