Batch PHP version fetches via GraphQL to stay within Worker subrequest limits#130
Merged
Merged
Conversation
…equest limits With 50+ releases, fetching composer.json individually for each release via the GitHub Contents API consumed more than 50 subrequests per Worker invocation, hitting Cloudflare's free-plan limit. This replaces N individual subrequests with a single GitHub GraphQL batch query that retrieves all composer.json files in one round trip. Releases already stored in the KV cache reuse their cached PHP version so updates only fetch data for genuinely new releases — keeping total subrequests at ≤2 per invocation regardless of how many releases exist. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
Replaces individual GitHub Contents API calls (one per release) with a single GraphQL batch request, eliminating the Cloudflare Workers subrequest limit error (50 subrequests on free plan) that occurred when processing 50+ releases. The new implementation builds a single GraphQL query with per-release field aliases and skips releases whose PHP version is already cached in KV, meaning the worst case is 2 subrequests total (1 REST + 1 GraphQL) regardless of release count. Test mocks updated across all test files to provide a proper @octokit/request.defaults stub so @octokit/graphql can initialise in the Cloudflare workerd environment, and to mock @octokit/graphql directly rather than stubbing fetch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
- Add REPO_OWNER / REPO_NAME constants and thread them through the REST call and the GraphQL query string instead of four separate hardcoded "FOSSBilling" literals - Collapse the intermediate `aliases` array in getBatchPhpVersions: the alias formula is now computed inline during the field-building pass and again during the result-reading pass, reducing allocations from 3 arrays to 2 (fields + result map) - Delete the exported getReleaseMinPhpVersion function and its companion GetReleaseMinPhpVersionResult interface — both were dead code after the batch GraphQL path replaced the per-release REST fetches - Simplify createGraphQLImplementation in test/utils/mock-helpers.ts: iterate the matchAll iterator directly with for..of instead of spreading it into an intermediate array Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
api | d246b2b | Commit Preview URL Branch Preview URL |
Jun 20 2026, 03:46 AM |
Deleted alongside getReleaseMinPhpVersion, which was its only call site. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the Versions API v1 service to avoid Cloudflare Worker subrequest-limit issues by batching composer.json retrieval via a single GitHub GraphQL query, reusing cached PHP requirements where possible.
Changes:
- Replace per-release
contentsREST calls with a single GraphQL batch query to fetchcomposer.jsonblobs for missing releases. - Reuse cached
minimum_php_versionvalues to keep subrequests bounded as release counts grow. - Add
@octokit/graphqldependency and update test mocks/helpers to support GraphQL-based fetching.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/services/versions/v1/index.ts |
Implements GraphQL batching and cache reuse for PHP requirement extraction. |
package.json |
Adds @octokit/graphql runtime dependency. |
package-lock.json |
Locks @octokit/graphql and related dependency tree updates. |
test/utils/mock-helpers.ts |
Adds GraphQL mock generator + wires GraphQL into GitHub API test setup helper. |
test/utils/test-types.ts |
Adds a GraphQL mock interface for tests. |
test/services/versions/v1/index.test.ts |
Updates versions tests to use GraphQL mocks and new call-count expectations. |
test/services/versions/v1/errors.test.ts |
Updates error-handling tests to simulate missing/malformed composer blobs via GraphQL. |
test/services/versions/v1/middleware.test.ts |
Updates middleware tests to include GraphQL mocking. |
test/services/stats/v1/index.test.ts |
Updates stats tests to provide GraphQL mocking via shared helper. |
test/integration/versions/index.test.ts |
Updates integration tests to include GraphQL mocking. |
test/integration/app.test.ts |
Updates full-app integration tests to include GraphQL mocking. |
test/app/index.test.ts |
Updates app tests to provide GraphQL mock implementation. |
wrangler.jsonc |
Minor JSONC formatting tweak in routes section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Name-derived aliases (v${tag.replace(/[^a-zA-Z0-9]/g, "_")}) can
collide for semver tags that differ only in separator characters
(e.g. 1.0.0-alpha.1 and 1.0.0-alpha-1 both produce v1_0_0_alpha_1),
which would cause one release's composer.json blob to overwrite another
in the response map.
Replacing with positional aliases r0, r1, r2... guarantees uniqueness
regardless of tag format and simplifies the alias derivation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNinmcp7f22H7ufvKozUeJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With 50+ releases, fetching
composer.jsonindividually for each release via the GitHub Contents API consumed more than 50 subrequests per Worker invocation, hitting Cloudflare's free-plan limit.This replaces N individual subrequests with a single GitHub GraphQL batch query that retrieves all
composer.jsonfiles in one round trip. Releases already stored in the KV cache reuse their cached PHP version so updates only fetch data for genuinely new releases, keeping total subrequests at ≤2 per invocation regardless of how many releases exist.