Skip to content

feat(api): OpenAPI→TS codegen + contract-drift gate (Wave 1)#191

Merged
mastermanas805 merged 1 commit into
mainfrom
feat/wave1-openapi-codegen
Jun 5, 2026
Merged

feat(api): OpenAPI→TS codegen + contract-drift gate (Wave 1)#191
mastermanas805 merged 1 commit into
mainfrom
feat/wave1-openapi-codegen

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Wave 1 contract-drift gate — docs/ci/01-CI-INTEGRATION-DESIGN.md (HIGHEST ROI).

Consumer-side half of the UI↔API contract-drift gate. Producer-side half (oasdiff breaking-change gate): InstaNode-dev/api#264.

Problem

The UI↔API wire contract was hand-mirrored in src/api/types.ts. A backend field rename passed tsc+vitest here (the web mirrored the old shape by hand) and broke prod at runtime — the login-break class.

What

  • openapi-typescript devDep + gen:api-typessrc/api/generated.ts, generated from a committed openapi.snapshot.json (byte-identical copy of the api repo's; committed for CI determinism rather than fetching prod at gen time).
    • npm overrides satisfies openapi-typescript@7's typescript@^5 peer with the repo's TS 6 — avoids --legacy-peer-deps (which drops the auto-installed @testing-library/dom peer and breaks the test type surface). npm ci reproduces the correct tree.
  • Derive the highest-value wire shapes from generated.ts and consume them in index.ts (this is what makes a rename fail tsc):
    • WireAuthMefetchMe (the field that broke login)
    • WireResourceItem / WireResourceListResponselistResources/getResource/adaptResource
    • WireBillingStatefetchBilling/mapBillingState
    • WireDeployItemlistDeployments/getDeployment/adaptDeployment
    • TODO in types.ts lists remaining hand-typed wire types + gate blind spots.
  • gen:api-types:check (scripts/check-api-types.mjs) fails CI if generated.ts is stale vs the snapshot; wired into ci.yml. prebuild regenerates it.
  • generated.ts excluded from coverage (type-only codegen artifact).

The proof that the gate works

Renaming ResourceItem.storage_bytesstorageBytes in the snapshot + npm run gen:api-types makes tsc fail at the consumer:

src/api/index.ts(669,22): error TS2551: Property 'storage_bytes' does not exist
  on type '... & { storageBytes?: number ... }'. Did you mean 'storageBytes'?

gen:api-types:check also reds on a stale generated.ts. Reverted after proving.

Verify

  • npm run gate green (tsc + build + prerender + vitest: 1129 passed / 3 skipped).
  • gen:api-types:check passes; snapshot byte-identical to api repo's.
  • Patch coverage 100% (diff-cover) — added a minimal-payload listResources test exercising the new fallbacks.
  • generated.ts is type-only → npm run build output functionally identical (no new runtime).

Latent drift bugs surfaced (worth fixing in follow-up)

  1. DeployItem.failure has no exit_code on the wire, but the UI's adaptFailure reads it (always undefinednull at runtime). Reconcile: add exit_code to DeployItem, or read it only from the events surface (rule 27).
  2. BillingStateResponse lacks razorpay_configured in the spec though the UI consumes it (fail-closed default preserved via local intersection).
  3. Gate blind spot: /api/v1/capabilities (CapabilitiesResponse/TierCapabilities) has no web consumer (static PricingGrid), so it can't be tsc-gated until a consumer exists.

🤖 Generated with Claude Code

Wave 1 contract-drift gate — docs/ci/01-CI-INTEGRATION-DESIGN.md.

The UI↔API wire contract was hand-mirrored in src/api/types.ts; a backend
field rename passed tsc+vitest here and broke prod at runtime (the login-
break class). This derives the wire types from the api's OpenAPI snapshot so
the same rename now fails tsc in `npm run gate` at PR time.

- Add openapi-typescript devDep + `gen:api-types` script → src/api/generated.ts
  generated from a committed openapi.snapshot.json (byte-identical copy synced
  from the api repo; committed for CI determinism over fetching prod).
  npm `overrides` satisfies openapi-typescript@7's typescript@^5 peer with the
  repo's TS 6 — avoids --legacy-peer-deps (which drops @testing-library/dom).
- Derive the highest-value wire shapes (the ones that broke before) from
  generated.ts and consume them in index.ts: WireAuthMe (fetchMe),
  WireResourceItem/ResourceListResponse (listResources/getResource/adaptResource),
  WireBillingState (fetchBilling/mapBillingState), WireDeployItem
  (listDeployments/getDeployment/adaptDeployment). TODO in types.ts lists the
  remaining hand-typed wire types + gate blind spots.
- gen:api-types:check (scripts/check-api-types.mjs) fails CI if generated.ts is
  stale vs the snapshot; wired into ci.yml. prebuild regenerates it.
- Exclude generated.ts from coverage (type-only codegen artifact).
- Proven: renaming ResourceItem.storage_bytes in the snapshot + regenerating
  fails tsc at adaptResource (src/api/index.ts:669). gate green; patch cov 100%.

Latent gaps surfaced (see report/types.ts TODO): DeployItem.failure has no
exit_code on the wire though the UI reads it; BillingStateResponse lacks
razorpay_configured in the spec though the UI consumes it; /api/v1/capabilities
has no web consumer so it can't be tsc-gated.

Cross-ref: api PR adds the oasdiff breaking-change gate (producer side).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-C51TcQjl.js 0 B (-100% 🔽)
dist/assets/index-BsJUZYRr.css 6.13 KB (0%)
dist/assets/index-CAUaKl6R.js 162.26 KB (+100% 🔺)

@mastermanas805 mastermanas805 merged commit 55f58e1 into main Jun 5, 2026
18 checks passed
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