Skip to content

Generate a typed Public API v2 client, with a CI check that it is current - #1346

Closed
anth-volk wants to merge 3 commits into
mainfrom
apiv2/1e-public-api-client
Closed

anth-volk wants to merge 3 commits into
mainfrom
apiv2/1e-public-api-client

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1342

Summary

Adds a TypeScript client for the Public API's v2 routes (policyengine-api), generated from its OpenAPI document, and a pull-request check that fails when the generated client is out of date. Nothing in the app uses it yet; #1347 (stacked on this PR) is the first user.

  • app/src/api/publicApi/openapi.json: a checked-in copy of the Public API's GET /v2/openapi.json.
  • app/src/api/publicApi/schema.d.ts: types generated from it by openapi-typescript and formatted with the repository's Prettier settings. Do not edit by hand.
  • app/src/api/publicApi/client.ts: createPublicApiClient() wraps openapi-fetch so route paths, query parameters, bodies and responses are type-checked. It sends the same credentials as the existing saved-work client (a session's bearer token, or this browser's unauthenticated user), and requireResult() unwraps the {status, message, result} envelope or throws the existing V2ApiError (status 0 for a network failure). v2UserClient.ts now exports its credential-header and error-message helpers so both clients share them.
  • app/scripts/public-api-client.ts, run from app/:
    • bun run public-api:refresh <source> copies the document from an API origin (https://api.policyengine.org), a full URL or a saved file, then regenerates the types;
    • bun run public-api:generate regenerates the types from the checked-in document;
    • bun run public-api:check exits non-zero when schema.d.ts differs from what the document generates.
  • .github/workflows/pr.yaml: a new job, "Public API client is current", runs public-api:check. A unit test (app/src/tests/unit/scripts/public-api-client.test.ts, with its OpenAPI fixture in app/src/tests/fixtures/scripts/) makes the same assertion, so bun run test also fails on an out-of-date client. Because the test imports the script, bun run typecheck checks the script too.

New dependencies (both released in February 2026): openapi-typescript 7.13.0 (development, app) and openapi-fetch 0.17.0 (runtime, app and calculator-app, which compiles app/src).

The OpenAPI document in this PR

It was generated from the Public API branch apiv2/1e-catalog-fields (the top of PolicyEngine/policyengine-api#3968, #3969 and #3970) by serving /v2/openapi.json locally, because those routes are not deployed yet. Merge this PR only after those three API PRs are deployed. Before merging, run bun run public-api:refresh https://api.policyengine.org in app/ and commit any difference.

What the check covers: it compares the generated types with the checked-in document, so it catches hand edits and forgotten regeneration. It does not call the deployed API, so app CI never depends on API availability; refreshing the document after an API contract change stays a deliberate step.

Environment variables and secrets

The new CI job reads no environment variables or secrets. It checks out the repository, installs with bun install --frozen-lockfile and runs the script; the script only reads and writes files under app/src/api/publicApi/ (the refresh command, run by hand, fetches a URL you pass). The client itself reads only the existing BASE_URL (from VITE_API_URL, mapped from NEXT_PUBLIC_API_URL in calculator-app), as the current API modules do.

Testing

  • bunx vitest run src/tests/unit/scripts/public-api-client.test.ts src/tests/unit/api/publicApi/client.test.ts src/tests/unit/api/v2UserClient.test.ts: 25 passed. They cover: generated types name routes and schemas; types from the same document are current and from an older document are not; an API origin maps to its /v2/openapi.json; the checked-in types match the checked-in document; requests go to the API origin with the right credentials (none, session token, or browser user); requireResult returns results, raises the API's status and message, and raises status 0 on network failure.
  • bun run public-api:check: passes.
  • bun run typecheck (app and calculator-app), bun run prettier, bun run eslint --max-warnings 0: pass.
  • bun run build in calculator-app: succeeds.
  • Full bun run vitest: everything passes except two ScorecardComparisons tests that fail the same way on main in my environment (number formatting £1B against £1.0B), unrelated to this change.

Rebased on main

The stack is rebased on main after the legacy Vite build was removed there (#1331). app/package.json keeps only the three public-api:* scripts from this PR, and bun.lock was regenerated from main's with Bun 1.2.21, the version CI installs from packageManager.

Merge order

Possible conflicts

Any other PR that regenerates openapi.json and schema.d.ts (for example group 1F's country list from /v2/countries) conflicts in those two files. Resolve by taking either side and running bun run public-api:refresh against the deployed API.

Pending (operator)

Optionally make "Public API client is current" a required status check in the repository's branch protection settings. I have not changed any settings.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
policyengine-calculator-next Ready Ready Preview Oct 20, 2026 1:25pm UTC
policyengine-website Ready Ready Preview Oct 20, 2026 1:25pm UTC

Request Review

anth-volk and others added 3 commits October 20, 2026 17:12
src/api/publicApi/schema.d.ts is generated by openapi-typescript from
src/api/publicApi/openapi.json, a checked-in copy of the PolicyEngine
API's GET /v2/openapi.json. createPublicApiClient wraps openapi-fetch
with the existing saved-work credentials (session token or this
browser's unauthenticated user), and requireResult unwraps the v2
success envelope or throws V2ApiError.

bun run public-api:refresh <origin|url|file> copies the document and
regenerates; public-api:check fails when the types do not match the
document. A new "PolicyEngine API client is current" job runs the
check on pull requests, and a test asserts the same.

Nothing in the app uses the client yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The testing guidance places tests under src/tests/<type>/ and test data
under src/tests/fixtures/. The test for scripts/public-api-client.ts sat
beside the script, outside the app's TypeScript configuration, so
`bun run typecheck` checked neither the test nor the script. From
src/tests/unit/scripts/ it imports the script, which brings both into the
type check.

This is a relocation, so no test could fail first. Checked instead by
adding a deliberate type error to the script: `bun run typecheck` now
reports it, and reported nothing with the old layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new client's comments, the generation script's messages and the new
pull-request job named the service "the PolicyEngine API". Public text in
this migration calls it the Public API, so the job is now "Public API
client is current". No setting refers to the old job name yet.

Wording only, so no test could fail first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the apiv2/1e-public-api-client branch from 75c05b9 to 133edc8 Compare October 20, 2026 13:23
@anth-volk anth-volk changed the title Generate a typed PolicyEngine API v2 client, with a CI check that it is current Generate a typed Public API v2 client, with a CI check that it is current Oct 20, 2026
@anth-volk anth-volk closed this Oct 20, 2026

This branch was successfully deployed

2 active deployments
Preview – policyengine-calculator-next — 133edc85 Deployed Oct 20, 2026 by vercel[bot]
Preview – policyengine-website — 133edc85 Deployed Oct 20, 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.

Generate a typed PolicyEngine API v2 client with a CI check

1 participant