From 56ae530b51860c51961139e263283807a7189318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Snorre=20Magnus=20Dav=C3=B8en?= Date: Sat, 30 Mar 2024 14:49:48 +0100 Subject: [PATCH] fix: Use status code specified in context Fix #56 Previously the html plugin would always return 200 for HTML responses. This commit adds support for reading the status code in set.status to determine which status code to use. It fallbacks to 200 if no status code is set. It uses the StatusMap exported by Elysia to map res.status values supplied as string literals. Example for statuscode has been added to examples folder showcasing returning Forbidden status. Added test for overriding status code. Needed to upgrade Elysia peer and dev dependency to a newer version as the StatusMap value was not available in elysia 1.0.2. Running bun install and pnpm install also updated the minor versions of @kitajs/* dependencies. --- bun.lockb | Bin 56554 -> 56999 bytes example/index.tsx | 4 ++ example/statuscode.tsx | 16 ++++++ package.json | 4 +- pnpm-lock.yaml | 120 ++++++++++++++++++++++++++--------------- src/handler.ts | 19 ++++--- src/html.ts | 15 ++++-- test/index.test.ts | 10 ++++ 8 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 example/statuscode.tsx diff --git a/bun.lockb b/bun.lockb index 4d701c07968f9b4dc6ebcb730fe2ad23ff9e177e..b847d243e26734aea36c5fbb28ef1ec9d4dd3e61 100755 GIT binary patch delta 1645 zcmYk-e{2(F9LMqN+I3xpL3DIPuuKw*in*;8x-r}8+^8_M$`9KiQZ_Ax38@jZ83gJy z(k0+3=Fu|JrePx%|5zHfTN6uB6g06}GOZF~@%esQ5-$0^?z#8v zdbcIL{gIHLzbHQ?J|^xx)Us^-kfCby?YB1FytFX6@AEt1|7OnB^!@ewhLPoM4LJeZ z=}#Q1s=oegy#Gsua?gRohx&RYv3f$G>NxxBzd7^lacQnL`!MToXqIlS?U%DgwQBK^ z#|2@%APmUp1DJkoljLd|Q&dm$qZ(;flT=x=)<8PjZVa6K>uwQW?qFb#E2QSreH z6irn8Fb9ohDgl^>77LXiEI{kiR3fklZO>4N!V7?R; zDX85@#S7C=XQkqU87MwS#Se4PxQR*t=AmUXl^`rYYZsLWEJEA!RHCp1?JrP?LFJ3I zcEUKUvr%!wB-Fe_#RF4N`!W?TOherkDn6Kj;#Mktn1jY`R01#$E!(LCVF6lSp%Q^b zXtPs^!Vlt)~@*nW_nkpI_8eeTsfAg@4I~No3Y!6Pi7)# zTTUczUH>Qk%h2D$A;ak{)BbhBjp+Dj;^0^OFS3{Jl!4+tTKi!R8vCdOU>;iDq!NS$ zXx&dG0*m_{-S<3e@ZM)#vtnTEJCo|kgCi{|O`GqDTjy2NvfO0AwUD?`mAEGSbpO$j z+xG2Sr&jMgk)4`4xpC3AmUjXV&5r&#Z<`FY>)2dvgXB7J{XRBdTQ9Zt53Xbb9(`@) z9+N?K`BT$-{D!4F<&EzBAs>1Nt)TMlTl_v>n)%S;Ax*bq8cnqE#wrra4QYm1dLcm&!@+K!C&%y{eFG# z^x-t?Ctq29{lfCT_Gs_ltqZHubK6gDoz&c&moEQ#{=$(%HwW7f51c+Yy0JUGV_xBk ze-LDnrEz=tsO3Yu)%dLaqSeT>XRLbb+HI<$ht~$g z0(Gv#IYNt6TaOZ@CF=2j?Ko(&iUXLNrHPd?+I{PaQ9!glT~~UqXq{BGr0PqO?R^n^9u4Og≺ev~w|1#k}1EN$M75~4ZU(uXoa^VG2eB}@y{`7%m`7OA!qB}z-w z^$JRimZ>L*5~mgF-G!2*RqES~lA<;0??*{f+W^i%nx)NqP(n0ETLw`^Xr4OuqJ(LI zIzuQCTBO>(K%e}uRHe4CrYg#b`mc;yBV$*hcTX((I&tfVhKDDwSEsvnS^ruxbZq9g zFWY`unm;nUqI0@e?+&bg>qh@rU}w*#p4U!%ynp$?(hEPeWfz}4rRb%7ct6*E7^7wC z8A6HE3iTd9Nzy9y9Yjgd8ubqc^czk08xI`1dZ*rf*D_=G(H`}GVu{)`QU7k{C-iS7 zHXN({Q(uVh$jFzcJc0+Z{(HPY&Tq0P$|>1#jYX!-Jilx1R@sls;QIH>)*$;T8LXI$ z&72O|yH_IcFO6%gT2FJuMYJqO4qsTw@dD8#}}y{ck3!`rM6nX xIW?%RHUb}Qx!1mIrvAk9Gx~wqO?qbbb^Dx|HqRUDjEhdW^5<)}EPC(J#J|mV&JzFt diff --git a/example/index.tsx b/example/index.tsx index 9ad1a64..8b46d02 100644 --- a/example/index.tsx +++ b/example/index.tsx @@ -71,3 +71,7 @@ app.handle(new Request('http://localhost:8080/')) app.handle(new Request('http://localhost:8080/')) .then((x) => x.headers.toJSON()) .then(console.log) + + app.handle(new Request('http://localhost:8080/')) + .then((x) => x.status) + .then(console.log) \ No newline at end of file diff --git a/example/statuscode.tsx b/example/statuscode.tsx new file mode 100644 index 0000000..cb401dd --- /dev/null +++ b/example/statuscode.tsx @@ -0,0 +1,16 @@ +import { Elysia } from 'elysia' +import { html } from '../src' + +const app = new Elysia() + .use(html({ autoDetect: true })) + .get('/a', ({ html, set }) => { + set.status = 'Forbidden' + return html(`

