Skip to content

Conversation

SaltyAom
Copy link
Member

@SaltyAom SaltyAom commented Sep 2, 2025

D9E66E5D-605F-48F3-906D-0178CD0E39C6_1_102_o

Summary by CodeRabbit

  • New Features

    • OpenAPI-based docs with selectable provider (provider may be null).
    • Attach custom headers to responses (withHeaders).
    • Optional route segments expanded into all valid paths.
    • Programmatic spec generation (toOpenAPI).
  • Breaking Changes

    • Package and public API renamed to @elysiajs/openapi; swagger → openapi.
    • Default docs route changed from /swagger to /openapi.
    • Configuration restructured with nested swagger/scalar blocks and consolidated excludes; several option names changed.
  • Bug Fixes

    • Fixed relative specPath handling.

Copy link

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@SaltyAom has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f410e31 and 3df25f4.

⛔ Files ignored due to path filters (1)
  • src/gen/index.ts is excluded by !**/gen/**
📒 Files selected for processing (4)
  • README.md (2 hunks)
  • package.json (4 hunks)
  • src/openapi.ts (1 hunks)
  • src/types.ts (2 hunks)

Walkthrough

Renames the package/plugin from swagger to openapi, adds an OpenAPI generator (src/openapi.ts) with helpers (toOpenAPISchema, getPossiblePath, withHeaders), restructures types/config/exports, replaces legacy utils, updates renders and examples, and adjusts tests, package metadata, and build/TS config.

Changes

Cohort / File(s) Summary
Changelog & Package metadata
CHANGELOG.md, package.json
Adds release 1.3.2 and rebrands package from @elysiajs/swagger@elysiajs/openapi; bumps version; updates exports/paths (./openapi, ./swagger/*, ./types, ./gen), dependencies, repo/homepage, and author metadata.
Core plugin entry
src/index.ts
Replaces exported swagger with openapi; introduces generic ElysiaOpenAPIConfig<Enabled,Path,Provider> with nested swagger/scalar blocks, provider (nullable), new default path /openapi, specPath handling, caching, and re-exports toOpenAPISchema and withHeaders.
OpenAPI generator & helpers (new)
src/openapi.ts
New module adding toOpenAPISchema, getPossiblePath, capitalize, and withHeaders; converts app routes to an OpenAPI 3.0.3 document, expands optional path segments, maps params/query/headers/cookies/body/responses, and aggregates components.schemas.
Removed legacy utils
src/utils.ts (deleted)
Removes previous OpenAPI-generation utilities (toOpenAPIPath, mapProperties, operationId generator, registerSchemaPath, filterPaths, etc.).
Types & config
src/types.ts
Introduces OpenAPIProvider, AdditionalReference(s), replaces ElysiaSwaggerConfig with generic ElysiaOpenAPIConfig, consolidates scalar and swagger nested configs, restructures exclude into { paths, methods, tags, staticFile }, and adds documentation and references.
Swagger UI render
src/swagger/index.ts
Refactors SwaggerUIRender to accept a config object (merged with swagger options), exports transformDateProperties, and updates schema transformation and template generation to use config-driven values.
Scalar UI render
src/scalar/index.ts
Updates ScalarRender signature to accept scalar config, embeds a new default CSS constant (elysiaCSS), uses config.cdn/config.url, and simplifies HTML scaffold generation.
Examples
example/index.ts, example/gen.ts (added), example/index2.ts (deleted), example/index3.ts (deleted)
Updates examples to use openapi and withHeaders; adds example/gen.ts demonstrating references/fromTypes usage; removes two legacy swagger-based example files.
Tests
test/index.test.ts, test/validate-schema.test.ts, test/openapi.test.ts (new), test/*node*
Tests switched from swagger()openapi() and /swagger/openapi; UI/version options nested under swagger/scalar; adds unit test for getPossiblePath; updates spec/response expectations.
Build & Tooling
build.ts, tsconfig.json, tsconfig.dts.json
Minor formatting in build.ts; changes TypeScript moduleResolution from nodebundler.
CJS/ESM test packages
test/node/cjs/package.json, test/node/esm/package.json
Test package dependencies updated to reference @elysiajs/openapi instead of @elysiajs/swagger.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer App
  participant App as Elysia App
  participant Plugin as openapi() Plugin
  participant Gen as toOpenAPISchema
  participant UI as Provider UI (Scalar / Swagger-UI)

  Dev->>App: new Elysia().use(openapi({ provider, path, documentation, exclude, swagger, scalar }))
  activate Plugin
  Plugin->>App: register UI routes if provider != null (e.g., `/openapi`, specPath `/openapi/json`)
  Note right of Plugin: plugin caches generated schema and tracks route count
  deactivate Plugin

  Client->>App: GET /openapi/json
  App->>Gen: toOpenAPISchema(app, exclude, references)
  Gen-->>App: { paths, components }
  App-->>Client: JSON spec
Loading
sequenceDiagram
  autonumber
  participant Gen as toOpenAPISchema
  participant Routes as App.getGlobalRoutes()
  participant Filter as Exclude Filter
  participant Expand as getPossiblePath()
  participant Mapper as Param/Body/Response Mapper

  Gen->>Routes: fetch global routes
  Routes-->>Gen: route list with hooks/schemas
  Gen->>Filter: apply exclude.{paths,methods,tags,staticFile}
  Filter-->>Gen: filtered routes
  Gen->>Expand: expand optional segments
  Expand-->>Gen: multiple concrete paths
  Gen->>Mapper: map params, query, headers, cookie, body, responses (supports withHeaders)
  Mapper-->>Gen: operations per path/status
  Gen-->>Gen: aggregate components.schemas and return document
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hop from Swagger's burrow to OpenAPI's glade,
I count each optional path the routes have made.
Headers tucked on 204s, schemas in a row,
I nibble docs, cache the flow, and watch routes grow.
A rabbit's cheer — new exports in tow! 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hana

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/swagger/index.ts (2)

21-46: Date-time transform likely misses nested schema.properties; fix traversal.

Iterating Object.entries(schema) won’t reach schema.properties[key]. The transform won’t touch createdAt/updatedAt under properties/items.

Proposed traversal:

 function transformDateProperties(schema: SchemaObject): SchemaObject {
   if (!isSchemaObject(schema) || typeof schema !== 'object' || schema === null) return schema;
-  const newSchema: OpenAPIV3.SchemaObject = { ...schema };
-  Object.entries(newSchema).forEach(([key, value]) => {
-    if (isSchemaObject(value)) {
-      if (isDateTimeProperty(key, value)) {
+  const newSchema: OpenAPIV3.SchemaObject = { ...schema };
+  // Dive into object properties
+  if (newSchema.properties && typeof newSchema.properties === 'object') {
+    for (const [key, prop] of Object.entries(newSchema.properties)) {
+      if (isSchemaObject(prop) && isDateTimeProperty(key, prop)) {
         const dateTimeFormat = value.anyOf?.find((item): item is OpenAPIV3.SchemaObject =>
           isSchemaObject(item) && item.format === 'date-time'
         );
         if (dateTimeFormat) {
           const dateTimeSchema: DateTimeSchema = {
             type: 'string',
             format: 'date-time',
             default: dateTimeFormat.default
           };
-          (newSchema as Record<string, SchemaObject>)[key] = dateTimeSchema;
+          (newSchema.properties as Record<string, SchemaObject>)[key] = dateTimeSchema;
         }
-      } else {
-        (newSchema as Record<string, SchemaObject>)[key] = transformDateProperties(value);
-      }
-    }
-  });
+      } else if (isSchemaObject(prop)) {
+        (newSchema.properties as Record<string, SchemaObject>)[key] = transformDateProperties(prop);
+      }
+    }
+  }
+  // Arrays / tuples
+  if (newSchema.items) newSchema.items = transformDateProperties(newSchema.items) as any;
+  // oneOf/allOf/anyOf
+  for (const k of ['oneOf','allOf','anyOf'] as const) {
+    if (Array.isArray((newSchema as any)[k])) {
+      (newSchema as any)[k] = (newSchema as any)[k].map((s: SchemaObject) => transformDateProperties(s));
+    }
+  }
   return newSchema;
 }

97-98: Invalid CSS hex color "#faf9a".

Hex must be 3/6/8 digits; this is 5 and will be ignored. Use "#faf9aa" or intended value.

-                color: #faf9a;
+                color: #faf9aa;
🧹 Nitpick comments (22)
tsconfig.json (1)

30-30: "declaration": true + "noEmit": true = no-op; remove for clarity.

Since noEmit prevents any output, keeping declaration here is confusing (d.ts are emitted from tsconfig.dts.json anyway).

Apply:

-    "declaration": true,
+    // Declarations are built via tsconfig.dts.json
CHANGELOG.md (1)

1-11: Polish CHANGELOG.md entry for 1.3.2

-# 1.3.2 - 22 Aug 2025
-Feature:
-- add `withHeader` for adding custom headers to response schema
-- spread all possible path for optional params
-
-Breaking change:
+# 1.3.2 - 22 Aug 2025
+Features:
+- add `withHeaders` for adding custom headers to response schema
+- spread all possible paths for optional params
+
+Breaking changes:
 - rename `@elysiajs/swagger` to `@elysiajs/openapi`
 - map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties
 - rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively
-- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles`	 to property of `excludes`
+- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property `excludes`
src/types.ts (3)

31-55: Fix typos and CDN domain in Scalar docs.

“Lease” → “Leave”; “jsdeliver.net” → “jsdelivr.com”; remove stray apostrophe.

-	 *'
+	 *
...
-		 * Lease blank to use default jsdeliver.net CDN
+		 * Leave blank to use default jsdelivr.com CDN

11-15: JSDoc references Swagger 2.0 but the type is OpenAPI v3.

Update wording and link.

-	 * Customize Swagger config, refers to Swagger 2.0 config
-	 *
-	 * @see https://swagger.io/specification/v2/
+	 * Customize OpenAPI document (3.x)
+	 *
+	 * @see https://swagger.io/specification/

100-106: Swagger CDN default version in docs mismatches runtime default.

src/index.ts defaults to 5.9.0; fix JSDoc.

-		 * @default 4.18.2
+		 * @default 5.9.0

If you intend to keep 4.18.2, update src/index.ts accordingly.

test/openapi.test.ts (1)

6-12: Test expects a duplicate '/user/name'; prefer dedup + order-insensitive assertion.

Prevents brittle tests and reflects unique route expansions.

-		expect(getPossiblePath('/user/:user?/name/:name?')).toEqual([
-			'/user/:user/name/:name',
-			'/user/name/:name',
-			'/user/name',
-			'/user/:user/name',
-			'/user/name'
-		])
+		const paths = getPossiblePath('/user/:user?/name/:name?')
+		expect([...new Set(paths)].sort()).toEqual(
+			[
+				'/user/:user/name/:name',
+				'/user/name/:name',
+				'/user/name',
+				'/user/:user/name'
+			].sort()
+		)

Optionally dedupe in getPossiblePath itself to avoid emitting duplicates.

test/validate-schema.test.ts (1)

82-83: Assert status and content-type before parsing.

Improves failure signals and guards against non-JSON responses.

-	const res = await app.handle(req('/openapi/json')).then((x) => x.json())
-	await SwaggerParser.validate(res).catch((err) => fail(err))
+	const response = await app.handle(req('/openapi/json'))
+	expect(response.status).toBe(200)
+	expect(response.headers.get('content-type') ?? '').toContain('application/json')
+	const spec = await response.json()
+	await SwaggerParser.validate(spec).catch((err) => fail(err))
src/openapi.ts (6)

37-51: Deduplicate expanded optional-path variants

getPossiblePath can return duplicates when multiple optional params exist. Deduplicate to reduce redundant work.

-  return paths
+  return Array.from(new Set(paths))

115-117: Avoid mutating route.hooks when converting string schemas to $ref

You overwrite hooks.* in-place (params/query/headers/cookie/body/response). This mutates the original route definitions and can cause side-effects in other plugins.

Create local variables (e.g., const paramsSchema = typeof hooks.params === 'string' ? toRef(hooks.params) : hooks.params) and use those for building the OpenAPI objects.

Also applies to: 133-135, 152-154, 171-173, 193-194, 281-283


12-12: Verify toRef correctness with Elysia/TypeBox

t.Ref usually references a symbol/name, not a full JSON Pointer. Using "#/components/schemas/${name}" may generate an invalid $ref.

If needed, switch to emitting a plain OAS ref:

-const toRef = (name: string) => t.Ref(`#/components/schemas/${name}`)
+const toRef = (name: string) => ({ $ref: `#/components/schemas/${name}` })

Confirm with the existing validator tests and real clients (Swagger UI/Scalar). I can add a focused test if you want.


62-67: exclude.staticFile is unused

The option is read but never applied; either implement filtering for static-file routes or remove the option to avoid confusion.


150-167: Headers/query/cookie: preserve required semantics for nested/array types

If any param schemas are arrays or have nested objects, OpenAPI expects style/explode flags and accurate “required” at the parameter level. Current mapping may misrepresent such cases.

Optionally add support for style/explode and handle array formats (e.g., csv/ssv) when detected. I can draft a helper.


251-294: Void/Null/Undefined responses deviate from OAS 3.0 content shape

Storing the schema object directly under responses[*].content is non-standard (though your tests expect it). This may trip external validators/tools.

Consider emitting either no content or an empty content object for 204/void-like types while keeping description/headers at the ResponseObject level. If you want, I can provide a compatibility toggle.

example/index.ts (1)

55-57: Reuse declared schema constant to avoid duplication

You already declared const schema; reuse it here instead of recreating the same object.

-  test: t.Literal('hello')
-}),
test/index.test.ts (3)

265-274: Add assertions for operationId uniqueness on .all expansion

Currently only counts methods. Also assert that operationIds are unique across the expanded methods.

Example:

const ops = response.paths['/all']
expect(new Set(Object.values(ops).map((o: any) => o.operationId)).size)
  .toBe(Object.keys(ops).length)

114-128: Tests for exclude paths (RegExp) and case-insensitive methods would harden behavior

Add specs that:

  • exclude.paths accepts a RegExp (e.g., /^/internal/)
  • exclude.methods honors 'OPTIONS' regardless of case

I can draft these tests if you share preferred route fixtures.


148-169: Non-standard 204 content shape: confirm long-term intent

These tests lock in a non-OAS content representation for void/undefined/null. If intentional, consider documenting this quirk; if not, adjust expectations.

I can add an additional validator step gated behind a flag to ensure downstream tooling compatibility.

Also applies to: 171-196, 198-219

src/index.ts (5)

54-54: Rename plugin identifier to match new name.

Update the plugin name for clarity and tooling consistency.

-const app = new Elysia({ name: '@elysiajs/swagger' })
+const app = new Elysia({ name: '@elysiajs/openapi' })

95-97: Use the correct charset token in Content-Type.

Minor correctness fix.

- 'content-type': 'text/html; charset=utf8'
+ 'content-type': 'text/html; charset=utf-8'

23-23: Normalize specPath to avoid double slashes when path ends with '/'.

Prevents '/openapi//json'.

-specPath = `${path}/json`,
+specPath = `${path.endsWith('/') ? path.slice(0, -1) : path}/json`,

36-41: Avoid 'latest' for Scalar bundle by default.

Pin to a known version for reproducibility, or expose a constant for easy updates.

-  version: scalarVersion = 'latest',
+  version: scalarVersion = '1.25.0',

157-157: Re-export OpenAPIProvider alongside config type.

Improves DX when importing types from the package.

-export type { ElysiaOpenAPIConfig }
+export type { ElysiaOpenAPIConfig, OpenAPIProvider }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 63ad74a and 598fea4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • CHANGELOG.md (1 hunks)
  • example/index.ts (2 hunks)
  • example/index2.ts (0 hunks)
  • example/index3.ts (0 hunks)
  • package.json (4 hunks)
  • src/index.ts (2 hunks)
  • src/openapi.ts (1 hunks)
  • src/swagger/index.ts (1 hunks)
  • src/types.ts (4 hunks)
  • src/utils.ts (0 hunks)
  • test/index.test.ts (10 hunks)
  • test/openapi.test.ts (1 hunks)
  • test/validate-schema.test.ts (1 hunks)
  • tsconfig.dts.json (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (3)
  • example/index3.ts
  • example/index2.ts
  • src/utils.ts
🧰 Additional context used
🧬 Code graph analysis (7)
test/openapi.test.ts (1)
src/openapi.ts (1)
  • getPossiblePath (37-51)
src/openapi.ts (1)
src/types.ts (1)
  • ElysiaOpenAPIConfig (7-135)
test/validate-schema.test.ts (1)
src/index.ts (1)
  • openapi (17-154)
test/index.test.ts (1)
src/index.ts (1)
  • openapi (17-154)
src/types.ts (1)
src/index.ts (1)
  • ElysiaOpenAPIConfig (157-157)
src/index.ts (4)
src/types.ts (2)
  • OpenAPIProvider (5-5)
  • ElysiaOpenAPIConfig (7-135)
src/swagger/index.ts (1)
  • SwaggerUIRender (51-127)
src/scalar/index.ts (1)
  • ScalarRender (5-48)
src/openapi.ts (1)
  • toOpenAPISchema (58-331)
example/index.ts (2)
src/index.ts (2)
  • openapi (17-154)
  • withHeaders (156-156)
src/openapi.ts (1)
  • withHeaders (333-336)
🪛 LanguageTool
CHANGELOG.md

[grammar] ~1-~1: There might be a mistake here.
Context: # 1.3.2 - 22 Aug 2025 Feature: - add withHeader for adding c...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ...rty of excludes # 1.3.1 - 28 Jun 2025 Bug fix: - Using relative path for specP...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

10-10: Hard tabs
Column: 76

(MD010, no-hard-tabs)

🔇 Additional comments (7)
tsconfig.dts.json (1)

31-31: moduleResolution 'bundler' looks safe—verify build config

  • No require() calls or node: imports found in TS/TSX sources, so deep-path resolution shouldn’t break.
  • Ensure your tsup/bun build is pointed at tsconfig.dts.json (or has its tsconfig option set) so .d.ts outputs honor moduleResolution: "bundler".
tsconfig.json (1)

30-30: Verify moduleResolution "bundler" compatibility across tooling. No deep or path-mapped imports detected in src/ (only standard packages like elysia, openapi-types, @scalar/*), but ensure ESLint (parserOptions.project), ts-node/bun test runners, and IDEs all consume this tsconfig.json or have matching overrides.

src/swagger/index.ts (1)

31-33: LGTM: Arrow function tightening preserves narrowing semantics.

Predicate still narrows to OpenAPIV3.SchemaObject correctly and behavior is unchanged.

package.json (2)

10-19: Top-level fields remain consistent with exports — LGTM.

main/module/types align with "." export.


35-44: Verify Swagger export files are published. After running the build, confirm that the following paths exist to match the exports in package.json:

  • dist/swagger/index.d.ts
  • dist/swagger/index.mjs
  • dist/cjs/swagger/index.js
  • dist/swagger/types.d.ts
  • dist/swagger/types.mjs
  • dist/cjs/swagger/types.js
example/index.ts (1)

58-67: Nice use of withHeaders for 204 example

Clear demonstration of header attachment on a no-content response.

src/index.ts (1)

17-20: No changes needed – TS @^5.5.3 supports const generics. Verified the repo’s TypeScript version (package.json) is ^5.5.3, which meets the ≥5.0 requirement.

Comment on lines +20 to 24
"./openapi": {
"types": "./dist/openapi.d.ts",
"import": "./dist/openapi.mjs",
"require": "./dist/cjs/openapi.js"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure subpath export "./openapi" is the documented entry.

Looks good; verify README and examples import from "@elysiajs/openapi/openapi".


🏁 Script executed:

#!/bin/bash
rg -nP "@elysiajs/(swagger|openapi)" -g 'README.md' -g 'docs/**' -S

Length of output: 194


🏁 Script executed:

#!/bin/bash
# Search for openapi references in all markdown files and examples directory
rg -nP "@elysiajs/openapi" -g '*.md' -S
rg -nP "@elysiajs/openapi" -g 'examples/**' -S

Length of output: 302


Update README and examples imports to use the new subpath
Replace all occurrences of @elysiajs/swagger in README.md (e.g. lines 1, 6, 12) with @elysiajs/openapi/openapi, and ensure any example files import from @elysiajs/openapi/openapi rather than the old package.

🤖 Prompt for AI Agents
In package.json around lines 20 to 24, the package subpath was changed to
"./openapi" but README and example files still import from the old package name;
update README.md (e.g. lines 1, 6, 12) and all example source files to replace
any occurrences of "@elysiajs/swagger" with "@elysiajs/openapi/openapi" and
update import statements accordingly so examples import from
"@elysiajs/openapi/openapi" (ensure default/named import forms remain correct
and run a quick project-wide search to catch all occurrences).

Comment on lines +57 to 63
"homepage": "https://github.com/elysiajs/elysia-openapi",
"repository": {
"type": "git",
"url": "https://github.com/elysiajs/elysia-swagger"
"url": "https://github.com/elysiajs/elysia-openapi"
},
"bugs": "https://github.com/elysiajs/elysia-swagger/issues",
"bugs": "https://github.com/elysiajs/elysia-openapi/issues",
"license": "MIT",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Repository/homepage/bugs URLs changed — validate correctness before release.

Avoid publishing with stale links.


🏁 Script executed:

#!/bin/bash
for u in \
  "https://github.com/elysiajs/elysia-openapi" \
  "https://github.com/elysiajs/elysia-openapi/issues"; do
  echo "Check: $u"
done

Length of output: 447


🏁 Script executed:

#!/bin/bash
for u in \
  "https://github.com/elysiajs/elysia-openapi" \
  "https://github.com/elysiajs/elysia-openapi/issues"; do
  echo "Checking: $u"
  curl -o /dev/null -s -w "HTTP %{http_code} - Final URL: %{url_effective}\n" -L "$u"
done

Length of output: 851


Broken homepage/repository/bugs URLs — update before release.
The URLs in package.json (lines 57–63) currently return HTTP 404. Replace them with the correct GitHub repository and issue-tracker links before publishing.

🤖 Prompt for AI Agents
In package.json around lines 57 to 63, the homepage/repository/bugs URLs are
invalid and must be updated before release; replace "homepage",
"repository.url", and "bugs" with the correct GitHub repository URL and the
repository's issues URL (e.g., the canonical https://github.com/<org>/<repo> and
https://github.com/<org>/<repo>/issues), ensure "repository" remains an object
with type and url, and verify there are no trailing commas or malformed JSON
after the change.

package.json Outdated
Comment on lines 75 to 86
"@apidevtools/swagger-parser": "^10.1.0",
"@types/bun": "1.1.14",
"@scalar/types": "^0.0.12",
"elysia": "1.3.0-exp.71",
"eslint": "9.6.0",
"tsup": "^8.1.0",
"typescript": "^5.5.3"
},
"dependencies": {
"@scalar/themes": "^0.9.52",
"@scalar/types": "^0.0.12",
"openapi-types": "^12.1.3",
"pathe": "^1.1.2"
"openapi-types": "^12.1.3"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Move @scalar/types to devDependencies only if not used at runtime.

If imported from runtime code, keep under dependencies to avoid prod install failures.


🏁 Script executed:

#!/bin/bash
rg -nP "from\s+['\"]@scalar/types['\"]|require\(['\"]@scalar/types['\"]\)" -g 'src/**' -S

Length of output: 331


Move @scalar/types to devDependencies
Since it’s only ever imported via import type (all uses are erased at build time), it isn’t needed at runtime and can be safely relocated from dependencies to devDependencies.

🤖 Prompt for AI Agents
package.json around lines 75 to 86: @scalar/types is listed under "dependencies"
but only used via import type so it should be moved to "devDependencies"; edit
package.json to remove the "@scalar/types" entry from the dependencies block and
add it to the devDependencies block (same version "^0.0.12"), then run your
package manager (npm/yarn/pnpm install) to update node_modules and the lockfile,
and verify builds/tests to ensure no runtime dependency breakage.

Comment on lines 49 to 50

const app = new Elysia({ name: '@elysiajs/swagger' })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix broken spec URL: UI currently fetches /openapi/openapi/json (relative path).

Don't strip the leading slash. Use an absolute URL for both Swagger UI and Scalar.

-const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath
+const specURL = specPath.startsWith('/') ? specPath : `/${specPath}`
...
-                  {
-                    url: relativePath,
+                  {
+                    url: specURL,
                     dom_id: '#swagger-ui',
                     ...swaggerOptions
                   },
...
-                {
-                  spec: {
-                    url: relativePath,
+                {
+                  spec: {
+                    url: specURL,
                     ...scalarConfig.spec
                   },

Also applies to: 66-76, 83-91

🤖 Prompt for AI Agents
In src/index.ts around lines 49-50 (and also update similarly at 66-76 and
83-91), the code strips the leading slash from specPath which produces a
relative URL like /openapi/openapi/json => openapi/openapi/json; change logic to
keep the leading slash and construct an absolute URL for Swagger UI and Scalar
(e.g., ensure spec URL begins with '/' or a full origin + path) so both
integrations fetch the correct absolute path; update all three locations to stop
slicing off the leading '/' and build/assign the absolute spec URL consistently.

Comment on lines 51 to 53
let totalRoutes = 0
let cachedSchema: OpenAPIV3.Document | undefined

const page = new Response(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Stale schema cache: route-count check uses plugin-local routes, not global routes.

Compare against global routes to avoid returning an out-of-date schema.

- if (totalRoutes === app.routes.length) return cachedSchema
-
- totalRoutes = app.routes.length
+ // Prefer global route count; fallback to local if unavailable
+ // @ts-ignore private API
+ const currentRouteCount =
+   // @ts-ignore
+   typeof (app as any).getGlobalRoutes === 'function'
+     // @ts-ignore
+     ? (app as any).getGlobalRoutes().length
+     : app.routes.length
+ if (totalRoutes === currentRouteCount) return cachedSchema
+ totalRoutes = currentRouteCount

Also applies to: 109-114

🤖 Prompt for AI Agents
In src/index.ts around lines 51-53 (and similarly 109-114), the cachedSchema
invalidation uses a plugin-local totalRoutes counter which can be stale; change
the check to compare against the application's global route count (or a single
shared/global routes array/length) when deciding to reuse cachedSchema. Update
the code to read the actual global routes length (or derive it from the
application's router/registry) instead of the plugin-local totalRoutes, and
invalidate/update cachedSchema whenever the global route count differs.

src/openapi.ts Outdated
Comment on lines 62 to 74
const {
methods: excludeMethods = ['OPTIONS'],
staticFile: excludeStaticFile = true,
tags: excludeTags
} = exclude ?? {}

const excludePaths = Array.isArray(exclude?.paths)
? exclude.paths
: typeof exclude?.paths !== 'undefined'
? [exclude.paths]
: []

const paths: OpenAPIV3.PathsObject = {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix exclude.methods case and add RegExp support for exclude.paths

  • methods: you compare lowercase route methods to an uppercase default ['OPTIONS'], so exclusions won’t trigger.
  • paths: RegExp values in exclude.paths are not honored (using includes).

Apply:

 const {
-  methods: excludeMethods = ['OPTIONS'],
+  methods: excludeMethods = ['OPTIONS'],
   staticFile: excludeStaticFile = true,
   tags: excludeTags
 } = exclude ?? {}

-const excludePaths = Array.isArray(exclude?.paths)
-  ? exclude.paths
-  : typeof exclude?.paths !== 'undefined'
-    ? [exclude.paths]
-    : []
+const excludePaths = Array.isArray(exclude?.paths)
+  ? exclude.paths
+  : typeof exclude?.paths !== 'undefined'
+    ? [exclude.paths]
+    : []
+const excludeMethodsNormalized = excludeMethods.map((m) => m.toLowerCase())
+const shouldExcludePath = (p: string) =>
+  excludePaths.some((x) => (typeof x === 'string' ? x === p : x.test(p)))

 ...
- if (excludePaths.includes(route.path)) continue
- if (excludeMethods.includes(method)) continue
+ if (shouldExcludePath(route.path)) continue
+ if (excludeMethodsNormalized.includes(method)) continue

Also applies to: 84-86

🤖 Prompt for AI Agents
In src/openapi.ts around lines 62 to 74 (also apply same fix at lines 84-86),
the exclude.methods default is uppercase ['OPTIONS'] and the code compares it to
lowercase route methods so exclusions never match; change to normalize both
sides to the same case (preferably store default as ['options'] or call
.toLowerCase() on exclude methods and route.method before comparing). For
exclude.paths, support RegExp entries by treating exclude.paths as an array of
strings or RegExps and when checking a route path use Array.prototype.some to
return true if any entry is a RegExp that tests the path or a string that equals
the path (instead of using includes), ensuring both single value and array forms
are handled.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/scalar/index.ts (1)

132-140: Escape injected text and fix Open Graph meta attribute

Avoid rendering “undefined” and prevent accidental HTML injection. Also OG tags use property, not name.

-    <title>${info.title}</title>
+    <title>${escapeHtml(info.title)}</title>
@@
-        content="${info.description}"
+        content="${escapeHtml(info.description ?? '')}"
@@
-        name="og:description"
-        content="${info.description}"
+        property="og:description"
+        content="${escapeHtml(info.description ?? '')}"

Add this helper (outside the shown range, near the top of the file):

const escapeHtml = (s: string) =>
  s.replace(/[&<>"']/g, (c) => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;', "'":'&#39;' }[c]!))
src/swagger/index.ts (1)

103-111: Escape dynamic HTML to prevent injection

info.title and info.description are interpolated into HTML/attributes without escaping.

-    <title>${info.title}</title>
+    <title>${escapeHtml(info.title)}</title>
@@
-        content="${info.description}"
+        content="${escapeHtml(info.description ?? '')}"
@@
-        content="${info.description}"
+        content="${escapeHtml(info.description ?? '')}"

Add this helper anywhere above:

function escapeHtml(s?: string) {
	const str = String(s ?? '')
	const map: Record<string, string> = {
		'&': '&amp;',
		'<': '&lt;',
		'>': '&gt;',
		'"': '&quot;',
		"'": '&#39;'
	}
	return str.replace(/[&<>"']/g, (c) => map[c])
}
♻️ Duplicate comments (6)
package.json (3)

20-24: New OpenAPI export path added successfully.

The new ./openapi subpath export provides proper access to the OpenAPI functionality with correct TypeScript definitions and build outputs.


57-62: Repository URLs need verification before release.

The URLs point to elysiajs/elysia-openapi but need verification that this repository exists and is properly configured.


77-77: Move @scalar/types to devDependencies for build optimization.

Since @scalar/types is only used for TypeScript type information (via import type), it should be in devDependencies rather than dependencies to reduce production install size.

src/index.ts (1)

40-41: Duplicate comment - broken spec URL issue persists.

The relative path construction still creates incorrect URLs by stripping the leading slash, resulting in relative URLs like openapi/json instead of absolute paths like /openapi/json.

src/types.ts (2)

7-11: Duplicate comment - Path generic default mismatch.

The Path generic still defaults to '/swagger' instead of '/openapi', creating a mismatch with the runtime default in src/index.ts where path = '/openapi'.


34-34: Duplicate comment - Use Provider generic for type narrowing.

The provider field should use the Provider generic instead of the concrete OpenAPIProvider type to enable proper type narrowing when callers specify a provider.

🧹 Nitpick comments (8)
test/node/cjs/index.js (1)

3-6: Also assert withHeaders to catch partial export regressions

Expand the check to ensure both primary exports are present.

Apply this diff:

-const { openapi } = require('@elysiajs/openapi')
+const { openapi, withHeaders } = require('@elysiajs/openapi')

-if (typeof openapi !== 'function') throw new Error('❌ CommonJS Node.js failed')
+if (typeof openapi !== 'function' || typeof withHeaders !== 'function')
+  throw new Error('❌ CommonJS Node.js failed')
src/scalar/index.ts (2)

3-3: Use a type-only import to avoid emitting runtime code

ElysiaOpenAPIConfig is types-only; import it with import type to prevent an unnecessary JS import.

-import { ElysiaOpenAPIConfig } from '../types'
+import type { ElysiaOpenAPIConfig } from '../types'

141-145: Minor: move charset meta first for best practice

Place <meta charset="utf-8" /> before other tags to ensure correct parsing.

example/index.ts (1)

52-69: Align declared 204 with runtime behavior

The handler currently returns a 200. Consider showing how to emit 204 with the custom header to match the schema.

Example:

.get('/', ({ set }) => {
  set.status = 204
  set.headers['X-Custom-Header'] = 'Elysia'
}, { response: { /* keep 200/204 schemas as-is */ }})
src/swagger/index.ts (4)

