Skip to content

feat: add OpenAPI spec and generate TS types from it - #251

Open
ezekielcyclops-ux wants to merge 1 commit into
ToluLabs:mainfrom
ezekielcyclops-ux:feat/openapi-spec-and-generated-types
Open

feat: add OpenAPI spec and generate TS types from it#251
ezekielcyclops-ux wants to merge 1 commit into
ToluLabs:mainfrom
ezekielcyclops-ux:feat/openapi-spec-and-generated-types

Conversation

@ezekielcyclops-ux

@ezekielcyclops-ux ezekielcyclops-ux commented Jul 29, 2026

Copy link
Copy Markdown
  • Add docs/openapi.yaml (OpenAPI 3.1.0) covering all 6 API routes: POST /api/issue, POST /api/witness, GET /api/health, GET /api/ready, GET /api/issuers, GET /api/plaid-balance
  • Generate frontend/types/api.d.ts via openapi-typescript@7
  • Add frontend/types/index.ts with ergonomic re-exports
  • Wire generated types into route handlers (ready, issuers, plaid-balance, issue) replacing inline interface declarations
  • Add pnpm generate-types script to keep types in sync
  • Serve Redoc UI at GET /api/docs and raw YAML at GET /api/docs/spec
  • Spec validates with @redocly/cli (0 errors, 0 warnings)
  • pnpm tsc --noEmit passes; all tests pass

What does this PR do?

Closes #

Type of change

  • Bug fix
  • New feature / credential type
  • Refactor / cleanup
  • Docs
  • CI / tooling

Checklist

  • cargo test passes (contracts)
  • pnpm tsc --noEmit passes (frontend)
  • pnpm build passes (frontend)
  • Circuit changes: fixtures/<type>/ artifacts updated
  • No NEXT_PUBLIC_ prefix on server-only env vars
  • No identity fields stored or logged after KYC provider call
  • prehash:false preserved on any issuer signing path touched
  • Issuer private key never referenced from client-bundled code

✅ Merge requirements

  • All CI checks pass (contracts / frontend / circuits as applicable)
  • Every Greptile review comment is addressed — no unresolved review threads
  • Greptile confidence score is 4/5 or higher

Notes for reviewers

closes #138

Greptile Summary

This PR introduces an OpenAPI 3.1.0 spec covering all six API routes, uses openapi-typescript@7 to generate frontend/types/api.d.ts, and wires the generated types into the route handlers — replacing the inline interface declarations in ready, issuers, plaid-balance, and issue. A Redoc UI is served at GET /api/docs and the raw YAML at GET /api/docs/spec.

  • Type hygiene: inline DependencyStatus, SignerStatus, and ReadyResponse interfaces removed from ready/route.ts; issue/route.ts now uses the generated IssueRequest type, though IssueResponse200 and IssueResponse202 are imported but not applied as return-type annotations.
  • Docs serving: api/docs/route.ts reads the full YAML file on every request purely to guard against a missing file, but never uses the content — the Redoc HTML points at the sibling /api/docs/spec endpoint instead.
  • CDN risk: the Redoc bundle is pulled from cdn.redoc.ly/redoc/latest with no subresource integrity hash and no pinned version, exposing the app's origin to CDN-supply-chain risk.

Confidence Score: 3/5

Safe to merge after addressing the CDN script loading issue in the Redoc handler; the type wiring across other routes is clean and non-breaking.

The core type-generation plumbing is solid — generated types correctly replace inline interfaces and TypeScript passes. The Redoc page at api/docs/route.ts loads a JavaScript bundle from an external CDN without an integrity hash and without pinning a version. Because that page is served from the app's own origin, a supply-chain compromise of the CDN would give attacker code full same-origin access. The unused IssueResponse200/IssueResponse202 imports and the wasteful full-file read in the docs handler are minor quality issues.

Files Needing Attention: frontend/app/api/docs/route.ts — the CDN script loading and unnecessary file read both need attention before this ships.

Security Review

  • frontend/app/api/docs/route.ts: The Redoc UI bundle is loaded from cdn.redoc.ly/redoc/latest with no integrity= (SRI) attribute and no version pin. Because the page is served from the app's own origin, a compromised CDN script executes with full same-origin access — cookies, localStorage, and credentialed API calls are all reachable. Pin to a specific version and add a crossorigin=\"anonymous\" integrity=\"sha384-...\" attribute.

