# Changelog All notable changes to this project are documented here. This file follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/). ## [1.13.0] — 2026-06-25 ### Added - **`npx doctreen codegen` — typed clients from your OpenAPI doc.** Generate a strict TypeScript declaration file and a fully-typed fetch client from the same `/docs/openapi.json` that DocTreen already emits: ```bash npx doctreen codegen types --from http://localhost:3000/docs --out src/api/types.d.ts npx doctreen codegen client --from http://localhost:3000/docs --out src/api/client.ts \ --base-url https://api.example.com ``` The `types` output mirrors `components.schemas` 1:1 as `export interface` declarations and emits per-operation `…Params` / `…Query` / `…Body` / `…Response` shapes. The `client` output is a single self-contained file exporting `createClient({ baseUrl, fetch?, headers?, onRequest? })` with one async method per operation, e.g. `await api.getUsersById({ params: { id: '1' } })` — every argument and return value typed end-to-end. Errors come back as `DoctreenHttpError` carrying `status` and the parsed body. - **`--watch [ms]` flag.** Re-generate on change. Polls URL sources every `` (default 2000ms); uses `fs.watch()` for file sources. Skips the write when the output is byte-identical so editor file-watchers stay quiet. - **Programmatic `doctreen/codegen` entry point.** `generateTypes(doc, opts?)`, `generateClient(doc, opts?)`, and `loadOpenApiDoc(from)` exported for build scripts that want to inline codegen. ### How it fits Works with any OpenAPI 3.x document — not just DocTreen-emitted ones. The generated client has zero dependencies (uses global `fetch`) and is safe to check in alongside the rest of your source. ## [1.12.0] — 2026-05-27 ### Added - **`npx doctreen mock` — spec-driven mock server.** Spin up an Express-backed fake of any OpenAPI 3.x document in seconds: ```bash npx doctreen mock --from http://localhost:3000/docs --port 4000 npx doctreen mock --from ./openapi.json --latency 100-500 --error-rate 0.1 ``` Routes, schemas, examples, and `$ref`s are read straight from the spec; responses come from the same schema→example generator that powers the docs UI. When `@faker-js/faker` is installed, fields with recognisable names (`email`, `name`, `uuid`, `createdAt`, …) and OpenAPI `format` strings (`uuid`, `email`, `date-time`, `uri`, …) get realistic values — without it, output is a deterministic placeholder. - **In-memory CRUD short-circuits.** `POST /resource`, `GET /resource`, `GET /resource/:id`, `PUT|PATCH /resource/:id`, `DELETE /resource/:id` share a per-resource store keyed by the first non-version path segment. POST returns 201 with a stamped `id` + `createdAt`; GET reads back what was created. Envelope responses (`{ products: [...], total, filters }`) are handled — the array is swapped in, the rest of the envelope is regenerated from the schema. Pass `--no-crud` to disable and always return synthesised examples. Pass `--persist ` to save the store to a JSON file across restarts. - **Latency + error injection.** `--latency 200` adds a fixed delay, `--latency 100-500` picks a random ms in range. `--error-rate 0.1` returns a randomly-selected declared 4xx/5xx response 10 % of the time — useful for shaking out frontend error paths. - **`@faker-js/faker` is an optional peer.** doctreen lazy-requires it; install only when you want richer examples. `--no-faker` forces placeholder output even when faker is present. - **Public schema→example helper.** The internal generator that powered Copy-as-cURL and Postman export is now a first-class export at `doctreen/example`: ```js const { generateExample } = require('doctreen/example'); generateExample({ type: 'object', properties: { email: { type: 'string', format: 'email' } } }); // → { email: 'user@example.com' } (or a Faker email if installed) ``` Accepts both doctreen `SchemaNode` and OpenAPI Schema Objects (with `$ref` resolved via the `components` option). - **Programmatic mock API.** Import `doctreen/mock` to embed the mock server in your own scripts or tests: ```js const { startMockFromOpenApi } = require('doctreen/mock'); const { server } = await startMockFromOpenApi({ from: './openapi.json', port: 4000 }); // … server.close(); ``` ### Migration No breaking changes for existing v1.11.x consumers. New exports (`doctreen/mock`, `doctreen/example`) are additive. ## [1.11.0] — 2026-05-27 ### Added - **`components.schemas` with `$ref` dedup.** Schemas registered via `defineSchema('Name', …)` are now promoted to `components.schemas.Name` and every occurrence in `requestBody` / `responses` / `parameters` is replaced with a single `$ref`. Anonymous object schemas with three or more properties that appear in two or more places are also auto-promoted under stable `Schema1`, `Schema2`, … names. The exported spec stays self-contained but no longer ships the same shape inlined dozens of times. - **Per-route + top-level tags.** `defineRoute({ tags: ['users'] })` and `@DocRoute({ tags: ['users'] })` override the legacy first-path-segment default. Top-level metadata lives at `config.openapi.tags`: ```js openapi: { tags: [ { name: 'users', description: 'User account management' }, { name: 'billing', description: 'Invoices + payment methods' }, ], } ``` Tags used by routes but not declared at the top level are auto-appended without metadata so the spec always validates. - **OpenAPI 3.1 callbacks + webhooks.** Per-operation callbacks via `defineRoute({ callbacks: { onPaymentSucceeded: { url, method, request, response } } })`, document-level webhooks via `config.openapi.webhooks: { userCreated: { method, request, response } }`. Both reuse the same request/response/error pipeline as routes — Zod or SchemaNode supported, `$ref` dedup applies. - **Multi-example bodies and responses.** ```js defineRoute(handler, { examples: { request: { basic: { value: { ... }, summary: 'Minimum' }, admin: { value: { ... }, summary: 'With role' }, }, response: { id: 1, name: 'Ada' }, // single example responses: { 422: { value: { errors: [...] } } }, // per-status-code }, }); ``` Renders as OpenAPI `example` (single) or `examples` (named map) on the corresponding Media Type Object. Aliases: `body` → `request`, `success` → `response`. - **`doctreen lint openapi`.** Spectral-lite linter for the exported (or any) OpenAPI 3.x document. Catches duplicate operationIds, missing `info.title`/`version`, undeclared path params, untagged operations, unused `components.schemas` entries, missing 4xx responses, missing tag descriptions. ```bash npx doctreen lint openapi --url http://localhost:3000/docs --fail-on warning npx doctreen lint openapi --file ./build/openapi.json --no-info ``` Exit code 1 when the configured `--fail-on` threshold is reached — drop into CI alongside `drift report`. ### Migration No breaking changes for existing v1.10.x consumers. - Specs that previously inlined the same schema multiple times will now see `$ref`s. Spec validators (Spectral, Redocly) handle this natively; custom consumers reading SchemaObjects directly must follow `$ref`s (one extra hop through `components.schemas`). - `tagFor()` is no longer exported from `src/exporters/openapi.js`; replaced by `defaultTagFor()`. Public API consumers were unlikely to reach into the exporter directly, but if you did: rename. ## [1.10.1] — 2026-05-27 ### Added - **Drift store reset endpoint.** `POST /drift/reset` clears the in-memory store on demand — useful between integration test runs, after deploys, or when a known bad client has finished its run. Opt-in via `drift.allowReset: true`; optionally protected by `drift.resetToken` matched against the `x-doctreen-drift-token` header or `?token=` query param. Available on all five adapters. ```js expressAdapter(app, { drift: { enabled: true, allowReset: true, resetToken: process.env.DOCTREEN_RESET_TOKEN, }, }); ``` - **`doctreen drift reset` CLI.** Companion to `drift report`. POSTs to `/drift/reset`, prints a confirmation, exits non-zero on failure. ```bash npx doctreen drift reset --url http://localhost:3000/docs --token "$DOCTREEN_RESET_TOKEN" ``` - **Daily buckets in drift report.** Alongside the existing rolling 24-hour hourly buckets, the store now keeps a rolling 7-day daily aggregate per route under `dailyBuckets`. Same dedup window, same sampling, no extra cost — exposed in `/drift.json` for dashboards that want a longer view than 24h. - **Redis-backed `DriftStore` reference implementation.** `example/drift-redis-store.js` ships a complete, multi-replica-safe implementation of the `DriftStore` interface for `ioredis` / `redis@4+` (bring your own client). Drop-in for production deployments that need aggregates to survive restarts and stay consistent across replicas. ### Migration No breaking changes. The reset endpoint defaults to disabled — existing v1.10.0 servers behave identically until `allowReset` is flipped. `dailyBuckets` is a new top-level field in the per-route report payload; existing consumers continue to read `buckets` (hourly) unchanged. ## [1.10.0] — 2026-05-27 ### Added - **Production-grade schema drift detection.** The experimental v1.5 `console.warn` is now a structured pipeline with an in-memory aggregator, per-route counters and hourly buckets, opt-in sampling (default 1%), a pluggable store interface for Redis / Postgres / etc., an `onDrift` callback, and fire-and-forget webhook dispatch. Every adapter (Express, Fastify, Hono, Koa, NestJS) emits events through the same pipeline so behaviour is uniform across frameworks. ```js expressAdapter(app, { drift: { enabled: true, sampleRate: 0.05, webhook: 'https://hooks.example.com/drift', onDrift: (event) => metrics.increment('api.drift', event.issues.length), }, }); ``` Defaults: enabled when `NODE_ENV !== 'production'`, `sampleRate: 0.01`, `logLevel: 'warn'`. Pass `drift: false` to disable entirely. - **`GET /drift.json`.** Every adapter exposes an aggregated drift report — totals per route, kind breakdown (`missing-required`, `unexpected-field`, `type-mismatch`), top fields, rolling hourly buckets, and the last N samples per route. The same payload powers the UI tab and the CLI. - **UI: Drift tab.** A new header tab appears in the docs page when drift is enabled. Shows total issues, routes affected, kind breakdown, and the latest sample per route. Routes with active drift get an inline `DRIFT N` badge in the routes table. The tab fetches `/drift.json` on activation and refreshes manually. - **CLI: `npx doctreen drift report`.** Umbrella CLI (new `doctreen` binary) that hits the running server's `/drift.json` and prints a CI-friendly summary. `--fail-on-mismatch` exits non-zero when drift is present; `--json` emits the raw payload; `--route ` filters by path substring. ``` npx doctreen drift report --url http://localhost:3000/docs --fail-on-mismatch ``` - **`DriftStore` interface.** Plug an external store (Redis, Postgres, external aggregator) by passing `drift.store` — any object with `record(event)`, `report()`, `reset()` works. ### Changed - The v1.5 experimental drift `console.warn` is now centralised in `src/internal/drift-store.js`. The same log line still fires for the default in-memory store (per unique drift signature, deduped), but it now also feeds the structured pipeline. Pass `drift.logLevel: 'silent'` to suppress the log without disabling detection. - Per-route `requestSchemaDeclared` is now set consistently across all adapters when a schema comes from `defineRoute`, `@DocRoute`, JSDoc, or Fastify native JSON Schema — so drift detection applies uniformly regardless of how the schema was provided. - Roadmap: v1.10 ticked. Next headline is **OpenAPI polish** (`$ref` dedup, first-class tags, callbacks/webhooks, multi-example, `npx doctreen lint openapi`). ### Migration No breaking changes. The experimental v1.5 drift detection is fully backward compatible — the warning still fires for unique mismatches. To opt into the new aggregated report and UI in **production**, flip the gate: ```js expressAdapter(app, { drift: { enabled: true, sampleRate: 0.01 } }); ``` At a 1% sample rate the runtime cost is negligible. The store retains 24 hours of hourly buckets per route and the most recent 5 samples, both configurable. ## [1.9.0] — 2026-05-26 ### Added - **`headHtml` config option.** Pass a raw HTML string and DocTreen appends it to the docs UI `` — useful for analytics scripts (Vercel Analytics, Plausible, PostHog), favicons, custom theme-color / OG / Twitter meta tags, branded web fonts, or extra CSS overrides. Trusted input only; DocTreen does not sanitise, so callers must not pipe user-submitted data through this option. ```js expressAdapter(app, { headHtml: '', }); ``` Implementation: `normalizeConfig` defaults `headHtml` to `null`; the generated HTML template emits the string between the built-in `