From 61096846ecf37b481231de3c5ff754355313c903 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Wed, 18 Sep 2024 14:52:20 +0700 Subject: [PATCH] :wrench: fix: #830 #827 #821 #820 #819 #810 --- CHANGELOG.md | 9 ++++ example/a.ts | 14 ++++- package.json | 2 +- src/compose.ts | 20 +++---- src/context.ts | 16 +++--- src/cookies.ts | 10 ++-- src/handler.ts | 20 ++----- src/index.ts | 16 +++--- src/types.ts | 21 ++++---- src/ws/index.ts | 6 ++- src/ws/types.ts | 1 - test/cookie/response.test.ts | 21 ++++++++ test/types/index.ts | 78 ++++++++++++++++++++++++++- test/units/map-early-response.test.ts | 29 ++++++++++ test/units/map-response.test.ts | 20 ++++++- test/validator/query.test.ts | 30 +++++++++-- 16 files changed, 244 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c30814af..01d0538b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.1.13 - 18 Sep 2024 +Bug fix: +- [#830](https://github.com/elysiajs/elysia/issues/830) Incorrect type for ws.publish +- [#827](https://github.com/elysiajs/elysia/issues/827) returning a response is forcing application/json content-type +- [#821](https://github.com/elysiajs/elysia/issues/821) handle "+" in query with validation +- [#820](https://github.com/elysiajs/elysia/issues/820) params in hooks inside prefixed groups are incorrectly typed never +- [#819](https://github.com/elysiajs/elysia/issues/819) setting cookie attribute before value cause cookie attribute to not be set +- [#810](https://github.com/elysiajs/elysia/issues/810) wrong inference of response in afterResponse, includes status code + # 1.1.12 - 4 Sep 2024 Feature: - setup provenance publish diff --git a/example/a.ts b/example/a.ts index c361744a..8a2c7979 100644 --- a/example/a.ts +++ b/example/a.ts @@ -1,4 +1,14 @@ import { Elysia, t } from '../src' -import { req } from '../test/utils' -const plugin = new Elysia().get('/plugin', 'static ') +new Elysia() + .ws('/', { + open: (ws) => { + ws.publish('channel', 'hello') + }, + response: t.String() + }) + .listen(3000) + +// app.handle(new Request('http://localhost')) +// .then((x) => x.headers.getSetCookie()) +// .then(console.log) diff --git a/package.json b/package.json index eaa3105e..0c433615 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Ergonomic Framework for Human", - "version": "1.1.12", + "version": "1.1.13", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/compose.ts b/src/compose.ts index f95de30c..dab52833 100644 --- a/src/compose.ts +++ b/src/compose.ts @@ -727,8 +727,8 @@ export const composeHandler = ({ let temp - if(memory === -1) temp = decodeURIComponent(url.slice(start)) - else temp = decodeURIComponent(url.slice(start, memory)) + if(memory === -1) temp = decodeURIComponent(url.slice(start).replace(/\\+/g, ' ')) + else temp = decodeURIComponent(url.slice(start, memory).replace(/\\+/g, ' ')) const charCode = temp.charCodeAt(0) if(charCode !== 91 && charCode !== 123) @@ -756,10 +756,10 @@ export const composeHandler = ({ a${index} = [] if(memory === -1) { - a${index}.push(decodeURIComponent(url.slice(start))) + a${index}.push(decodeURIComponent(url.slice(start)).replace(/\\+/g, ' ')) break } - else a${index}.push(decodeURIComponent(url.slice(start, memory))) + else a${index}.push(decodeURIComponent(url.slice(start, memory)).replace(/\\+/g, ' ')) memory = url.indexOf('&${key}=', memory) if(memory === -1) break @@ -773,8 +773,8 @@ export const composeHandler = ({ const start = memory + ${key.length + 2} memory = url.indexOf('&', start) - if(memory === -1) a${index} = decodeURIComponent(url.slice(start)) - else a${index} = decodeURIComponent(url.slice(start, memory)) + if(memory === -1) a${index} = decodeURIComponent(url.slice(start).replace(/\\+/g, ' ')) + else a${index} = decodeURIComponent(url.slice(start, memory).replace(/\\+/g, ' ')) if (a${index} !== undefined) { try { @@ -791,9 +791,9 @@ export const composeHandler = ({ const start = memory + ${key.length + 2} memory = url.indexOf('&', start) - if(memory === -1) a${index} = decodeURIComponent(url.slice(start)) + if(memory === -1) a${index} = decodeURIComponent(url.slice(start).replace(/\\+/g, ' ')) else { - a${index} = decodeURIComponent(url.slice(start, memory)) + a${index} = decodeURIComponent(url.slice(start, memory).replace(/\\+/g, ' ')) ${ anyOf @@ -812,8 +812,8 @@ export const composeHandler = ({ deepMemory = url.indexOf('&', start) let value - if(deepMemory === -1) value = decodeURIComponent(url.slice(start)) - else value = decodeURIComponent(url.slice(start, deepMemory)) + if(deepMemory === -1) value = decodeURIComponent(url.slice(start).replace(/\\+/g, ' ')) + else value = decodeURIComponent(url.slice(start, deepMemory).replace(/\\+/g, ' ')) const vStart = value.charCodeAt(0) const vEnd = value.charCodeAt(value.length - 1) diff --git a/src/context.ts b/src/context.ts index 9e6c1f2e..bfa084c2 100644 --- a/src/context.ts +++ b/src/context.ts @@ -29,7 +29,7 @@ export type ErrorContext< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = Prettify< { body: Route['body'] @@ -102,7 +102,7 @@ export type Context< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = Prettify< { body: Route['body'] @@ -110,9 +110,11 @@ export type Context< ? Record : Route['query'] params: undefined extends Route['params'] - ? Path extends `${string}/${':' | '*'}${string}` - ? ResolvePath - : never + ? undefined extends Path + ? Record + : Path extends `${string}/${':' | '*'}${string}` + ? ResolvePath + : never : Route['params'] headers: undefined extends Route['headers'] ? Record @@ -172,8 +174,8 @@ export type Context< route: string request: Request store: Singleton['store'] - response?: Route['response'] - } & ({} extends Route['response'] + response?: Route['response'][keyof Route['response']] + } & ({} extends Route['response'] ? { error: typeof error } diff --git a/src/cookies.ts b/src/cookies.ts index 26d389fe..34a3c6e2 100644 --- a/src/cookies.ts +++ b/src/cookies.ts @@ -133,7 +133,7 @@ export class Cookie implements ElysiaCookie { ) {} get cookie() { - if (!(this.name in this.jar)) return this.initial + if (!(this.name in this.jar)) this.jar[this.name] = this.initial return this.jar[this.name] } @@ -149,9 +149,7 @@ export class Cookie implements ElysiaCookie { } set value(value: T) { - if (!(this.name in this.jar)) this.jar[this.name] = this.initial - - this.jar[this.name].value = value + this.cookie.value = value } get expires() { @@ -160,6 +158,8 @@ export class Cookie implements ElysiaCookie { set expires(expires) { this.cookie.expires = expires + + console.log(this.cookie) } get maxAge() { @@ -270,7 +270,7 @@ export class Cookie implements ElysiaCookie { toString() { return typeof this.value === 'object' ? JSON.stringify(this.value) - : this.value?.toString() ?? '' + : (this.value?.toString() ?? '') } } diff --git a/src/handler.ts b/src/handler.ts index 9e046db1..c06ecb65 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -7,7 +7,7 @@ import { Cookie } from './cookies' import { ELYSIA_RESPONSE } from './error' import type { Context } from './context' -import { LocalHook } from './types' +import type { LocalHook } from './types' const hasHeaderShorthand = 'toJSON' in new Headers() @@ -580,11 +580,7 @@ export const mapResponse = ( default: if (response instanceof Response) - return new Response(response.body, { - headers: { - 'Content-Type': 'application/json' - } - }) + return response if (response instanceof Promise) return response.then((x) => mapResponse(x, set)) as any @@ -971,11 +967,7 @@ export const mapEarlyResponse = ( default: if (response instanceof Response) - return new Response(response.body, { - headers: { - 'Content-Type': 'application/json' - } - }) + return response if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any @@ -1110,11 +1102,7 @@ export const mapCompactResponse = ( default: if (response instanceof Response) - return new Response(response.body, { - headers: { - 'Content-Type': 'application/json' - } - }) + return response if (response instanceof Promise) return response.then((x) => diff --git a/src/index.ts b/src/index.ts index 9205eb9e..5b6b242b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -934,8 +934,7 @@ export default class Elysia< Ephemeral['derive'] & Volatile['derive'] resolve: {} - }, - BasePath + } > > ): this @@ -1045,8 +1044,7 @@ export default class Elysia< Ephemeral['derive'] & Volatile['derive'] resolve: {} - }, - BasePath + } > > ): this @@ -1570,8 +1568,7 @@ export default class Elysia< Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] - }, - BasePath + } > > ): this @@ -1675,8 +1672,7 @@ export default class Elysia< Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] - }, - BasePath + } > > ): this @@ -4681,7 +4677,9 @@ export default class Elysia< headers: Schema['headers'] response: {} extends Schema['response'] ? unknown - : Schema['response'] + : Schema['response'] extends Record<200, unknown> + ? Schema['response'][200] + : unknown } } >, diff --git a/src/types.ts b/src/types.ts index 67e44243..7a62b365 100644 --- a/src/types.ts +++ b/src/types.ts @@ -155,6 +155,7 @@ export type ElysiaConfig< * Enable Bun static response * * @default true + * @since 1.1.11 */ nativeStaticResponse?: boolean } @@ -388,7 +389,7 @@ export interface UnwrapRoute< export interface UnwrapGroupGuardRoute< in out Schema extends InputSchema, in out Definitions extends Record = {}, - Path extends string = '' + Path extends string | undefined = undefined > { body: UnwrapBodySchema headers: UnwrapSchema< @@ -547,7 +548,7 @@ export type Handler< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = ( context: Context ) => MaybePromise< @@ -592,7 +593,7 @@ export type InlineHandler< derive: {} resolve: {} }, - Path extends string = '', + Path extends string | undefined = undefined, MacroContext = {} > = | (( @@ -644,7 +645,7 @@ export type OptionalHandler< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = Handler extends ( context: infer Context @@ -660,7 +661,7 @@ export type AfterHandler< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = Handler extends ( context: infer Context @@ -668,7 +669,7 @@ export type AfterHandler< ? ( context: Prettify< { - response: Route['response'] + response: Route['response'][keyof Route['response']] } & Context > ) => Returned | MaybePromise @@ -682,7 +683,7 @@ export type MapResponse< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = Handler< Omit & { response: MaybePromise @@ -713,7 +714,7 @@ export type TransformHandler< derive: {} resolve: {} }, - BasePath extends string = '' + Path extends string | undefined = undefined > = { ( context: Prettify< @@ -722,7 +723,7 @@ export type TransformHandler< Omit & { resolve: {} }, - BasePath + Path > > ): MaybePromise @@ -736,7 +737,7 @@ export type BodyHandler< derive: {} resolve: {} }, - Path extends string = '' + Path extends string | undefined = undefined > = ( context: Prettify< { diff --git a/src/ws/index.ts b/src/ws/index.ts index 9aea0f2a..0e29b788 100644 --- a/src/ws/index.ts +++ b/src/ws/index.ts @@ -63,7 +63,11 @@ export class ElysiaWS< get publish() { return ( topic: string, - data: Route['response'] = undefined, + data: {} extends Route['response'] + ? unknown + : Route['response'] extends Record<200, unknown> + ? Route['response'][200] + : unknown = undefined, compress?: boolean ) => { if (this.validator?.Check(data) === false) diff --git a/src/ws/types.ts b/src/ws/types.ts index d573c689..3a2c7da5 100644 --- a/src/ws/types.ts +++ b/src/ws/types.ts @@ -9,7 +9,6 @@ import type { Context } from '../context' import type { SingletonBase, Handler, - VoidHandler, ErrorHandler, InputSchema, RouteSchema, diff --git a/test/cookie/response.test.ts b/test/cookie/response.test.ts index da6c79ee..949c73db 100644 --- a/test/cookie/response.test.ts +++ b/test/cookie/response.test.ts @@ -361,4 +361,25 @@ describe('Cookie Response', () => { // @ts-expect-error expect(res).toEqual({}) }) + + it('set cookie attribute before value', async () => { + const date = new Date( + Date.now() + 1000 * 60 * 60 * 24 + ) + + const app = new Elysia().get('/', ({ cookie }) => { + cookie.my_cookie.expires = date + cookie.my_cookie.value = 'my_cookie_value' + + return 'HI' + }) + + const setCookie = await app + .handle(new Request('http://localhost')) + .then((x) => x.headers.getSetCookie()) + + expect(setCookie).toEqual([ + `my_cookie=my_cookie_value; Path=/; Expires=${date.toUTCString()}` + ]) + }) }) diff --git a/test/types/index.ts b/test/types/index.ts index 2ae4ea45..c7b093cf 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -1901,5 +1901,81 @@ app.get('/', ({ set }) => { })) .as('plugin') - expectTypeOf().toHaveProperty('pluginMethod') + expectTypeOf().toHaveProperty( + 'pluginMethod' + ) +} + +// ? afterResponse type +{ + const app = new Elysia().get( + '/', + () => { + return { + duration: 200 + } + }, + { + response: { + 200: t.Object({ + duration: t.Number() + }), + 400: t.Object({ + stuff: t.Number() + }) + }, + afterResponse({ response }) { + expectTypeOf().toEqualTypeOf< + | { + duration: number + } + | { + stuff: number + } + | undefined + >() + } + } + ) +} + +// ? params in lifecycle shouldn't be never +{ + new Elysia() + .onParse(({ params }) => { + expectTypeOf().toEqualTypeOf< + Record + >() + }) + .onTransform(({ params }) => { + expectTypeOf().toEqualTypeOf< + Record + >() + }) + .onBeforeHandle(({ params }) => { + expectTypeOf().toEqualTypeOf< + Record + >() + }) + .onAfterHandle(({ params }) => { + expectTypeOf().toEqualTypeOf< + Record + >() + }) + // .mapResponse(({ params }) => { + // expectTypeOf().toEqualTypeOf>() + // }) + // .onAfterResponse(({ params }) => { + // expectTypeOf().toEqualTypeOf>() + // }) +} + +// ? Websocket Response +{ + new Elysia().ws('/', { + open: (ws) => { + ws.publish('channel', 'hello') + }, + response: t.String() + }) } diff --git a/test/units/map-early-response.test.ts b/test/units/map-early-response.test.ts index 3309347f..5ea5ea8b 100644 --- a/test/units/map-early-response.test.ts +++ b/test/units/map-early-response.test.ts @@ -28,6 +28,8 @@ class Student { } } +class CustomResponse extends Response {} + describe('Map Early Response', () => { it('map string', async () => { const response = mapEarlyResponse('Shiroko', defaultContext) @@ -109,6 +111,33 @@ describe('Map Early Response', () => { expect(response?.status).toBe(200) }) + it('map custom Response', async () => { + const response = mapEarlyResponse( + new CustomResponse('Shiroko'), + defaultContext + )! + + expect(response).toBeInstanceOf(Response) + expect(await response.text()).toEqual('Shiroko') + expect(response.status).toBe(200) + }) + + it('map custom Response with custom headers', async () => { + const response = mapEarlyResponse(new CustomResponse('Shiroko'), { + ...defaultContext, + headers: { + 'content-type': 'text/html; charset=utf8' + } + })! + + expect(response).toBeInstanceOf(Response) + expect(await response.text()).toEqual('Shiroko') + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toBe( + 'text/html; charset=utf8' + ) + }) + it('map custom class', async () => { const response = mapEarlyResponse(new Student('Himari'), defaultContext) diff --git a/test/units/map-response.test.ts b/test/units/map-response.test.ts index 83653360..57cd41a3 100644 --- a/test/units/map-response.test.ts +++ b/test/units/map-response.test.ts @@ -147,6 +147,22 @@ describe('Map Response', () => { expect(response.status).toBe(200) }) + it('map custom Response with custom headers', async () => { + const response = mapResponse(new CustomResponse('Shiroko'), { + ...defaultContext, + headers: { + 'content-type': 'text/html; charset=utf8' + } + }) + + expect(response).toBeInstanceOf(Response) + expect(await response.text()).toEqual('Shiroko') + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toBe( + 'text/html; charset=utf8' + ) + }) + it('map custom class', async () => { const response = mapResponse(new Student('Himari'), defaultContext) @@ -389,7 +405,7 @@ describe('Map Response', () => { } }) - const response = await app.handle(req('/')).then(x => x.text()) + const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('b') }) @@ -405,7 +421,7 @@ describe('Map Response', () => { } }) - const response = await app.handle(req('/')).then(x => x.text()) + const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('b') }) diff --git a/test/validator/query.test.ts b/test/validator/query.test.ts index 1d9c1e47..58fe5461 100644 --- a/test/validator/query.test.ts +++ b/test/validator/query.test.ts @@ -639,16 +639,38 @@ describe('Query Validator', () => { trailing: t.String() }) }) - + // console.log(app.routes[0].composed?.toString()) - - const response = await app.handle(req('/?leading=foo&arr=bar&arr=baz&trailing=qux&arr=xd')) + + const response = await app + .handle(req('/?leading=foo&arr=bar&arr=baz&trailing=qux&arr=xd')) .then((x) => x.json()) - + expect(response).toEqual({ leading: 'foo', arr: ['bar', 'baz', 'xd'], trailing: 'qux' }) }) + + it('parse + in query', async () => { + const api = new Elysia() + .get('', ({ query }) => query, { + query: t.Object({ + keyword: t.String() + }) + }) + + const url = new URL('http://localhost:3000/') + url.searchParams.append('keyword', 'hello world') + console.log(url.href) //http://localhost:3000/?keyword=hello+world + + const result = await api + .handle(new Request(url.href)) + .then((response) => response.json()) + + expect(result).toEqual({ + 'keyword': 'hello world' + }) + }) })