diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8448a3..aae5e033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # 1.4.23 +Bug fix: +- [#1700](https://github.com/elysiajs/elysia/issues/1700) distinct union object - [#1683](https://github.com/elysiajs/elysia/pull/1683) response validation returns 500 instead of 422 for nested schemas in dynamic mode +Change: +- update exact-mirror to 0.2.7 + # 1.4.22 - 14 Jan 2026 Improvement: - use imperative check for `replaceURLPath` instead of allocating `new URL` diff --git a/bun.lock b/bun.lock index 96efb547..e386b29c 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "elysia", "dependencies": { "cookie": "^1.1.1", - "exact-mirror": "^0.2.6", + "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0", }, @@ -34,7 +34,7 @@ "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", - "exact-mirror": ">= 0.0.9", + "exact-mirror": "^0.2.6", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0", @@ -363,7 +363,7 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], + "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], diff --git a/example/a.ts b/example/a.ts index 9ac17700..df313af1 100644 --- a/example/a.ts +++ b/example/a.ts @@ -1,24 +1,20 @@ import { Elysia, t } from '../src' +import { req } from '../test/utils' -const verifyToken = new Elysia() - .macro('verifyToken', { - headers: t.Object({ - authorization: t.String() - }), - resolve({ headers }) { - // headers should be typed as { authorization: string } - // but TypeScript shows: Record - const token = headers.authorization // Type is 'string | undefined', not 'string' - ? headers.authorization.substring(7) - : undefined - - return { token } +const app = new Elysia() + .get('/health', () => ({ status: 'healthy' }) as const, { + response: { + 200: t.Union([ + t.Object({ + status: t.Literal('a'), + a: t.Object({ b: t.Integer() }) // <-- if you comment this line, you won't have an error + }), + t.Object({ status: t.Literal('healthy') }) + ]) } }) - .get( - '/', - ({ token }) => {}, - { - verifyToken: true - } - ) + .listen(3000) + +app.handle(req('/health')) + .then((x) => x.status) + .then(console.log) diff --git a/package.json b/package.json index 32fb5f81..3644122e 100644 --- a/package.json +++ b/package.json @@ -191,7 +191,7 @@ }, "dependencies": { "cookie": "^1.1.1", - "exact-mirror": "^0.2.6", + "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, diff --git a/src/error.ts b/src/error.ts index cdedf025..9ad6bc47 100644 --- a/src/error.ts +++ b/src/error.ts @@ -8,7 +8,7 @@ import type { import { StatusMap, InvertedStatusMap } from './utils' import type { ElysiaTypeCheck } from './schema' -import { Prettify, StandardSchemaV1Like } from './types' +import { StandardSchemaV1Like } from './types' // ? Cloudflare worker support const env = @@ -160,7 +160,7 @@ interface ValueErrorWithSummary extends ValueError { summary?: string } -export const mapValueError = (error: ValueError): ValueErrorWithSummary => { +export const mapValueError = (error: ValueError | undefined): ValueErrorWithSummary | undefined => { if (!error) return error let { message, path, value, type } = error @@ -388,7 +388,7 @@ export class ValidationError extends Error { // @ts-ignore private field const schema = validator?.schema ?? validator - if (!isProduction && !allowUnsafeValidationDetails) { + if (!isProduction && !allowUnsafeValidationDetails) try { expected = Value.Create(schema) } catch (error) { @@ -399,7 +399,6 @@ export class ValidationError extends Error { error } } - } customError = error?.schema?.message || error?.schema?.error !== undefined @@ -418,7 +417,7 @@ export class ValidationError extends Error { property: accessor, message: error?.message, summary: - mapValueError(error).summary, + mapValueError(error)?.summary, found: value, expected, errors: @@ -458,7 +457,7 @@ export class ValidationError extends Error { on: type, property: accessor, message: error?.message, - summary: mapValueError(error).summary, + summary: mapValueError(error)?.summary, expected, found: value, errors: @@ -596,9 +595,7 @@ export class ValidationError extends Error { on: this.type, property: this.valueError?.path || 'root', message, - summary: this.valueError - ? mapValueError(this.valueError).summary - : undefined, + summary: mapValueError(this.valueError)?.summary, found: value, expected, errors diff --git a/test/validator/response.test.ts b/test/validator/response.test.ts index e1bb36b3..060b8974 100644 --- a/test/validator/response.test.ts +++ b/test/validator/response.test.ts @@ -370,7 +370,7 @@ describe('Response Validator', () => { }) it('return static undefined with status based schema', async () => { - const app = new Elysia().get('/', undefined, { + const app = new Elysia().get('/', undefined as any, { response: { 200: t.Union([ t.Void(), @@ -562,4 +562,24 @@ describe('Response Validator', () => { expect(result.join('')).toContain('data: {"name":"Name"}') }) + + it('handle distinct union', () => { + const app = new Elysia() + .get('/health', () => ({ status: 'healthy' }) as const, { + response: { + 200: t.Union([ + t.Object({ + status: t.Literal('a'), + a: t.Object({ b: t.Integer() }) + }), + t.Object({ status: t.Literal('healthy') }) + ]) + } + }) + .listen(3000) + + const status = app.handle(req('/health')).then((x) => x.status) + + expect(status).resolves.toBe(200) + }) })