Important Files Changed

Filename Overview
docs/openapi.yaml New OpenAPI 3.1.0 spec covering all 6 API routes with full request/response schemas and reusable components; passes Redocly validation with 0 errors.
frontend/app/api/docs/route.ts Serves Redoc HTML but reads the full YAML file on every request without ever using its content (dead I/O), and loads the Redoc bundle from an unpinned CDN URL with no SRI integrity attribute.
frontend/app/api/docs/spec/route.ts Correctly serves raw YAML from disk with CORS wildcard; straightforward and safe for a public API spec endpoint.
frontend/app/api/issue/route.ts Inline interface replaced with generated IssueRequest type; IssueResponse200 and IssueResponse202 are imported but unused.
frontend/app/api/issuers/route.ts Return type annotation added using generated RegisteredIssuer; clean and correct.
frontend/app/api/plaid-balance/route.ts Return type annotated with generated PlaidBalanceResponse union; sendResponse helper made generic — no behavioral changes.
frontend/app/api/ready/route.ts Inline DependencyStatus, SignerStatus, and ReadyResponse interface declarations removed and replaced with imported types; logic unchanged.
frontend/types/api.d.ts Auto-generated from openapi-typescript@7; faithfully represents all schemas from the OpenAPI spec.
frontend/types/index.ts Ergonomic re-export layer for generated types with clear per-operation aliases; well-structured.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[docs/openapi.yaml] -->|pnpm generate-types| B[frontend/types/api.d.ts]
    B --> C[frontend/types/index.ts\nErgonomic re-exports]

    C -->|IssueRequest| D[api/issue/route.ts]
    C -->|RegisteredIssuer| E[api/issuers/route.ts]
    C -->|PlaidBalanceResponse| F[api/plaid-balance/route.ts]
    C -->|ReadyResponse, SignerStatus, DependencyStatus| G[api/ready/route.ts]

    A -->|read from disk| H[api/docs/spec/route.ts\nGET /api/docs/spec]
    H -->|YAML response| I[Redoc / Postman / tooling]

    J[api/docs/route.ts\nGET /api/docs] -->|existence check only\nspec content unused| A
    J -->|serves HTML with\nspec-url pointing to H| K[Browser: Redoc UI]
    L[cdn.redoc.ly/redoc/latest] -->|no SRI, unpinned| K
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Prompt To Fix All With AI
### Issue 1
frontend/app/api/docs/route.ts:8-13
**Spec file read is unused — dead I/O on every request**

`specYaml` holds the parsed YAML but is never referenced again; the HTML response hard-codes `spec-url="/api/docs/spec"` and fetches it from the sibling endpoint. The read serves only as a file-existence guard, meaning the entire YAML (~858 lines) is loaded into memory and discarded on every Redoc page load. A stat check (`fs.existsSync` or `statSync`) would achieve the same guard without reading the file contents.

### Issue 2
frontend/app/api/docs/route.ts:23
**Unpinned CDN script without subresource integrity**

The Redoc bundle is fetched from `cdn.redoc.ly/redoc/latest` with no `integrity=` attribute. Two problems compound here: (1) `latest` is a moving pointer — a breaking release silently breaks the docs UI; (2) without SRI the browser will execute whatever the CDN serves, so a compromised or hijacked CDN delivers arbitrary JavaScript in your app's origin. Because `/api/docs` is served from the same origin as the API, any injected script can read `document.cookie`, `localStorage`, and make credentialed same-origin requests. Pin to a specific version and add a `crossorigin="anonymous" integrity="sha384-..."` attribute.

### Issue 3
frontend/app/api/issue/route.ts:6
**Unused type imports**

`IssueResponse200` and `IssueResponse202` are imported but not used anywhere in the file — no function return type is annotated with them. They were likely intended to type the `NextResponse.json(...)` call sites, but that wiring was not completed.

