Repository navigation
fix(codegen-ts): api reference follows routes generator filter - #462
Merged
Merged
Conversation
…arns on unmarked owned copies Entities a routes generator's own filter rejects are documented with no routes; a pre-mountsRoutes owned copy is named in a gen warning. Adds an end-to-end regression for expose omitting create/update/delete.
…DocsFile now also honours routesFile filter. CHANGELOG Unreleased entry already covered the change. No other stale docs found.
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.
What Changed
apiDocsFile()) now drops entities that a routes generator'sfilterrejects, and documents only the verbs the surviving routes mount. Filters andexposeare unioned per surface (Fastify and Hono apart), resolved inui-tier-gate.tsrunRoutesExpose. Output with neither option is unchanged.routesFileandroutesFileHono(reference and generator copies) publish theirfilterthrough themountsRoutesmarker. TheGenerator.mountsRoutestype gains an optionalfilter.api-model.tsskips REST and Hono symbols for entities no surface mounts.meta genwarns, by name, when an api reference is written and a routes generator carries nomountsRoutesmarker (an owned copy ejected before the marker existed). Adding the marker or re-ejecting withmeta eject routes --forcesilences it. Also updates theown-your-codegendoc and the CHANGELOG Fixed entry, and syncs the example and test-generator routes copies.Validation
Risk Assessment
✅ Low: Docs-only generator change, bounded to the api reference model. The fix rounds are verified correct: read-only surfaces keep list+get regardless of
exposeand drop only whenfilterrejects them, and the added tests cover both cases.Testing
Baseline ts-fast/ts-unit passed. Three live scenarios driven through the real meta gen CLI against disposable projects: a routes filter removes a projection's routes and its API docs; an expose that narrows a projection to nothing still documents GET list and GET by id, matching the mounted routes; per-entity expose narrowing still shows in docs. All pass. Full regression suite (bun test in server/typescript) passed: 12088 pass, 0 fail, recorded as a test run, not a live product check. Not driven live: the Hono routesFileHono filter path, the stale owned-copy warning, and the full-suite regression; the first two are covered only by unit tests in api-docs-expose.test.ts.
Evidence: Live scenario summary
Evidence: Full regression suite output
Evidence: Scenario A generated docs (filtered projection)
Evidence: Scenario B generated docs (expose none over projection)
Evidence: Scenario C generated API index (per-entity expose)
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (2) ✅
server/typescript/packages/codegen-ts/src/generators/api-model.ts:738- The newfilterhandling is bypassed for read-only surfaces.surfaceExposenow returns[]for an entity the routes generator'sfilterrejects, butmountedVerbsinapi-model.tsreturns["list","get"]for anyisReadOnlySurfaceobject (projection, report, view-backed read model) before it readsexpose. SoroutesFile({ filter: e => e.name !== "ProductSummary" })mounts nothing for the projection, yet the API page still documentsGET /product-summariesandGET /product-summaries/:id. This is the same lie the change set out to remove (docs list routes that are never mounted), and it is reachable in normal use because projections and reports are the entities adopters most often filter out of a write API. The CHANGELOG claim that pages document 'the routes the generators mount and nothing else' is false for these. The same short-circuit applies to the Hono surface (restHonoSymbols->mountedVerbs, api-model.ts ~line 920). The new tests only filter a writable entity (AuditEntry), so this is not covered. Fix: inmountedVerbs, resolveexposefirst and let an empty result win over the read-only default (for example, ifresolveExpose(obj, expose)is an empty array, return an empty set; otherwise keep the read-only set). Add a test that filters out a projection.server/typescript/packages/codegen-ts/src/ui-tier-gate.ts:105- ThetphDiscriminatorBase(entity) ?? entityremap insurfaceExpose(and thetph.jsimport) is not needed by the intent.restSymbolsandrestHonoSymbolsare only reached throughisQueryable, which excludes TPH subtypes (servesReadApi && !isTphSubtype). So the expose function is never called with a subtype, and the remap is dead code. It also adds an untested rule: a subtype would inherit its base's filter and expose. Smallest form: remove the remap and passentitystraight tom.filterandresolveExpose.🔧 Fix applied.
2 warnings still open:
server/typescript/packages/codegen-ts/src/generators/api-model.ts:738- The newfilterhandling is bypassed for read-only surfaces.surfaceExposenow returns[]for an entity the routes generator'sfilterrejects, butmountedVerbsinapi-model.tsreturns["list","get"]for anyisReadOnlySurfaceobject (projection, report, view-backed read model) before it readsexpose. SoroutesFile({ filter: e => e.name !== "ProductSummary" })mounts nothing for the projection, yet the API page still documentsGET /product-summariesandGET /product-summaries/:id. This is the same lie the change set out to remove (docs list routes that are never mounted), and it is reachable in normal use because projections and reports are the entities adopters most often filter out of a write API. The CHANGELOG claim that pages document 'the routes the generators mount and nothing else' is false for these. The same short-circuit applies to the Hono surface (restHonoSymbols->mountedVerbs, api-model.ts ~line 920). The new tests only filter a writable entity (AuditEntry), so this is not covered. Fix: inmountedVerbs, resolveexposefirst and let an empty result win over the read-only default (for example, ifresolveExpose(obj, expose)is an empty array, return an empty set; otherwise keep the read-only set). Add a test that filters out a projection.server/typescript/packages/codegen-ts/src/generators/api-model.ts:738- Fix round 1 over-correctedmountedVerbs: a read-only surface (projection/report) now INTERSECTS the author'sexposewith list+get, but the routes generators ignoreexposefor these.reference/routes.ts~line 166 andtemplates/routes-file.ts~line 105 say 'A projection mount is read-only by construction, soexposecannot narrow it', and the projection branch never readsexpose. The old code was right to return list+get regardless ofexpose; only afilterrejection should empty it. Concrete failure:routesFile({ expose: ["list"] })(a read-only API) mounts GET list AND GET /:id for a projectionProductSummary, but the API page now documents only GET list.expose: (e) => e.name === "Order" ? ["list"] : []or anyexposethat returns[]/omitsgetfor a projection has the same understatement. This is a docs-lies-in-the-other-direction regression. Cause:filteris folded into the sameExposeOptionfunction as an empty array (ui-tier-gate.tssurfaceExpose), somountedVerbscannot tell 'filtered out' from 'expose narrowed to []'. Same on the Hono surface viarestHonoSymbols->mountedVerbs(api-model.ts ~919). The new projection test only usesfilter, so it passes with this bug. Smallest honest fix: carry the filter verdict separately from expose (e.g.RoutesExposegets a per-entity 'mounted' predicate, or surfaceExpose returns a distinguishable sentinel), havemountedVerbsreturn list+get for read-only surfaces unless the generators' filter rejects them, and add a test withroutesFile({ expose: ["list"] })over a projection expecting both routes. TPH base keeps intersect, matchingrenderTphRoutes.🔧 Fix applied.
✅ Re-checked - no issues remain.
🔧 **Test** - 2 issues found → auto-fixed ✅
examples/showcase/codegen/generators/routes.ts- The configured gate failed because the owned-copy sync gate (scripts/sync-owned-template-copies.ts --check) foundexamples/showcase/codegen/generators/routes.tsandexamples/advanced-modeling/codegen/generators/routes.tsout of date with the referenceroutes.ts. The re-synced copies sit in the worktree as uncommitted modifications, and the command passes with them. They must be committed with the change or the gate fails again.routesFile(): the API docs list all five CRUD verbs for writable entities and GET list + GET by id for the projectionroutesFile({expose:[list,get]}): the docs for Product and AuditEntry drop POST, PUT, PATCH and DELETEroutesFile({expose:[list]}): writable entities document only GET list, while the read-only projection still documents list+get, matching what the generated projection routes file mountsmountReadOnlyCrudRoutes, which has noexposeroutesFile({filter})rejecting a writable entity: AuditEntry gets no routes file and its docs page lists no routesroutesFile({filter})rejecting a read-only projection: ProductSummary gets no routes file and its docs page lists no routes (review-1 regression)routesFileHono({filter, expose:[list,get]})limits the Hono docs section to Product's list and get, with none for the filtered AuditEntry and ProductSummaryroutes.tscopies as current with the referencebun scripts/sync-owned-template-copies.ts --checkreports 114 owned copies current. This holds only with the uncommitted example changes.scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsscripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains(passes in the current worktree)bun scripts/sync-owned-template-copies.ts --check(114 owned copies current with the uncommitted example copies)bun test test/api-docs-expose.test.tsin codegen-ts (16 pass)bun testinserver/typescript/packages/codegen-ts(2770 pass)bun testinserver/typescript/packages/cli(1737 pass, 3 skip, 0 fail)Livemeta genon a disposable project withProduct,AuditEntryand aProductSummaryprojection, over six configs: defaultroutesFile(),expose: [list,get],expose: [list],filterdroppingAuditEntry,filterdropping the projection, androutesFileHono({filter, expose}). I compared the emitted API docs with the routes files actually emitted and theexposepassed to the mount.🔧 Fix applied.
✅ Re-checked - no issues remain.
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsBaseline (given): scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainscd server/typescript && bun test packages/codegen-ts/test/api-docs-expose.test.ts (16 pass, 0 fail)Live scenario A: scratch project live-proj, bun <worktree>/server/typescript/packages/cli/bin/meta.ts gen with routesFile({filter: e => e.name !== "ProductSummary"}) + apiDocsFile()Live scenario B: scratch project live-proj-b, routesFile({expose: () => []}) over projection; meta gen, then read ProductSummary.md and ProductSummary.routes.tsLive scenario C: scratch project live-proj-c, routesFile({expose: e => e.name==="Product" ? ["list","get"] : undefined}); read README.md, per-entity docs, Product.routes.ts mount argsFull regression: cd server/typescript && bun test (12138 tests across 1076 files; 12088 pass, 0 fail; exit 0)Scenario summary written to ~/.no-mistakes/evidence/01M5T7AXVVWZ7FRRWEVP74S05D/live-scenarios.txtWorktree git status clean after run; scratch projects live outside worktree in evidence dir✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.