From 9a020addba538d990e0e6fd9922513caf25be488 Mon Sep 17 00:00:00 2001 From: truehazker <40111175+truehazker@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:29:17 +0400 Subject: [PATCH 1/2] :wrench: fix: enhance type safety and fix schema validation return types --- src/index.ts | 2 +- src/schema.ts | 9 +- src/types.ts | 11 +- test/validator/schema-validator.test.ts | 167 ++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 test/validator/schema-validator.test.ts diff --git a/src/index.ts b/src/index.ts index f667387e..a96a4934 100644 --- a/src/index.ts +++ b/src/index.ts @@ -452,7 +452,7 @@ export default class Elysia< get models(): { [K in keyof Definitions['typebox']]: ModelValidator< - Definitions['typebox'][K] + Extract > } & { modules: diff --git a/src/schema.ts b/src/schema.ts index 0c8fa39a..86a4ed8d 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -6,7 +6,8 @@ import { TObject, TransformKind, TSchema, - type TAnySchema + type TAnySchema, + type Static } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import { TypeCompiler } from '@sinclair/typebox/compiler' @@ -39,10 +40,10 @@ export interface ElysiaTypeCheck provider: 'typebox' | 'standard' schema: T config: Object - Clean?(v: unknown): T - parse(v: unknown): T + Clean?(v: unknown): Static + parse(v: unknown): Static safeParse(v: unknown): - | { success: true; data: T; error: null } + | { success: true; data: Static; error: null } | { success: false data: null diff --git a/src/types.ts b/src/types.ts index b481969b..4b3064ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { Elysia, AnyElysia, InvertedStatusMap } from './index' +import type { Elysia, AnyElysia, InvertedStatusMap, Static } from './index' import type { ElysiaFile } from './universal/file' import type { Serve } from './universal/server' @@ -2353,13 +2353,12 @@ export interface ModelValidatorError extends ValueError { summary: string } -// @ts-ignore trust me bro -export interface ModelValidator extends TypeCheck { - parse(a: T): T +export interface ModelValidator extends TypeCheck { + parse(a: T): Static safeParse(a: T): - | { success: true; data: T; error: null } + | { success: true; data: Static; error: null } | { - success: true + success: false data: null error: string errors: ModelValidatorError[] diff --git a/test/validator/schema-validator.test.ts b/test/validator/schema-validator.test.ts new file mode 100644 index 00000000..74829c4a --- /dev/null +++ b/test/validator/schema-validator.test.ts @@ -0,0 +1,167 @@ +import { t } from '../../src' +import { getSchemaValidator } from '../../src/schema' + +import { describe, expect, it } from 'bun:test' + +describe('Schema Validator', () => { + describe('parse', () => { + it('should parse valid object', () => { + const validator = getSchemaValidator( + t.Object({ + name: t.String() + }) + ) + + const result = validator.parse({ name: 'test' }) + + expect(result).toEqual({ name: 'test' }) + }) + + it('should pass object with additional properties when allowed', () => { + const validator = getSchemaValidator( + t.Object({ + a: t.String() + }), + { additionalProperties: true } + ) + + const result = validator.parse({ + a: 'hello', + b: 'world' + }) + + expect(result.a).toBe('hello') + }) + + it('should reject additional properties by default', () => { + const validator = getSchemaValidator( + t.Object({ + a: t.String() + }) + ) + + expect(() => + validator.parse({ + a: 'hello', + b: 'world' + }) + ).toThrow() + }) + + it('should throw on invalid data', () => { + const validator = getSchemaValidator( + t.Object({ + name: t.String() + }) + ) + + expect(() => validator.parse({ name: 123 })).toThrow() + }) + }) + + describe('safeParse', () => { + it('should return success with data on valid input', () => { + const validator = getSchemaValidator( + t.Object({ + a: t.String() + }) + ) + + const result = validator.safeParse({ a: 'test' }) + + expect(result.success).toBe(true) + if (result.success) { + expect(result.data).toEqual({ a: 'test' }) + expect(result.error).toBeNull() + } + }) + + it('should return failure with error on invalid input', () => { + const validator = getSchemaValidator( + t.Object({ + a: t.String() + }) + ) + + const result = validator.safeParse({ a: 123 }) + + expect(result.success).toBe(false) + if (!result.success) { + expect(result.data).toBeNull() + expect(result.error).toBeDefined() + expect(result.errors).toBeDefined() + } + }) + + it('should work with union types', () => { + // Sample union schema + const eventPayload = t.Union([ + t.Object({ + type: t.Literal('message'), + content: t.String() + }), + t.Object({ + type: t.Literal('error'), + code: t.Number() + }) + ]) + type eventPayload = typeof eventPayload.static + + const validator = getSchemaValidator(eventPayload) + + // Test message type + const messageResult = validator.safeParse({ + type: 'message', + content: 'hello' + }) + + expect(messageResult.success).toBe(true) + if (messageResult.success) { + expect(messageResult.data).toEqual({ + type: 'message', + content: 'hello' + }) + } + + // Test error type + const errorResult = validator.safeParse({ + type: 'error', + code: 500 + }) + + expect(errorResult.success).toBe(true) + if (errorResult.success) { + expect(errorResult.data).toEqual({ + type: 'error', + code: 500 + }) + } + + // Test invalid type + const invalidResult = validator.safeParse({ + type: 'unknown' + }) + + expect(invalidResult.success).toBe(false) + }) + + it('should have correct type inference for data', () => { + const validator = getSchemaValidator( + t.Object({ + id: t.Number(), + name: t.String() + }) + ) + + const result = validator.safeParse({ id: 1, name: 'test' }) + + if (result.success) { + const id: number = result.data.id + const name: string = result.data.name + + expect(id).toBe(1) + expect(name).toBe('test') + } + }) + }) +}) From 930dc2b25b8202b22d931101152e2be81e3fac8e Mon Sep 17 00:00:00 2001 From: truehazker <40111175+truehazker@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:51:01 +0400 Subject: [PATCH 2/2] :wrench: fix: update type definitions for parse and safeParse to accept unknown type --- src/schema.ts | 6 +++--- src/types.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/schema.ts b/src/schema.ts index 86a4ed8d..bdc39787 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -994,7 +994,7 @@ export const getSchemaValidator = < error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false, @@ -1156,7 +1156,7 @@ export const getSchemaValidator = < error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false, @@ -1229,7 +1229,7 @@ export const getSchemaValidator = < error: null } } catch (error) { - const errors = [...compiled.Errors(v)].map(mapValueError) + const errors = [...validator.Errors(v)].map(mapValueError) return { success: false, diff --git a/src/types.ts b/src/types.ts index 4b3064ee..ea3b2503 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2354,8 +2354,8 @@ export interface ModelValidatorError extends ValueError { } export interface ModelValidator extends TypeCheck { - parse(a: T): Static - safeParse(a: T): + parse(a: unknown): Static + safeParse(a: unknown): | { success: true; data: Static; error: null } | { success: false