Skip to content

fix(cloudflare): correct TPR's zone-analytics query and export its selection helpers#2702

Open
kHorozhanov wants to merge 2 commits into
cloudflare:mainfrom
kHorozhanov:fix/tpr-analytics-query
Open

fix(cloudflare): correct TPR's zone-analytics query and export its selection helpers#2702
kHorozhanov wants to merge 2 commits into
cloudflare:mainfrom
kHorozhanov:fix/tpr-analytics-query

Conversation

@kHorozhanov

Copy link
Copy Markdown

What

Two changes to @vinext/cloudflare's TPR module:

  1. Fix the zone-analytics query — it cannot succeed as written, so --experimental-tpr silently pre-renders nothing.
  2. Export the traffic-selection helpers, so ranked route selection can be reused with the already-public warmCdnCache.

The bug

queryTraffic asks httpRequestsAdaptiveGroups for orderBy: [sum_requests_DESC] and selects sum { requests }. Neither exists on that dataset. Verified against a live zone:

query result
orderBy: [sum_requests_DESC] + sum { requests } error parsing args … orderBy: unknown enum value sum_requests_DESC
orderBy: [count_DESC] + sum { requests } unknown field "requests"
orderBy: [count_DESC] + count ✅ returns rows

These are schema-validation failures, not plan gating — contrast a genuinely plan-gated field like edgeResponseContentTypeName, which fails with code: authz ("does not have access to the field"). So the query fails on every zone regardless of plan.

Why it went unnoticed: runTPR catches a failing traffic query and treats it as "no traffic data", then skips gracefully — which is indistinguishable from a zone that genuinely has no traffic. The feature reports success and does nothing.

Fixed by using count_DESC / count, and mapping g.count instead of g.sum.requests.

Tests

tests/tpr-traffic-query.test.ts is new — there was no coverage of queryTraffic at all (only tpr-kv-keys.test.ts, which covers the KV key format). It pins the parts of the request the schema actually validates, the response mapping, and error surfacing. I verified the guard fails on the old query and passes on the new one:

AssertionError: expected '{\n    viewer {\n      zones(filter: …' to contain 'orderBy: [count_DESC]'

It also covers filterTrafficPaths and selectRoutes, which were previously untested.

The export

warmCdnCache is already exported from @vinext/cloudflare/internal/cdn-warm and takes an arbitrary targetUrl + paths, which makes it usable outside the bundled deploy pipeline — useful when the deployment shape isn't the one vinext-cloudflare deploy builds (in our case a gateway worker in front of the renderer, so warming has to go through the gateway URL). But it has no way to decide which paths to warm, and the logic for that already exists here.

So this exports queryTraffic, filterTrafficPaths, selectRoutes and resolveZoneId (plus the TrafficEntry / SelectedRoutes types) from @vinext/cloudflare/internal/tpr, following the same precedent that made cdn-warm a separate entry point. No behaviour change — the only public entry today is runTPR, which also does the local pre-render and KV upload.

One thing worth flagging for anyone reusing filterTrafficPaths: it assumes zone == one app. On a zone shared by several Workers the raw top paths include the other Workers' routes, which callers need to filter by their own base path.

Kostiantyn Horozhanov added 2 commits July 24, 2026 22:24
…lection helpers

queryTraffic asked httpRequestsAdaptiveGroups for orderBy: [sum_requests_DESC]
and sum { requests }. Neither exists on that dataset — the API rejects them
with "unknown enum value sum_requests_DESC" and "unknown field requests".
Those are schema-validation errors, not plan gating, so the query fails on
every zone. runTPR treats a failed traffic query as "no traffic data" and
skips gracefully, so --experimental-tpr silently pre-rendered nothing.

Corrected to count_DESC / count, and added tests that pin the request shape:
the previous query fails them.

Also exports queryTraffic, filterTrafficPaths, selectRoutes and resolveZoneId
so traffic-ranked selection can be reused with warmCdnCache, which is already
public but has no way to choose which paths to warm.
`toContain("count")` was satisfied by `count_DESC` in orderBy, so it held
even when the selection set asked for `sum { requests }`. Match `count` as a
field on its own line instead — verified it fails when only the selector is
reverted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant