github-graphql-node-count 0.0.1

Compute, offline, the node count and the rate-limit point cost GitHub's GraphQL API charges a query, from the query text and its page-size variables.
Documentation
  • Coverage
  • 100%
    42 out of 42 items documented2 out of 4 items with examples
  • Size
  • Source code size: 94.63 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 681.66 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 8s Average build duration of successful builds.
  • all releases: 8s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • nickderobertis/github-graphql-node-count
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • nickderobertis

github-graphql-node-count

Compute, offline, the two numbers GitHub's GraphQL API charges a query — the worst-case node count it may return and the rate-limit points one call of it spends — from the query text and its page-size variables, before you send it.

[dependencies]
github-graphql-node-count = "0"
use github_graphql_node_count::{node_count, point_cost, Variables, NODE_LIMIT};

let document = r#"
    query($repos: Int!) {
      viewer {
        repositories(first: $repos) {
          edges { node { name issues(first: 10) { edges { node { title } } } } }
        }
      }
    }
"#;
let variables = Variables::from([("repos".to_string(), 50)]);

// 50 repositories + 50 x 10 issues.
let nodes = node_count(document, &variables).expect("a well-formed document counts");
assert_eq!(nodes, 550);
assert!(nodes < NODE_LIMIT);

// The other limit: `repositories` is resolved once and `issues` fifty times,
// so 51 requests round to one rate-limit point.
let points = point_cost(document, &variables).expect("a well-formed document prices");
assert_eq!(points, 1);

What it is for

GitHub rejects a query whose node count reaches 500,000, and it charges every call against an hourly budget one credential shares across everything it does. A query that grew past either is usually one nobody measured — and the second is worse, because it is observable only by asking GitHub, which a required check on a fork pull request cannot do. This crate is the measurement, for both: put it in a test, and a query that would be rejected, or one that quietly gave back a cost reduction somebody made on purpose, fails your build instead of your production call.

Two numbers, two limits

GitHub meters two different numbers against two different limits. Both are computed here, from one traversal of the document.

what it counts what it is limited against computed by
nodeCount the maximum number of nodes one query may return 500,000, per query node_count
cost the rate-limit points one call spends an hourly budget, per credential, across everything that credential does point_cost

They are not two views of one quantity. A cheap query run in a loop exhausts the hourly budget without ever approaching the node limit; a single enormous query is rejected outright while costing a handful of points. So gating on one is not gating on the other, and this crate never renames one into the other.

Individual calls cannot request more than 500,000 total nodes. — GitHub: Rate limits and node limits for the GraphQL API

The point rule from the same page: add up the requests needed to fulfil each unique connection in the call, assuming every request reaches its page-size limit; divide that aggregate by 100 and round to the nearest whole number; and never answer below GitHub's minimum of 1. A connection is resolved once per parent node, so what it costs is the product of the page sizes strictly above it — its own page size changes how many nodes it returns and not how often it is resolved. point_aggregate returns the raw pre-rounding aggregate, for a gate that wants to see a change smaller than a whole point.

No schema, and that is the design

Every function here is handed document text and nothing else — no network, no credential, no GraphQL schema. So the crate cannot know which fields are connections by type. Instead, a field carrying a first: or a last: argument is a connection: it multiplies the count beneath it and it is one of the connections whose requests are aggregated. Every other field contributes no multiplier, no nodes and no requests.

That is what makes this crate runnable in a fork pull request with no secret, which is the whole reason you can put it in a gate. The price is one blind spot, stated plainly and applying to both answers: a connection supplying neither first nor last is invalid to GitHub and invisible here, so it is treated as an ordinary field and both answers are an undercount. A connection supplying both is charged at the larger of the two, so both stay a worst case.

What it answers with

node_count, point_cost and point_aggregate return an error rather than a panic, a zero, or a wrong number when the text is not GraphQL, when the document holds no operation or more than one, when a first:/last: names an unbound variable or falls outside 1..=100, and when a spread names a fragment the document does not define. There is one error type across all three, because there is one parse and one walk: a document either yields both numbers or fails identically for either. Every message names the field or the line:column at fault. See the API documentation for the full list.

Versioning

Semver, pre-1.0: a breaking change bumps the minor version. NodeCountError is #[non_exhaustive], so a new variant is not a breaking change — match it with a _ => arm.

Releases and their notes are in CHANGELOG.md, written from Conventional Commits with no manual step.

Contributing

just bootstrap from a clean clone, then just check — the whole gate, offline and credential-free. AGENTS.md is the durable instruction layer.

License

MIT