diff --git a/example/a.ts b/example/a.ts index 8a84251b..68479359 100644 --- a/example/a.ts +++ b/example/a.ts @@ -1,10 +1,11 @@ import { Elysia } from '../src' import { NodeAdapter } from '../src/adapter/node' -new Elysia({ adapter: NodeAdapter }) - .get('/', ({ cookie: { a, b } }) => { - a.value = 'hi' - b.value = 'hi' +import { createServer } from 'node:http' + +const app = new Elysia({ adapter: NodeAdapter }) + .get('/', ({ query }) => { + console.log(query) return 'hi' }) diff --git a/package.json b/package.json index cf829c74..6ebeb898 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Ergonomic Framework for Human", - "version": "1.2.0-exp.11", + "version": "1.2.0-exp.12", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/adapter/bun/index.ts b/src/adapter/bun/index.ts index 75930a94..161abf93 100644 --- a/src/adapter/bun/index.ts +++ b/src/adapter/bun/index.ts @@ -12,6 +12,7 @@ import { WebStandardAdapter } from '../web-standard/index' export const BunAdapter: ElysiaAdapter = { ...WebStandardAdapter, + name: 'bun', handler: { ...WebStandardAdapter.handler, createNativeStaticHandler diff --git a/src/adapter/node/handler.ts b/src/adapter/node/handler.ts index 78f8db08..d490b6ef 100644 --- a/src/adapter/node/handler.ts +++ b/src/adapter/node/handler.ts @@ -1,16 +1,19 @@ +/* eslint-disable sonarjs/no-duplicated-branches */ /* eslint-disable sonarjs/no-nested-switch */ /* eslint-disable sonarjs/no-duplicate-string */ import { serialize } from 'cookie' +import { type IncomingMessage, type ServerResponse } from 'node:http' + import { Readable } from 'node:stream' import { isNotEmpty, hasHeaderShorthand, StatusMap } from '../../utils' import { Cookie } from '../../cookies' +import { ElysiaCustomStatusResponse } from '../../error' import type { Context } from '../../context' import type { HTTPHeaders, Prettify } from '../../types' -import { ElysiaCustomStatusResponse } from '../../error' type SetResponse = Prettify< Omit & { @@ -99,7 +102,7 @@ export const serializeCookie = (cookies: Context['set']['cookie']) => { const handleStream = ( generator: Generator | AsyncGenerator, set?: Context['set'], - abortSignal?: AbortSignal + res?: HttpResponse ): ElysiaNodeResponse => { if (!set) set = { @@ -114,41 +117,39 @@ const handleStream = ( set.headers['content-type'] = 'text/event-stream;charset=utf-8' } - return [ - handleStreamResponse(generator, set, abortSignal), - set as SetResponse - ] + if (res) res.writeHead(set.status as number, set.headers) + + return [handleStreamResponse(generator, set, res), set as SetResponse] } export const handleStreamResponse = ( generator: Generator | AsyncGenerator, set?: Context['set'], - abortSignal?: AbortSignal + res?: HttpResponse ) => { const readable = new Readable({ read() {} }) + if (res) readable.pipe(res) ;(async () => { let init = generator.next() if (init instanceof Promise) init = await init if (init.done) { - if (set) return mapResponse(init.value, set, abortSignal) - return mapCompactResponse(init.value, abortSignal) + if (set) return mapResponse(init.value, set) + return mapCompactResponse(init.value) } - let end = false + // abortSignal?.addEventListener('abort', () => { + // end = true - abortSignal?.addEventListener('abort', () => { - end = true - - try { - readable.push(null) - } catch { - // nothing - } - }) + // try { + // readable.push(null) + // } catch { + // // nothing + // } + // }) if (init.value !== undefined && init.value !== null) { if (typeof init.value === 'object') @@ -161,7 +162,6 @@ export const handleStreamResponse = ( } for await (const chunk of generator) { - if (end) break if (chunk === undefined || chunk === null) continue if (typeof chunk === 'object') @@ -203,10 +203,14 @@ export async function* streamResponse(response: Response) { } } +type HttpResponse = ServerResponse & { + req: IncomingMessage +} + export const mapResponse = ( response: unknown, set: Context['set'], - abortSignal?: AbortSignal + res?: HttpResponse ): ElysiaNodeResponse => { if ( isNotEmpty(set.headers) || @@ -232,16 +236,35 @@ export const mapResponse = ( case 'String': set.headers['content-type'] = 'text/plain;charset=utf-8' + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Blob': - return [response as File | Blob, set as SetResponse] + response = handleFile(response as File | Blob) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Array': case 'Object': set.headers['content-type'] = 'application/json;charset=utf-8' - return [JSON.stringify(response), set as SetResponse] + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code @@ -249,7 +272,7 @@ export const mapResponse = ( return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) case 'ReadableStream': @@ -261,26 +284,47 @@ export const mapResponse = ( set.headers['content-type'] = 'text/event-stream;charset=utf-8' - abortSignal?.addEventListener( - 'abort', - { - handleEvent() { - if (!abortSignal.aborted) - (response as ReadableStream).cancel() - } - }, - { - once: true - } - ) + set.headers['transfer-encoding'] = 'chunked' + + if (res) { + res.writeHead(set.status!, set.headers) + readableStreamToReadable(response as ReadableStream).pipe( + res + ) + } + + // abortSignal?.addEventListener( + // 'abort', + // { + // handleEvent() { + // if (!abortSignal.aborted) + // (response as ReadableStream).cancel() + // } + // }, + // { + // once: true + // } + // ) return [response as ReadableStream, set as SetResponse] case undefined: - if (!response) return ['', set as SetResponse] + if (!response) { + if (res) { + res.writeHead(set.status!, set.headers) + res.end('') + } + + return ['', set as SetResponse] + } set.headers['content-type'] = 'application/json;charset=utf-8' + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Response': @@ -322,13 +366,22 @@ export const mapResponse = ( return handleStream( streamResponse(response as Response), set, - abortSignal + res ) as any + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] case 'Error': - return errorToResponse(response as Error, set) + response = errorToResponse(response as Error, set) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse case 'Promise': return (response as Promise).then((x) => @@ -342,10 +395,12 @@ export const mapResponse = ( case 'Boolean': set.headers['content-type'] = 'text/plain;charset=utf-8' - return [ - (response as number | boolean).toString(), - set as SetResponse - ] + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Cookie': if (response instanceof Cookie) @@ -354,6 +409,11 @@ export const mapResponse = ( return mapResponse(response?.toString(), set) case 'FormData': + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response as FormData, set as SetResponse] default: @@ -396,14 +456,26 @@ export const mapResponse = ( ).headers.entries()) if (key in set.headers) set.headers[key] = value + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] } if (response instanceof Promise) return response.then((x) => mapResponse(x, set)) as any - if (response instanceof Error) - return errorToResponse(response as Error, set) + if (response instanceof Error) { + response = errorToResponse(response as Error, set) + + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + } if (response instanceof ElysiaCustomStatusResponse) { set.status = ( @@ -413,13 +485,13 @@ export const mapResponse = ( return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) } // @ts-expect-error if (typeof response?.next === 'function') - return handleStream(response as any, set, abortSignal) + return handleStream(response as any, set, res) if ('toResponse' in (response as any)) return mapResponse((response as any).toResponse(), set) @@ -432,13 +504,24 @@ export const mapResponse = ( set.headers['content-type'] = 'application/json;charset=utf-8' - return new Response( - JSON.stringify(response), - set as SetResponse - ) as any + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] } } + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response as any, set as SetResponse] } } else @@ -446,19 +529,35 @@ export const mapResponse = ( case 'String': set.headers['content-type'] = 'text/plain;charset=utf-8' + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Blob': - return [ - handleFile(response as File | Blob, set), - set as SetResponse - ] + response = handleFile(response as File | Blob) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Array': case 'Object': set.headers['content-type'] = 'application/json;charset=utf-8' - return [JSON.stringify(response), set as SetResponse] + response = JSON.stringify(response) + + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code @@ -466,32 +565,52 @@ export const mapResponse = ( return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) case 'ReadableStream': set.headers['content-type'] = 'text/event-stream;charset=utf-8' + set.headers['transfer-encoding'] = 'chunked' - abortSignal?.addEventListener( - 'abort', - { - handleEvent() { - if (!abortSignal?.aborted) - (response as ReadableStream).cancel() - } - }, - { - once: true - } - ) + if (res) { + res.writeHead(200, set.headers) + readableStreamToReadable(response as ReadableStream).pipe( + res + ) + } + + // abortSignal?.addEventListener( + // 'abort', + // { + // handleEvent() { + // if (!abortSignal?.aborted) + // (response as ReadableStream).cancel() + // } + // }, + // { + // once: true + // } + // ) return [response as ReadableStream, set as SetResponse] case undefined: - if (!response) return ['', set as SetResponse] + if (!response) { + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return ['', set as SetResponse] + } set.headers['content-type'] = 'application/json;charset=utf-8' + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Response': @@ -502,36 +621,47 @@ export const mapResponse = ( return handleStream( streamResponse(response as Response), set, - abortSignal + res ) as any + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] case 'Error': - return errorToResponse(response as Error, set) + response = errorToResponse(response as Error, set) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse case 'Promise': // @ts-ignore return (response as any as Promise).then((x) => { - const r = mapCompactResponse(x, abortSignal) + const r = mapCompactResponse(x, res) - if (r !== undefined) return r + if (r !== undefined) return [r, set] - return new Response('') + return ['', set as SetResponse] }) // ? Maybe response or Blob case 'Function': - return mapCompactResponse((response as Function)(), abortSignal) + return mapCompactResponse((response as Function)(), res) case 'Number': case 'Boolean': set.headers['content-type'] = 'text/plain;charset=utf-8' - return [ - (response as number | boolean).toString(), - set as SetResponse - ] + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Cookie': if (response instanceof Cookie) @@ -540,17 +670,35 @@ export const mapResponse = ( return mapResponse(response?.toString(), set) case 'FormData': + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response as FormData, set as SetResponse] default: - if (response instanceof Response) + if (response instanceof Response) { + if (res) responseToValue(response as Response, res) + return [response, set as SetResponse] + } if (response instanceof Promise) return response.then((x) => mapResponse(x, set)) as any - if (response instanceof Error) - return errorToResponse(response as Error, set) + if (response instanceof Error) { + response = errorToResponse(response as Error, set) + + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + } if (response instanceof ElysiaCustomStatusResponse) { set.status = ( @@ -560,13 +708,13 @@ export const mapResponse = ( return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) } // @ts-expect-error if (typeof response?.next === 'function') - return handleStream(response as any, set, abortSignal) + return handleStream(response as any, set, res) if ('toResponse' in (response as any)) return mapResponse((response as any).toResponse(), set) @@ -579,13 +727,24 @@ export const mapResponse = ( set.headers['content-type'] = 'application/json;charset=utf-8' - return new Response( - JSON.stringify(response), - set as SetResponse - ) as any + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] } } + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response, set as SetResponse] } } @@ -593,7 +752,7 @@ export const mapResponse = ( export const mapEarlyResponse = ( response: unknown, set: Context['set'], - abortSignal?: AbortSignal + res?: HttpResponse ): ElysiaNodeResponse | undefined => { if (response === undefined || response === null) return @@ -622,9 +781,19 @@ export const mapEarlyResponse = ( case 'String': set.headers['content-type'] = 'text/plain;charset=utf-8' + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Blob': + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [ handleFile(response as File | Blob, set), set as SetResponse @@ -634,7 +803,14 @@ export const mapEarlyResponse = ( case 'Object': set.headers['content-type'] = 'application/json;charset=utf-8' - return [JSON.stringify(response), set as SetResponse] + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code @@ -642,7 +818,7 @@ export const mapEarlyResponse = ( return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) case 'ReadableStream': @@ -654,23 +830,39 @@ export const mapEarlyResponse = ( set.headers['content-type'] = 'text/event-stream;charset=utf-8' - abortSignal?.addEventListener( - 'abort', - { - handleEvent() { - if (!abortSignal?.aborted) - (response as ReadableStream).cancel() - } - }, - { - once: true - } - ) + set.headers['transfer-encoding'] = 'chunked' + + if (res) { + res.writeHead(set.status!, set.headers) + readableStreamToReadable(response as ReadableStream).pipe( + res + ) + } + + // abortSignal?.addEventListener( + // 'abort', + // { + // handleEvent() { + // if (!abortSignal?.aborted) + // (response as ReadableStream).cancel() + // } + // }, + // { + // once: true + // } + // ) return [response as ReadableStream, set as SetResponse] case undefined: - if (!response) return + if (!response) { + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return ['', set as SetResponse] + } set.headers['content-type'] = 'application/json;charset=utf-8' @@ -715,21 +907,30 @@ export const mapEarlyResponse = ( return handleStream( streamResponse(response as Response), set, - abortSignal + res ) as any + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] + case 'Error': + response = errorToResponse(response as Error, set) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + case 'Promise': // @ts-ignore return (response as Promise).then((x) => { const r = mapEarlyResponse(x, set) - if (r !== undefined) return r + if (r !== undefined) return [r, set] }) - case 'Error': - return errorToResponse(response as Error, set) - case 'Function': return mapEarlyResponse((response as Function)(), set) @@ -737,10 +938,12 @@ export const mapEarlyResponse = ( case 'Boolean': set.headers['content-type'] = 'text/plain;charset=utf-8' - return [ - (response as number | boolean).toString(), - set as SetResponse - ] + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response as number | boolean, set as SetResponse] case 'Cookie': if (response instanceof Cookie) @@ -749,6 +952,11 @@ export const mapEarlyResponse = ( return mapEarlyResponse(response?.toString(), set) case 'FormData': + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response as FormData, set as SetResponse] default: @@ -784,14 +992,26 @@ export const mapEarlyResponse = ( if ((response as Response).status !== set.status) set.status = (response as Response).status + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] } if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any - if (response instanceof Error) - return errorToResponse(response as Error, set) + if (response instanceof Error) { + response = errorToResponse(response as Error, set) + + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + } if (response instanceof ElysiaCustomStatusResponse) { set.status = ( @@ -801,13 +1021,13 @@ export const mapEarlyResponse = ( return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) } // @ts-ignore if (typeof response?.next === 'function') - return handleStream(response as any, set, abortSignal) + return handleStream(response as any, set, res) if ('toResponse' in (response as any)) return mapEarlyResponse((response as any).toResponse(), set) @@ -820,13 +1040,24 @@ export const mapEarlyResponse = ( set.headers['content-type'] = 'application/json;charset=utf-8' - return new Response( - JSON.stringify(response), - set as SetResponse - ) as any + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] } } + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response, set as SetResponse] } } else @@ -834,19 +1065,35 @@ export const mapEarlyResponse = ( case 'String': set.headers['content-type'] = 'text/plain;charset=utf-8' + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Blob': - return [ - handleFile(response as File | Blob, set), - set as SetResponse - ] + response = handleFile(response as File | Blob) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Array': case 'Object': set.headers['content-type'] = 'application/json;charset=utf-8' - return [JSON.stringify(response), set as SetResponse] + response = JSON.stringify(response) + + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code @@ -854,32 +1101,52 @@ export const mapEarlyResponse = ( return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) case 'ReadableStream': set.headers['content-type'] = 'text/event-stream;charset=utf-8' + set.headers['transfer-encoding'] = 'chunked' - abortSignal?.addEventListener( - 'abort', - { - handleEvent() { - if (!abortSignal?.aborted) - (response as ReadableStream).cancel() - } - }, - { - once: true - } - ) + if (res) { + res.writeHead(200, set.headers) + readableStreamToReadable(response as ReadableStream).pipe( + res + ) + } + + // abortSignal?.addEventListener( + // 'abort', + // { + // handleEvent() { + // if (!abortSignal?.aborted) + // (response as ReadableStream).cancel() + // } + // }, + // { + // once: true + // } + // ) return [response, set as SetResponse] case undefined: - if (!response) return ['', set as SetResponse] + if (!response) { + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return ['', set as SetResponse] + } set.headers['content-type'] = 'application/json;charset=utf-8' + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response, set as SetResponse] case 'Response': @@ -888,11 +1155,25 @@ export const mapEarlyResponse = ( 'chunked' ) return handleStream( - streamResponse(response as Response) + streamResponse(response as Response), + set, + res ) as any + if (res) responseToValue(response as Response, res) + return [response as Response, set as SetResponse] + case 'Error': + response = errorToResponse(response as Error, set) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + case 'Promise': // @ts-ignore return (response as Promise).then((x) => { @@ -900,20 +1181,19 @@ export const mapEarlyResponse = ( if (r !== undefined) return r }) - case 'Error': - return errorToResponse(response as Error, set) - case 'Function': - return mapCompactResponse((response as Function)(), abortSignal) + return mapCompactResponse((response as Function)(), res) case 'Number': case 'Boolean': set.headers['content-type'] = 'text/plain;charset=utf-8' - return [ - (response as number | boolean).toString(), - set as SetResponse - ] + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + + return [response, set as SetResponse] case 'Cookie': if (response instanceof Cookie) @@ -925,17 +1205,35 @@ export const mapEarlyResponse = ( ) case 'FormData': + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + return [response as FormData, set as SetResponse] default: - if (response instanceof Response) + if (response instanceof Response) { + if (res) responseToValue(response as Response, res) + return [response, set as SetResponse] + } if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any - if (response instanceof Error) - return errorToResponse(response as Error, set) + if (response instanceof Error) { + response = errorToResponse(response as Error, set) + + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return response as ElysiaNodeResponse + } if (response instanceof ElysiaCustomStatusResponse) { set.status = ( @@ -945,13 +1243,13 @@ export const mapEarlyResponse = ( return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, - abortSignal + res ) } // @ts-expect-error if (typeof response?.next === 'function') - return handleStream(response as any, set, abortSignal) + return handleStream(response as any, set, res) if ('toResponse' in (response as any)) return mapEarlyResponse((response as any).toResponse(), set) @@ -964,25 +1262,43 @@ export const mapEarlyResponse = ( set.headers['content-type'] = 'application/json;charset=utf-8' - return new Response( - JSON.stringify(response), - set as SetResponse - ) as any + response = JSON.stringify(response) + + if (res) { + res.writeHead(set.status!, set.headers) + res.end(response) + } + + return [response, set as SetResponse] } } + set.headers['content-type'] = 'text/plain;charset=utf-8' + + if (res) { + res.writeHead(200, set.headers) + res.end(response) + } + return [response as any, set as SetResponse] } } export const mapCompactResponse = ( response: unknown, - abortSignal?: AbortSignal + res?: HttpResponse ): ElysiaNodeResponse => { switch (response?.constructor?.name) { case 'String': + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end(response) + } + return [ - new Response(response as string), + response, { status: 200, headers: { @@ -992,6 +1308,11 @@ export const mapCompactResponse = ( ] case 'Blob': + if (res) { + res.writeHead(200) + res.end(response) + } + return [ response as File | Blob, { @@ -1001,8 +1322,17 @@ export const mapCompactResponse = ( case 'Array': case 'Object': + response = JSON.stringify(response) + + if (res) { + res.writeHead(200, { + 'content-type': 'application/json;charset=utf-8' + }) + res.end(response) + } + return [ - JSON.stringify(response), + response, { status: 200, headers: { @@ -1021,18 +1351,26 @@ export const mapCompactResponse = ( ) case 'ReadableStream': - abortSignal?.addEventListener( - 'abort', - { - handleEvent() { - if (!abortSignal?.aborted) - (response as ReadableStream).cancel() - } - }, - { - once: true - } - ) + // abortSignal?.addEventListener( + // 'abort', + // { + // handleEvent() { + // if (!abortSignal?.aborted) + // (response as ReadableStream).cancel() + // } + // }, + // { + // once: true + // } + // ) + // + if (res) { + res.writeHead(200, { + 'content-type': 'text/event-stream;charset=utf-8', + 'transfer-encoding': 'chunked' + }) + readableStreamToReadable(response as ReadableStream).pipe(res) + } return [ response as ReadableStream, @@ -1045,13 +1383,21 @@ export const mapCompactResponse = ( ] case undefined: - if (!response) + if (!response) { + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end('') + } + return [ '', { status: 200 } ] + } return [ JSON.stringify(response), @@ -1068,7 +1414,13 @@ export const mapCompactResponse = ( (response as Response).headers.get('transfer-encoding') === 'chunked' ) - return handleStream(streamResponse(response as Response)) as any + return handleStream( + streamResponse(response as Response), + undefined, + res + ) as any + + if (res) responseToValue(response as Response, res) return [ response as Response, @@ -1078,22 +1430,38 @@ export const mapCompactResponse = ( ] case 'Error': - return errorToResponse(response as Error) + response = errorToResponse(response as Error) + + if (res) { + res.writeHead(200, { + 'content-type': 'application/json;charset=utf-8' + }) + res.end(response) + } + + return response as ElysiaNodeResponse case 'Promise': // @ts-ignore return (response as any as Promise).then((x) => - mapCompactResponse(x, abortSignal) + mapCompactResponse(x, res) ) // ? Maybe response or Blob case 'Function': - return mapCompactResponse((response as Function)(), abortSignal) + return mapCompactResponse((response as Function)(), res) case 'Number': case 'Boolean': + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end(response) + } + return [ - (response as number | boolean).toString(), + response, { status: 200, headers: { @@ -1106,6 +1474,13 @@ export const mapCompactResponse = ( if (response instanceof Cookie) return mapCompactResponse(response.value) + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end(response?.toString()) + } + return [ response?.toString(), { @@ -1117,6 +1492,11 @@ export const mapCompactResponse = ( ] case 'FormData': + if (res) { + res.writeHead(200) + res.end(response) + } + return [ response as FormData, { @@ -1125,15 +1505,27 @@ export const mapCompactResponse = ( ] default: - if (response instanceof Response) return [response, { status: 200 }] + if (response instanceof Response) { + if (res) responseToValue(response, res) + + return [response, { status: 200 }] + } if (response instanceof Promise) - return response.then((x) => - mapCompactResponse(x, abortSignal) - ) as any + return response.then((x) => mapCompactResponse(x, res)) as any + + if (response instanceof Error) { + response = errorToResponse(response as Error) - if (response instanceof Error) - return errorToResponse(response as Error) + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end(response) + } + + return response as ElysiaNodeResponse + } if (response instanceof ElysiaCustomStatusResponse) return mapResponse( @@ -1147,7 +1539,7 @@ export const mapCompactResponse = ( // @ts-expect-error if (typeof response?.next === 'function') - return handleStream(response as any, undefined, abortSignal) + return handleStream(response as any, undefined, res) if ('toResponse' in (response as any)) return mapCompactResponse((response as any).toResponse()) @@ -1156,15 +1548,35 @@ export const mapCompactResponse = ( const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) { - return new Response(JSON.stringify(response), { - headers: { - 'Content-Type': 'application/json' - } - }) as any + response = JSON.stringify(response) + + if (res) { + res.writeHead(200, { + 'content-type': 'application/json;charset=utf-8' + }) + res.end(response) + } + + return [response, { status: 200 } as SetResponse] } } - return [response as any, { status: 200 }] + if (res) { + res.writeHead(200, { + 'content-type': 'text/plain;charset=utf-8' + }) + res.end(response) + } + + return [ + response as any, + { + status: 200, + headers: { + 'content-type': 'text/plain;charset=utf-8' + } + } + ] } } @@ -1201,3 +1613,11 @@ export const readableStreamToReadable = (webStream: ReadableStream) => } } }) + +export const responseToValue = async (r: Response, res: HttpResponse) => { + for (const [name, value] of Object.entries(r.headers)) + res.setHeader(name, value) + + res.writeHead(r.status) + res.end(await r.text()) +} diff --git a/src/adapter/node/index.ts b/src/adapter/node/index.ts index 34d69d45..58bf08ff 100644 --- a/src/adapter/node/index.ts +++ b/src/adapter/node/index.ts @@ -1,21 +1,11 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { - createServer, - type IncomingMessage, - type ServerResponse -} from 'node:http' +import { createServer, type IncomingMessage } from 'node:http' import { Readable } from 'node:stream' -import { - mapResponse, - mapEarlyResponse, - mapCompactResponse, - ElysiaNodeResponse -} from './handler' +import { mapResponse, mapEarlyResponse, mapCompactResponse } from './handler' import { isNumericString } from '../../utils' import type { ElysiaAdapter } from '../types' -import { AnyElysia } from '../..' export const ElysiaNodeContext = Symbol('ElysiaNodeContext') @@ -28,51 +18,6 @@ const getUrl = (req: IncomingMessage) => { return `http://localhost${req.url}` } -const createNodeHandler = - (app: AnyElysia) => - async ( - req: IncomingMessage, - res: ServerResponse & { - req: IncomingMessage - } - ) => { - let r = app.fetch(req as any) as unknown as ElysiaNodeResponse - if (r instanceof Promise) r = await r - - if (r instanceof Response) { - for (const [name, value] of Object.entries(r.headers)) - res.setHeader(name, value) - - res.writeHead(r.status) - res.end(await r.text()) - - return - } - - if (r[0] instanceof Promise) r[0] = await r[0] - - // Web Standard - if (r[0] instanceof Response) { - for (const [name, value] of Object.entries(r[0].headers)) - res.setHeader(name, value) - - res.writeHead(r[0].status) - res.end(await r[0].text()) - - return - } - - if (r[1].status) res.writeHead(r[1].status, r[1].headers) - else res.writeHead(200, r[1].headers) - - if (r[0] instanceof Readable) { - r[0].pipe(res) - return - } - - res.end(r[0]) - } - export const nodeRequestToWebstand = ( req: IncomingMessage, abortController: AbortController @@ -103,6 +48,7 @@ export const nodeRequestToWebstand = ( } export const NodeAdapter: ElysiaAdapter = { + name: 'node', handler: { mapResponse, mapEarlyResponse, @@ -114,12 +60,13 @@ export const NodeAdapter: ElysiaAdapter = { }) as any }, composeHandler: { + declare: `const req = c[ElysiaNodeContext].req\n`, + mapResponseContext: 'c[ElysiaNodeContext].res', + headers: `c.headers = req.headers\n`, inject: { ElysiaNodeContext }, - declare: `const req = c[ElysiaNodeContext].req\n`, - abortSignal: 'req.signal', - headers: `c.headers = req.headers\n`, + errorContext: `req,c[ElysiaNodeContext].res`, parser: { json() { let fnLiteral = @@ -169,13 +116,20 @@ export const NodeAdapter: ElysiaAdapter = { } }, composeGeneralHandler: { + parameters: 'r,res', inject: { nodeRequestToWebstand, ElysiaNodeContext }, createContext: (app) => { let decoratorsLiteral = '' - let fnLiteral = 'const p=r.url\n' + let fnLiteral = + `const u=r.url,` + + `s=u.indexOf('/'),` + + `qi=u.indexOf('?', s + 1)\n` + + `let p\n` + + `if(qi===-1)p=u.substring(s)\n` + + `else p=u.substring(s, qi)\n` // @ts-expect-error private const defaultHeaders = app.setHeaders @@ -196,7 +150,7 @@ export const NodeAdapter: ElysiaAdapter = { `return _request = nodeRequestToWebstand(r) ` + `},` + `store,` + - `qi: -1,` + + `qi,` + `path:p,` + `url:r.url,` + `redirect,` + @@ -205,6 +159,7 @@ export const NodeAdapter: ElysiaAdapter = { fnLiteral += '[ElysiaNodeContext]:{' + 'req:r,' + + 'res,' + '_signal:undefined,' + 'get signal(){' + 'if(this._signal) return this._signal\n' + @@ -235,8 +190,45 @@ export const NodeAdapter: ElysiaAdapter = { }, websocket() { return '\n' + }, + error404(hasEventHook, hasErrorHook) { + let findDynamicRoute = `if(route===null){` + + if (hasErrorHook) + findDynamicRoute += `return app.handleError(c,notFound,false,${this.parameters})` + else + findDynamicRoute += + `if(c.set.status===200)c.set.status=404\n` + + `res.writeHead(c.set.status, c.set.headers)\n` + + `res.end(error404Message)\n` + + `return [error404Message, c.set]` + + findDynamicRoute += '}' + + return { + declare: hasErrorHook + ? '' + : `const error404Message=notFound.message.toString()\n`, + code: findDynamicRoute + } } }, + composeError: { + inject: { + ElysiaNodeContext + }, + mapResponseContext: ',context[ElysiaNodeContext].res', + validationError: + `c.set.headers['content-type'] = 'application/json;charset=utf-8'\n` + + `res.writeHead(c.set.status, c.set.headers)\n` + + `res.end(error404Message)\n` + + `return [error.message, c.set]`, + unknownError: + `c.set.status = error.status\n` + + `res.writeHead(c.set.status, c.set.headers)\n` + + `res.end(error.message)\n` + + `return [error.message, c.set]` + }, listen(app) { return (options, callback) => { app.compile() @@ -248,14 +240,14 @@ export const NodeAdapter: ElysiaAdapter = { options = parseInt(options) } - const server = createServer(createNodeHandler(app)).listen( - options, - () => { - if (callback) - // @ts-ignore - callback() - } - ) + const server = createServer( + // @ts-ignore private property + app._handle + ).listen(options, () => { + if (callback) + // @ts-ignore + callback() + }) for (let i = 0; i < app.event.start.length; i++) app.event.start[i].fn(this) diff --git a/src/adapter/types.ts b/src/adapter/types.ts index 8f55b333..c4029787 100644 --- a/src/adapter/types.ts +++ b/src/adapter/types.ts @@ -4,12 +4,14 @@ import type { Context } from '../context' import type { Prettify, LocalHook } from '../types' export interface ElysiaAdapter { + name: string listen( app: AnyElysia ): ( options: string | number | Partial, callback?: ListenCallback ) => void + isWebStandard?: boolean handler: { /** * Map return response on every case @@ -17,7 +19,7 @@ export interface ElysiaAdapter { mapResponse( response: unknown, set: Context['set'], - abortSignal?: AbortSignal + ...params: unknown[] ): unknown /** * Map response on truthy value @@ -25,15 +27,12 @@ export interface ElysiaAdapter { mapEarlyResponse( response: unknown, set: Context['set'], - abortSignal?: AbortSignal + ...params: unknown[] ): unknown /** * Map response without cookie, status or headers */ - mapCompactResponse( - response: unknown, - abortSignal?: AbortSignal - ): unknown, + mapCompactResponse(response: unknown, ...params: unknown[]): unknown /** * Compile inline to value * @@ -45,21 +44,23 @@ export interface ElysiaAdapter { createStaticHandler( handle: unknown, hooks: LocalHook, - setHeaders?: Context['set']['headers'] + setHeaders?: Context['set']['headers'], + ...params: unknown[] ): (() => unknown) | undefined /** * If the runtime support cloning response * * eg. Bun.serve({ static }) - */ + */ createNativeStaticHandler?( handle: unknown, hooks: LocalHook, - setHeaders?: Context['set']['headers'] + setHeaders?: Context['set']['headers'], + ...params: unknown[] ): (() => Response) | undefined } composeHandler: { - abortSignal?: string + mapResponseContext?: string /** * Declare any variable that will be used in the general handler */ @@ -95,8 +96,14 @@ export interface ElysiaAdapter { declare?: string } > + errorContext?: string } composeGeneralHandler: { + parameters?: string + error404(hasEventHook: boolean, hasErrorHook: boolean): { + declare: string + code: string + } /** * fnLiteral of the general handler * @@ -111,4 +118,10 @@ export interface ElysiaAdapter { */ inject?: Record } + composeError: { + inject?: Record + mapResponseContext: string + validationError: string + unknownError: string + } } diff --git a/src/adapter/web-standard/index.ts b/src/adapter/web-standard/index.ts index 0df5dde4..c8effd95 100644 --- a/src/adapter/web-standard/index.ts +++ b/src/adapter/web-standard/index.ts @@ -10,6 +10,8 @@ import { getLoosePath } from '../../utils' import type { ElysiaAdapter } from '../types' export const WebStandardAdapter: ElysiaAdapter = { + name: 'web-standard', + isWebStandard: true, handler: { mapResponse, mapEarlyResponse, @@ -17,7 +19,7 @@ export const WebStandardAdapter: ElysiaAdapter = { createStaticHandler }, composeHandler: { - abortSignal: 'c.request.signal', + mapResponseContext: 'c.request.signal', preferWebstandardHeaders: true, // @ts-ignore Bun specific headers: @@ -60,6 +62,7 @@ export const WebStandardAdapter: ElysiaAdapter = { } }, composeGeneralHandler: { + parameters: 'r', createContext(app) { let decoratorsLiteral = '' let fnLiteral = '' @@ -145,8 +148,48 @@ export const WebStandardAdapter: ElysiaAdapter = { } return fnLiteral + }, + error404(hasEventHook, hasErrorHook) { + let findDynamicRoute = `if(route===null)return ` + + if (hasErrorHook) + findDynamicRoute += `app.handleError(c,notFound,false,${this.parameters})` + else + findDynamicRoute += hasEventHook + ? `new Response(error404Message,{` + + `status:c.set.status===200?404:c.set.status,` + + `headers:c.set.headers` + + `})` + : `error404.clone()` + + return { + declare: hasErrorHook + ? '' + : `const error404Message=notFound.message.toString()\n` + + `const error404=new Response(error404Message,{status:404})\n`, + code: findDynamicRoute + } } }, + composeError: { + mapResponseContext: '', + validationError: + `return new Response(` + + `error.message,` + + `{` + + `headers:Object.assign(` + + `{'content-type':'application/json'},` + + `set.headers` + + `),` + + `status:set.status` + + `}` + + `)`, + unknownError: + `return new Response(` + + `error.message,` + + `{headers:set.headers,status:error.status}` + + `)` + }, listen() { return () => { throw new Error( diff --git a/src/compose.ts b/src/compose.ts index f4b729aa..30285985 100644 --- a/src/compose.ts +++ b/src/compose.ts @@ -390,6 +390,8 @@ export const isAsync = (v: Function | HookContainer) => { if (literal.includes('=> response.clone(')) return false if (literal.includes('await')) return true if (literal.includes('async')) return true + // v8 minified + if (literal.includes('=>response.clone(')) return false return !!literal.match(matchFnReturn) } @@ -857,7 +859,9 @@ export const composeHandler = ({ (isHandleFn && hasDefaultHeaders) || maybeStream - const abortSignal = adapter.abortSignal ? `,${adapter.abortSignal}` : '' + const mapResponseContext = adapter.mapResponseContext + ? `,${adapter.mapResponseContext}` + : '' fnLiteral += `c.route=\`${path}\`\n` const parseReporter = report('parse', { @@ -1410,7 +1414,7 @@ export const composeHandler = ({ fnLiteral += encodeCookie fnLiteral += `return mapEarlyResponse(${saveResponse}be,c.set${ - abortSignal + mapResponseContext })}\n` } } @@ -1504,11 +1508,11 @@ export const composeHandler = ({ if (hasSet) fnLiteral += `return mapResponse(${saveResponse}r,c.set${ - abortSignal + mapResponseContext })\n` else fnLiteral += `return mapCompactResponse(${saveResponse}r${ - abortSignal + mapResponseContext })\n` } else { const handleReporter = report('handle', { @@ -1560,7 +1564,7 @@ export const composeHandler = ({ `c.set.status!==200||` + `c.set.redirect||` + `c.set.cookie)return mapResponse(${saveResponse}${handle}.clone(),c.set${ - abortSignal + mapResponseContext })` + `else return ${handle}.clone()` : `return ${handle}.clone()` @@ -1568,11 +1572,11 @@ export const composeHandler = ({ fnLiteral += '\n' } else if (hasSet) fnLiteral += `return mapResponse(${saveResponse}r,c.set${ - abortSignal + mapResponseContext })\n` else fnLiteral += `return mapCompactResponse(${saveResponse}r${ - abortSignal + mapResponseContext })\n` } else if (hasCookie || hasTrace) { fnLiteral += isAsyncHandler @@ -1611,11 +1615,11 @@ export const composeHandler = ({ if (hasSet) fnLiteral += `return mapResponse(${saveResponse}r,c.set${ - abortSignal + mapResponseContext })\n` else fnLiteral += `return mapCompactResponse(${saveResponse}r${ - abortSignal + mapResponseContext })\n` } else { handleReporter.resolve() @@ -1631,17 +1635,17 @@ export const composeHandler = ({ `c.set.redirect||` + `c.set.cookie)` + `return mapResponse(${saveResponse}${handle}.clone(),c.set${ - abortSignal + mapResponseContext })\n` + `else return ${handle}.clone()\n` : `return ${handle}.clone()\n` } else if (hasSet) fnLiteral += `return mapResponse(${saveResponse}${handled},c.set${ - abortSignal + mapResponseContext })\n` else fnLiteral += `return mapCompactResponse(${saveResponse}${handled}${ - abortSignal + mapResponseContext })\n` } } @@ -1709,7 +1713,7 @@ export const composeHandler = ({ mapResponseReporter.resolve() - fnLiteral += `er=mapEarlyResponse(er,set${abortSignal})\n` + fnLiteral += `er=mapEarlyResponse(er,set${mapResponseContext})\n` fnLiteral += `if(er){` if (hasTrace) { @@ -1725,7 +1729,7 @@ export const composeHandler = ({ errorReporter.resolve() - fnLiteral += `return handleError(c,error,true)` + fnLiteral += `return handleError(c,error,true${adapter.errorContext ? ',' + adapter.errorContext : ''})` if (!maybeAsync) fnLiteral += '})()' fnLiteral += '}' @@ -1923,6 +1927,10 @@ export const composeGeneralHandler = ( { asManifest = false }: { asManifest?: false } = {} ) => { const adapter = app['~adapter'].composeGeneralHandler + const error404 = adapter.error404( + !!app.event.request.length, + !!app.event.error.length + ) let fnLiteral = '' @@ -1931,16 +1939,7 @@ export const composeGeneralHandler = ( let findDynamicRoute = `const route=router.find(r.method,p)` findDynamicRoute += router.http.root.ALL ? '??router.find("ALL",p)\n' : '\n' - findDynamicRoute += `if(route===null)return ` - if (app.event.error.length) - findDynamicRoute += `app.handleError(c,notFound)` - else - findDynamicRoute += app.event.request.length - ? `new Response(error404Message,{` + - `status:c.set.status===200?404:c.set.status,` + - `headers:c.set.headers` + - `})` - : `error404.clone()` + findDynamicRoute += error404.code findDynamicRoute += `\nc.params=route.params\n` + @@ -1993,10 +1992,7 @@ export const composeGeneralHandler = ( if (app.event.request.length) fnLiteral += `const onRequest=app.event.request.map(x=>x.fn)\n` - if (!app.event.error.length) - fnLiteral += - `const error404Message=notFound.message.toString()\n` + - `const error404=new Response(error404Message,{status:404})\n` + fnLiteral += error404.declare if (app.event.trace.length) fnLiteral += @@ -2006,7 +2002,7 @@ export const composeGeneralHandler = ( .join(',') + '\n' - fnLiteral += `${maybeAsync ? 'async ' : ''}function map(r){` + fnLiteral += `${maybeAsync ? 'async ' : ''}function map(${adapter.parameters}){` if (app.event.request.length) fnLiteral += `let re\n` @@ -2043,8 +2039,7 @@ export const composeGeneralHandler = ( fnLiteral += `re=mapEarlyResponse(` + `${maybeAsync ? 'await ' : ''}onRequest[${i}](c),` + - `c.set,` + - `r)\n` + `c.set)\n` endUnit('re') fnLiteral += `if(re!==undefined)return re\n` @@ -2054,7 +2049,7 @@ export const composeGeneralHandler = ( } } - fnLiteral += `}catch(error){return app.handleError(c,error)}` + fnLiteral += `}catch(error){return app.handleError(c,error,false)}` } reporter.resolve() @@ -2069,9 +2064,9 @@ export const composeGeneralHandler = ( let handler = 'map' // @ts-expect-error private property for (let i = 0; i < app.extender.higherOrderFunctions.length; i++) - handler = `hoc[${i}](${handler},r)` + handler = `hoc[${i}](${handler},${adapter.parameters})` - fnLiteral += `return function hocMap(r){return ${handler}(r)}` + fnLiteral += `return function hocMap(${adapter.parameters}){return ${handler}(${adapter.parameters})}` } else fnLiteral += `return map` const handleError = composeErrorHandler(app) as any @@ -2102,6 +2097,11 @@ export const composeErrorHandler = (app: AnyElysia) => { const hooks = app.event let fnLiteral = '' + const adapter = app['~adapter'].composeError + const adapterVariables = adapter.inject + ? Object.keys(adapter.inject).join(',') + ',' + : '' + fnLiteral += `const {` + `app:{` + @@ -2116,6 +2116,7 @@ export const composeErrorHandler = (app: AnyElysia) => { `ERROR_CODE,` + `ElysiaCustomStatusResponse,` + `ELYSIA_TRACE,` + + adapterVariables + `ELYSIA_REQUEST_ID` + `}=inject\n` @@ -2135,6 +2136,8 @@ export const composeErrorHandler = (app: AnyElysia) => { const hasTrace = app.event.trace.length > 0 + fnLiteral += '' + if (hasTrace) fnLiteral += 'const id=context[ELYSIA_REQUEST_ID]\n' const report = createReport({ @@ -2147,7 +2150,7 @@ export const composeErrorHandler = (app: AnyElysia) => { fnLiteral += `const set = context.set\n` + - `let r\n` + + `let _r\n` + `if(!context.code)context.code=error.code??error[ERROR_CODE]\n` + `if(!(context.error instanceof Error))context.error = error\n` + `if(error instanceof ElysiaCustomStatusResponse){` + @@ -2173,9 +2176,9 @@ export const composeErrorHandler = (app: AnyElysia) => { if (hasReturn(handler)) { fnLiteral += - `r=${response}\nif(r!==undefined){` + - `if(r instanceof Response)return r\n` + - `if(r instanceof ElysiaCustomStatusResponse){` + + `_r=${response}\nif(_r!==undefined){` + + `if(_r instanceof Response)return mapResponse(_r,set${adapter.mapResponseContext})\n` + + `if(_r instanceof ElysiaCustomStatusResponse){` + `error.status=error.code\n` + `error.message = error.response` + `}` + @@ -2195,8 +2198,8 @@ export const composeErrorHandler = (app: AnyElysia) => { ) fnLiteral += - `context.response=r` + - `r=${isAsyncName(mapResponse) ? 'await ' : ''}onMapResponse[${i}](context)\n` + `context.response=_r` + + `_r=${isAsyncName(mapResponse) ? 'await ' : ''}onMapResponse[${i}](context)\n` endUnit() } @@ -2204,7 +2207,7 @@ export const composeErrorHandler = (app: AnyElysia) => { mapResponseReporter.resolve() - fnLiteral += `return mapResponse(${saveResponse}r,set,context.request)}` + fnLiteral += `return mapResponse(${saveResponse}_r,set${adapter.mapResponseContext})}` } else fnLiteral += response fnLiteral += '}' @@ -2213,22 +2216,11 @@ export const composeErrorHandler = (app: AnyElysia) => { fnLiteral += `if(error.constructor.name==="ValidationError"||error.constructor.name==="TransformDecodeError"){` + `set.status = error.status??422\n` + - `return new Response(` + - `error.message,` + - `{` + - `headers:Object.assign(` + - `{'content-type':'application/json'},` + - `set.headers` + - `),` + - `status:set.status` + - `}` + - `)` + + adapter.validationError + `}else{` + - `if(error.code&&typeof error.status==="number")` + - `return new Response(` + - `error.message,` + - `{headers:set.headers,status:error.status}` + - `)\n` + `if(error.code&&typeof error.status==="number"){` + + adapter.unknownError + + '}' const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse.length, @@ -2255,7 +2247,7 @@ export const composeErrorHandler = (app: AnyElysia) => { mapResponseReporter.resolve() - fnLiteral += `\nreturn mapResponse(${saveResponse}error,set,context.request)}}` + fnLiteral += `\nreturn mapResponse(${saveResponse}error,set${adapter.mapResponseContext})}}` return Function( 'inject', @@ -2266,6 +2258,7 @@ export const composeErrorHandler = (app: AnyElysia) => { ERROR_CODE, ElysiaCustomStatusResponse, ELYSIA_TRACE, - ELYSIA_REQUEST_ID + ELYSIA_REQUEST_ID, + ...adapter.inject }) } diff --git a/src/index.ts b/src/index.ts index 87782636..5fc7fe80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5946,15 +5946,21 @@ export default class Elysia< } compile() { - this.fetch = this.config.aot - ? composeGeneralHandler(this) - : createDynamicHandler(this) + if (this['~adapter'].isWebStandard) { + this.fetch = this.config.aot + ? composeGeneralHandler(this) + : createDynamicHandler(this) + + if (typeof this.server?.reload === 'function') + this.server.reload({ + ...(this.server || {}), + fetch: this.fetch + }) - if (typeof this.server?.reload === 'function') - this.server.reload({ - ...(this.server || {}), - fetch: this.fetch - }) + return this + } + + this._handle = composeGeneralHandler(this) return this } @@ -5972,7 +5978,12 @@ export default class Elysia< : createDynamicHandler(this))(request) } - private handleError = async ( + /** + * Custom handle written by adapter + */ + protected _handle?(...a: unknown[]): unknown + + protected handleError = async ( context: Partial< Context< MergeSchema<