@graphql-hive/federation-gateway-audit
Version:
Audit tool for Apollo Federation Gateway
270 lines (265 loc) • 7.25 kB
JavaScript
import { composeServices } from '@apollo/composition';
import { parse } from 'graphql';
import { Response } from 'fets';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { createYoga } from 'graphql-yoga';
function getSupergraph(subgraphs) {
const result = composeServices(
subgraphs.map((subgraph) => ({
name: subgraph.name,
typeDefs: parse(subgraph.typeDefs),
url: subgraph.url
}))
);
if (!result.supergraphSdl) {
result.errors?.forEach((error) => console.error(error.message));
throw new Error("Failed to compose supergraph");
}
return result.supergraphSdl;
}
function serve(id, subgraphs, tests) {
return {
id,
createRoutes(router) {
let subgraphNames = /* @__PURE__ */ new Set();
for (const subgraph of subgraphs) {
if (subgraphNames.has(subgraph.name)) {
throw new Error(`Duplicate subgraph name ${subgraph.name}`);
}
subgraphNames.add(subgraph.name);
subgraph.createRoutes(id, router);
}
function serveSupergraph(request) {
const supergraph = getSupergraph(
subgraphs.map((subgraph) => ({
name: subgraph.name,
typeDefs: subgraph.typeDefs,
url: `${request.parsedUrl.origin}/${id}/${subgraph.name}`
}))
);
const response = new Response(supergraph, {
status: 200,
headers: {
"Content-Type": "text/plain",
"Cache-Control": "public, max-age=604800, stale-while-revalidate=432000"
}
});
return response;
}
router.route({
method: "GET",
path: `/${id}/supergraph`,
tags: [id],
description: "Supergraph SDL endpoint",
operationId: "supergraph",
schemas: {
responses: {
200: {
type: "string"
}
}
},
handler(req) {
return serveSupergraph(req);
}
});
router.route({
method: "GET",
path: `/${id}/supergraph.graphql`,
tags: [id],
description: "Supergraph SDL endpoint",
operationId: "supergraph.graphql",
schemas: {
responses: {
200: {
type: "string"
}
}
},
handler(req) {
return serveSupergraph(req);
}
});
router.route({
method: "GET",
path: `/${id}/subgraphs`,
tags: [id],
description: "A list of subgraphs with their SDLs, URLs and names",
operationId: "subgraphs",
schemas: {
responses: {
200: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
url: { type: "string" },
sdl: { type: "string" }
},
required: ["name", "url", "sdl"]
}
}
}
},
handler(req) {
return Response.json(
subgraphs.map((subgraph) => ({
name: subgraph.name,
sdl: subgraph.typeDefs,
url: `${req.parsedUrl.origin}/${id}/${subgraph.name}`
}))
);
}
});
router.route({
method: "GET",
path: `/${id}/tests`,
tags: [id],
description: "Endpoint with a list of tests",
operationId: "tests",
schemas: {
responses: {
200: {
type: "array",
items: {
type: "object",
properties: {
query: { type: "string" },
expected: {
type: "object",
properties: {
data: {
anyOf: [
{ type: "object" },
{
type: "null"
}
]
},
errors: {
type: "boolean",
description: "Indicates that errors are expected",
default: false
}
},
additionalProperties: false
}
},
additionalProperties: false,
required: ["query", "expected"]
}
}
}
},
handler() {
const testsArray = Array.isArray(tests) ? tests : tests();
return Response.json(testsArray);
}
});
return id;
}
};
}
function createSubgraph(name, schemaParameters) {
let schema;
function lazySchema() {
if (!schema) {
schema = buildSubgraphSchema({
typeDefs: parse(schemaParameters.typeDefs),
resolvers: schemaParameters.resolvers
});
}
return schema;
}
function lazyYoga() {
if (!yoga) {
yoga = createYoga({
schema: lazySchema(),
graphqlEndpoint: "*",
logging: false,
maskedErrors: false
});
}
return yoga;
}
let yoga;
return {
createRoutes(testCaseId, router) {
router.route({
method: "GET",
path: `/${testCaseId}/${name}`,
tags: [testCaseId],
operationId: "GraphiQL",
schemas: {
request: {
query: {
type: "object",
properties: {
query: { type: "string" }
},
additionalProperties: true,
required: ["query"]
}
}
},
handler(req) {
return lazyYoga()(req);
}
});
router.route({
method: "POST",
path: `/${testCaseId}/${name}`,
tags: [testCaseId],
operationId: `"${name}" subgraph`,
description: "GraphQL endpoint for the subgraph",
schemas: {
request: {
json: {
type: "object",
properties: {
query: { type: "string" },
variables: { type: "object" }
},
additionalProperties: true,
required: ["query"]
}
},
responses: {
200: {
type: "object",
properties: {
data: { type: "object" },
errors: {
type: "array",
items: {
type: "object",
properties: {
message: { type: "string" }
},
required: ["message"],
additionalProperties: false
}
}
},
required: ["data"],
additionalProperties: false
}
}
},
handler(req) {
return lazyYoga()(req);
}
});
},
name,
typeDefs: schemaParameters.typeDefs
};
}
function createTest(query, expected) {
return {
query,
expected
};
}
export { createTest as a, createSubgraph as c, serve as s };