feat(openapi): auto-generate spec from Drizzle schema with CI drift check#203
Merged
texture-fleet-agent[bot] merged 1 commit intomainfrom May 6, 2026
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…heck
The hand-written public/openapi.json (last touched 2026-04-23) had drifted
badly from reality — e.g. Utility documented 14 fields while the DB table
and API returned all 43. Multiple endpoint families shipped since then
(substations, transmission-lines, power-plants/substations,
pricing-nodes/versions, programs/versions, territories/lookup, changelog)
weren't in the spec at all.
Before vs after field counts (public schema objects):
Utility 14 → 37
PowerPlant 9 → 31
EvStation 11 → 26
Program 7 → 31
PricingNode 5 → 17
BalancingAuthority 4 → 17
Iso 5 → 14
Rto 4 → 14
Region 4 → 14
Substation — → 22
TransmissionLine — → 18
Territory 5 → 9
Approach:
- scripts/generate-openapi.ts walks every Drizzle table via getTableColumns
and maps PG column types → OpenAPI 3.1 schemas. Internal fields
(submittedBy, reviewedAt, reviewedBy, lockedStatus, searchVector,
notionPageId, geography/geometry) are stripped by default. jsonb columns
with known shape get per-field overrides.
- scripts/openapi/endpoints.ts is the canonical per-endpoint registry.
Query params mirror the actual Zod schemas / URL parsing in
app/api/v1/**. Auth-required/internal endpoints (mod/*, developer/*,
contributions, discussions, follows, notifications, me, editable-fields,
webhooks, revalidate, health, tiles) are intentionally excluded.
- scripts/openapi/base.ts preserves the previous info/servers/tags and
shared components (ErrorResponse, PaginatedMeta, SearchResults) plus
adds ChangelogResponse, EntityVersion, GeoJsonFeature.
New endpoints now documented:
/substations, /substations/{slug}, /substations/{slug}/transmission-lines
/power-plants/{slug}/substations
/pricing-nodes/{slug}/versions, /programs/{slug}/versions
/territories/lookup
/changelog
npm scripts:
npm run openapi # regenerate public/openapi.json
npm run openapi:check # exit 1 on drift, used by CI
CI: new 'openapi' job runs openapi:check on every PR; drift fails the build.
Fixes ALL-728
2a0999a to
160f2a0
Compare
texture-fleet-agent Bot
pushed a commit
that referenced
this pull request
May 6, 2026
Sanitize public API responses to exclude fields that are intentionally absent from the OpenAPI spec (notionPageId, searchVector, reviewedAt, reviewedBy, lockedStatus, submittedBy), plus geometry fields that have their own dedicated endpoints. Single source of truth for the internal-field list now lives in lib/api/internal-fields.ts, consumed by both the OpenAPI generator (scripts/openapi/schema-from-drizzle.ts) and the API route handlers (lib/api/public-response.ts). Applied the new stripInternal / publicJsonResponse / publicPaginatedResponse helpers across every public resource endpoint: utilities, isos, rtos, balancing-authorities, regions, territories, power-plants, ev-stations, substations, pricing-nodes, programs, transmission-lines, search, and their /versions sub-endpoints. Auth-gated endpoints (/mod/*, /me, /contributions, /discussions, /follows, /notifications, /developer/*, /editable-fields) continue to use jsonResponse directly since they legitimately surface review/moderation metadata to their audiences. Regression coverage in lib/api/__tests__/public-response.test.ts (10 tests) asserts the helpers strip every INTERNAL_FIELDS key from single objects, arrays, nested include payloads, and grouped /search responses, while leaving public fields intact. From Morgan's Relay bug report (memory/specs/relay-commongrid-bugs-2026-05-06.md), bug #1 residual after PR #203. Fixes ALL-730
texture-fleet-agent Bot
added a commit
that referenced
this pull request
May 6, 2026
* fix(api): strip internal fields from public responses Sanitize public API responses to exclude fields that are intentionally absent from the OpenAPI spec (notionPageId, searchVector, reviewedAt, reviewedBy, lockedStatus, submittedBy), plus geometry fields that have their own dedicated endpoints. Single source of truth for the internal-field list now lives in lib/api/internal-fields.ts, consumed by both the OpenAPI generator (scripts/openapi/schema-from-drizzle.ts) and the API route handlers (lib/api/public-response.ts). Applied the new stripInternal / publicJsonResponse / publicPaginatedResponse helpers across every public resource endpoint: utilities, isos, rtos, balancing-authorities, regions, territories, power-plants, ev-stations, substations, pricing-nodes, programs, transmission-lines, search, and their /versions sub-endpoints. Auth-gated endpoints (/mod/*, /me, /contributions, /discussions, /follows, /notifications, /developer/*, /editable-fields) continue to use jsonResponse directly since they legitimately surface review/moderation metadata to their audiences. Regression coverage in lib/api/__tests__/public-response.test.ts (10 tests) asserts the helpers strip every INTERNAL_FIELDS key from single objects, arrays, nested include payloads, and grouped /search responses, while leaving public fields intact. From Morgan's Relay bug report (memory/specs/relay-commongrid-bugs-2026-05-06.md), bug #1 residual after PR #203. Fixes ALL-730 * fix(lint): update Node.js import protocol and biome schema version * fix(lint): remove unused variables and fix optional chaining * chore(api): sort imports in public-response.ts --------- Co-authored-by: texture-coding-agent <coding-agent@texturehq.com>
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.
Summary
Auto-generate
public/openapi.jsonfrom the Drizzle schema, so the published spec can never drift again. CI fails if the committed file doesn't match the generator output.Fixes ALL-728.
Why this matters
The hand-written
public/openapi.json(last touched 2026-04-23, PR #149) had drifted badly from the real API. A few highlights:On top of field drift, several endpoint families shipped since April weren't documented at all. This PR adds them:
/substations,/substations/{slug},/substations/{slug}/transmission-lines/power-plants/{slug}/substations/pricing-nodes/{slug}/versions,/programs/{slug}/versions/territories/lookup/changelogTotal: 19 schemas, 35 paths now documented.
How it works
scripts/generate-openapi.tswalks every Drizzle table viagetTableColumns()and maps Postgres column types → OpenAPI 3.1 schemas.submittedBy,reviewedAt,reviewedBy,lockedStatus,searchVector,notionPageId,geography,geometry) are stripped by default. Geometry endpoints return a GeoJSON envelope separately.jsonbcolumns with known runtime shape (e.g.states: string[],assetTypes: string[]) get per-field overrides inscripts/openapi/resources.ts.scripts/openapi/endpoints.tsmirror the actual Zod schemas / URL parsing fromapp/api/v1/**/route.ts— nothing fabricated.mod/*,developer/*,contributions,discussions,follows,notifications,me,editable-fields,webhooks,revalidate,health, tiles) are intentionally excluded from the v1 public spec.info,servers,tags, shared components) preserved from the previous spec so the Developer Portal prose is unchanged.New npm scripts
npm run openapiregeneratespublic/openapi.jsonin place.npm run openapi:checkregenerates in memory and exits non-zero if the committed file is stale (prints first-divergence hint).CI drift check
A new
openapijob in.github/workflows/ci.ymlrunsnpm run openapi:checkon every PR. If a column is added/renamed/removed and the generator output changes but the committed spec isn't regenerated, CI fails.Local verification
npm run openapi→ "19 schemas, 35 paths"npm run openapi:check→ greennpm run lint:biome→ green (no new errors introduced)npm run build→ greenMaintenance
Adding a column:
lib/db/schema/*.ts.npm run openapi.public/openapi.json.Adding an endpoint:
app/api/v1/**.scripts/openapi/endpoints.tswith the actual query params, tag, and response shape.npm run openapiand commit.