Skip to content

feat(graphql): expose response headers and X-GraphQL-Keys parser - #176

Merged
AssisrMatheus merged 1 commit into
mainfrom
feat/graphql-response-headers
Oct 1, 2026
Merged

AssisrMatheus merged 1 commit into
mainfrom
feat/graphql-response-headers

Conversation

@AssisrMatheus

Copy link
Copy Markdown
Member

Summary

@perimetre/graphql minor (changeset included).

  • Every executor (createApqExecutor, createTrustedDocumentExecutor, createPassthroughExecutor, and createWpGraphql().executeGraphqlRequest) now also exposes .withMeta(document, variables?, options?), resolving to { data, headers, transport, durationMs }. headers are the raw fetch Response.headers of the response that produced data — after an APQ miss that is the register POST's (or the fallback POST's), never the GET's that missed. The plain call is unchanged and still resolves to data only. New GraphqlExecutor type; ExecuteGraphqlRequest is untouched so custom executors keep compiling.
  • GraphqlRequestPlugin.onResponse now receives headers and transport next to data / error / durationMs (GraphqlResponseOutcome). Headers are also recovered from a thrown graphql-request ClientError; undefined only when no response arrived.
  • POST paths (mutations, unhashed documents, every fallback, passthrough) go through client.rawRequest so they carry headers too. Same middlewares and errorPolicy as client.request.
  • New @perimetre/graphql/keys subpath (also re-exported from the root): parseGraphqlKeys(headers | string) → { nodeIds, lists, skipped, operation?, queryId?, root? }. Pure, dependency-free, never throws. lists / skipped stay in wire form (list:post, skipped:post) since those are the keys WPGraphQL Smart Cache purges by.
  • Executor internals exported for custom transports: createExecutor, postViaClient, GraphqlTransport*, GraphqlResponseMeta, GraphqlExecuteResult, ExecuteGraphqlRequestWithMeta.
  • Bun unit tests under packages/graphql/src/__tests__ (31 tests), wired as pnpm --filter @perimetre/graphql test and run before tsdown on build.

Also in this PR

  • middlewares.ts: the two fetch wrappers are cast as typeof fetch — bun-types' typeof fetch requires preconnect, which a plain wrapper never implements. No behaviour change; tsc -p src/__tests__/tsconfig.json is now clean.
  • CLAUDE.md, AGENTS.md, README.md still pointed at the LLMs/*.md guides deleted in 48f6c3e. They now point at the ai-toolkit perimetre-apps plugin (app-code-review/references/*.md, app-caching, seo).

Usage

// src/server/cms/cache.ts — a Next.js 'use cache: remote' reader tagged with what the CMS resolved
import { cacheLife, cacheTag } from 'next/cache';
import { parseGraphqlKeys } from '@perimetre/graphql/keys';
import { executeGraphqlRequest } from '@/server/graphql';

export async function getPage(uri: string) {
  'use cache: remote';
  cacheLife('max');
  const { data, headers } = await executeGraphqlRequest.withMeta(GetPageDocument, { uri });
  const { nodeIds, lists, skipped } = parseGraphqlKeys(headers);
  cacheTag('cms', ...nodeIds, ...lists, ...skipped);
  return data;
}

Test plan

  • pnpm turbo run lint typecheck build --filter=@perimetre/graphql --force — 4/4 tasks successful
  • pnpm --filter @perimetre/graphql test — 31 pass, 0 fail, 113 expect() calls, 4 files
  • tsc -p packages/graphql/src/__tests__/tsconfig.json — exit 0 (previously 2 preconnect errors)
  • pnpm install --frozen-lockfile — lockfile in sync

🤖 Generated with Claude Code

Every executor (createApqExecutor, createTrustedDocumentExecutor,
createPassthroughExecutor, createWpGraphql().executeGraphqlRequest) now also
exposes `.withMeta(document, variables?, options?)`, resolving to
`{ data, headers, transport, durationMs }` for the final response (after an
APQ miss that is the register/fallback POST, never the GET that missed). The
plain call is unchanged and still resolves to data only.

`GraphqlRequestPlugin.onResponse` now receives `headers` and `transport` next
to `data` / `error` / `durationMs`; headers are also recovered from a thrown
graphql-request ClientError. POST paths go through `client.rawRequest` so they
carry headers too.

New `@perimetre/graphql/keys` subpath (re-exported from the root):
`parseGraphqlKeys(headers | string)` splits WPGraphQL's X-GraphQL-Keys header
into `{ nodeIds, lists, skipped, operation?, queryId?, root? }` for tagging
`'use cache: remote'` reads. Executor internals (`createExecutor`,
`postViaClient`, transport types) are exported for custom transports.

Adds bun unit tests under src/__tests__ (run on `test` and before `build`),
and casts the fetch wrappers in middlewares.ts to `typeof fetch` so the test
tsconfig (bun-types, which adds `preconnect`) typechecks clean.

Docs: CLAUDE.md, AGENTS.md and README.md pointed at the deleted LLMs/ guides;
they now point at the ai-toolkit `perimetre-apps` plugin (app-code-review
references, app-caching, seo).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
framework-ui Ready Ready Preview Oct 1, 2026 2:10pm UTC

Request Review

@AssisrMatheus
AssisrMatheus merged commit 0f0a08b into main Oct 1, 2026
9 checks passed
@AssisrMatheus
AssisrMatheus deleted the feat/graphql-response-headers branch October 1, 2026 14:13

This branch was successfully deployed

1 active deployment
Preview — 1a17b367 Deployed Oct 1, 2026 by vercel[bot]
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