71-77: Pin default Swagger UI version; align with docs

Types say default is 4.18.2, but here it’s “latest”. Pinning avoids CDN drift and surprises; you can still override via config.

-	const {
-		version = 'latest',
-		theme = `https://unpkg.com/swagger-ui-dist@${version ?? 'latest'}/swagger-ui.css`,
+	const {
+		version = '4.18.2',
+		theme = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`,
 		cdn = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js`,
 		autoDarkMode = true,
 		...rest
 	} = config

If the project intends to move to “latest”, update the JSDoc in src/types.ts to match. Do you want me to submit that follow-up?


113-130: Fix invalid hex color in dark-mode CSS

#faf9a is a 5-digit hex and gets ignored.

-        color: #faf9a;
+        color: #faf9fa;

140-140: Set an explicit crossorigin mode

For broader browser compatibility and clearer intent.

-    <script src="${cdn}" crossorigin></script>
+    <script src="${cdn}" crossorigin="anonymous"></script>

Optional: consider adding SRI integrity when using the default CDN.


79-87: Minor: keep replacer only where needed

You already exclude function-valued options; no need to stringify/parse the whole object earlier. The updated diff above scopes sanitization to spec and the final UI options payload, avoiding unnecessary parse/serialize work.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 598fea4 and d028a9d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CHANGELOG.md (1 hunks)
  • build.ts (1 hunks)
  • example/index.ts (2 hunks)
  • package.json (4 hunks)
  • src/index.ts (2 hunks)
  • src/openapi.ts (1 hunks)
  • src/scalar/index.ts (2 hunks)
  • src/swagger/index.ts (2 hunks)
  • src/types.ts (4 hunks)
  • test/node/cjs/index.js (1 hunks)
  • test/node/cjs/package.json (1 hunks)
  • test/node/esm/index.js (1 hunks)
  • test/node/esm/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • build.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openapi.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/scalar/index.ts (1)
src/types.ts (1)
  • ElysiaOpenAPIConfig (7-149)
src/swagger/index.ts (2)
src/types.ts (1)
  • ElysiaOpenAPIConfig (7-149)
src/swagger/types.ts (1)
  • SwaggerUIOptions (6-266)
🪛 LanguageTool
CHANGELOG.md

[grammar] ~1-~1: There might be a mistake here.
Context: # 1.3.2 - 22 Aug 2025 Feature: - add withHeader for adding c...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...rty of excludes # 1.3.1 - 28 Jun 2025 Bug fix: - Using relative path for specP...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

12-12: Hard tabs
Column: 76

(MD010, no-hard-tabs)

🔇 Additional comments (25)
test/node/esm/package.json (2)

4-4: LGTM! Dependency update aligns with the OpenAPI refactoring.

The change from @elysiajs/swagger to @elysiajs/openapi is consistent with the broader package rename across the codebase.


6-6: File formatting improved with trailing newline.

Good addition of the trailing newline for consistent file formatting.

package.json (4)

2-4: Package identity successfully updated to OpenAPI.

The package name change from @elysiajs/swagger to @elysiajs/openapi and updated description properly reflects the transition to OpenAPI documentation focus.


35-49: Swagger compatibility exports and types restoration look good.

The addition of ./swagger and ./swagger/types exports maintains backward compatibility, while the restoration of ./types export ensures existing type consumers continue to work.


53-56: Keywords appropriately updated for OpenAPI focus.

Adding "openapi" to the keywords while retaining "swagger" and "scalar" ensures discoverability across the ecosystem.


84-84: Dependency cleanup aligns with OpenAPI focus.

The updated openapi-types version and removal of unused dependencies like @scalar/themes and pathe streamlines the dependency list.

test/node/cjs/package.json (2)

4-4: LGTM! CommonJS test dependency properly updated.

The dependency change from @elysiajs/swagger to @elysiajs/openapi ensures the CommonJS test environment uses the new package correctly.


6-6: File formatting improved with trailing newline.

Good addition for consistent file formatting across the codebase.

test/node/esm/index.js (3)

1-1: Bun guard appropriately condensed to single line.

The guard logic remains functionally identical while improving readability.


3-3: Import successfully updated to OpenAPI.

The import change from swagger to openapi from the new package name correctly reflects the API surface changes.


5-5: Runtime check properly updated for OpenAPI function.

The validation now correctly checks for the openapi function instead of swagger, ensuring the ESM test validates the right export.

test/node/cjs/index.js (1)

3-5: Rename to OpenAPI looks good

Importing and validating the openapi export is correct for the CJS test.

example/index.ts (2)

58-66: Nice use of withHeaders for a typed 204 response

Demonstrates header typing clearly.


76-77: Double-check parse options spelling

Verify that 'formdata' is the correct token for Elysia’s parser (vs 'formData' or similar), to avoid silent no-parse.

Run a quick grep against Elysia’s type defs or docs to confirm accepted values.

src/index.ts (6)

6-6: Import addition aligns with new architecture.

The import of toOpenAPISchema from the new openapi module correctly supports the architectural shift from manual route scanning to the centralized OpenAPI generation approach.


17-30: Generic signature properly structured.

The new generic parameters with proper defaults provide good type safety and flexibility. The function signature follows TypeScript best practices.


42-44: Caching mechanism properly implemented.

The route-based cache invalidation using totalRoutes and cachedSchema is a good optimization to avoid unnecessary OpenAPI schema regeneration.


45-79: Provider-driven route registration is well-designed.

The conditional route registration based on the provider parameter is elegant, allowing users to disable UI routes entirely by setting provider to null.


87-90: Centralized schema generation improves maintainability.

The shift to using toOpenAPISchema instead of manual path building is a significant improvement that centralizes OpenAPI generation logic.


129-132: Public API exports are appropriate.

The re-exports of toOpenAPISchema, withHeaders, and the type export provide a clean public API surface.

src/types.ts (4)

5-5: OpenAPIProvider type is well-defined.

The union type including null is appropriate for allowing complete disabling of UI routes.


40-61: Scalar configuration structure is well-organized.

The nested structure with version and CDN overrides provides good flexibility for customizing Scalar deployments.


79-125: Swagger UI configuration comprehensive and well-structured.

The configuration options properly exclude function-based options that can't be serialized, and the additional theme, version, autoDarkMode, and CDN options provide good customization capabilities.


127-148: Exclusion configuration is comprehensive.

The unified exclusion structure covering paths, methods, tags, and static files provides good flexibility for customizing what gets included in the OpenAPI documentation.

src/swagger/index.ts (1)

19-28: Scope the date-key check to properties only (contextual note)

isDateTimeProperty relies on the key name; after the recursion fix, it runs only for schema properties, which matches intent. No change needed—just flagging to ensure future edits don’t call it on non-property keys.

Comment on lines +1 to +13
# 1.3.2 - 22 Aug 2025
Feature:
- add `withHeader` for adding custom headers to response schema
- spread all possible path for optional params
- provider can be `null` to disable provider
- export `toOpenAPI` to generate spec programmatically

Breaking change:
- rename `@elysiajs/swagger` to `@elysiajs/openapi`
- map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties
- rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively
- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property of `excludes`

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Document the breaking changes clearly but fix formatting issues.

The changelog properly documents the major refactoring from Swagger to OpenAPI, but there are formatting issues that need to be addressed.

Apply this diff to fix the formatting issues:

-# 1.3.2 - 22 Aug 2025
-Feature:
+# 1.3.2 - 22 Aug 2025
+Features:
 - add `withHeader` for adding custom headers to response schema
 - spread all possible path for optional params
 - provider can be `null` to disable provider
 - export `toOpenAPI` to generate spec programmatically

-Breaking change:
+Breaking changes:
 - rename `@elysiajs/swagger` to `@elysiajs/openapi`
 - map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties
 - rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively
-- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles`	 to property of `excludes`
+- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property of `excludes`

-# 1.3.1 - 28 Jun 2025
+# 1.3.1 - 28 Jun 2025
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 1.3.2 - 22 Aug 2025
Feature:
- add `withHeader` for adding custom headers to response schema
- spread all possible path for optional params
- provider can be `null` to disable provider
- export `toOpenAPI` to generate spec programmatically
Breaking change:
- rename `@elysiajs/swagger` to `@elysiajs/openapi`
- map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties
- rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively
- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property of `excludes`
# 1.3.2 - 22 Aug 2025
Features:
- add `withHeader` for adding custom headers to response schema
- spread all possible path for optional params
- provider can be `null` to disable provider
- export `toOpenAPI` to generate spec programmatically
Breaking changes:
- rename `@elysiajs/swagger` to `@elysiajs/openapi`
- map all `swagger`, and `scalar` prefix to respective `swagger` and `scalar` properties
- rename `swaggerConfig`, and `scalarConfig` to `swagger` and `scalar` respectively
- map `excludePaths`, `excludeMethods`, `excludeTags`, `excludeStaticFiles` to property of `excludes`
# 1.3.1 - 28 Jun 2025
🧰 Tools
🪛 LanguageTool

[grammar] ~1-~1: There might be a mistake here.
Context: # 1.3.2 - 22 Aug 2025 Feature: - add withHeader for adding c...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)

12-12: Hard tabs
Column: 76

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In CHANGELOG.md around lines 1 to 13, tidy the formatting of the release notes:
ensure the top-level heading is "## 1.3.2 - 22 Aug 2025" (use a consistent
markdown header level), add blank lines between the header and sections, change
"Feature:" and "Breaking change:" to bold section headings like "### Features"
and "### Breaking changes", normalize bullet items to start with "- " and keep
code identifiers wrapped in backticks (e.g., `withHeader`, `toOpenAPI`), rename
"Breaking change:" to plural and make each mapping line consistent (e.g., "-
rename `@elysiajs/swagger` to `@elysiajs/openapi`"), fix the tab/spacing issue
on the last mapping line so it reads "- map `excludePaths`, `excludeMethods`,
`excludeTags`, `excludeStaticFiles` to properties of `excludes`", and ensure
consistent capitalization and punctuation across bullets.

id="api-reference"
data-url="${config.spec?.url}"
data-configuration='${JSON.stringify(config)}'
data-url="${config.url}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Restore compatibility: fall back to spec.url when config.url is absent

ApiReferenceConfiguration commonly uses spec.url. Using only config.url risks an empty data-url and a blank UI.

-      data-url="${config.url}"
+      data-url="${(config as any).url ?? (config as any).spec?.url ?? ''}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data-url="${config.url}"
data-url="${(config as any).url ?? (config as any).spec?.url ?? ''}"
🤖 Prompt for AI Agents
In src/scalar/index.ts around line 157, the template sets data-url using only
config.url which can be empty; update it to fall back to spec.url when
config.url is absent (e.g. use config.url || spec.url or equivalent) so data-url
is never blank, and ensure the code accesses spec safely (handle undefined spec)
and prefer config.url when present otherwise use spec.url.

? cdn
: `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${version}/dist/browser/standalone.min.js`
}" crossorigin></script>
<script src="${config.cdn}" crossorigin></script>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: provide a default CDN URL (and honor version) when cdn is not set

scalar.cdn is optional per types. Without a fallback the UI will not load.

-    <script src="${config.cdn}" crossorigin></script>
+    <script
+      src="${
+        config.cdn ||
+        `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${config.version ?? 'latest'}/dist/browser/standalone.js`
+      }"
+      crossorigin="anonymous"
+      defer
+    ></script>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<script src="${config.cdn}" crossorigin></script>
<script
src="${
config.cdn ||
`https://cdn.jsdelivr.net/npm/@scalar/api-reference@${config.version ?? 'latest'}/dist/browser/standalone.js`
}"
crossorigin="anonymous"
defer
></script>
🤖 Prompt for AI Agents
In src/scalar/index.ts around line 160, the template uses config.cdn directly
which breaks the UI when scalar.cdn is undefined; update the code to compute a
fallback CDN URL that honors the configured scalar version (e.g. use config.cdn
if present, otherwise build a default URL that includes config.version pointing
to the scalar bundle on a public CDN), then use that resolved cdn value in the
script tag and keep the crossorigin attribute unchanged.

