Skip to content

Releases: CanDgrmc/doctreen

v1.15.0 — Validation, completed

Choose a tag to compare

@CanDgrmc CanDgrmc released this 04 Jul 09:31
8d3eef9

Validation, completed — this release closes the gap between what a route
documents and what it enforces, and fixes the codegen type-safety holes
that pushed consumers back to hand-written types.

Added

  • Path-parameter schemas + validationdefineRoute now accepts
    request: { params }, so :id and friends are validated by the same Zod
    schema that documents them, with a structured 422 on mismatch. Works across
    Express, Fastify, Hono, Koa, and NestJS — no more hand-rolled requireUuid
    helpers repeated on every route.
  • Request write-back. Opt in with validate: { writeback: true } and the
    parsed payload — Zod coercions and .default()s applied — is written back
    onto req.body / req.query / req.params, so the handler reads coerced
    values instead of re-parsing them. (Koa and Hono expose coerced path params
    via ctx.state.doctreenValidated / c.get('doctreenValidated').)
  • Dev-mode response assertion. validate: { response: 'warn' | 'throw' }
    checks the handler's response against the declared Zod response schema —
    'warn' logs a mismatch and passes through, 'throw' surfaces a 500 in
    development. Never coerces the response.
  • Status-keyed responses. response: { 201: schema, 200: schema } documents
    each status with its own schema and drives the OpenAPI export; the plain
    response: schema form still means 200.
  • defaultErrors config. Declare shared error responses (401 / 403 / 422 …)
    once — they merge into every route, with the route's own errors winning on a
    status conflict.
  • Standard DoctreenValidationError envelope. Validated routes document
    their 422 { error, issues[] } body as a named component, so codegen emits a
    DoctreenValidationError type and clients stop guessing the error shape.
  • Offline OpenAPI emit. getOpenApiDocument(app) (on every adapter) and the
    new doctreen emit-openapi CLI build a static openapi.json without starting
    a server, so codegen and CI can run fully offline.

Fixed

  • defineSchema now works with Zod schemas. Named schemas were matched by
    object identity, which Zod→SchemaNode conversion breaks — so Zod-defined named
    schemas fell through to anonymous Schema1 / Schema2 types in codegen. They
    now emit $ref: '#/components/schemas/<name>' and codegen produces
    interface User, at the top level, nested, and inside arrays.
  • Deep schemas no longer collapse to unknown. The Zod conversion depth cap
    was raised from 5 to 12, so deep-but-non-recursive trees (menu → categories →
    items → variants → options) keep their leaf types. Recursion is best expressed
    with defineSchema, which now emits a terminating $ref.

v1.14.1 — codegen: valid TS for nullable objects

Choose a tag to compare

@CanDgrmc CanDgrmc released this 02 Jul 18:41
5ebd1df