Forbidden!

`) + }) + .compile() + +console.log(app.routes[0]?.composed?.toString()) + +app.handle(new Request('http://localhost:8080/a')) + .then((x) => x.status) + .then(console.log) diff --git a/package.json b/package.json index 4ac883e..22c1c36 100644 --- a/package.json +++ b/package.json @@ -52,13 +52,13 @@ "format": "prettier --write ." }, "peerDependencies": { - "elysia": ">= 1.0.2" + "elysia": ">= 1.0.6" }, "devDependencies": { "@elysiajs/stream": "^0.7.2", "@types/bun": "^1.0.4", "@types/node": "^20.7.2", - "elysia": "1.0.2", + "elysia": "1.0.6", "eslint": "^8.50.0", "rimraf": "^5.0.5", "typescript": "^5.2.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e019e84..4dd3feb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -6,22 +6,25 @@ settings: dependencies: '@kitajs/html': - specifier: ^3.0.0 - version: 3.0.0(@kitajs/ts-html-plugin@1.1.1) + specifier: ^3.0.2 + version: 3.1.2(@kitajs/ts-html-plugin@1.3.4) '@kitajs/ts-html-plugin': - specifier: ^1.1.1 - version: 1.1.1(@kitajs/html@3.0.0)(typescript@5.2.2) + specifier: ^1.2.0 + version: 1.3.4(@kitajs/html@3.1.2)(typescript@5.2.2) devDependencies: + '@elysiajs/stream': + specifier: ^0.7.2 + version: 0.7.2(elysia@1.0.6) + '@types/bun': + specifier: ^1.0.4 + version: 1.0.11 '@types/node': specifier: ^20.7.2 version: 20.7.2 - bun-types: - specifier: ^1.0.3 - version: 1.0.3 elysia: - specifier: ^0.7.15 - version: 0.7.15(typescript@5.2.2) + specifier: 1.0.6 + version: 1.0.6(@sinclair/typebox@0.32.20)(typescript@5.2.2) eslint: specifier: ^8.50.0 version: 8.50.0 @@ -39,6 +42,15 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@elysiajs/stream@0.7.2(elysia@1.0.6): + resolution: {integrity: sha512-mHdpGKVpDvJt161+Da1JUUkMtMZ9sL/l2768Bu1EgUAVBLapgK6KVj3T110vVRy7sMR6OXz66eooTY2yqLILlw==} + peerDependencies: + elysia: '>= 0.7.20' + dependencies: + elysia: 1.0.6(@sinclair/typebox@0.32.20)(typescript@5.2.2) + nanoid: 5.0.6 + dev: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -108,24 +120,24 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true - /@kitajs/html@3.0.0(@kitajs/ts-html-plugin@1.1.1): - resolution: {integrity: sha512-8k9TH+9IBjq4XE/ct7Bv/jpKGuPqsTEbS8XFk4fQRNzQPRjJUm2sgQad9kIAZqh5UPA/QWw1pYMTI32UwKPRWg==} + /@kitajs/html@3.1.2(@kitajs/ts-html-plugin@1.3.4): + resolution: {integrity: sha512-igMLn8VCrAyjFuK1OOsCkiiu95EQ+hK/C96moz9+MzX3lsMukZO/AqXRxdhTeB80AtE61pL+lUTuwTkqz/s+rQ==} engines: {node: '>=12'} peerDependencies: - '@kitajs/ts-html-plugin': '>=1.1' + '@kitajs/ts-html-plugin': '>=1.3.3' dependencies: - '@kitajs/ts-html-plugin': 1.1.1(@kitajs/html@3.0.0)(typescript@5.2.2) - csstype: 3.1.2 + '@kitajs/ts-html-plugin': 1.3.4(@kitajs/html@3.1.2)(typescript@5.2.2) + csstype: 3.1.3 dev: false - /@kitajs/ts-html-plugin@1.1.1(@kitajs/html@3.0.0)(typescript@5.2.2): - resolution: {integrity: sha512-LV/6b23stMYoIg3lJBMgEhn10bvU75kdwTrE0pnLjfn64CJdvCNmpiDtnGWd9TIzoBaRAPCh5nPFxXJg6pm4Eg==} + /@kitajs/ts-html-plugin@1.3.4(@kitajs/html@3.1.2)(typescript@5.2.2): + resolution: {integrity: sha512-AAht1OvLkQizJ59DM70qBgb0VwdyW9KUtDaH66JrfanMMvSSoM598WspJrVdVbe50olw69H+nnTj0lEfNDVmPQ==} hasBin: true peerDependencies: - '@kitajs/html': '>=2' - typescript: '>=5' + '@kitajs/html': ^3.1.1 + typescript: ^5.2.2 dependencies: - '@kitajs/html': 3.0.0(@kitajs/ts-html-plugin@1.1.1) + '@kitajs/html': 3.1.2(@kitajs/ts-html-plugin@1.3.4) chalk: 4.1.2 tslib: 2.6.2 typescript: 5.2.2 @@ -160,10 +172,32 @@ packages: dev: true optional: true + /@sinclair/typebox@0.32.20: + resolution: {integrity: sha512-ziK497ILSIYMxD/thl496idIb03IZPlha04itLQu1xAFQbumWZ+Dj4PMMCkDRpAYhvVSdmRlTjGu2B2MA5RplQ==} + dev: true + + /@types/bun@1.0.11: + resolution: {integrity: sha512-kU4yU7vs/J/yIBoocc9j0sR/CxQ/WD4hSx3rl+WA2nLjcYuGR5aHJXVIBRcT5zvMmTt4CnSVkiONiWPob5trFg==} + dependencies: + bun-types: 1.0.35 + dev: true + + /@types/node@20.11.30: + resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/node@20.7.2: resolution: {integrity: sha512-RcdC3hOBOauLP+r/kRt27NrByYtDjsXyAuSbR87O6xpsvi763WI+5fbSIvYJrXnt9w4RuxhV6eAXfIs7aaf/FQ==} dev: true + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 20.11.30 + dev: true + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -228,8 +262,11 @@ packages: balanced-match: 1.0.2 dev: true - /bun-types@1.0.3: - resolution: {integrity: sha512-XlyKVdYCHa7K5PHYGcwOVOrGE/bMnLS51y7zFA3ZAAXyiQ6dTaNXNCWTTufgII/6ruN770uhAXphQmzvU/r2fQ==} + /bun-types@1.0.35: + resolution: {integrity: sha512-JlFllUCVMZbDyGfbv9dBWXd2tRdZSzyP1EWDKcTTVRViYNYX8AEsfpMN/vu6Hk8CBhKrhbbBkKZcNjJev8QQHQ==} + dependencies: + '@types/node': 20.11.30 + '@types/ws': 8.5.10 dev: true /callsites@3.1.0: @@ -266,13 +303,8 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /cookie-signature@1.2.1: - resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} - engines: {node: '>=6.6.0'} - dev: true - - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} dev: true @@ -285,8 +317,8 @@ packages: which: 2.0.2 dev: true - /csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: false /debug@4.3.4: @@ -316,25 +348,23 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /elysia@0.7.15(typescript@5.2.2): - resolution: {integrity: sha512-b1gVxVMb7tlwMFM7NRSCEpYCmtGGeHSzkU4HmocmZmieLYxCEQ2og5kamv38gwvY8Ju41aysv3aKijmPsu5xew==} + /elysia@1.0.6(@sinclair/typebox@0.32.20)(typescript@5.2.2): + resolution: {integrity: sha512-vr+ARNglPJ4Q2TNGSvAT1WEfxkPgfdXMfbkUacijxKz7eYvWnqA3ir751Pe24o31mJszl1HNhjQA56axorlyug==} peerDependencies: '@sinclair/typebox': '>= 0.31.0' openapi-types: '>= 12.0.0' typescript: '>= 5.0.0' peerDependenciesMeta: - '@sinclair/typebox': - optional: true openapi-types: optional: true typescript: optional: true dependencies: - cookie: 0.5.0 - cookie-signature: 1.2.1 + '@sinclair/typebox': 0.32.20 + cookie: 0.6.0 eventemitter3: 5.0.1 + fast-decode-uri-component: 1.0.1 fast-querystring: 1.1.2 - memoirist: 0.1.4 typescript: 5.2.2 dev: true @@ -681,10 +711,6 @@ packages: engines: {node: 14 || >=16.14} dev: true - /memoirist@0.1.4: - resolution: {integrity: sha512-D6GbPSqO2nUVOmm7VZjJc5tC60pkOVUPzLwkKl1vCiYP+2b1cG8N9q1O3P0JmNM68u8vsgefPbxRUCSGxSXD+g==} - dev: true - /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -707,6 +733,12 @@ packages: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /nanoid@5.0.6: + resolution: {integrity: sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==} + engines: {node: ^18 || >=20} + hasBin: true + dev: true + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -906,6 +938,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: diff --git a/src/handler.ts b/src/handler.ts index 3b706d7..154908f 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -5,8 +5,10 @@ import { isHtml, isTagHtml } from './utils' export function handleHtml( value: string | Readable | Promise, options: HtmlOptions, - hasContentType: boolean + hasContentType: boolean, + status?: number ): Promise | Response | string { + // Only use promises if value is a promise itself if (value instanceof Promise) { return value.then((v) => handleHtml(v, options, hasContentType)) @@ -24,9 +26,7 @@ export function handleHtml( return new Response( value, - hasContentType - ? undefined - : { headers: { 'content-type': options.contentType! } } + initValue(options, hasContentType, status) ) } @@ -60,8 +60,13 @@ export function handleHtml( return new Response( stream as any, - hasContentType - ? undefined - : { headers: { 'content-type': options.contentType! } } + initValue(options, hasContentType, status) ) } + + +function initValue(options: HtmlOptions, hasContentType: boolean, status?: number) { + return hasContentType + ? { status: status?? 200 } + : { headers: { 'content-type': options.contentType! }, status: status?? 200 } +} \ No newline at end of file diff --git a/src/html.ts b/src/html.ts index c7d66f5..975b04f 100644 --- a/src/html.ts +++ b/src/html.ts @@ -1,4 +1,4 @@ -import { Elysia } from 'elysia' +import { Elysia, StatusMap } from 'elysia' import { Readable } from 'node:stream' import { renderToStream } from '@kitajs/html/suspense' @@ -17,22 +17,26 @@ export function html(options: HtmlOptions = {}) { name: '@elysiajs/html', seed: options }).derive({ as: 'global' }, ({ set }) => { + return { html( value: Readable | JSX.Element ): Promise | Response | string { - return handleHtml(value, options, 'content-type' in set.headers) + const status = typeof set.status === 'string' ? StatusMap[set.status] : set.status + return handleHtml(value, options, 'content-type' in set.headers, status) }, stream( value: (this: void, arg: A & { id: number }) => JSX.Element, args: A ) { + const status = typeof set.status === 'string' ? StatusMap[set.status] : set.status return handleHtml( renderToStream((id) => (value as Function)({ ...args, id }) ), options, - 'content-type' in set.headers + 'content-type' in set.headers, + status ) } } @@ -48,10 +52,13 @@ export function html(options: HtmlOptions = {}) { // @kitajs/html stream (value instanceof Readable && 'rid' in value) ) { + const status = typeof set.status === 'string' ? StatusMap[set.status] : set.status + const response = await handleHtml( value, options, - 'content-type' in set.headers + 'content-type' in set.headers, + status ) if (response instanceof Response) return response diff --git a/test/index.test.ts b/test/index.test.ts index 262088d..b1a58a1 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -69,6 +69,16 @@ describe('HTML', () => { 'text/html; charset=utf8' ) }) + + it('returns user provided status code', async () => { + const app = new Elysia().use(html()).get('/', ({ html, set }) => { + set.status = 404 + return html('

Not Found

') + }) + + const res = await app.handle(req('/')) + expect(res.status).toBe(404) + }) }) describe('HTML vs No html - header', () => {