@graphql-yoga/plugin-apollo-usage-report
Version:
Apollo's GraphOS usage report plugin for GraphQL Yoga.
152 lines (150 loc) • 6.35 kB
JavaScript
const require_reporter = require('./reporter.cjs');
let graphql = require("graphql");
let graphql_yoga = require("graphql-yoga");
let _apollo_utils_usagereporting = require("@apollo/utils.usagereporting");
let _graphql_tools_utils = require("@graphql-tools/utils");
let _graphql_yoga_plugin_apollo_inline_trace = require("@graphql-yoga/plugin-apollo-inline-trace");
//#region src/index.ts
function useApolloUsageReport(options = {}) {
const [instrumentation, ctxForReq] = (0, _graphql_yoga_plugin_apollo_inline_trace.useApolloInstrumentation)(options);
const makeReporter = options.reporter ?? ((...args) => new require_reporter.Reporter(...args));
let schemaIdSet$;
let currentSchema;
let yoga;
let reporter;
const setCurrentSchema = async (schema) => {
try {
currentSchema = {
id: await hashSHA256((0, _graphql_tools_utils.printSchemaWithDirectives)(schema), yoga.fetchAPI),
schema
};
} catch (error) {
logger.error("Failed to calculate schema hash: ", error);
}
schemaIdSet$ = void 0;
};
const logger = Object.fromEntries([
"error",
"warn",
"info",
"debug"
].map((level) => [level, (...messages) => yoga.logger[level]("[ApolloUsageReport]", ...messages)]));
let clientNameFactory = (req) => req.headers.get("apollographql-client-name");
if (typeof options.clientName === "function") clientNameFactory = options.clientName;
let clientVersionFactory = (req) => req.headers.get("apollographql-client-version");
if (typeof options.clientVersion === "function") clientVersionFactory = options.clientVersion;
return { onPluginInit({ addPlugin }) {
addPlugin(instrumentation);
addPlugin({
onYogaInit(args) {
yoga = args.yoga;
reporter = makeReporter(options, yoga, logger);
if (!require_reporter.getEnvVar("APOLLO_KEY", options.apiKey)) throw new Error(`[ApolloUsageReport] Missing API key. Please provide one in plugin options or with 'APOLLO_KEY' environment variable.`);
if (!require_reporter.getEnvVar("APOLLO_GRAPH_REF", options.graphRef)) throw new Error(`[ApolloUsageReport] Missing Graph Ref. Please provide one in plugin options or with 'APOLLO_GRAPH_REF' environment variable.`);
if (!schemaIdSet$ && !currentSchema) {
const { schema } = yoga.getEnveloped();
if (schema) schemaIdSet$ = setCurrentSchema(schema);
}
},
onSchemaChange({ schema }) {
if (schema && yoga) schemaIdSet$ = setCurrentSchema(schema);
},
onRequestParse() {
return schemaIdSet$;
},
onParse() {
return function onParseEnd({ result, context }) {
const ctx = ctxForReq.get(context.request)?.traces.get(context);
if (!ctx) {
logger.debug("operation tracing context not found, this operation will not be traced.");
return;
}
ctx.schemaId = currentSchema.id;
if (isDocumentNode(result)) {
if ((0, graphql.getOperationAST)(result, context.params.operationName)) return;
ctx.operationKey = `## GraphQLUnknownOperationName\n`;
} else ctx.operationKey = "## GraphQLParseFailure\n";
if (!options.sendUnexecutableOperationDocuments) {
ctxForReq.delete(context.request);
return;
}
ctx.trace.unexecutedOperationName = context.params.operationName || "";
ctx.trace.unexecutedOperationBody = context.params.query || "";
};
},
onValidate({ params: { documentAST: document } }) {
return ({ valid, context }) => {
const ctx = ctxForReq.get(context.request)?.traces.get(context);
if (!ctx) {
logger.debug("operation tracing context not found, this operation will not be traced.");
return;
}
if (valid) {
if (!currentSchema) throw new Error("should not happen: schema doesn't exists");
const opName = (0, graphql.getOperationAST)(document, context.params.operationName)?.name?.value;
ctx.referencedFieldsByType = (0, _apollo_utils_usagereporting.calculateReferencedFieldsByType)({
document,
schema: currentSchema.schema,
resolvedOperationName: opName ?? null
});
ctx.operationKey = `# ${opName || "-"}\n${(0, _apollo_utils_usagereporting.usageReportingSignature)(document, opName ?? "")}`;
} else if (options.sendUnexecutableOperationDocuments) {
ctx.operationKey = "## GraphQLValidationFailure\n";
ctx.trace.unexecutedOperationName = context.params.operationName ?? "";
ctx.trace.unexecutedOperationBody = context.params.query ?? "";
} else ctxForReq.delete(context.request);
};
},
onResultProcess({ request, result, serverContext }) {
if ((0, graphql_yoga.isAsyncIterable)(result)) {
logger.debug("async iterable results not implemented for now");
return;
}
const reqCtx = ctxForReq.get(request);
if (!reqCtx) {
logger.debug("operation tracing context not found, this operation will not be traced.");
return;
}
for (const trace of reqCtx.traces.values()) {
if (!trace.schemaId || !trace.operationKey) {
logger.debug("Misformed trace, missing operation key or schema id");
continue;
}
const clientName = clientNameFactory(request);
if (clientName) trace.trace.clientName = clientName;
const clientVersion = clientVersionFactory(request);
if (clientVersion) trace.trace.clientVersion = clientVersion;
serverContext.waitUntil(reporter.addTrace(currentSchema.id, {
statsReportKey: trace.operationKey,
trace: trace.trace,
referencedFieldsByType: trace.referencedFieldsByType ?? {},
asTrace: true,
nonFtv1ErrorPaths: [],
maxTraceBytes: options.maxTraceSize
}));
}
},
async onDispose() {
await reporter?.flush();
}
});
} };
}
async function hashSHA256(text, api = globalThis) {
const inputUint8Array = new api.TextEncoder().encode(text);
const arrayBuf = await api.crypto.subtle.digest({ name: "SHA-256" }, inputUint8Array);
const outputUint8Array = new Uint8Array(arrayBuf);
let hash = "";
for (const byte of outputUint8Array) {
const hex = byte.toString(16);
hash += "00".slice(0, Math.max(0, 2 - hex.length)) + hex;
}
return hash;
}
function isDocumentNode(data) {
const isObject = (data) => !!data && typeof data === "object";
return isObject(data) && data["kind"] === graphql.Kind.DOCUMENT;
}
//#endregion
exports.hashSHA256 = hashSHA256;
exports.useApolloUsageReport = useApolloUsageReport;