Fixed

  • codegen no longer emits invalid TypeScript for nullable objects. A
    nullable object schema — OpenAPI 3.1 type: ["object", "null"] or 3.0
    nullable: true — renders as { … } | null, which starts with { but is
    not a bare object body. The generator mistook it for an interface body and
    produced export interface Profile { … } | null (a syntax error). Such
    schemas now emit a valid type alias (export type Profile = { … } | null);
    plain objects still emit export interface.

v1.14.0 — schema enum/nullable/default builders

Choose a tag to compare

@CanDgrmc CanDgrmc released this 02 Jul 13:10
08de3b9

Added

  • s.enum, s.nullable, s.default, and s.literal schema builders. The
    s helper can now express value-level facets that previously had nowhere to
    live in a SchemaNode:

    defineRoute(handler, {
      request: {
        body: s.object({
          role:   s.enum(['admin', 'user', 'guest']),
          status: s.nullable(s.enum(['active', 'inactive'])),
          limit:  s.default(s.number(), 20),
          kind:   s.literal('user'),
        }),
      },
    });

    These flow through to the exported OpenAPI document, the docs UI schema tree,
    and every request example (Copy as cURL, Postman export, mock server).

Fixed

  • defineRoute now emits enum and nullable. SchemaNode could only
    carry type / properties / items / optional, so enum members and
    nullability were dropped before reaching the OpenAPI export or docs UI —
    z.enum([...]) collapsed to a bare string and .nullable() was demoted to
    optional. Both the s builder and the Zod converter now preserve them, and
    the exporter emits enum plus OpenAPI 3.1 type: [<type>, 'null'].

  • Default values are now applied in request examples. A schema default
    (via s.default(...) or Zod .default(...)) is emitted to OpenAPI and seeds
    the generated request bodies and query parameters used by Copy as cURL, the
    Postman export, and the mock server. Fields with a default are treated as
    optional and excluded from required.

v1.13.1 — codegen: all methods per path + correct enum/null

Choose a tag to compare

@CanDgrmc CanDgrmc released this 02 Jul 12:28
defae78

Fixed

  • codegen now emits every HTTP method on a shared path. Operations that
    reused an operationId across HTTP verbs — or used a verb-agnostic
    operationId — collapsed to a single generated name, so the typed client
    silently kept only the last method on a path (e.g. POST /users clobbered
    GET /users).
    Generated type and function names are now guaranteed unique; colliding names
    are disambiguated by HTTP method (usersGet / usersPost).

  • codegen enum / nullable output is now correct. A null present both in
    a 3.1 type: ["string", "null"] array (or via nullable: true) and in an
    enum produced a doubled union such as "active" | "inactive" | null | null.
    Nullability is now folded into a single trailing | null and enum members are
    deduplicated. Type coverage was extended along the way to const literals,
    prefixItems tuples, and closed objects (additionalProperties: false
    Record<string, never>).

v1.12.0 — Mock server

Choose a tag to compare

@CanDgrmc CanDgrmc released this 27 May 16:35
e954f37

DocTreen v1.12.0 — Mock server

Every doctreen-enabled server already exposes a full OpenAPI 3.1 document at /docs/openapi.json. v1.12 turns that into the most direct payoff possible: spin up an Express-backed fake of any spec in seconds, with realistic data, CRUD short-circuits, latency, and error injection — no separate spec-stitching, no manual handler stubs.

What's new

🎭 npx doctreen mock — spec-driven mock server. Point it at a live /docs URL or a local OpenAPI JSON file, get an Express app that serves every operation with a schema-faithful response:

# From a running doctreen-enabled server
npx doctreen mock --from http://localhost:3000/docs --port 4000

# From an OpenAPI file on disk
npx doctreen mock --from ./openapi.json --port 4000

# With latency + random declared-error injection
npx doctreen mock --from ./openapi.json --latency 100-500 --error-rate 0.1

Responses come from the same schema→example generator that powers the docs UI's Copy-as-cURL and Postman buttons. $ref, oneOf / anyOf / allOf, enum, const, default, and operation-level example / examples are all respected.

🛒 In-memory CRUD short-circuits. When the spec exposes /resource plus /resource/:id, POST actually creates a row in an in-memory store, GET /resource lists, GET /resource/:id reads, PUT / PATCH mutate, DELETE removes. Envelope responses ({ products: [...], total, filters }) are detected automatically — the array is swapped in while the surrounding shape is regenerated from the schema:

curl -s http://localhost:4000/products
# → {"products":[],"total":0,"filters":{}}

curl -s -X POST -d '{"name":"Widget","price":9.99}' \
  -H "Content-Type: application/json" \
  http://localhost:4000/products
# → {"name":"Widget","price":9.99,"id":1,"createdAt":"…"}

curl -s http://localhost:4000/products/1
# → {"name":"Widget","price":9.99,"id":1,"createdAt":"…"}

Pass --persist ./fixtures.json to save the store across restarts. Pass --no-crud if you want every endpoint to return synthesised examples instead of stateful CRUD.

🎲 Optional Faker integration. Install @faker-js/faker and fields with recognisable names (email, name, uuid, createdAt, …) plus OpenAPI format strings (uuid, email, date-time, uri, ipv4, …) automatically get realistic values:

generateExample(
  { type: 'object', properties: { email: { type: 'string', format: 'email' } } },
  { faker: true, seed: 42 }
);
// → { email: 'Aida.Effertz18@yahoo.com' }

Without Faker installed, output falls back to deterministic placeholders ('string', 0, true). --no-faker forces placeholders even when Faker is present. --seed <n> makes Faker output reproducible.

Latency + error injection. Stress-test frontend error paths without touching your backend:

# Fixed 200ms delay on every response
--latency 200

# Random delay between 100 and 500ms
--latency 100-500

# Return a randomly-picked declared 4xx/5xx response 10% of the time
--error-rate 0.1

Both flags compose. The selected error response is always one the spec already declares — the body is generated from its declared schema.

🧰 Public schema→example helper. The generator behind the docs UI's Copy-as-cURL and Postman buttons is now a first-class export. Use it anywhere you need a JS value from a schema:

const { generateExample } = require('doctreen/example');

generateExample(
  { type: 'object', properties: {
    email: { type: 'string', format: 'email' },
    createdAt: { type: 'string', format: 'date-time' },
    tags: { type: 'array', items: { type: 'string' } },
  }},
  { faker: true }
);

Accepts both doctreen SchemaNode and OpenAPI 3.x Schema Objects. Pass components to resolve $refs.

🧪 Programmatic mock API. Embed the mock server in your own scripts or tests instead of shelling out:

const { startMockFromOpenApi } = require('doctreen/mock');

const { app, server, routeCount } = await startMockFromOpenApi({
  from: './openapi.json',
  port: 4000,
  latency: [100, 500],
  errorRate: 0.05,
  persistPath: './fixtures.json',
});

// later, e.g. in afterAll()
server.close();

createMockApp(...) skips the listen step if you want to mount the mock as middleware inside another Express app.

Why this matters

The natural lifecycle of a doctreen project so far has been: define routes → ship docs and OpenAPI → wire integration flows → catch drift in CI. v1.12 fills the gap on the outside-in side: frontend devs can now build against a faithful fake of the API before the backend is finished, and contract tests can run against the same fake to verify both ends agree on the shape.

The Mock Server is a peer of the existing CLI workflow:

Command What it does
doctreen drift report --fail-on-mismatch Catch shape drift between code and live traffic (v1.10)
doctreen drift reset Clear the drift store between CI runs (v1.10.1)
doctreen lint openapi Spectral-lite linter for the exported spec (v1.11)
doctreen mock --from <url|file> Serve a spec-driven fake API with CRUD + faker + injection

Compatibility

No breaking changes for existing v1.11.x consumers. New exports (doctreen/mock, doctreen/example) are additive; @faker-js/faker is loaded lazily so it stays optional.

Upgrade

npm install doctreen@1.12.0
# optional, for richer mock data
npm install -D @faker-js/faker

Full changelog →

v1.11.0 — OpenAPI polish

Choose a tag to compare

@CanDgrmc CanDgrmc released this 27 May 16:04
77860dc

DocTreen v1.11.0 — OpenAPI polish

The OpenAPI exporter that landed in v1.7 was deliberately scrappy: schemas inlined everywhere, tags inferred from the path, no callbacks, no examples beyond a single inferred shape. v1.11 closes that gap. Every roadmap item under "OpenAPI polish" ships in this release.

What's new

🪢 components.schemas with $ref deduplication. Wrap any schema with defineSchema('Name', ...) and the exporter promotes it to components.schemas.Name automatically — every occurrence across requestBody, responses, parameters, callbacks, and webhooks turns into { $ref: '#/components/schemas/Name' }:

const { s, defineSchema } = require('doctreen');

const User = defineSchema('User', s.object({
  id:    s.number(),
  name:  s.string(),
  email: s.string(),
}));

app.post('/users',    defineRoute(handler, { request: { body: User }, response: User }));
app.get('/users/:id', defineRoute(handler, { response: User }));

Anonymous object schemas with three or more properties that appear in two or more places also auto-promote under stable Schema1, Schema2, … names — you get the dedup benefit without naming every shape.

🏷 Per-route + top-level tags. defineRoute({ tags: [...] }) (and @DocRoute({ tags: [...] }) on NestJS) overrides the legacy first-path-segment default. Document-level metadata lives at config.openapi.tags:

expressAdapter(app, {
  openapi: {
    tags: [
      { name: 'users',   description: 'User account management' },
      { name: 'billing', description: 'Invoices + payment methods' },
    ],
  },
});

app.post('/users', defineRoute(handler, { tags: ['users', 'public'], /* ... */ }));

Tags used by routes but not declared at the top level are auto-appended so the spec stays consistent. The new doctreen lint openapi warns about undescribed tags.

📞 OpenAPI 3.1 callbacks + webhooks. Per-operation callbacks for the spec's runtime-expression callback URLs:

app.post('/payments', defineRoute(handler, {
  description: 'Create a payment',
  callbacks: {
    onPaymentSucceeded: {
      url:      '{$request.body#/callbackUrl}',
      method:   'POST',
      request:  { body: s.object({ paymentId: s.string() }) },
      response: s.object({ ok: s.boolean() }),
    },
  },
}));

Document-level webhooks for events the server emits:

expressAdapter(app, {
  openapi: {
    webhooks: {
      userDeleted: {
        method:  'POST',
        summary: 'Fired when a user closes their account',
        request: { body: s.object({ userId: s.number(), deletedAt: s.string() }) },
      },
    },
  },
});

Both reuse the request/response/error pipeline — Zod or SchemaNode accepted, $ref dedup applies.

🧪 Multi-example bodies and responses.

defineRoute(handler, {
  request:  { body: User },
  response: User,
  errors:   { 422: 'Validation failed' },
  examples: {
    request: {
      basic: { value: { name: 'Ada',  email: 'ada@example.com' }, summary: 'Minimum' },
      admin: { value: { name: 'Boss', email: 'boss@x.com', role: 'admin' }, summary: 'With role' },
    },
    response:  { id: 1, name: 'Ada', email: 'ada@example.com' },
    responses: { 422: { value: { errors: ['email is required'] } } },
  },
});

Single values render as OpenAPI example; named maps render as examples. Aliases: bodyrequest, successresponse.

🩺 npx doctreen lint openapi. Spectral-lite linter for the exported (or any) OpenAPI 3.x document. Nine rules across error / warning / info:

  • duplicate operationIds
  • missing info.title / info.version
  • undeclared path parameters ({id} in template, missing from parameters[])
  • untagged operations / undescribed tags
  • missing 4xx responses
  • unused components.schemas entries
# Live URL
npx doctreen lint openapi --url http://localhost:3000/docs

# Local file
npx doctreen lint openapi --file ./build/openapi.json --fail-on warning

# CI-friendly JSON
npx doctreen lint openapi --url https://api.example.com/docs --json

Exits 1 when the configured --fail-on threshold is reached — drop into CI alongside doctreen drift report.

Migration

No breaking changes for users.

  • Specs that previously inlined the same schema multiple times now ship a single components.schemas entry referenced by $ref. Spec validators (Spectral, Redocly) handle this natively; custom consumers reading SchemaObjects directly must follow $refs (one extra hop through components.schemas).
  • tagFor() is no longer exported from src/exporters/openapi.js; renamed to defaultTagFor(). Public API consumers reach buildOpenApiDocument only, so this is invisible — but if you were importing the helper directly, rename.

What's next

  • Mock server (v1.12) — npx doctreen mock serves a fake API from the registry; Faker-backed examples; --latency, --error-rate flags; --from openapi.json for spec-driven mocking
  • Type & client codegen (v1.13) — typed request/response interfaces and a tRPC-style fetch wrapper, in watch mode for dev

Try it

npm install doctreen@latest

Full changelog: CHANGELOG.md.
Feedback welcome — open an issue.

v1.10.1 — Drift reset, daily buckets, Redis store

Choose a tag to compare

@CanDgrmc CanDgrmc released this 27 May 16:04
9ac02a5

DocTreen v1.10.1 — Drift reset, daily buckets, Redis store

A focused follow-up to v1.10 that closes the four open items from the drift sprint. Pure additive: existing v1.10.0 servers behave identically until the new flags are flipped.

What's new

♻️ POST <docsPath>/drift/reset. Clear the in-memory drift store on demand — between integration test runs, after a deploy, or once a misbehaving client has been fixed. Opt-in and ideally protected by a shared secret:

expressAdapter(app, {
  drift: {
    enabled:    true,
    allowReset: true,
    resetToken: process.env.DOCTREEN_RESET_TOKEN,
  },
});
# CI / cron / one-off
npx doctreen drift reset --url http://localhost:3000/docs --token "$DOCTREEN_RESET_TOKEN"

# Or directly
curl -X POST -H "x-doctreen-drift-token: $TOKEN" http://localhost:3000/docs/drift/reset

Without allowReset: true the endpoint returns 405. Without resetToken it's open — only flip that on internal-only networks. Works on all five adapters.

🛠 doctreen drift reset CLI. Companion to drift report. POSTs to /drift/reset, prints a confirmation, exits non-zero on failure. Supports --url, --token, --json.

📆 Rolling 7-day daily buckets. Alongside the existing 24-hour hourly buckets (buckets), every per-route report now carries a dailyBuckets field with the last 7 daily counts. Same sampling, no extra cost — pick whichever resolution your dashboard wants:

{
  "method": "POST",
  "path":   "/users",
  "buckets":      { "2026-05-27T13": 5, "2026-05-27T14": 3 },
  "dailyBuckets": { "2026-05-26": 12, "2026-05-27": 8 }
}

🗄 Redis-backed DriftStore reference. A complete, multi-replica-safe implementation of the DriftStore interface ships at example/drift-redis-store.js. Bring your own ioredis/redis@4+ client; the store handles record / report / reset and survives process restarts:

const Redis = require('ioredis');
const { createRedisDriftStore } = require('doctreen/example/drift-redis-store');

const redis = new Redis(process.env.REDIS_URL);

expressAdapter(app, {
  drift: {
    enabled:    true,
    sampleRate: 0.01,
    store:      createRedisDriftStore({ client: redis, prefix: 'doctreen:drift:' }),
    allowReset: true,
    resetToken: process.env.DOCTREEN_RESET_TOKEN,
  },
});

Storage layout uses sets + hashes + lists with key prefixes documented in the file header. Multiple replicas can share one Redis and aggregate into the same dashboard.

Migration

No breaking changes.

  • Reset endpoint defaults to disabled — existing v1.10.0 servers are unchanged until allowReset is set.
  • dailyBuckets is a new top-level field on every per-route report payload; consumers that only read buckets continue working.
  • DriftStore implementations from v1.10.0 keep working — report() consumers that strictly type their schema may want to add dailyBuckets: Record<string, number> to it.

What's next

  • OpenAPI polish (v1.11) — already merged: $ref dedup, first-class tags, callbacks/webhooks, multi-example, doctreen lint openapi
  • Mock server (v1.12) — npx doctreen mock serves a fake API from the registry, latency/error injection, --from openapi.json

Try it

npm install doctreen@latest

Full changelog: CHANGELOG.md.
Feedback welcome — open an issue.

v1.10.0 — Production-grade schema drift reporting

Choose a tag to compare

@CanDgrmc CanDgrmc released this 27 May 16:04

DocTreen v1.10.0 — Production-grade schema drift reporting

v1.5 shipped an experimental dev-only console.warn for schema drift on Express. v1.10 promotes that signal into a full reporting pipeline you can run in production: structured aggregates, opt-in sampling, pluggable storage, a UI tab, a JSON endpoint, and a CI-friendly CLI — and it works on all five adapters (Express, Fastify, Hono, Koa, NestJS).

What's new

🪡 Structured drift pipeline. A new src/internal/drift-store.js aggregates per-route counts, kind breakdowns (missing-required / unexpected-field / type-mismatch), rolling 24-hour hourly buckets, and the last N samples per route. All adapters emit through the same pipeline, so the shape of the report is identical no matter which framework you run.

⚙️ The drift config block.

expressAdapter(app, {
  drift: {
    enabled:    true,           // default: NODE_ENV !== 'production'
    sampleRate: 0.01,           // 1% of mismatching requests
    onDrift:    (event) => log.warn(event),
    webhook:    'https://hooks.example.com/drift',
    logLevel:   'silent',       // suppress the per-process console.warn
  },
});

Pass false to disable, true to enable with defaults, or an object to fine-tune. The webhook fires on every recorded event; onDrift runs synchronously for in-process side effects.

📊 GET /docs/drift.json. Every adapter exposes the aggregated report at the docs path. Same shape powers the UI tab and the CLI:

{
  "generatedAt": 1748371000000,
  "totalIssues": 12,
  "routes": [{
    "method":  "POST",
    "path":    "/users",
    "total":   8,
    "kinds":   { "missing-required": 3, "unexpected-field": 2, "type-mismatch": 3 },
    "parts":   { "body": 7, "query": 1 },
    "fields":  { "email": 3, "age": 2 },
    "buckets": { "2026-05-27T13": 5, "2026-05-27T14": 3 },
    "samples": [ /* up to maxSamples recent events */ ]
  }]
}

🖥 UI: Drift tab. Shown in the docs UI when drift is enabled. Kind summary across the whole API, per-route cards with samples and hourly histogram, and inline DRIFT N badges on the routes table so problem routes are visible without leaving the regular view.

🔬 npx doctreen drift report CLI. New umbrella binary at bin/doctreen.js. Hits /drift.json on a running server, prints a CI-friendly table, exits 1 when --fail-on-mismatch is set and drift is present:

npx doctreen drift report --url http://localhost:3000/docs --fail-on-mismatch
# 0 routes with drift  → exit 0
# 3 routes with drift  → table + exit 1

Drop it into integration test runs so PRs catch shape changes before they merge.

🔌 Pluggable DriftStore. The default in-memory store is fine for single-process apps. For multi-replica or long-running deployments, implement the three-method interface and pass it as drift.store:

interface DriftStore {
  record(event: DriftEvent): void | Promise<void>;
  report(): DriftReport | Promise<DriftReport>;
  reset(): void | Promise<void>;
}

A Redis-backed reference implementation lands in v1.10.1.

Migration

No breaking changes. The experimental v1.5 console.warn still fires (per unique drift signature). To unlock the full report, opt in:

expressAdapter(app, { drift: { enabled: true, sampleRate: 0.01 } });

The drift hook on Fastify / Hono / Koa / NestJS — previously a roadmap item — ships in this release.

What's next

  • Reset endpoint, daily buckets, Redis store reference (v1.10.1) — already in flight
  • OpenAPI polish (v1.11) — $ref dedup, first-class tags, callbacks/webhooks, multi-example, doctreen lint openapi

Try it

npm install doctreen@latest

Full changelog: CHANGELOG.md.
Feedback welcome — open an issue.

v1.8.0 — security schemes + hide-a-route

Choose a tag to compare

@CanDgrmc CanDgrmc released this 26 May 14:52

DocTreen v1.8.0 — security schemes + hide-a-route

v1.7 shipped a valid OpenAPI 3.1 document. v1.8 makes it a good one:
securitySchemes declared once, attached automatically, with the
Authorization header no longer awkwardly mirrored as a plain
parameter. Plus a long-requested escape hatch for endpoints that should
serve traffic but not show up in the docs.

What's new

🛡️ securitySchemes + per-route security. Declare auth schemes
once on the adapter, set a global default, override per route as
needed:

expressAdapter(app, {
  openapi: {
    securitySchemes: {
      bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
    },
    security: [{ bearerAuth: [] }],   // applies to every route by default
  },
});

// Override per route
app.post('/auth/login', defineRoute(handler, { security: [] }));         // public
app.get('/admin/stats', defineRoute(handler, { security: [{ adminAuth: [] }] }));

When a route has any effective security requirement, DocTreen also
strips the now-redundant Authorization header from parameters[],
so Redocly's security-defined rule passes cleanly.

🙈 hidden: true per route. Useful for internal admin tools,
experimental flags, or routes you can't remove yet:

app.get('/internal/metrics', defineRoute(handler, { hidden: true }));

NestJS gains a @DocHidden() shorthand alongside the existing
@DocRoute({ hidden: true }) form.

🌐 config.openapi.servers. Point the spec at your real
environments:

expressAdapter(app, {
  openapi: {
    servers: [
      { url: 'https://api.example.com',         description: 'Production' },
      { url: 'https://staging.api.example.com', description: 'Staging' },
    ],
  },
});

Defaults to [{ url: '/' }] (same origin as the docs page) when
omitted — Swagger UI's "Try it out" keeps working.

📚 Type surface caught up. UserConfig now declares validate
(back-filled from v1.6) and the new openapi block; RouteEntry
lists requestValidators, validateOverride, hidden, and
security; RouteRegistry gains type stubs for getVisible,
find, and findByRequestPath that have been live since v1.6/v1.8.

Migration

No breaking changes. Every new field defaults to "behave like v1.7"
when omitted. Set openapi.security: [{ bearerAuth: [] }] and you'll
notice the Authorization header drop out of the spec — that's the
intended effect, not a regression.

What's next

  • Production-grade schema drift reporting — sampling, aggregation,
    and a dashboard view of declared vs. observed schemas.
  • Drift hooks on Fastify / Hono / Koa / NestJS — port the v1.5
    Express dev warning to the remaining adapters.

Try it

npm install doctreen@latest

The live demo exercises the new
fields end-to-end.


Full changelog: CHANGELOG.md.
Feedback welcome — open an issue.

v1.7.0 — OpenAPI 3.1 export

Choose a tag to compare

@CanDgrmc CanDgrmc released this 26 May 14:24

DocTreen v1.7.0 — OpenAPI 3.1 export, no extra annotations

v1.5 made one Zod schema per route the source of truth for documentation.
v1.6 made the same schema reject invalid requests. v1.7 turns the same
schema into a standards-compliant OpenAPI 3.1 document that drops into
Scalar, Redoc, Swagger UI, or any other spec-driven tool — without
writing a separate YAML file or adding a single decorator.

What's new

📜 OpenAPI 3.1 at <docsPath>/openapi.json. Every adapter — Express,
Fastify, Hono, Koa, NestJS — now ships an OpenAPI 3.1 endpoint built
from the same schemas that already drive the docs UI. Hit it with curl,
download it from the new header button, or point any spec consumer at
it directly:

curl https://your-api.example.com/docs/openapi.json > openapi.json

🔗 Drops into the ecosystem you already use. The output passes
@apidevtools/swagger-cli validate, renders cleanly in
Scalar, Redoc,
and Swagger UI, and includes path parameters, query parameters, request
body with required[] arrays derived from your Zod schemas, every
declared error response, and tags grouped by the first path segment.

🖱️ One-click "Export to OpenAPI 3.1" button in the docs UI, sitting
next to "Export to Postman". Same JSON file the endpoint serves.

🛣️ Roadmap reshuffled. Validation (v1.6) and OpenAPI export (v1.7)
are both ticked. Next big items: first-class openapi.servers config +
securitySchemes so Authorization headers stop showing up as plain
parameters, and production-grade schema drift reporting.

Out of scope for now

  • securitySchemes / security blocks — coming in v1.8. Authorization
    and similar headers currently render as parameters[].in = header,
    which is valid spec but Redocly flags as suboptimal.
  • callbacks, webhooks, links.
  • $ref-based schema deduplication — everything is inlined for now.

Migration

No breaking changes. The new endpoint is additive; existing routes,
adapters, and defineRoute calls are untouched. Drop in v1.7 and you
get the export for free.

Try it

npm install doctreen@latest

The live demo shows the button in the
header — click it, paste the file into your spec viewer of choice, and
the routes render with full request / response detail.


Full changelog: CHANGELOG.md.
Feedback welcome — open an issue.