@graphql-yoga/plugin-apollo-usage-report
Version:
Apollo's GraphOS usage report plugin for GraphQL Yoga.
118 lines (116 loc) • 4.55 kB
JavaScript
const require_stats = require('./stats.cjs');
let _apollo_usage_reporting_protobuf = require("@apollo/usage-reporting-protobuf");
//#region src/reporter.ts
const DEFAULT_REPORTING_ENDPOINT = "https://usage-reporting.api.apollographql.com/api/ingress/traces";
var Reporter = class {
reportHeaders;
options;
reportsBySchema = {};
nextSendAfterDelay;
sending = [];
constructor(options, yoga, logger) {
this.yoga = yoga;
this.logger = logger;
this.options = {
...options,
maxBatchDelay: options.maxBatchDelay ?? 2e4,
maxBatchUncompressedSize: options.maxBatchUncompressedSize ?? 4 * 1024 * 1024,
maxTraceSize: options.maxTraceSize ?? 10 * 1024 * 1024,
exportTimeout: options.exportTimeout ?? 3e4,
onError: options.onError ?? ((err) => this.logger.error("Failed to send report", err))
};
this.reportHeaders = {
graphRef: getGraphRef(options),
hostname: options.hostname ?? getEnvVar("HOSTNAME") ?? "",
uname: options.uname ?? "",
runtimeVersion: options.runtimeVersion ?? "",
agentVersion: options.agentVersion || `graphql-yoga@${yoga.version}`
};
}
addTrace(schemaId, options) {
const report = this.getReport(schemaId);
report.addTrace(options);
if (this.options.alwaysSend || report.sizeEstimator.bytes >= this.options.maxBatchUncompressedSize) return this._sendReport(schemaId);
this.nextSendAfterDelay ||= setTimeout(() => this.flush(), this.options.maxBatchDelay);
}
async flush() {
return Promise.allSettled([...this.sending, ...Object.keys(this.reportsBySchema).map((schemaId) => this._sendReport(schemaId))]);
}
async sendReport(schemaId) {
const sending = this._sendReport(schemaId);
this.sending.push(sending);
sending.finally(() => this.sending = this.sending?.filter((p) => p !== sending));
return sending;
}
async _sendReport(schemaId) {
const { fetchAPI: { fetch, CompressionStream, ReadableStream } } = this.yoga;
const report = this.reportsBySchema[schemaId];
if (!report) throw new Error(`No report to send for schema ${schemaId}`);
if (this.nextSendAfterDelay != null) {
clearTimeout(this.nextSendAfterDelay);
this.nextSendAfterDelay = void 0;
}
delete this.reportsBySchema[schemaId];
report.endTime = dateToProtoTimestamp(/* @__PURE__ */ new Date());
report.ensureCountsAreIntegers();
const validationError = _apollo_usage_reporting_protobuf.Report.verify(report);
if (validationError) throw new TypeError(`Invalid report: ${validationError}`);
const { apiKey = getEnvVar("APOLLO_KEY"), endpoint = DEFAULT_REPORTING_ENDPOINT } = this.options;
const encodedReport = _apollo_usage_reporting_protobuf.Report.encode(report).finish();
let lastError;
for (let tries = 0; tries < 5; tries++) try {
this.logger.debug(`Sending report (try ${tries}/5)`);
const response = await fetch(endpoint, {
method: "POST",
headers: {
"content-type": "application/protobuf",
"content-encoding": "gzip",
"x-api-key": apiKey,
accept: "application/json"
},
body: new ReadableStream({ start(controller) {
controller.enqueue(encodedReport);
controller.close();
} }).pipeThrough(new CompressionStream("gzip")),
signal: AbortSignal.timeout(this.options.exportTimeout)
});
const result = await response.text();
if (response.ok) {
this.logger.debug("Report sent:", result);
return;
}
throw result;
} catch (err) {
lastError = err;
this.logger.error("Failed to send report:", err);
}
this.options.onError(new Error("Failed to send traces after 5 tries", { cause: lastError }));
}
getReport(schemaId) {
const report = this.reportsBySchema[schemaId];
if (report) return report;
return this.reportsBySchema[schemaId] = new require_stats.OurReport(new _apollo_usage_reporting_protobuf.ReportHeader({
...this.reportHeaders,
executableSchemaId: schemaId
}));
}
};
function getGraphRef(options) {
const graphRef = options.graphRef || getEnvVar("APOLLO_GRAPH_REF");
if (!graphRef) throw new Error("Missing GraphRef. Either provide `graphRef` option or `APOLLO_GRAPH_REF` environment variable");
return graphRef;
}
function getEnvVar(name, defaultValue) {
return globalThis.process?.env?.[name] || defaultValue || void 0;
}
function dateToProtoTimestamp(date) {
const totalMillis = date.getTime();
const millis = totalMillis % 1e3;
return new _apollo_usage_reporting_protobuf.google.protobuf.Timestamp({
seconds: (totalMillis - millis) / 1e3,
nanos: millis * 1e6
});
}
//#endregion
exports.Reporter = Reporter;
exports.getEnvVar = getEnvVar;