UNPKG

@graphql-yoga/plugin-apollo-usage-report

Version:

Apollo's GraphOS usage report plugin for GraphQL Yoga.

117 lines (115 loc) 4.37 kB
import { OurReport } from "./stats.mjs"; import { Report, ReportHeader, google } from "@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 = Report.verify(report); if (validationError) throw new TypeError(`Invalid report: ${validationError}`); const { apiKey = getEnvVar("APOLLO_KEY"), endpoint = DEFAULT_REPORTING_ENDPOINT } = this.options; const encodedReport = 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 OurReport(new 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 google.protobuf.Timestamp({ seconds: (totalMillis - millis) / 1e3, nanos: millis * 1e6 }); } //#endregion export { Reporter, getEnvVar };