diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1cd8cdfd3..6a68c6d2f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All of the packages in the `apollo-server` repo are released with the same versi ### vNEXT +- update apollo-engine-reporting-protobuf to non-beta [#1429](https://github.com/apollographql/apollo-server/pull/1429) + ### rc.10 - Fix and Export Extension and Playground Types [#1360](https://github.com/apollographql/apollo-server/pull/1360) diff --git a/packages/apollo-engine-reporting-protobuf/.circleci/config.yml b/packages/apollo-engine-reporting-protobuf/.circleci/config.yml new file mode 100644 index 00000000000..6e7477b36de --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/.circleci/config.yml @@ -0,0 +1,94 @@ +version: 2 +# +# Reusable Snippets! +# +# These are re-used by the various tests below, to avoid repetition. +# +run_install_desired_npm: &run_install_desired_npm + run: + # Due to a bug, npm upgrades from the version of npm that ships with + # Node.js 6 (npm v3.10.10) go poorly and generally cause other problems + # within the environment. Since `yarn` is already available here we can + # use that to work-around the issue. It's possible that `npm` artifact + # jockeying might avoid this need, but this can be removed once Node 6 is + # no longer being built below. + name: Install npm@5, but with yarn. + command: sudo yarn global add npm@5 + +# These are the steps used for each version of Node which we're testing +# against. Thanks to YAMLs inability to merge arrays (though it is able +# to merge objects), every version of Node must use the exact same steps, +# or these steps would need to be repeated in a version of Node that needs +# something different. Probably best to avoid that, out of principle, though. +common_test_steps: &common_test_steps + steps: + # Install the latest npm, rather than using the versions which ship + # in older versions of Node.js This allows for a more consistent + # installation, and typically users of older Node versions at least + # update their npm to the latest. + - *run_install_desired_npm + + - checkout + + # Build a cache key which will bust with Node.js ABI and version changes. + - run: | + node -p process.config | tee .cache_key + node -p process.versions | tee -a .cache_key + cat package-lock.json | tee -a .cache_key + + - restore_cache: + keys: + - v1-dependencies-{{ checksum ".cache_key" }} + + - run: node --version + + - run: npm --version + + - run: npm install + + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum ".cache_key" }} + + # test with coverage. + - run: npm run circle + + +jobs: + # Platform tests, each with the same tests but different platform or version. + # The docker tag represents the Node.js version and the full list is available + # at https://hub.docker.com/r/circleci/node/. + Node.js 4: + docker: [ { image: 'circleci/node:4' } ] + <<: *common_test_steps + + Node.js 6: + docker: [ { image: 'circleci/node:6' } ] + <<: *common_test_steps + + Node.js 8: + docker: [ { image: 'circleci/node:8' } ] + <<: *common_test_steps + + Node.js 10: + docker: [ { image: 'circleci/node:10' } ] + <<: *common_test_steps + + # Linting can run on the latest Node, which already has the latest `npm` and + # doesn't warrant run_install_desired_npm (which is more critical above) + Linting: + docker: [ { image: 'circleci/node:8' } ] + steps: + # (speed) Intentionally omitted (unnecessary) run_install_desired_npm. + - checkout + - run: npm install + - run: npm run lint + +workflows: + version: 2 + Build and Test: + jobs: + # There isn't any non-generated code in this package now, so we only + # bother to run one test job and no linting. + - Node.js 10 diff --git a/packages/apollo-engine-reporting-protobuf/.envrc b/packages/apollo-engine-reporting-protobuf/.envrc new file mode 100644 index 00000000000..a8a760658e1 --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/.envrc @@ -0,0 +1 @@ +layout node diff --git a/packages/apollo-engine-reporting-protobuf/.gitignore b/packages/apollo-engine-reporting-protobuf/.gitignore new file mode 100644 index 00000000000..c925c21d56c --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/packages/apollo-engine-reporting-protobuf/.npmignore b/packages/apollo-engine-reporting-protobuf/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-engine-reporting-protobuf/README.md b/packages/apollo-engine-reporting-protobuf/README.md new file mode 100644 index 00000000000..d420ece8695 --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/README.md @@ -0,0 +1,7 @@ +# apollo-engine-reporting-protobuf + +This contains generated Javascript/TypeScript code for the protobuf definitions +for the Engine reporting API. + +The Engine reporting API is currently subject to change at any time; do not rely +on this to build your own client. diff --git a/packages/apollo-engine-reporting-protobuf/package-lock.json b/packages/apollo-engine-reporting-protobuf/package-lock.json new file mode 100644 index 00000000000..ad613a5d708 --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/package-lock.json @@ -0,0 +1,99 @@ +{ + "name": "apollo-engine-reporting-protobuf", + "version": "0.0.0-beta.7", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "1.1.2", + "@protobufjs/inquire": "1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@types/node": { + "version": "8.10.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.16.tgz", + "integrity": "sha512-KlK7YiZXSY8E6v8E4+cCor9IT071bfZrfYqKf0SEj8SJ0Qk4DEz1sgL02Wt6mebNNM9d7870PEoJRHAsUcJPrw==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.8.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.6.tgz", + "integrity": "sha512-eH2OTP9s55vojr3b7NBaF9i4WhWPkv/nq55nznWNp/FomKrLViprUcqnBjHph2tFQ+7KciGPTPsVWGz0SOhL0Q==", + "requires": { + "@protobufjs/aspromise": "1.1.2", + "@protobufjs/base64": "1.1.2", + "@protobufjs/codegen": "2.0.4", + "@protobufjs/eventemitter": "1.1.0", + "@protobufjs/fetch": "1.1.0", + "@protobufjs/float": "1.0.2", + "@protobufjs/inquire": "1.1.0", + "@protobufjs/path": "1.1.2", + "@protobufjs/pool": "1.1.0", + "@protobufjs/utf8": "1.1.0", + "@types/long": "3.0.32", + "@types/node": "8.10.16", + "long": "4.0.0" + }, + "dependencies": { + "@types/long": { + "version": "3.0.32", + "resolved": "https://registry.npmjs.org/@types/long/-/long-3.0.32.tgz", + "integrity": "sha512-ZXyOOm83p7X8p3s0IYM3VeueNmHpkk/yMlP8CLeOnEcu6hIwPH7YjZBvhQkR0ZFS2DqZAxKtJ/M5fcuv3OU5BA==" + } + } + } + } +} diff --git a/packages/apollo-engine-reporting-protobuf/package.json b/packages/apollo-engine-reporting-protobuf/package.json new file mode 100644 index 00000000000..daf6629db9f --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/package.json @@ -0,0 +1,36 @@ +{ + "name": "apollo-engine-reporting-protobuf", + "version": "0.0.0-beta.7", + "description": "Protobuf format for Apollo Engine", + "main": "dist/index.js", + "scripts": { + "prepublish": "npm run pbjs && npm run pbts", + "pbjs": "bash -c 'mkdir -p dist && pbjs --target static-module --out dist/index.js --wrap commonjs --force-number <(grep -v \"package mdg.engine.proto\" reports.proto)'", + "pbts": "pbts -o dist/index.d.ts dist/index.js", + "circle": "npm run prepare" + }, + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-engine-reporting/tree/master/packages/apollo-engine-reporting-protobuf" + }, + "keywords": [ + "GraphQL", + "Apollo", + "Engine", + "Server", + "Javascript" + ], + "author": "Apollo ", + "license": "MIT", + "bugs": { + "url": "https://github.com/apollographql/apollo-engine-reporting/issues" + }, + "homepage": "https://github.com/apollographql/apollo-engine-reporting#readme", + "dependencies": { + "protobufjs": "^6.8.6" + }, + "typings": "dist/index.d.ts", + "typescript": { + "definition": "dist/index.d.ts" + } +} diff --git a/packages/apollo-engine-reporting-protobuf/reports.proto b/packages/apollo-engine-reporting-protobuf/reports.proto new file mode 100644 index 00000000000..005597cb6ae --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/reports.proto @@ -0,0 +1,342 @@ +syntax = "proto3"; + +package mdg.engine.proto; + +import "google/protobuf/timestamp.proto"; + +option optimize_for = SPEED; + +message Trace { + message CachePolicy { + enum Scope { + UNKNOWN = 0; + PUBLIC = 1; + PRIVATE = 2; + } + + Scope scope = 1; + int64 max_age_ns = 2; // use 0 for absent, -1 for 0 + } + + message Details { + // The variables associated with this query (unless the reporting agent is + // configured to keep them all private). Values are JSON: ie, strings are + // enclosed in double quotes, etc. The value of a private variable is + // the empty string. + map variables_json = 4; + // Deprecated. Engineproxy did not encode variable values as JSON, so you + // couldn't tell numbers from numeric strings. Send variables_json instead. + map variables = 1; + // Optional: this is the original full query before the signature algorithm + // is applied. Engineproxy always sent this in all traces, which meant that + // literal-masking done by the signature algorithm didn't fully hide + // sensitive data from Engine servers. apollo-engine-reporting does not + // include this by default. (The Engine frontend does not currently show + // this field.) + string raw_query = 2; + // Don't include this in traces inside a FullTracesReport; the operation + // name for these traces comes from the key of the traces_per_query map. + string operation_name = 3; + } + + message Error { + string message = 1; // required + repeated Location location = 2; + uint64 time_ns = 3; + string json = 4; + } + + message HTTP { + message Values { + repeated string value = 1; + } + + enum Method { + UNKNOWN = 0; + OPTIONS = 1; + GET = 2; + HEAD = 3; + POST = 4; + PUT = 5; + DELETE = 6; + TRACE = 7; + CONNECT = 8; + PATCH = 9; + } + Method method = 1; + string host = 2; + string path = 3; + + // Should exclude manual blacklist ("Auth" by default) + map request_headers = 4; + map response_headers = 5; + + uint32 status_code = 6; + + bool secure = 8; // TLS was used + string protocol = 9; // by convention "HTTP/1.0", "HTTP/1.1", "HTTP/2" or "h2" + } + + message Location { + uint32 line = 1; + uint32 column = 2; + } + + message Node { + // The name of the field (for Nodes representing a resolver call) or + // the index in a list (for intermediate Nodes representing elements of + // a list). Note that nodes representing indexes (and the root node) + // don't contain all Node fields (eg types and times). + oneof id { + string field_name = 1; + uint32 index = 2; + } + + // The field's return type; e.g. "String!" for User.email:String! + string type = 3; + + // The field's parent type; e.g. "User" for User.email:String! + string parent_type = 13; + + CachePolicy cache_policy = 5; + + // relative to the trace's start_time, in ns + uint64 start_time = 8; + // relative to the trace's start_time, in ns + uint64 end_time = 9; + + repeated Error error = 11; + repeated Node child = 12; + + reserved 4; + } + + // Wallclock time when the trace began. + google.protobuf.Timestamp start_time = 4; // required + // Wallclock time when the trace ended. + google.protobuf.Timestamp end_time = 3; // required + // High precision duration of the trace; may not equal end_time-start_time + // (eg, if your machine's clock changed during the trace). + uint64 duration_ns = 11; // required + + // These fields are specific to engineproxy. + google.protobuf.Timestamp origin_reported_start_time = 15; + google.protobuf.Timestamp origin_reported_end_time = 16; + uint64 origin_reported_duration_ns = 17; + + // In addition to details.raw_query, we include a "signature" of the query, + // which can be normalized: for example, you may want to discard aliases, drop + // unused operations and fragments, sort fields, etc. The most important thing + // here is that the signature match the signature in StatsReports. In + // StatsReports signatures show up as the key in the per_query map (with the + // operation name prepended). The signature should be a valid GraphQL query. + // All traces must have a signature; if this Trace is in a FullTracesReport + // that signature is in the key of traces_per_query rather than in this field. + // Engineproxy provides the signature in legacy_signature_needs_resigning + // instead. + string signature = 19; + + // Older agents (eg the Go engineproxy) relied to some degree on the Engine + // backend to run their own semi-compatible implementation of a specific + // variant of query signatures. The backend won't do that for new agents, but + // we keep this old field around to ensure that queries only get new IDs when + // you upgrade to the new agent, not just when we make backend changes + // to minimize backend-side signature calculation. Deprecated and ignored + // in FullTracesReports. + string legacy_signature_needs_resigning = 5; + + Details details = 6; + + // Note: engineproxy always sets these to "none". apollo-engine-reporting + // should allow for them to be set by the user. + string client_name = 7; + string client_version = 8; + string client_address = 9; + + HTTP http = 10; + + CachePolicy cache_policy = 18; + + Node root = 14; + + // Was this response served from a full query response cache? (In that case + // the node tree will have no resolvers.) + bool full_query_cache_hit = 20; + + // Was this query specified successfully as a persisted query hash? + bool persisted_query_hit = 21; + // Did this query contain both a full query string and a persisted query hash? + // (This typically means that a previous request was rejected as an unknown + // persisted query.) + bool persisted_query_register = 22; + + // removed: Node parse = 12; Node validate = 13; + // Id128 server_id = 1; Id128 client_id = 2; + reserved 12, 13, 1, 2; +} + +message ReportHeader { + string service = 3; + // eg "host-01.example.com" + string hostname = 5; + + // eg "engineproxy 0.1.0" + string agent_version = 6; // required + // eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`) + string service_version = 7; + // eg "node v4.6.0" + string runtime_version = 8; + // eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux" + string uname = 9; +} + +message PathErrorStats { + map children = 1; + uint64 errors_count = 4; + uint64 requests_with_errors_count = 5; +} + +message ClientNameStats { + // Duration histogram for non-cache-hit queries. + // (See docs/histograms.md for the histogram format.) + repeated int64 latency_count = 1; + reserved 2; // removed: repeated uint64 error_count = 2; + map requests_count_per_version = 3; // required + map cache_hits_per_version = 4; + map persisted_query_hits_per_version = 10; + map persisted_query_misses_per_version = 11; + repeated int64 cache_latency_count = 5; // Duration histogram; see docs/histograms.md + PathErrorStats root_error_stats = 6; + uint64 requests_with_errors_count = 7; + // TTL histograms for cache misses for the public cache. + repeated int64 public_cache_ttl_count = 8; + // TTL histograms for cache misses for the private cache. + repeated int64 private_cache_ttl_count = 9; +} + +message FieldStat { + string name = 2; // deprecated; only set when stored in TypeStat.field + string return_type = 3; // required; eg "String!" for User.email:String! + uint64 errors_count = 4; + uint64 count = 5; + uint64 requests_with_errors_count = 6; + repeated int64 latency_count = 8; // Duration histogram; see docs/histograms.md +} + +message TypeStat { + string name = 1; // deprecated; only set when stored in QueryStats.per_type + repeated FieldStat field = 2; // deprecated; use per_field_stat instead + // Key is (eg) "email" for User.email:String! + map per_field_stat = 3; +} + +message QueryStats { + map per_client_name = 1; // required + repeated TypeStat per_type = 2; // deprecated; use per_type_stat instead + // Key is the parent type, e.g. "User" for User.email:String! + map per_type_stat = 3; +} + +// Top-level message type for the server-side traces endpoint +message TracesReport { + ReportHeader header = 1; // required + repeated Trace trace = 2; // required +} + +message Field { + string name = 2; // required; eg "email" for User.email:String! + string return_type = 3; // required; eg "String!" for User.email:String! +} + +message Type { + string name = 1; // required; eg "User" for User.email:String! + repeated Field field = 2; +} + +message MemStats { + uint64 total_bytes = 1; // MemStats.Sys + uint64 stack_bytes = 2; // MemStats.StackSys + uint64 heap_bytes = 3; // MemStats.HeapSys + uint64 heap_released_bytes = 13; // MemStats.HeapReleased + uint64 gc_overhead_bytes = 4; // MemStats.GCSys + + uint64 stack_used_bytes = 5; // MemStats.StackInuse + uint64 heap_allocated_bytes = 6; // MemStats.HeapAlloc + uint64 heap_allocated_objects = 7; // MemStats.HeapObjects + + uint64 heap_allocated_bytes_delta = 8; // MemStats.TotalAlloc delta + uint64 heap_allocated_objects_delta = 9; // MemStats.Mallocs delta + uint64 heap_freed_objects_delta = 10; // MemStats.Frees delta + + uint64 gc_stw_ns_delta = 11; // MemStats.PauseTotalNs delta + uint64 gc_count_delta = 12; // MemStats.NumGC delta +} + +message TimeStats { + uint64 uptime_ns = 1; + uint64 real_ns_delta = 2; + uint64 user_ns_delta = 3; + uint64 sys_ns_delta = 4; +} + +// Top-level message type for the server-side stats endpoint +message StatsReport { + ReportHeader header = 1; // required + + // These fields are about properties of the engineproxy and are not generated + // from FullTracesReports. + MemStats mem_stats = 2; + TimeStats time_stats = 3; + + // Beginning of the period over which stats are collected. + google.protobuf.Timestamp start_time = 8; + // End of the period of which stats are collected. + google.protobuf.Timestamp end_time = 9; + // Only used to interpret mem_stats and time_stats; not generated from + // FullTracesReports. + uint64 realtime_duration = 10; + + + // Maps from query descriptor to QueryStats. Required unless + // legacy_per_query_missing_operation_name is set. The keys are strings of the + // form `# operationName\nsignature` (literal hash and space), with + // operationName - if there is no operation name. + map per_query = 14; + + // Older agents (Go engineproxy) didn't explicitly include the operation name + // in the key of this map, and the server had to parse it out (after a + // re-signing operation). The key here is just the query + // signature. Deprecated. + map legacy_per_query_implicit_operation_name = 12; + + // Deprecated: it was useful in Optics where we had access to the whole schema + // but has not been ever used in Engine. apollo-engine-reporting will not + // send it. + repeated Type type = 13; +} + +// This is the top-level message used by the new traces ingress. This +// is designed for the apollo-engine-reporting TypeScript agent and will +// eventually be documented as a public ingress API. This message consists +// solely of traces; the equivalent of the StatsReport is automatically +// generated server-side from this message. Agents should send traces +// for all requests in this report. Generally, buffering up until a large +// size has been reached (say, 4MB) or 5-10 seconds has passed is appropriate. +message FullTracesReport { + ReportHeader header = 1; + + // key is statsReportKey (# operationName\nsignature) Note that the nested + // traces will *not* have a signature or details.operationName (because the + // key is adequate). + // + // We also assume that traces don't have + // legacy_per_query_implicit_operation_name, and we don't require them to have + // details.raw_query (which would consume a lot of space and has privacy/data + // access issues, and isn't currently exposed by our app anyway). + map traces_per_query = 5; +} + +// Just a sequence of traces with the same statsReportKey. +message Traces { + repeated Trace trace = 1; +} diff --git a/packages/apollo-engine-reporting-protobuf/tsconfig.json b/packages/apollo-engine-reporting-protobuf/tsconfig.json new file mode 100644 index 00000000000..564cda66232 --- /dev/null +++ b/packages/apollo-engine-reporting-protobuf/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/apollo-engine-reporting/README.md b/packages/apollo-engine-reporting/README.md index b34ba15880f..49ab766eacc 100644 --- a/packages/apollo-engine-reporting/README.md +++ b/packages/apollo-engine-reporting/README.md @@ -3,4 +3,4 @@ [![npm version](https://badge.fury.io/js/apollo-engine-reporting.svg)](https://badge.fury.io/js/apollo-engine-reporting) [![Build Status](https://circleci.com/gh/apollographql/apollo-server.svg?style=svg)](https://circleci.com/gh/apollographql/apollo-server) -This package is a pure JS implementation of the Apollo Engine reporting feature. It is a work in progress and is not recommended for production use. +This package is a pure JS implementation of the Apollo Engine reporting feature. It enables aggregated reporting in stateful environments and sending single reports for stateless environments.