Skip to content

Commit a71a4b5

Browse files
authored
Merge branch 'main' into fix-open-standard-schema-calls
2 parents c7463d2 + f3c0393 commit a71a4b5

File tree

6 files changed

+136
-24
lines changed

6 files changed

+136
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
# 1.4.13 - 26 Dec 2025
2+
Bug fix:
3+
- merge multiple standalone standard schema
4+
15
# 1.4.12 - 18 Dec 2025
6+
Improvement:
27
- support standard json schema conversion
38
- [#300](https://github.com/elysiajs/elysia-openapi/pull/300) support standalone schema by @MarcelOlsen
49

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/index.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
import { Elysia, t } from 'elysia'
2-
import z from 'zod'
3-
import { JSONSchema, Schema } from 'effect'
2+
import { fromTypes, openapi } from '../src'
3+
import * as z from 'zod'
44

5-
import { openapi } from '../src/index'
6-
7-
const app = new Elysia()
8-
.use(openapi())
9-
.post('/', ({ body }) => body, {
10-
body: z.object({
11-
name: z.string()
5+
new Elysia()
6+
.use(
7+
openapi({
8+
references: fromTypes(),
9+
mapJsonSchema: {
10+
zod: z.toJSONSchema
11+
}
1212
})
13+
)
14+
.macro('fooBar', {
15+
query: z.object({
16+
foo: z.optional(z.string())
17+
}),
18+
resolve({ query }) {
19+
return { test: query.foo ? 'foo' : 'bar' }
20+
}
1321
})
14-
.get('/test', ({ status }) => {}, {
15-
response: {
16-
403: t.Object({
17-
name: t.String()
18-
})
22+
.get(
23+
'/',
24+
({ test, query }) => {
25+
const { foo, bar } = query
26+
return { ok: true, test, foo, bar }
27+
},
28+
{
29+
query: z.object({
30+
bar: z.optional(z.string())
31+
}),
32+
fooBar: true
1933
}
34+
)
35+
.listen(3000, () => {
36+
console.log('server started')
2037
})
21-
.listen(3000)

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elysiajs/openapi",
3-
"version": "1.4.12",
3+
"version": "1.4.13",
44
"description": "Plugin for Elysia to auto-generate API documentation",
55
"author": {
66
"name": "saltyAom",
@@ -73,7 +73,9 @@
7373
"build": "bun build.ts",
7474
"release": "npm run build && npm run test && npm publish --access public"
7575
},
76-
"dependencies": {},
76+
"dependencies": {
77+
"elysia": "https://pkg.pr.new/elysiajs/elysia@1637"
78+
},
7779
"devDependencies": {
7880
"@apidevtools/swagger-parser": "^12.0.0",
7981
"@scalar/types": "^0.2.13",

src/openapi.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import { defineConfig } from 'tsup'
2626
export const capitalize = (word: string) =>
2727
word.charAt(0).toUpperCase() + word.slice(1)
2828

29-
const toRef = (name: string) => t.Ref(`#/components/schemas/${name}`)
29+
const toRef = (name: string) => t.Ref(
30+
name.startsWith('#/')
31+
? name
32+
: `#/components/schemas/${name}`
33+
)
3034

3135
const toOperationId = (method: string, paths: string) => {
3236
let operationId = method.toLowerCase()
@@ -237,7 +241,7 @@ const normalizeSchemaReference = (
237241

238242
// Convert string reference to t.Ref node
239243
// This allows string aliases to participate in schema composition
240-
return t.Ref(schema)
244+
return toRef(schema)
241245
}
242246

243247
/**
@@ -252,7 +256,7 @@ const mergeSchemaProperty = (
252256
if (!incoming) return existing
253257

254258
// Normalize string references to TRef nodes so they can be merged
255-
const existingSchema = normalizeSchemaReference(existing)
259+
let existingSchema = normalizeSchemaReference(existing)
256260
let incomingSchema = normalizeSchemaReference(incoming)
257261

258262
if (!existingSchema) return incoming
@@ -261,7 +265,11 @@ const mergeSchemaProperty = (
261265
if (!isTSchema(incomingSchema) && incomingSchema['~standard'])
262266
incomingSchema = unwrapSchema(incomingSchema, vendors) as any
263267

264-
if (!incomingSchema) return existing
268+
if (!isTSchema(existingSchema) && existingSchema['~standard'])
269+
existingSchema = unwrapSchema(existingSchema, vendors) as any
270+
271+
if (!incomingSchema) return existingSchema
272+
if (!existingSchema) return incomingSchema
265273

266274
// If both are object schemas, merge them
267275
const { schema: mergedSchema, notObjects } = mergeObjectSchemas([
@@ -306,7 +314,11 @@ const unwrapResponseSchema = (
306314
? normalizeSchemaReference(schema)
307315
: isTSchema(schema)
308316
? schema
309-
: unwrapSchema(schema as any, vendors, 'output')
317+
: unwrapSchema(
318+
schema as any,
319+
vendors,
320+
'output'
321+
)
310322
])
311323
)
312324

@@ -506,7 +518,15 @@ export const unwrapSchema = (
506518
if (typeof schema === 'string') schema = toRef(schema)
507519
if (Kind in schema) return enumToOpenApi(schema)
508520

509-
if (Kind in schema || !schema?.['~standard']) return
521+
// Already unwrapped by merging standalone validators
522+
if (
523+
!schema?.['~standard'] &&
524+
// @ts-ignore
525+
(schema.$schema || schema.type || schema.properties || schema.items)
526+
)
527+
return schema as OpenAPIV3.SchemaObject
528+
529+
if (!schema?.['~standard']) return
510530

511531
// @ts-ignore
512532
const vendor = schema['~standard'].vendor
@@ -592,6 +612,11 @@ export const unwrapSchema = (
592612
}
593613
}
594614

615+
/**
616+
* Convert TypeBox enum-like Union schemas to OpenAPI enum schemas
617+
*
618+
* Otherwise, return the schema as is
619+
*/
595620
export const enumToOpenApi = <
596621
T extends
597622
| TAnySchema
@@ -995,7 +1020,11 @@ export function toOpenAPISchema(
9951020
}
9961021
}
9971022
} else {
998-
const response = unwrapSchema(hooks.response as any, vendors, 'output')
1023+
const response = unwrapSchema(
1024+
hooks.response as any,
1025+
vendors,
1026+
'output'
1027+
)
9991028

10001029
if (response) {
10011030
// @ts-ignore

test/openapi/to-openapi-schema.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'bun:test'
22
import { AnyElysia, Elysia, t } from 'elysia'
33

44
import { toOpenAPISchema } from '../../src/openapi'
5+
import { z } from 'zod'
56

67
const is = <T extends AnyElysia>(
78
app: T,
@@ -1219,4 +1220,60 @@ describe('OpenAPI > toOpenAPISchema', () => {
12191220
}
12201221
})
12211222
})
1223+
1224+
it('merge multiple standard standalone schema', () => {
1225+
const app = new Elysia()
1226+
.macro('fooBar', {
1227+
query: z.object({
1228+
foo: z.optional(z.string())
1229+
}),
1230+
resolve({ query }) {
1231+
return { test: query.foo ? 'foo' : 'bar' }
1232+
}
1233+
})
1234+
.get(
1235+
'/',
1236+
({ test, query }) => {
1237+
const { foo, bar } = query
1238+
return { ok: true, test, foo, bar }
1239+
},
1240+
{
1241+
query: z.object({
1242+
bar: z.optional(z.string())
1243+
}),
1244+
fooBar: true
1245+
}
1246+
)
1247+
1248+
is(app, {
1249+
components: {
1250+
schemas: {}
1251+
},
1252+
paths: {
1253+
'/': {
1254+
get: {
1255+
operationId: 'getIndex',
1256+
parameters: [
1257+
{
1258+
in: 'query',
1259+
name: 'bar',
1260+
required: false,
1261+
schema: {
1262+
type: 'string'
1263+
}
1264+
},
1265+
{
1266+
in: 'query',
1267+
name: 'foo',
1268+
required: false,
1269+
schema: {
1270+
type: 'string'
1271+
}
1272+
}
1273+
]
1274+
}
1275+
}
1276+
}
1277+
})
1278+
})
12221279
})

0 commit comments

Comments
 (0)