Skip to content

fix(codegen-ts): make meta docs honour routesFile expose option - #424

Merged
dmealing merged 6 commits into
mainfrom
fm/mo-1-1-0-docs-routes-expose
Oct 11, 2026
Merged

dmealing merged 6 commits into
mainfrom
fm/mo-1-1-0-docs-routes-expose

Conversation

@dmealing

@dmealing dmealing commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • routesFile({ expose }) and routesFileHono({ expose }) now emit a mountsRoutes marker on the generator. runRoutesExpose in codegen-ts reads it off the resolved suite per routes surface (Fastify and Hono separately), and meta docs passes the result to the API pages. A narrowed surface documents only the verbs it mounts, so DELETE no longer appears for an entity exposed as ["list", "get"]. Output without expose is byte-identical.
  • api-model and the API docs generator apply the per-surface expose. update lists both PATCH and PUT, and a read-only projection or report still lists GET. A TPH base is narrowed from its read-only pair. Covered by codegen-ts/test/api-docs-expose.test.ts and cli/test/docs-expose.test.ts.
  • Owned (ejected) routes generators need the mountsRoutes marker to be honoured. Re-run meta eject routes --force (or routes-hono --force), or add the marker by hand. Documented in CHANGELOG.md and docs/features/own-your-codegen.md. TypeScript only.

🤖 Generated with Claude Code

Validation

Risk Assessment

✅ Low: Docs-only behavior change gated on an opt-in marker; output without expose is unchanged, verb logic matches the routes generators, and earlier review decisions are honored.

Testing

Drove meta docs live against four disposable projects built from ejected reference routes generators. Narrowed expose: [list,get] lists only GET verbs. Control with no expose lists all five verbs. expose: [get] lists only GET by id. Mixed Fastify-narrowed plus default Hono keeps Fastify REST narrow and Hono full, per surface. Targeted tests, the full cli and codegen-ts suites, and typecheck all pass. Scenario "meta gen with apiDocsFile()" was not tested, per the recorded decision that meta gen skips api-docs by design. Full server-wide bun test was not run, per the recorded instruction (machine overloaded); cli and codegen-ts suites run instead.

  • Live validation: ✅ go - 4 of 5 scenarios driven live against the product
Scenario Result Live Evidence
meta docs with routesFile({ expose: [list,get] }) lists only GET list and GET by id, no write verbs ✅ pass live live-docs-narrow/docs-out/api/AuditEntry.md: only GET /audit_entries and GET /audit_entries/:id
meta docs with routesFile() and no expose still lists all five verbs (control) ✅ pass live live-docs-control/docs-out/api/AuditEntry.md: GET, POST, PUT, PATCH, DELETE listed
adversarial: expose [get] omits the list verb and all writes ✅ pass live live-docs-getonly/docs-out/api/AuditEntry.md: only GET /audit_entries/:id
mixed config: narrowed Fastify routesFile beside default Hono routesFileHono narrows only the Fastify REST section; Hono section keeps all verbs ✅ pass live live-docs-mixed/docs-out/api/AuditEntry.md: REST section lines 107-142 list GET only; REST (Hono) section lists POST, PATCH, PUT, DELETE
meta gen with apiDocsFile() produces an API docs page honouring expose ⏸️ untested no Invalid scenario per recorded user decision: meta gen skips api-docs by design (ADR-0025, DEPRECATED_DOC_GENERATORS). Not added.
Evidence: narrow api page (expose list,get)
<!-- @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. -->

# AuditEntry API

**Model / metadata:** [AuditEntry](../AuditEntry.md)

> Import paths are relative to your generated-output directory.

## Setup

Obtain the runtime handles the calls below need:

- `db` — your Drizzle connection — the generated queries take `db: Db` (a `PgDatabase` / `BaseSQLiteDatabase` alias). Construct one over your own driver and pass any compatible Drizzle instance:

`` `ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }));
`` `

## Example

`` `ts
import { createAuditEntry, findAuditEntryById, updateAuditEntry, deleteAuditEntryById } from "./AuditEntry.queries";

const created = await createAuditEntry(db, { note: "…" });
const found = await findAuditEntryById(db, created.id);
const updated = await updateAuditEntry(db, created.id, { note: "…" });
const removed = await deleteAuditEntryById(db, created.id);
`` `

## Model