```suggestion
import type { IssueRequest } from "../../../types/index.js";
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat: add OpenAPI spec and generate TS t..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

- Add docs/openapi.yaml (OpenAPI 3.1.0) covering all 6 API routes:
  POST /api/issue, POST /api/witness, GET /api/health, GET /api/ready,
  GET /api/issuers, GET /api/plaid-balance
- Generate frontend/types/api.d.ts via openapi-typescript@7
- Add frontend/types/index.ts with ergonomic re-exports
- Wire generated types into route handlers (ready, issuers,
  plaid-balance, issue) replacing inline interface declarations
- Add pnpm generate-types script to keep types in sync
- Serve Redoc UI at GET /api/docs and raw YAML at GET /api/docs/spec
- Spec validates with @redocly/cli (0 errors, 0 warnings)
- pnpm tsc --noEmit passes; all tests pass
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@ezekielcyclops-ux Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Comment on lines +8 to +13
let specYaml: string;
try {
specYaml = readFileSync(specPath, "utf-8");
} catch {
return NextResponse.json({ error: "OpenAPI spec not found" }, { status: 404 });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Spec file read is unused — dead I/O on every request

specYaml holds the parsed YAML but is never referenced again; the HTML response hard-codes spec-url="/api/docs/spec" and fetches it from the sibling endpoint. The read serves only as a file-existence guard, meaning the entire YAML (~858 lines) is loaded into memory and discarded on every Redoc page load. A stat check (fs.existsSync or statSync) would achieve the same guard without reading the file contents.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/docs/route.ts
Line: 8-13

Comment:
**Spec file read is unused — dead I/O on every request**

`specYaml` holds the parsed YAML but is never referenced again; the HTML response hard-codes `spec-url="/api/docs/spec"` and fetches it from the sibling endpoint. The read serves only as a file-existence guard, meaning the entire YAML (~858 lines) is loaded into memory and discarded on every Redoc page load. A stat check (`fs.existsSync` or `statSync`) would achieve the same guard without reading the file contents.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>StellarCred API Reference</title>
<!-- Redoc standalone bundle (no React dependency needed) -->
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Unpinned CDN script without subresource integrity

The Redoc bundle is fetched from cdn.redoc.ly/redoc/latest with no integrity= attribute. Two problems compound here: (1) latest is a moving pointer — a breaking release silently breaks the docs UI; (2) without SRI the browser will execute whatever the CDN serves, so a compromised or hijacked CDN delivers arbitrary JavaScript in your app's origin. Because /api/docs is served from the same origin as the API, any injected script can read document.cookie, localStorage, and make credentialed same-origin requests. Pin to a specific version and add a crossorigin="anonymous" integrity="sha384-..." attribute.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/docs/route.ts
Line: 23

Comment:
**Unpinned CDN script without subresource integrity**

The Redoc bundle is fetched from `cdn.redoc.ly/redoc/latest` with no `integrity=` attribute. Two problems compound here: (1) `latest` is a moving pointer — a breaking release silently breaks the docs UI; (2) without SRI the browser will execute whatever the CDN serves, so a compromised or hijacked CDN delivers arbitrary JavaScript in your app's origin. Because `/api/docs` is served from the same origin as the API, any injected script can read `document.cookie`, `localStorage`, and make credentialed same-origin requests. Pin to a specific version and add a `crossorigin="anonymous" integrity="sha384-..."` attribute.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

import { IssuerClient, CREDENTIAL_TYPES, type CredentialType, type ClaimParams } from "@stellarcred/issuer";
import { fetchIssuerPubkey } from "@/lib/issuer-registry";
import { logger, stripSensitiveFields, resolveRequestId } from "../../../lib/logger";
import type { IssueRequest, IssueResponse200, IssueResponse202 } from "../../../types/index.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused type imports

IssueResponse200 and IssueResponse202 are imported but not used anywhere in the file — no function return type is annotated with them. They were likely intended to type the NextResponse.json(...) call sites, but that wiring was not completed.

Suggested change
import type { IssueRequest, IssueResponse200, IssueResponse202 } from "../../../types/index.js";
import type { IssueRequest } from "../../../types/index.js";
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/issue/route.ts
Line: 6

Comment:
**Unused type imports**

`IssueResponse200` and `IssueResponse202` are imported but not used anywhere in the file — no function return type is annotated with them. They were likely intended to type the `NextResponse.json(...)` call sites, but that wiring was not completed.

```suggestion
import type { IssueRequest } from "../../../types/index.js";
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor

@Psalmuel01

Copy link
Copy Markdown
Collaborator

@ezekielcyclops-ux resolve conversations and fix conflicts

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.

OpenAPI spec for API routes with generated types

2 participants