Comment on lines +13 to 17
function isSchemaObject(
schema: SchemaObject
): schema is OpenAPIV3.SchemaObject {
return 'type' in schema || 'properties' in schema || 'items' in schema
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Date transform never reaches schema.properties; type guard is unreliable

  • isSchemaObject checks 'type' in schema || 'properties' in schema || 'items' in schema'. This can incorrectly return true for a properties dictionary if it contains a property named type.
  • transformDateProperties iterates top-level keys of a schema object and doesn’t traverse properties, items, allOf/oneOf/anyOf, additionalProperties, etc. As a result, createdAt/updatedAt fields inside properties won’t be transformed.

Apply:

-function isSchemaObject(
-	schema: SchemaObject
-): schema is OpenAPIV3.SchemaObject {
-	return 'type' in schema || 'properties' in schema || 'items' in schema
-}
+function isSchemaObject(schema: SchemaObject): schema is OpenAPIV3.SchemaObject {
+	// ReferenceObject always has $ref; SchemaObject never does
+	return !('$ref' in (schema as OpenAPIV3.ReferenceObject))
+}
@@
-export function transformDateProperties(schema: SchemaObject): SchemaObject {
-	if (
-		!isSchemaObject(schema) ||
-		typeof schema !== 'object' ||
-		schema === null
-	)
-		return schema
-
-	const newSchema: OpenAPIV3.SchemaObject = { ...schema }
-
-	Object.entries(newSchema).forEach(([key, value]) => {
-		if (isSchemaObject(value)) {
-			if (isDateTimeProperty(key, value)) {
-				const dateTimeFormat = value.anyOf?.find(
-					(item): item is OpenAPIV3.SchemaObject =>
-						isSchemaObject(item) && item.format === 'date-time'
-				)
-
-				if (dateTimeFormat) {
-					const dateTimeSchema: DateTimeSchema = {
-						type: 'string',
-						format: 'date-time',
-						default: dateTimeFormat.default
-					}
-					;(newSchema as Record<string, SchemaObject>)[key] =
-						dateTimeSchema
-				}
-			} else {
-				;(newSchema as Record<string, SchemaObject>)[key] =
-					transformDateProperties(value)
-			}
-		}
-	})
-
-	return newSchema
-}
+export function transformDateProperties(schema: SchemaObject): SchemaObject {
+	if (!isSchemaObject(schema)) return schema
+	const out: OpenAPIV3.SchemaObject = { ...schema }
+
+	// properties
+	if (out.properties && typeof out.properties === 'object') {
+		out.properties = Object.fromEntries(
+			Object.entries(out.properties).map(([prop, propSchema]) => {
+				if (isSchemaObject(propSchema) && isDateTimeProperty(prop, propSchema)) {
+					const dateTime = propSchema.anyOf?.find(
+						(i): i is OpenAPIV3.SchemaObject =>
+							isSchemaObject(i) && i.format === 'date-time'
+					)
+					if (dateTime) {
+						const dateTimeSchema: DateTimeSchema = {
+							type: 'string',
+							format: 'date-time',
+							default: dateTime.default
+						}
+						return [prop, dateTimeSchema]
+					}
+				}
+				return [prop, transformDateProperties(propSchema)]
+			})
+		)
+	}
+
+	// arrays and composites
+	if (out.items) out.items = transformDateProperties(out.items as SchemaObject)
+	if (out.additionalProperties && typeof out.additionalProperties === 'object')
+		out.additionalProperties = transformDateProperties(
+			out.additionalProperties as SchemaObject
+		)
+	if (out.allOf) out.allOf = out.allOf.map((s) => transformDateProperties(s))
+	if (out.oneOf) out.oneOf = out.oneOf.map((s) => transformDateProperties(s))
+	if (out.anyOf) out.anyOf = out.anyOf.map((s) => transformDateProperties(s))
+	if (out.not) out.not = transformDateProperties(out.not as SchemaObject)
+
+	return out
+}

Also applies to: 30-65

🤖 Prompt for AI Agents
In src/swagger/index.ts around lines 13-17 (and extend to 30-65), the type guard
and date transformation are both incomplete: replace isSchemaObject with a
reliable discriminator that treats ReferenceObject by presence of $ref (e.g.
return typeof schema === 'object' && schema !== null && !('$ref' in schema));
then rewrite transformDateProperties to recursively traverse schema nodes
(handle schema.properties, schema.items, schema.additionalProperties,
schema.allOf/oneOf/anyOf arrays, schema.not, and nested items) and apply the
date transformation at property-level when schema.format === 'date-time' or
property names match createdAt/updatedAt; ensure you recurse into each child
schema and handle arrays and boolean/Record forms for additionalProperties so
date formats inside nested objects are transformed.

Comment on lines +79 to +87
// remove function in rest
const stringifiedOptions = JSON.stringify(
{
dom_id: '#swagger-ui',
...rest
},
(_, value) => (typeof value === 'function' ? undefined : value)
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Spec transformation is applied to the wrong object and then discarded

You're parsing UI options as an OpenAPI document and transforming options.components.schemas (which won't exist unless nested in spec). Also, you never use the transformed object; SwaggerUIBundle is called with the original stringifiedOptions. Transform spec and pass the transformed options to the bundle.

Apply:

-	// remove function in rest
-	const stringifiedOptions = JSON.stringify(
-		{
-			dom_id: '#swagger-ui',
-			...rest
-		},
-		(_, value) => (typeof value === 'function' ? undefined : value)
-	)
-
-	const options: OpenAPIV3.Document = JSON.parse(stringifiedOptions)
-
-	if (options.components && options.components.schemas)
-		options.components.schemas = Object.fromEntries(
-			Object.entries(options.components.schemas).map(([key, schema]) => [
-				key,
-				transformDateProperties(schema)
-			])
-		)
+	// sanitize and transform spec, then build UI options
+	const { spec, ...uiOptions } = rest
+	const sanitizedSpec =
+		spec && typeof spec === 'object'
+			? JSON.parse(
+					JSON.stringify(spec, (_, v) =>
+						typeof v === 'function' ? undefined : v
+					)
+			  )
+			: undefined
+
+	if (sanitizedSpec?.components?.schemas)
+		sanitizedSpec.components.schemas = Object.fromEntries(
+			Object.entries(sanitizedSpec.components.schemas).map(([key, schema]) => [
+				key,
+				transformDateProperties(schema)
+			])
+		)
+
+	const stringifiedUIOptions = JSON.stringify(
+		{
+			dom_id: '#swagger-ui',
+			...uiOptions,
+			...(sanitizedSpec ? { spec: sanitizedSpec } : {})
+		},
+		(_, value) => (typeof value === 'function' ? undefined : value)
+	)
@@
-            window.ui = SwaggerUIBundle(${stringifiedOptions});
+            window.ui = SwaggerUIBundle(${stringifiedUIOptions});

Also applies to: 88-96, 143-144

🤖 Prompt for AI Agents
In src/swagger/index.ts around lines 79-87 (and similarly update 88-96 and
143-144): the code currently parses UI options as an OpenAPI doc, transforms
options.components.schemas (which won't exist when spec is nested) and then
discards the transformed result by still passing the original stringified
options to SwaggerUIBundle. Fix by locating the OpenAPI spec (options.spec if
present, otherwise the root object), apply the schema transformation to
spec.components.schemas (or create components if missing), assign the
transformed spec back into the options object, then JSON.stringify that updated
options and pass the updated stringified options to SwaggerUIBundle; replicate
the same correction in the other affected blocks (lines 88-96 and 143-144).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (5)
src/types.ts (1)

26-30: Align default Path generic with OpenAPI default.

The generic still defaults to '/swagger' while docs and runtime default to '/openapi'. This causes type-level inconsistencies for consumers.

Apply:

-export interface ElysiaOpenAPIConfig<
-	Enabled extends boolean = true,
-	Path extends string = '/swagger',
-	Provider extends OpenAPIProvider = 'scalar'
-> {
+export interface ElysiaOpenAPIConfig<
+	Enabled extends boolean = true,
+	Path extends string = '/openapi',
+	Provider extends OpenAPIProvider = 'scalar'
+> {
src/openapi.ts (2)

66-77: Normalize exclude.methods and support RegExp in exclude.paths.

Uppercase defaults never match lowercased route methods; exclude.paths should accept RegExp.

Apply:

  const {
-		methods: excludeMethods = ['OPTIONS'],
+		methods: excludeMethods = ['OPTIONS'],
 		staticFile: excludeStaticFile = true,
 		tags: excludeTags
 	} = exclude ?? {}

-	const excludePaths = Array.isArray(exclude?.paths)
-		? exclude.paths
-		: typeof exclude?.paths !== 'undefined'
-			? [exclude.paths]
-			: []
+	const excludePaths = Array.isArray(exclude?.paths)
+		? exclude.paths
+		: typeof exclude?.paths !== 'undefined'
+			? [exclude.paths]
+			: []
+	const excludeMethodsNormalized = excludeMethods.map((m) => m.toLowerCase())
+	const shouldExcludePath = (p: string) =>
+		excludePaths.some((x) => (typeof x === 'string' ? x === p : x.test(p)))
@@
-		if (excludePaths.includes(route.path)) continue
-		if (excludeMethods.includes(method)) continue
+		if (shouldExcludePath(route.path)) continue
+		if (excludeMethodsNormalized.includes(method)) continue

Also applies to: 96-101


328-360: Ensure unique operationId per emitted HTTP method (including .all).

Compute per-method opIds; current code reuses one opId across all variants.

Apply:

-		for (let path of getPossiblePath(route.path)) {
-			const operationId = toOperationId(route.method, path)
+		for (let path of getPossiblePath(route.path)) {
 			path = path.replace(/:([^/]+)/g, '{$1}')

 			if (!paths[path]) paths[path] = {}

 			const current = paths[path] as any

 			if (method !== 'all') {
 				current[method] = {
 					...operation,
-					operationId
+					operationId: toOperationId(method, path)
 				}
 				continue
 			}

 			// Handle 'ALL' method by assigning operation to all standard methods
-			for (const method of [
+			for (const m of [
 				'get',
 				'post',
 				'put',
 				'delete',
 				'patch',
 				'head',
 				'options',
 				'trace'
 			])
-				current[method] = {
+				current[m] = {
 					...operation,
-					operationId
+					operationId: toOperationId(m, path)
 				}
 		}
src/index.ts (2)

41-41: Fix spec URL: don’t strip the leading slash (UI fetches wrong path).

Using a relative path turns “/openapi/json” into “openapi/json”, which the browser resolves to “/openapi/openapi/json”. Keep it absolute.

-const relativePath = specPath.startsWith('/') ? specPath.slice(1) : specPath
+const specURL = specPath.startsWith('/') ? specPath : `/${specPath}`
@@
-                                url: relativePath,
+                                url: specURL,
@@
-                                url: relativePath,
+                                url: specURL,

Also applies to: 55-56, 62-63


84-87: Schema cache can go stale: compare against global route count.

app.routes is plugin-local; use global routes when available to avoid stale caches after other plugins add routes.

-                if (totalRoutes === app.routes.length) return cachedSchema
-
-                totalRoutes = app.routes.length
+                // Prefer global route count; fallback to local
+                // @ts-ignore private API
+                const currentRouteCount =
+                  typeof (app as any).getGlobalRoutes === 'function'
+                    // @ts-ignore
+                    ? (app as any).getGlobalRoutes().length
+                    : app.routes.length
+                if (totalRoutes === currentRouteCount) return cachedSchema
+                totalRoutes = currentRouteCount
🧹 Nitpick comments (9)
src/types.ts (4)

36-41: Fix documentation JSDoc: not Swagger 2.0.

This field is OpenAPI v3 (typed as OpenAPIV3.Document). Link and wording should reflect that.

Apply:

-	/**
-	 * Customize Swagger config, refers to Swagger 2.0 config
-	 *
-	 * @see https://swagger.io/specification/v2/
-	 */
+	/**
+	 * Customize OpenAPI document (OpenAPI v3)
+	 *
+	 * @see https://spec.openapis.org/oas/v3.0.3
+	 */

56-59: Remove stray apostrophe in JSDoc.

Apply:

-	 *'
+	 *

72-79: Fix typos and CDN name in Scalar CDN docs.

“Lease” → “Leave”, “jsdeliver.net” → “jsDelivr”.

Apply:

-		 * Lease blank to use default jsdeliver.net CDN
+		 * Leave blank to use the default jsDelivr CDN

125-133: Update Swagger UI version default docs to match runtime.

Runtime sets version 'latest' (see src/index.ts). Doc says 4.18.2.

Apply:

-		 * @default 4.18.2
+		 * @default 'latest'

If you intend to pin a specific version, also update src/index.ts to keep them consistent.

example/gen.ts (1)

8-9: Avoid self-referencing in fromTypes to reduce tooling brittleness.

Point the generator at a separate app file (e.g., example/index.ts) to avoid circular edits impacting generation.

Apply:

-			references: fromTypes('example/gen.ts')
+			references: fromTypes('example/index.ts')
src/openapi.ts (1)

40-54: Optional: de-duplicate paths emitted by getPossiblePath.

Avoid emitting the same path multiple times for multi-optional segments.

Apply:

-	return paths
+	return Array.from(new Set(paths))
example/index.ts (1)

55-69: Prefer reusing registered schema name to avoid duplication.

Since you model()‘d 'schema', you can reference it directly.

Apply:

-				200: t.Object({
-					test: t.Literal('hello')
-				}),
+				200: 'schema',
src/index.ts (1)

69-71: Content-Type charset casing.

Use utf-8 (with hyphen) for correctness.

-                            'content-type': 'text/html; charset=utf8'
+                            'content-type': 'text/html; charset=utf-8'
package.json (1)

1-4: Consider adding a files whitelist to slim the published package.

 {
     "name": "@elysiajs/openapi",
     "version": "1.3.2",
     "description": "Plugin for Elysia to auto-generate OpenAPI documentation",
+    "files": [
+        "dist/",
+        "README.md",
+        "LICENSE",
+        "package.json"
+    ],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d028a9d and f410e31.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • src/gen/index.ts is excluded by !**/gen/**
📒 Files selected for processing (6)
  • example/gen.ts (1 hunks)
  • example/index.ts (2 hunks)
  • package.json (4 hunks)
  • src/index.ts (2 hunks)
  • src/openapi.ts (1 hunks)
  • src/types.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
example/gen.ts (2)
src/index.ts (1)
  • openapi (17-128)
src/gen/index.ts (1)
  • fromTypes (58-192)
src/openapi.ts (3)
example/gen.ts (1)
  • app (5-22)
example/index.ts (1)
  • app (18-85)
src/types.ts (3)
  • ElysiaOpenAPIConfig (26-170)
  • AdditionalReferences (22-24)
  • AdditionalReference (10-20)
example/index.ts (3)
example/gen.ts (1)
  • app (5-22)
src/index.ts (2)
  • openapi (17-128)
  • withHeaders (130-130)
src/openapi.ts (1)
  • withHeaders (374-377)
src/index.ts (4)
src/types.ts (2)
  • OpenAPIProvider (6-6)
  • ElysiaOpenAPIConfig (26-170)
src/swagger/index.ts (1)
  • SwaggerUIRender (67-148)
src/scalar/index.ts (1)
  • ScalarRender (126-162)
src/openapi.ts (1)
  • toOpenAPISchema (61-372)
src/types.ts (1)
src/index.ts (1)
  • ElysiaOpenAPIConfig (131-131)
🔇 Additional comments (6)
src/types.ts (1)

53-53: Good: provider type uses the Provider generic.

Enables narrowing when callers specify a provider.

src/openapi.ts (1)

66-71: Unused excludeStaticFile — confirm intent and implement or remove.

The flag is read but never applied. If intended, add a check to skip static-file routes; otherwise, drop the option to avoid confusion.

Would you like me to wire this to whatever marker Elysia exposes for static assets (e.g., a route kind/detail flag) and send a patch?

example/index.ts (2)

20-47: Docs config looks consistent with OpenAPI switch.

Provider, documentation, and securitySchemes updates look good.


76-82: Nice: parse + named body + per-status responses.

Good showcase for generator coverage.

package.json (2)

82-83: Good: @scalar/types moved to devDependencies; runtime deps minimal.

Also applies to: 89-91


62-68: URLs resolve correctly – both the homepage and bugs links return HTTP 200.

Comment on lines +20 to 29
"./gen": {
"types": "./dist/gen/index.d.ts",
"import": "./dist/gen/index.mjs",
"require": "./dist/cjs/gen/index.js"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/cjs/utils.js"
"./openapi": {
"types": "./dist/openapi.d.ts",
"import": "./dist/openapi.mjs",
"require": "./dist/cjs/openapi.js"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Sanity-check built artifacts for new export subpaths.

Ensure these files exist after build to prevent “module not found” at runtime.

Also applies to: 40-54


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
# Run after building locally (npm run build)
for f in \
  dist/openapi.mjs dist/openapi.d.ts dist/cjs/openapi.js \
  dist/swagger/index.mjs dist/swagger/index.d.ts dist/cjs/swagger/index.js \
  dist/types.mjs dist/types.d.ts dist/cjs/types.js \
  dist/gen/index.mjs dist/gen/index.d.ts dist/cjs/gen/index.js; do
  test -f "$f" && echo "OK: $f" || echo "MISSING: $f"
done

Length of output: 4165


Missing built artifacts for export subpaths
All expected files under dist/openapi, dist/swagger, dist/types, and dist/gen are not generated after npm run build:

  • openapi: dist/openapi.{mjs,d.ts}, dist/cjs/openapi.js
  • swagger: dist/swagger/index.{mjs,d.ts}, dist/cjs/swagger/index.js
  • types: dist/types.{mjs,d.ts}, dist/cjs/types.js
  • gen: dist/gen/index.{mjs,d.ts}, dist/cjs/gen/index.js

Update the build configuration to emit these artifacts to match the export map in package.json.

🤖 Prompt for AI Agents
In package.json around lines 20–29 the export subpaths (./gen, ./openapi,
./swagger, ./types) expect built artifacts that are not produced by the current
build; update the build configuration (tsconfig and/or your bundler like
tsup/rollup/webpack) and npm scripts so the build emits ESM .mjs, CommonJS .js
under dist/cjs, and TypeScript declaration .d.ts files for each subpath (e.g.,
dist/openapi.mjs + dist/openapi.d.ts + dist/cjs/openapi.js;
dist/swagger/index.mjs + dist/swagger/index.d.ts + dist/cjs/swagger/index.js;
dist/types.mjs + dist/types.d.ts + dist/cjs/types.js; dist/gen/index.mjs +
dist/gen/index.d.ts + dist/cjs/gen/index.js). Ensure tsconfig.json has
"declaration": true and correct "outDir", or configure your bundler’s entry
points and outputs to produce both esm and cjs bundles and generate .d.ts (or
add a step to copy/rename outputs) so the files match the package.json export
map.

Comment on lines +286 to +325
if (
typeof hooks.response === 'object' &&
!(hooks.response as TSchema).type &&
!(hooks.response as TSchema).$ref
) {
for (let [status, schema] of Object.entries(hooks.response)) {
if (typeof schema === 'string') schema = toRef(schema)

// Must exclude $ref from root options
const { type, examples, $ref, ...options } = schema

operation.responses[status] = {
description: `Response for status ${status}`,
...options,
content:
type === 'void' ||
type === 'null' ||
type === 'undefined'
? schema
: {
'application/json': {
schema
}
}
}
}
} else {
if (typeof hooks.response === 'string')
hooks.response = toRef(hooks.response)

// It's a single schema, default to 200
operation.responses['200'] = {
description: 'Successful response',
content: {
'application/json': {
schema: hooks.response
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid ResponseObject when schema is void/null/undefined.

Setting content to a schema object is not valid OAS; omit content for empty bodies while preserving headers and other options.

Apply:

-				for (let [status, schema] of Object.entries(hooks.response)) {
+				for (let [status, schema] of Object.entries(hooks.response)) {
 					if (typeof schema === 'string') schema = toRef(schema)

 					// Must exclude $ref from root options
 					const { type, examples, $ref, ...options } = schema
-
-					operation.responses[status] = {
-						description: `Response for status ${status}`,
-						...options,
-						content:
-							type === 'void' ||
-							type === 'null' ||
-							type === 'undefined'
-								? schema
-								: {
-										'application/json': {
-											schema
-										}
-									}
-					}
+					const resp: OpenAPIV3.ResponseObject = {
+						description: `Response for status ${status}`,
+						...options
+					}
+					if (
+						type !== 'void' &&
+						type !== 'null' &&
+						type !== 'undefined'
+					) {
+						resp.content = {
+							'application/json': { schema }
+						}
+					}
+					operation.responses[status] = resp
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
typeof hooks.response === 'object' &&
!(hooks.response as TSchema).type &&
!(hooks.response as TSchema).$ref
) {
for (let [status, schema] of Object.entries(hooks.response)) {
if (typeof schema === 'string') schema = toRef(schema)
// Must exclude $ref from root options
const { type, examples, $ref, ...options } = schema
operation.responses[status] = {
description: `Response for status ${status}`,
...options,
content:
type === 'void' ||
type === 'null' ||
type === 'undefined'
? schema
: {
'application/json': {
schema
}
}
}
}
} else {
if (typeof hooks.response === 'string')
hooks.response = toRef(hooks.response)
// It's a single schema, default to 200
operation.responses['200'] = {
description: 'Successful response',
content: {
'application/json': {
schema: hooks.response
}
}
}
}
// … inside the `if (typeof hooks.response === 'object' && …)` branch:
for (let [status, schema] of Object.entries(hooks.response)) {
if (typeof schema === 'string') schema = toRef(schema)
// Must exclude $ref from root options
const { type, examples, $ref, ...options } = schema
// Build the response object, omitting `content` for empty bodies
const resp: OpenAPIV3.ResponseObject = {
description: `Response for status ${status}`,
...options
}
// Only add a JSON media type when there's a concrete schema
if (
type !== 'void' &&
type !== 'null' &&
type !== 'undefined'
) {
resp.content = {
'application/json': { schema }
}
}
operation.responses[status] = resp
}
// …
🤖 Prompt for AI Agents
In src/openapi.ts around lines 286 to 325, the response generation wrongly sets
content to a schema object when the response schema has type
'void'|'null'|'undefined' (invalid OAS). Change both branches so that for
empty-body types you omit the content property entirely but still include
description and other response options (headers, examples, $ref-excluded
options) for that status; for non-empty types keep the existing application/json
content wrapper. Ensure the single-schema (default 200) path applies the same
rule (omit content for void/null/undefined) while preserving other response
options.

@SaltyAom
Copy link
Member Author

SaltyAom commented Sep 2, 2025

LGTM 👍

@SaltyAom SaltyAom merged commit fa956f4 into main Sep 2, 2025
2 checks passed
@redbmk
Copy link

redbmk commented Sep 30, 2025

@SaltyAom

150-167: Headers/query/cookie: preserve required semantics for nested/array types

If any param schemas are arrays or have nested objects, OpenAPI expects style/explode flags and accurate “required” at the parameter level. Current mapping may misrepresent such cases.

Optionally add support for style/explode and handle array formats (e.g., csv/ssv) when detected. I can draft a helper.

It looks like this was an AI making this suggestion, but I'm actually running into a case now where I'd like to add explode: true, style: "deepObject". As far as I can tell, this piece didn't make it in, right? I don't see any way to update those details in query params without overriding the full detail.parameters manually.

Would be nice if there was a setting or hook of some sort to update that. Should I create a feature request?

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.

2 participants