\### `interface AuditEntry`

The typed shape of a AuditEntry row, generated from its metadata.

`` `ts
import { AuditEntry } from "./AuditEntry"
`` `

Fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

## Data access

\### `findAuditEntryById(db: Db, id: number): Promise<AuditEntry | null>`

Fetch a single AuditEntry by its primary key; null when not found.

`` `ts
import { findAuditEntryById } from "./AuditEntry.queries"
`` `

\### `listAuditEntries(db: Db, opts?: { limit?: number; offset?: number }): Promise<AuditEntry[]>`

List AuditEntry rows with optional limit/offset paging.

`` `ts
import { listAuditEntries } from "./AuditEntry.queries"
`` `

\### `createAuditEntry(db: Db, data: AuditEntryCreate): Promise<AuditEntry>`

Validate (via AuditEntryInsertSchema) and insert a new AuditEntry.

`` `ts
import { createAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when data fails AuditEntryInsertSchema validation.

\### `updateAuditEntry(db: Db, id: number, patch: AuditEntryPatch): Promise<AuditEntry | null>`

Partially update a AuditEntry by primary key — writes only the assigned fields; null when not found. A renamed/dropped field is a compile error.

`` `ts
import { updateAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when the patch fails AuditEntryUpdateSchema validation.

\### `deleteAuditEntryById(db: Db, id: number): Promise<boolean>`

Delete a AuditEntry by primary key; true when a row was removed.

`` `ts
import { deleteAuditEntryById } from "./AuditEntry.queries"
`` `

## REST

\### `GET /audit_entries`

List AuditEntry (supports filter/sort/paging query params).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `GET /audit_entries/:id`

Fetch a single AuditEntry by id (404 when not found).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

## Validation

\### `AuditEntryInsertSchema: ZodType`

Zod schema validating the body of a create<AuditEntry> / POST request (auto-generated PKs excluded).

`` `ts
import { AuditEntryInsertSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

\### `AuditEntryUpdateSchema: ZodType`

Zod schema validating the body of an update / PATCH request (all fields optional).

`` `ts
import { AuditEntryUpdateSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |
Evidence: control api page (no expose)
<!-- @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. -->

# AuditEntry API

**Model / metadata:** [AuditEntry](../AuditEntry.md)

> Import paths are relative to your generated-output directory.

## Setup

Obtain the runtime handles the calls below need:

- `db` — your Drizzle connection — the generated queries take `db: Db` (a `PgDatabase` / `BaseSQLiteDatabase` alias). Construct one over your own driver and pass any compatible Drizzle instance:

`` `ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }));
`` `

## Example

`` `ts
import { createAuditEntry, findAuditEntryById, updateAuditEntry, deleteAuditEntryById } from "./AuditEntry.queries";

const created = await createAuditEntry(db, { note: "…" });
const found = await findAuditEntryById(db, created.id);
const updated = await updateAuditEntry(db, created.id, { note: "…" });
const removed = await deleteAuditEntryById(db, created.id);
`` `

## Model

\### `interface AuditEntry`

The typed shape of a AuditEntry row, generated from its metadata.

`` `ts
import { AuditEntry } from "./AuditEntry"
`` `

Fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

## Data access

\### `findAuditEntryById(db: Db, id: number): Promise<AuditEntry | null>`

Fetch a single AuditEntry by its primary key; null when not found.

`` `ts
import { findAuditEntryById } from "./AuditEntry.queries"
`` `

\### `listAuditEntries(db: Db, opts?: { limit?: number; offset?: number }): Promise<AuditEntry[]>`

List AuditEntry rows with optional limit/offset paging.

`` `ts
import { listAuditEntries } from "./AuditEntry.queries"
`` `

\### `createAuditEntry(db: Db, data: AuditEntryCreate): Promise<AuditEntry>`

Validate (via AuditEntryInsertSchema) and insert a new AuditEntry.

`` `ts
import { createAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when data fails AuditEntryInsertSchema validation.

\### `updateAuditEntry(db: Db, id: number, patch: AuditEntryPatch): Promise<AuditEntry | null>`

Partially update a AuditEntry by primary key — writes only the assigned fields; null when not found. A renamed/dropped field is a compile error.

`` `ts
import { updateAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when the patch fails AuditEntryUpdateSchema validation.

\### `deleteAuditEntryById(db: Db, id: number): Promise<boolean>`

Delete a AuditEntry by primary key; true when a row was removed.

`` `ts
import { deleteAuditEntryById } from "./AuditEntry.queries"
`` `

## REST

\### `GET /audit_entries`

List AuditEntry (supports filter/sort/paging query params).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `GET /audit_entries/:id`

Fetch a single AuditEntry by id (404 when not found).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `POST /audit_entries`

Create a AuditEntry (body validated by AuditEntryInsertSchema).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `PATCH /audit_entries/:id`

Partially update a AuditEntry by id (body validated by AuditEntryUpdateSchema).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `PUT /audit_entries/:id`

PUT alias of PATCH — the same handler and partial body (validated by AuditEntryUpdateSchema).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `DELETE /audit_entries/:id`

Delete a AuditEntry by id.

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Mount: `await auditEntryRoutes(fastify)`

## Validation

\### `AuditEntryInsertSchema: ZodType`

Zod schema validating the body of a create<AuditEntry> / POST request (auto-generated PKs excluded).

`` `ts
import { AuditEntryInsertSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

\### `AuditEntryUpdateSchema: ZodType`

Zod schema validating the body of an update / PATCH request (all fields optional).

`` `ts
import { AuditEntryUpdateSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |
Evidence: getonly api page (expose get)
<!-- @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. -->

# AuditEntry API

**Model / metadata:** [AuditEntry](../AuditEntry.md)

> Import paths are relative to your generated-output directory.

## Setup

Obtain the runtime handles the calls below need:

- `db` — your Drizzle connection — the generated queries take `db: Db` (a `PgDatabase` / `BaseSQLiteDatabase` alias). Construct one over your own driver and pass any compatible Drizzle instance:

`` `ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }));
`` `

## Example

`` `ts
import { createAuditEntry, findAuditEntryById, updateAuditEntry, deleteAuditEntryById } from "./AuditEntry.queries";

const created = await createAuditEntry(db, { note: "…" });
const found = await findAuditEntryById(db, created.id);
const updated = await updateAuditEntry(db, created.id, { note: "…" });
const removed = await deleteAuditEntryById(db, created.id);
`` `

## Model

\### `interface AuditEntry`

The typed shape of a AuditEntry row, generated from its metadata.

`` `ts
import { AuditEntry } from "./AuditEntry"
`` `

Fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

## Data access

\### `findAuditEntryById(db: Db, id: number): Promise<AuditEntry | null>`

Fetch a single AuditEntry by its primary key; null when not found.

`` `ts
import { findAuditEntryById } from "./AuditEntry.queries"
`` `

\### `listAuditEntries(db: Db, opts?: { limit?: number; offset?: number }): Promise<AuditEntry[]>`

List AuditEntry rows with optional limit/offset paging.

`` `ts
import { listAuditEntries } from "./AuditEntry.queries"
`` `

\### `createAuditEntry(db: Db, data: AuditEntryCreate): Promise<AuditEntry>`

Validate (via AuditEntryInsertSchema) and insert a new AuditEntry.

`` `ts
import { createAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when data fails AuditEntryInsertSchema validation.

\### `updateAuditEntry(db: Db, id: number, patch: AuditEntryPatch): Promise<AuditEntry | null>`

Partially update a AuditEntry by primary key — writes only the assigned fields; null when not found. A renamed/dropped field is a compile error.

`` `ts
import { updateAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when the patch fails AuditEntryUpdateSchema validation.

\### `deleteAuditEntryById(db: Db, id: number): Promise<boolean>`

Delete a AuditEntry by primary key; true when a row was removed.

`` `ts
import { deleteAuditEntryById } from "./AuditEntry.queries"
`` `

## REST

\### `GET /audit_entries/:id`

Fetch a single AuditEntry by id (404 when not found).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

## Validation

\### `AuditEntryInsertSchema: ZodType`

Zod schema validating the body of a create<AuditEntry> / POST request (auto-generated PKs excluded).

`` `ts
import { AuditEntryInsertSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

\### `AuditEntryUpdateSchema: ZodType`

Zod schema validating the body of an update / PATCH request (all fields optional).

`` `ts
import { AuditEntryUpdateSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |
Evidence: mixed api page (Fastify narrow, Hono default)
<!-- @generated by @metaobjectsdev/codegen-ts — DO NOT EDIT. -->

# AuditEntry API

**Model / metadata:** [AuditEntry](../AuditEntry.md)

> Import paths are relative to your generated-output directory.

## Setup

Obtain the runtime handles the calls below need:

- `db` — your Drizzle connection — the generated queries take `db: Db` (a `PgDatabase` / `BaseSQLiteDatabase` alias). Construct one over your own driver and pass any compatible Drizzle instance:

`` `ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }));
`` `

## Example

`` `ts
import { createAuditEntry, findAuditEntryById, updateAuditEntry, deleteAuditEntryById } from "./AuditEntry.queries";

const created = await createAuditEntry(db, { note: "…" });
const found = await findAuditEntryById(db, created.id);
const updated = await updateAuditEntry(db, created.id, { note: "…" });
const removed = await deleteAuditEntryById(db, created.id);
`` `

## Model

\### `interface AuditEntry`

The typed shape of a AuditEntry row, generated from its metadata.

`` `ts
import { AuditEntry } from "./AuditEntry"
`` `

Fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

## Data access

\### `findAuditEntryById(db: Db, id: number): Promise<AuditEntry | null>`

Fetch a single AuditEntry by its primary key; null when not found.

`` `ts
import { findAuditEntryById } from "./AuditEntry.queries"
`` `

\### `listAuditEntries(db: Db, opts?: { limit?: number; offset?: number }): Promise<AuditEntry[]>`

List AuditEntry rows with optional limit/offset paging.

`` `ts
import { listAuditEntries } from "./AuditEntry.queries"
`` `

\### `createAuditEntry(db: Db, data: AuditEntryCreate): Promise<AuditEntry>`

Validate (via AuditEntryInsertSchema) and insert a new AuditEntry.

`` `ts
import { createAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when data fails AuditEntryInsertSchema validation.

\### `updateAuditEntry(db: Db, id: number, patch: AuditEntryPatch): Promise<AuditEntry | null>`

Partially update a AuditEntry by primary key — writes only the assigned fields; null when not found. A renamed/dropped field is a compile error.

`` `ts
import { updateAuditEntry } from "./AuditEntry.queries"
`` `

Request body (data):

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Throws: ZodError when the patch fails AuditEntryUpdateSchema validation.

\### `deleteAuditEntryById(db: Db, id: number): Promise<boolean>`

Delete a AuditEntry by primary key; true when a row was removed.

`` `ts
import { deleteAuditEntryById } from "./AuditEntry.queries"
`` `

## REST

\### `GET /audit_entries`

List AuditEntry (supports filter/sort/paging query params).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

\### `GET /audit_entries/:id`

Fetch a single AuditEntry by id (404 when not found).

`` `ts
import { auditEntryRoutes } from "./AuditEntry.routes"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `await auditEntryRoutes(fastify)`

## REST (Hono)

\### `GET /audit_entries`

[Hono] List AuditEntry (filter/sort/paging query params).

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `registerAuditEntryRoutes(app, { db })`

\### `GET /audit_entries/:id`

[Hono] Fetch a single AuditEntry by id (404 when not found).

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Response body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | `number` | yes |  |
| `note` | `string` |  |  |

Mount: `registerAuditEntryRoutes(app, { db })`

\### `POST /audit_entries`

[Hono] Create a AuditEntry (body validated by AuditEntryInsertSchema).

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `registerAuditEntryRoutes(app, { db })`

\### `PATCH /audit_entries/:id`

[Hono] Partially update a AuditEntry by id (body validated by AuditEntryUpdateSchema).

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `registerAuditEntryRoutes(app, { db })`

\### `PUT /audit_entries/:id`

[Hono] PUT alias of PATCH — the same handler and partial body (validated by AuditEntryUpdateSchema).

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Request body:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Mount: `registerAuditEntryRoutes(app, { db })`

\### `DELETE /audit_entries/:id`

[Hono] Delete a AuditEntry by id.

`` `ts
import { registerAuditEntryRoutes } from "./AuditEntry.routes.hono"
`` `

Mount: `registerAuditEntryRoutes(app, { db })`

## Validation

\### `AuditEntryInsertSchema: ZodType`

Zod schema validating the body of a create<AuditEntry> / POST request (auto-generated PKs excluded).

`` `ts
import { AuditEntryInsertSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

\### `AuditEntryUpdateSchema: ZodType`

Zod schema validating the body of an update / PATCH request (all fields optional).

`` `ts
import { AuditEntryUpdateSchema } from "./AuditEntry"
`` `

Accepted fields:

| Field | Type | Required | Notes |
|---|---|---|---|
| `note` | `string` |  |  |

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 5 scenarios driven live against the product
Scenario Result Live Evidence
meta docs with routesFile({ expose: [list,get] }) lists only GET list and GET by id, no write verbs ✅ pass live live-docs-narrow/docs-out/api/AuditEntry.md: only GET /audit_entries and GET /audit_entries/:id
meta docs with routesFile() and no expose still lists all five verbs (control) ✅ pass live live-docs-control/docs-out/api/AuditEntry.md: GET, POST, PUT, PATCH, DELETE listed
adversarial: expose [get] omits the list verb and all writes ✅ pass live live-docs-getonly/docs-out/api/AuditEntry.md: only GET /audit_entries/:id
mixed config: narrowed Fastify routesFile beside default Hono routesFileHono narrows only the Fastify REST section; Hono section keeps all verbs ✅ pass live live-docs-mixed/docs-out/api/AuditEntry.md: REST section lines 107-142 list GET only; REST (Hono) section lists POST, PATCH, PUT, DELETE
meta gen with apiDocsFile() produces an API docs page honouring expose ⏸️ untested no Invalid scenario per recorded user decision: meta gen skips api-docs by design (ADR-0025, DEPRECATED_DOC_GENERATORS). Not added.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • bun test test/docs-expose.test.ts (cli): 4 pass
  • bun test test/api-docs-expose.test.ts (codegen-ts): 10 pass
  • bun run build in codegen-ts and cli (dist rebuilt from current source)
  • Live: disposable projects under evidence dir, ejected reference routes.ts/routes-hono.ts into codegen/generators/, ran bun dist/bin/meta.js docs . --out docs-out (exit 0 for all four)
  • Live scenario check: grep of verbs in generated api/AuditEntry.md per project
  • bun test full cli package: 1473 tests, 0 fail
  • bun test full codegen-ts package: 2555 tests, 0 fail
  • bun run typecheck in cli and codegen-ts: clean
  • Confirmed no routesExpose threading remains in codegen-ts runner.ts (only docs.ts path, per recorded decision)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Test evidence (maintainer-recorded)

  • Commit tested: e1cceb2566c649de38fce96daf75e9160e0c068a (the pushed head), suites run one after the other.
  • server/typescript/packages/cli: 1470 pass, 3 skip, 0 fail. server/typescript/packages/codegen-ts: 2555 pass, 0 fail. Workspace typecheck clean.
  • Pinned by meta docs and routesFile({ expose }) > a routes generator that exposes list and get documents no write verb and the two mixed cases (narrowed Fastify beside default Hono, and the reverse) in cli/test/docs-expose.test.ts, and by each routes surface documents its own generator's expose plus api docs follow the routes generator's expose option in codegen-ts/test/api-docs-expose.test.ts, which also asserts that output without expose equals exposing every verb.
  • A meta gen + apiDocsFile() scenario cannot exist: meta gen skips api-docs with a warning by design (ADR-0025), pinned by runner-docs-shim.test.ts.
  • The whole-repo server/typescript suite did not complete under machine load in the first run (about 30 minutes with no output) and was not run again.
  • Validation runs: 2.

…tion

The API pages listed all five verbs for an entity whose routes file mounts
fewer. Routes generators now carry the option in a mountsRoutes marker and
meta docs documents only the verbs mounted. Output without expose is
byte-identical.
… generators only

Assisted-by: no-mistakes:claude:claude-sonnet-5-5
…a gen runner

Assisted-by: no-mistakes:claude:claude-sonnet-5-5
Assisted-by: no-mistakes:claude:claude-haiku-5-5
A narrowed Fastify routes file beside a default Hono one (or the reverse)
made the shared verb list undefined, so the narrowed surface documented all
five verbs. Each API docs surface now reads the expose of the generators that
produce it.
…urface semantics

Assisted-by: no-mistakes:claude:claude-haiku-5-5
@dmealing
dmealing merged commit 0d8b09a into main Oct 11, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-1-1-0-docs-routes-expose branch October 11, 2026 14:20
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