diff --git a/CHANGELOG.md b/CHANGELOG.md index c87e226b..eb16b4dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# 1.4.6 - 18 Sep 2025 +Improvement: +- [#1406](https://github.com/elysiajs/elysia/issues/1406) strictly check for 200 inline status code +- coerce union status value and return type +- add `BunHTMLBundleLike` to Elysia inline handler +- [#1405](https://github.com/elysiajs/elysia/issues/1405) prevent Elysia from being a dependency of itself +- [#1416](https://github.com/elysiajs/elysia/issues/1416) check if object is frozen before merging, add try-catch to prevent crash +- [#1419](https://github.com/elysiajs/elysia/issues/1419) guard doesn't apply scoped/global schema to object macro +- [#1425](https://github.com/elysiajs/elysia/issues/1425) DELETE doesn't ihnerit derive/resolve type + +Change: +- [#1409](https://github.com/elysiajs/elysia/issues/1409) onTransform now doesn't include type as it isn't validated yet, creating confusion + +Bug fix: +- [#1410](https://github.com/elysiajs/elysia/issues/1410) handle union derive/resolve property +- probably fix mergeDeep attempted to assign to readonly property +- derive/resolve inherit type in inline handler + # 1.4.5 - 15 Sep 2025 Improvement: - soundness for guard, group diff --git a/build.ts b/build.ts index f0e5d462..e34b1eca 100644 --- a/build.ts +++ b/build.ts @@ -1,5 +1,9 @@ import { $ } from 'bun' import { build, type Options } from 'tsup' +import pack from './package.json' + +if ('elysia' in pack.dependencies) + throw new Error("Error can't be a dependency of itself") const external = ['@sinclair/typebox', 'file-type'] diff --git a/bun.lock b/bun.lock index 434b9a80..19ffd95f 100644 --- a/bun.lock +++ b/bun.lock @@ -5,13 +5,12 @@ "name": "elysia", "dependencies": { "cookie": "^1.0.2", - "elysia": "1.4.3-beta.0", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1", }, "devDependencies": { - "@elysiajs/openapi": "^1.3.11", - "@types/bun": "^1.2.16", + "@elysiajs/openapi": "^1.4.1", + "@types/bun": "^1.2.12", "@types/cookie": "^1.0.0", "@types/fast-decode-uri-component": "^1.0.0", "@typescript-eslint/eslint-plugin": "^8.30.1", @@ -47,7 +46,7 @@ "@ark/util": ["@ark/util@0.49.0", "", {}, "sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA=="], - "@elysiajs/openapi": ["@elysiajs/openapi@1.3.11", "", { "dependencies": { "@sinclair/typemap": "^0.10.1", "openapi-types": "^12.1.3" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-+jwAEIDbIGmSKjl/kePwdDaHFgsIX/cEpw01sSjzm7SosjwZuQjJDVgDCab2p68lIO+VUOsvkKuP+5Sm5ehCvQ=="], + "@elysiajs/openapi": ["@elysiajs/openapi@1.4.2", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-i+9YYVmX9Qx40dGRI9DE9zZJrEo4qXo5h2lfuxf1WuQZKYY8RonnYQZYbdWr/Azs8upTfIdyCifgjfu3Cht8rw=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], @@ -189,8 +188,6 @@ "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], - "@sinclair/typemap": ["@sinclair/typemap@0.10.1", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.30", "valibot": "^1.0.0", "zod": "^3.24.1" } }, "sha512-UXR0fhu/n3c9B6lB+SLI5t1eVpt9i9CdDrp2TajRe3LbKiUhCTZN2kSfJhjPnpc3I59jMRIhgew7+0HlMi08mg=="], - "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], diff --git a/example/a.ts b/example/a.ts index e27a475c..c6cee058 100644 --- a/example/a.ts +++ b/example/a.ts @@ -1,17 +1,18 @@ import { Elysia, t } from '../src' -import z from 'zod' -import { req } from '../test/utils' -const app = new Elysia() - .macro('guestOrUser', { - resolve: () => { - return { - user: null +new Elysia() + .macro({ + token: { + resolve: () => { + return { + __token: '123' + } } } }) - .macro('user', { - guestOrUser: true, - body: t.String(), - resolve: ({ body, status, user }) => {} + .macro('some', { + token: true, + beforeHandle: ({ __token }) => { + console.log('__token', __token) + } }) diff --git a/package.json b/package.json index 73fcd112..438dbac8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Ergonomic Framework for Human", - "version": "1.4.5", + "version": "1.4.6", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", @@ -184,13 +184,12 @@ }, "dependencies": { "cookie": "^1.0.2", - "elysia": "1.4.3-beta.0", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "devDependencies": { - "@elysiajs/openapi": "^1.3.11", - "@types/bun": "^1.2.16", + "@elysiajs/openapi": "^1.4.1", + "@types/bun": "^1.2.12", "@types/cookie": "^1.0.0", "@types/fast-decode-uri-component": "^1.0.0", "@typescript-eslint/eslint-plugin": "^8.30.1", diff --git a/src/error.ts b/src/error.ts index 176ddbfb..6ec92c08 100644 --- a/src/error.ts +++ b/src/error.ts @@ -41,7 +41,8 @@ const emptyHttpStatus = { export class ElysiaCustomStatusResponse< const in out Code extends number | keyof StatusMap, - in out T = Code extends keyof InvertedStatusMap + // no in out here so the response can be sub type of return type + T = Code extends keyof InvertedStatusMap ? InvertedStatusMap[Code] : Code, const in out Status extends Code extends keyof StatusMap @@ -79,12 +80,6 @@ export const status = < response?: T ) => new ElysiaCustomStatusResponse(code, response as any) -const a = status(403, 'a') -const b = status(403, 'b') - -type a = typeof a -type b = typeof b - export class InternalServerError extends Error { code = 'INTERNAL_SERVER_ERROR' status = 500 diff --git a/src/index.ts b/src/index.ts index f90f16b2..181e6d5d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,7 +164,9 @@ import type { MacroProperty, MaybeValueOrVoidFunction, IntersectIfObject, - IntersectIfObjectSchema + IntersectIfObjectSchema, + EmptyRouteSchema, + UnknownRouteSchema } from './types' export type AnyElysia = Elysia @@ -1285,9 +1287,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, 'global' extends Type ? { @@ -1426,17 +1428,7 @@ export default class Elysia< onTransform( handler: MaybeArray< TransformHandler< - MergeSchema< - Schema, - MergeSchema< - Volatile['schema'], - MergeSchema - >, - BasePath - > & - Metadata['standaloneSchema'] & - Ephemeral['standaloneSchema'] & - Volatile['standaloneSchema'], + UnknownRouteSchema>, { decorator: Singleton['decorator'] store: Singleton['store'] @@ -1470,22 +1462,13 @@ export default class Elysia< options: { as: Type }, handler: MaybeArray< TransformHandler< - MergeSchema< - Schema, - MergeSchema< - Volatile['schema'], - MergeSchema - >, - BasePath - > & - Metadata['standaloneSchema'] & - Ephemeral['standaloneSchema'] & - Volatile['standaloneSchema'] & + UnknownRouteSchema< 'global' extends Type - ? { params: Record } - : 'scoped' extends Type - ? { params: Record } - : {}, + ? { [name: string]: string | undefined } + : 'scoped' extends Type + ? { [name: string]: string | undefined } + : ResolvePath + >, 'global' extends Type ? { decorator: Singleton['decorator'] @@ -1564,9 +1547,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2064,9 +2047,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2188,9 +2171,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2434,9 +2417,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2554,9 +2537,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2725,9 +2708,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -2838,9 +2821,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type @@ -3918,7 +3901,9 @@ export default class Elysia< Omit, Definitions['typebox'] >, - const BeforeHandle extends MaybeArray> , + const BeforeHandle extends MaybeArray< + OptionalHandler + >, const AfterHandle extends MaybeArray>, const ErrorHandle extends MaybeArray< ErrorHandler @@ -3932,9 +3917,10 @@ export default class Elysia< Schema & MacroContext, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] - resolve: Ephemeral['resolve'] & Volatile['resolve'] & - // @ts-ignore - MacroContext['response'] + resolve: Ephemeral['resolve'] & + Volatile['resolve'] & + // @ts-ignore + MacroContext['response'] }, keyof Metadata['parser'], BeforeHandle, @@ -4144,7 +4130,9 @@ export default class Elysia< >, const GuardType extends GuardSchemaType, const AsType extends LifeCycleType, - const BeforeHandle extends MaybeArray> , + const BeforeHandle extends MaybeArray< + OptionalHandler + >, const AfterHandle extends MaybeArray>, const ErrorHandle extends MaybeArray< ErrorHandler @@ -4156,9 +4144,10 @@ export default class Elysia< Schema & MacroContext, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] - resolve: Ephemeral['resolve'] & Volatile['resolve'] & - // @ts-ignore - MacroContext['response'] + resolve: Ephemeral['resolve'] & + Volatile['resolve'] & + // @ts-ignore + MacroContext['response'] }, keyof Metadata['parser'], BeforeHandle, @@ -5409,7 +5398,22 @@ export default class Elysia< InputSchema, const NewMacro extends Macro< Input, - Metadata['schema'], + IntersectIfObjectSchema< + MergeSchema< + UnwrapRoute< + Input, + Definitions['typebox'], + BasePath + >, + MergeSchema< + Volatile['schema'], + MergeSchema + > + >, + Metadata['standaloneSchema'] & + Ephemeral['standaloneSchema'] & + Volatile['standaloneSchema'] + >, Singleton & { derive: Partial resolve: Partial @@ -5495,7 +5499,9 @@ export default class Elysia< if (k === 'detail') { if (!localHook.detail) localHook.detail = {} - localHook.detail = mergeDeep(localHook.detail, value) + localHook.detail = mergeDeep(localHook.detail, value, { + mergeArray: true + }) delete localHook[key] continue @@ -5946,6 +5952,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6050,6 +6057,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6154,6 +6162,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6258,6 +6267,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6362,6 +6372,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6466,6 +6477,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6570,6 +6582,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > @@ -6676,6 +6689,7 @@ export default class Elysia< Input, // @ts-ignore Schema & MacroContext, + Decorator, Definitions['error'], keyof Metadata['parser'] > & { @@ -7450,9 +7464,9 @@ export default class Elysia< Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type - ? { params: Record } + ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type diff --git a/src/types.ts b/src/types.ts index 8a4707d2..349732ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,7 @@ import type { AnyWSLocalHook } from './ws/types' import type { WebSocketHandler } from './ws/bun' import type { Instruction as ExactMirrorInstruction } from 'exact-mirror' +import { BunHTMLBundlelike } from './universal/types' export type IsNever = [T] extends [never] ? true : false @@ -1063,7 +1064,8 @@ export type MacroToContext< // @ts-expect-error type is checked in key mapping Value['resolve'] > - > & MacroToContext< + > & + MacroToContext< MacroFn, // @ts-ignore trust me bro Pick< @@ -1143,6 +1145,25 @@ type InlineResponse = | AnyElysiaCustomStatusResponse | ElysiaFile | Record + | BunHTMLBundlelike + +type LastOf = + UnionToIntersect T : never> extends () => infer R + ? R + : never + +type Push = [...T, V] + +type TuplifyUnion< + T, + L = LastOf, + N = [T] extends [never] ? true : false +> = true extends N ? [] : Push>, L> + +export type Tuple< + T, + A extends T[] = [] +> = TuplifyUnion['length'] extends A['length'] ? [...A] : Tuple export type InlineHandler< Route extends RouteSchema = {}, @@ -1177,7 +1198,13 @@ export type InlineHandler< | (Route['response'] extends { 200: any } - ? Route['response'][200] + ? + | Route['response'][200] + | ElysiaCustomStatusResponse< + 200, + Route['response'][200], + 200 + > : unknown) // This could be possible because of set.status | Route['response'][keyof Route['response']] @@ -1732,7 +1759,7 @@ export type GuardLocalHook< AfterHandle extends MaybeArray>, ErrorHandle extends MaybeArray>, GuardType extends GuardSchemaType = 'standalone', - AsType extends LifeCycleType = 'local', + AsType extends LifeCycleType = 'local' > = (Input extends any ? Input : Prettify) & { /** * @default 'override' @@ -2015,6 +2042,17 @@ export interface EmptyRouteSchema { response: unknown } +export interface UnknownRouteSchema< + Params = { [name: string]: string | undefined } +> { + body: unknown + headers: { [name: string]: string | undefined } + query: { [name: string]: string | undefined } + params: Params + cookie: {} + response: unknown +} + type Extract200 = T extends AnyElysiaCustomStatusResponse ? | Exclude @@ -2190,10 +2228,10 @@ type PartialIf = Condition extends true // Exclude return error() export type ExcludeElysiaResponse = PartialIf< Exclude, AnyElysiaCustomStatusResponse> extends infer A - ? IsNever extends true + ? IsNever extends true ? {} : // Intersect all union and fallback never to {} - UnionToIntersect + A & {} : {}, undefined extends Awaited ? true : false > diff --git a/src/universal/types.ts b/src/universal/types.ts index a77d11bb..19a862a2 100644 --- a/src/universal/types.ts +++ b/src/universal/types.ts @@ -206,3 +206,18 @@ export abstract class WebStandardResponse implements BodyMixin { return Response.redirect(url, status) } } + +export interface BunHTMLBundlelike { + index: string + files?: { + input?: string + path: string + loader: any + isEntry: boolean + headers: { + etag: string + 'content-type': string + [key: string]: string + } + }[] +} diff --git a/src/utils.ts b/src/utils.ts index abb6b9a2..803d132c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,7 +63,7 @@ export const mergeDeep = < ): A & B => { const skipKeys = options?.skipKeys const override = options?.override ?? true - const mergeArray = options?.mergeArray ?? true + const mergeArray = options?.mergeArray ?? false if (!isObject(target) || !isObject(source)) return target as A & B @@ -81,17 +81,22 @@ export const mergeDeep = < } if (!isObject(value) || !(key in target) || isClass(value)) { - if (override || !(key in target)) - target[key as keyof typeof target] = value + if ((override || !(key in target)) && !Object.isFrozen(target)) + try { + target[key as keyof typeof target] = value + } catch {} continue } - target[key as keyof typeof target] = mergeDeep( - (target as any)[key] as any, - value, - { skipKeys, override, mergeArray } - ) + if (!Object.isFrozen(target[key])) + try { + target[key as keyof typeof target] = mergeDeep( + (target as any)[key] as any, + value, + { skipKeys, override, mergeArray } + ) + } catch {} } return target as A & B diff --git a/test/a.test.ts b/test/a.test.ts deleted file mode 100644 index 7b2d85e3..00000000 --- a/test/a.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Elysia } from '../src' -import { it, expect } from 'bun:test' - -it('DELETE requests returns 400 for no reason', async () => { - const app = new Elysia() - // I am doing group parse: ['application/json'] becouse I dont want to paste parse: ['application/json'] on every API - .group( - 'user', - { parse: 'application/json', detail: { tags: ['Users'] } }, - (group) => - group - .delete('/:id', ({ params: { id } }) => { - return id - }, { - parse: 'none' - }) - .get('/', () => ({ - id: '123', - name: 'John Doe' - })) - // and 10 GETs more.. - ) - .listen(3000) - - const responseForGet = await app.handle( - new Request('http://localhost/user', { method: 'GET' }) - ) - expect(responseForGet.status).toBe(200) - - const responseForDelete = await app.handle( - new Request('http://localhost/user/123', { method: 'DELETE' }) - ) - // 👇 this is weird - expect(responseForDelete.status).toBe(400) -}) diff --git a/test/types/index.ts b/test/types/index.ts index c6400769..3d1cd37c 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -2032,9 +2032,7 @@ type a = keyof {} return {} }) .onTransform(({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{}>() }) .onBeforeHandle(({ params }) => { expectTypeOf().toEqualTypeOf< @@ -2095,48 +2093,48 @@ type a = keyof {} { new Elysia({ prefix: '/:id' }) .onParse({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .derive({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() return {} }) .resolve({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() return {} }) .onTransform({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onBeforeHandle({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onAfterHandle({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .mapResponse({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onAfterResponse({ as: 'global' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) } @@ -2144,48 +2142,48 @@ type a = keyof {} { new Elysia({ prefix: '/:id' }) .onParse({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .derive({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() return {} }) .resolve({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() return {} }) .onTransform({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onBeforeHandle({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onAfterHandle({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .mapResponse({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) .onAfterResponse({ as: 'scoped' }, ({ params }) => { - expectTypeOf().toEqualTypeOf< - Record - >() + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() }) } @@ -2687,3 +2685,72 @@ type a = keyof {} { new Elysia().get('/file', file('public/takodachi.png')) } + +// derive should add property union correctly +{ + const app = new Elysia() + .derive(({ request, status }) => { + const apiKey = request.headers.get('x-api-key') + if (!apiKey) return { auth: null } + + if (Math.random() > 0.5) return status(401) + + return { auth: { id: 1 } } + }) + .onBeforeHandle(({ auth }) => { + expectTypeOf().toEqualTypeOf<{ + readonly id: 1 + } | null>() + }) +} + +// resolve should add property union correctly +{ + const app = new Elysia() + .resolve(({ request, status }) => { + const apiKey = request.headers.get('x-api-key') + if (!apiKey) return { auth: null } + + if (Math.random() > 0.5) return status(401) + + return { auth: { id: 1 } } + }) + .onBeforeHandle(({ auth }) => { + expectTypeOf().toEqualTypeOf<{ + readonly id: 1 + } | null>() + }) +} + +// transform shouldn't inherit schema type +{ + new Elysia() + .guard({ + body: t.Object({ + a: t.String() + }), + params: t.Object({ + id: t.String() + }) + }) + .onTransform(({ params, body }) => { + expectTypeOf().toEqualTypeOf<{}>() + + expectTypeOf().toBeUnknown() + }) +} + +// transform should cast params to unknown when scope is over local +{ + new Elysia({ prefix: '/:id' }) + .onTransform(({ params }) => { + expectTypeOf().toEqualTypeOf<{ + id: string + }>() + }) + .onTransform({ as: 'scoped' }, ({ params }) => { + expectTypeOf().toEqualTypeOf<{ + [name: string]: string | undefined + }>() + }) +} diff --git a/test/types/lifecycle/soundness.ts b/test/types/lifecycle/soundness.ts index 258e70f2..53fa3caf 100644 --- a/test/types/lifecycle/soundness.ts +++ b/test/types/lifecycle/soundness.ts @@ -2082,3 +2082,91 @@ import { Prettify } from '../../../src/types' } }) } + +// Handle 200 status for inline status +{ + new Elysia().get( + '/test', + ({ status }) => { + if (Math.random() > 0.1) + return status(200, { + key: 1, + id: 1 + }) + + if (Math.random() > 0.1) + return status(200, { + // @ts-expect-error + key: 'a', + id: 1 + }) + + return status(200, { key2: 's', id: 2 }) + }, + { + response: { + 200: t.Union([ + t.Object({ + key2: t.String(), + id: t.Literal(2) + }), + t.Object({ + key: t.Number(), + id: t.Literal(1) + }) + ]) + } + } + ) +} + +// coerce union status value and return type +{ + new Elysia().get( + '/test', + ({ status }) => { + return status(200, { key2: 's', id: 2 }) + }, + { + response: { + 200: t.Union([ + t.Object({ + key2: t.String(), + id: t.Literal(2) + }), + t.Object({ + key: t.Number(), + id: t.Literal(1) + }) + ]) + } + } + ) +} + +// Macro should inherit schema type +{ + new Elysia({ name: 'my-middleware-1' }) + .guard({ + as: 'scoped', + headers: t.Object({ + role: t.UnionEnum(['admin', 'user']) + }), + body: t.Object({ + foo: t.String() + }) + }) + .macro({ + auth: { + resolve: ({ headers, body }) => { + expectTypeOf(headers).toEqualTypeOf<{ + role: 'admin' | 'user' + }>() + + expectTypeOf(body).toEqualTypeOf<{ + foo: string + }>() + } + } + }) +} diff --git a/test/types/standard-schema/index.ts b/test/types/standard-schema/index.ts index 54e05eca..01ec147e 100644 --- a/test/types/standard-schema/index.ts +++ b/test/types/standard-schema/index.ts @@ -84,7 +84,7 @@ import { expectTypeOf } from 'expect-type' response: z.literal('lilith') }) // @ts-expect-error - .get('/lilith', () => 'focou' as const, { + .get('/lilith', () => 'a' as const, { response: z.literal('lilith') }) } diff --git a/test/units/merge-deep.test.ts b/test/units/merge-deep.test.ts index 8ceba512..f2767269 100644 --- a/test/units/merge-deep.test.ts +++ b/test/units/merge-deep.test.ts @@ -80,4 +80,10 @@ describe('mergeDeep', () => { expect(response).toBe('ok') }) + + it('handle freezed object', () => { + new Elysia() + .decorate('db', Object.freeze({ hello: 'world' })) + .guard({}, (app) => app) + }) })