@elysiajs/node
Version:
Elysia adapter to run Elysia on Node.js
594 lines (591 loc) • 19.5 kB
JavaScript
// src/handle.ts
import { FastResponse as Response2 } from "srvx";
import {
createStreamHandler as createStreamHandler2,
handleSet,
responseToSetHeaders as responseToSetHeaders2,
streamResponse as streamResponse2
} from "elysia/adapter/utils";
// src/utils.ts
import { FastResponse as Response } from "srvx";
import { isNotEmpty, StatusMap } from "elysia/utils";
import {
createStreamHandler,
responseToSetHeaders,
streamResponse
} from "elysia/adapter/utils";
var handleFile = (response, set2) => {
if (response instanceof Promise)
return response.then((res) => handleFile(res, set2));
const size = response.size;
const immutable = set2 && (set2.status === 206 || set2.status === 304 || set2.status === 412 || set2.status === 416);
const defaultHeader = immutable ? {
"transfer-encoding": "chunked"
} : {
"accept-ranges": "bytes",
"content-range": size ? `bytes 0-${size - 1}/${size}` : void 0,
"transfer-encoding": "chunked"
};
if (!set2 && !size) return new Response(response);
if (!set2)
return new Response(response, {
headers: defaultHeader
});
if (set2.headers instanceof Headers) {
let setHeaders = defaultHeader;
setHeaders = {};
for (const [key, value] of set2.headers.entries())
if (key in set2.headers) setHeaders[key] = value;
if (immutable) {
delete set2.headers["content-length"];
delete set2.headers["accept-ranges"];
}
return new Response(response, {
status: set2.status,
headers: setHeaders
});
}
if (isNotEmpty(set2.headers))
return new Response(response, {
status: set2.status,
headers: Object.assign(defaultHeader, set2.headers)
});
return new Response(response, {
status: set2.status,
headers: defaultHeader
});
};
function mergeHeaders(responseHeaders, setHeaders) {
const headers = new Headers(Object.fromEntries(responseHeaders.entries()));
if (setHeaders instanceof Headers)
for (const key of setHeaders.keys()) {
if (key === "set-cookie") {
if (headers.has("set-cookie")) continue;
for (const cookie of setHeaders.getSetCookie())
headers.append("set-cookie", cookie);
} else if (!responseHeaders.has(key))
headers.set(key, setHeaders?.get(key) ?? "");
}
else
for (const key in setHeaders)
if (key === "set-cookie")
headers.append(key, setHeaders[key]);
else if (!responseHeaders.has(key))
headers.set(key, setHeaders[key]);
return headers;
}
function mergeStatus(responseStatus, setStatus) {
if (typeof setStatus === "string") setStatus = StatusMap[setStatus];
if (responseStatus === 200) return setStatus;
return responseStatus;
}
var createResponseHandler = (handler) => {
const handleStream2 = createStreamHandler(handler);
return (response, set2, request) => {
const newResponse = new Response(response.body, {
headers: mergeHeaders(response.headers, set2.headers),
status: mergeStatus(response.status, set2.status)
});
if (!newResponse.headers.has("content-length") && newResponse.headers.get("transfer-encoding") === "chunked")
return handleStream2(
streamResponse(newResponse),
responseToSetHeaders(newResponse, set2),
request,
// @ts-ignore
true
// don't auto-format SSE for pre-formatted Response
);
return newResponse;
};
};
// src/handle.ts
import { mime } from "elysia/universal/file";
import { isNotEmpty as isNotEmpty2 } from "elysia/utils";
import { Cookie } from "elysia/cookies";
import { ElysiaCustomStatusResponse } from "elysia/error";
var handleElysiaFile = (file, set2 = {
headers: {}
}) => {
const path = file.path;
const contentType = mime[path.slice(path.lastIndexOf(".") + 1)];
if (contentType) set2.headers["content-type"] = contentType;
if (file.stats && set2 && set2.status !== 206 && set2.status !== 304 && set2.status !== 412 && set2.status !== 416)
return file.stats.then((stat) => {
const size = stat.size;
if (size !== void 0) {
set2.headers["content-range"] = `bytes 0-${size - 1}/${size}`;
set2.headers["content-length"] = size;
}
return handleFile(file.value, set2);
});
return handleFile(file.value, set2);
};
var mapResponse = (response, set2, request) => {
if (isNotEmpty2(set2.headers) || set2.status !== 200 || set2.cookie) {
handleSet(set2);
switch (response?.constructor?.name) {
case "String":
set2.headers["content-type"] = "text/plain";
return new Response2(response, set2);
case "Array":
case "Object":
set2.headers["content-type"] = "application/json";
return new Response2(JSON.stringify(response), set2);
case "ElysiaFile":
return handleElysiaFile(response, set2);
case "File":
return handleFile(response, set2);
case "Blob":
return handleFile(response, set2);
case "ElysiaCustomStatusResponse":
set2.status = response.code;
return mapResponse(
response.response,
set2,
request
);
case "ReadableStream":
if (!set2.headers["content-type"]?.startsWith(
"text/event-stream"
))
set2.headers["content-type"] = "text/event-stream; charset=utf-8";
request?.signal?.addEventListener(
"abort",
{
handleEvent() {
if (request?.signal && !request?.signal?.aborted)
response.cancel();
}
},
{
once: true
}
);
return new Response2(response, set2);
case void 0:
if (!response) return new Response2(null, set2);
return new Response2(JSON.stringify(response), set2);
case "Response":
return handleResponse(response, set2, request);
case "Error":
return errorToResponse(response, set2);
case "Promise":
return response.then(
(x) => mapResponse(x, set2, request)
);
case "Function":
return mapResponse(response(), set2, request);
case "Number":
case "Boolean":
return new Response2(
response.toString(),
set2
);
case "Cookie":
if (response instanceof Cookie)
return new Response2(response.value, set2);
return new Response2(response?.toString(), set2);
case "FormData":
return new Response2(response, set2);
default:
if (response instanceof Response2)
return handleResponse(response, set2, request);
if (response instanceof Promise)
return response.then((x) => mapResponse(x, set2));
if (response instanceof Error)
return errorToResponse(response, set2);
if (response instanceof ElysiaCustomStatusResponse) {
set2.status = response.code;
return mapResponse(
response.response,
set2,
request
);
}
if (typeof response?.next === "function")
return handleStream(response, set2, request);
if (typeof response?.then === "function")
return response.then((x) => mapResponse(x, set2));
if (typeof response?.toResponse === "function")
return mapResponse(response.toResponse(), set2);
if ("charCodeAt" in response) {
const code = response.charCodeAt(0);
if (code === 123 || code === 91) {
if (!set2.headers["Content-Type"])
set2.headers["Content-Type"] = "application/json";
return new Response2(
JSON.stringify(response),
set2
);
}
}
return new Response2(response, set2);
}
}
if (response instanceof Response2 && !response.headers.has("content-length") && response.headers.get("transfer-encoding") === "chunked")
return handleStream(
streamResponse2(response),
responseToSetHeaders2(response, set2),
request
);
if (
// @ts-expect-error
typeof response?.next === "function" || response instanceof ReadableStream
)
return handleStream(response, set2, request);
return mapCompactResponse(response, request);
};
var mapEarlyResponse = (response, set2, request) => {
if (response === void 0 || response === null) return;
if (isNotEmpty2(set2.headers) || set2.status !== 200 || set2.cookie) {
handleSet(set2);
switch (response?.constructor?.name) {
case "String":
set2.headers["content-type"] = "text/plain";
return new Response2(response, set2);
case "Array":
case "Object":
set2.headers["content-type"] = "application/json";
return new Response2(JSON.stringify(response), set2);
case "ElysiaFile":
return handleElysiaFile(response, set2);
case "File":
return handleFile(response, set2);
case "Blob":
return handleFile(response, set2);
case "ElysiaCustomStatusResponse":
set2.status = response.code;
return mapEarlyResponse(
response.response,
set2,
request
);
case "ReadableStream":
if (!set2.headers["content-type"]?.startsWith(
"text/event-stream"
))
set2.headers["content-type"] = "text/event-stream; charset=utf-8";
request?.signal?.addEventListener(
"abort",
{
handleEvent() {
if (request?.signal && !request?.signal?.aborted)
response.cancel();
}
},
{
once: true
}
);
return new Response2(response, set2);
case void 0:
if (!response) return;
return new Response2(JSON.stringify(response), set2);
case "Response":
return handleResponse(response, set2, request);
case "Promise":
return response.then(
(x) => mapEarlyResponse(x, set2)
);
case "Error":
return errorToResponse(response, set2);
case "Function":
return mapEarlyResponse(response(), set2);
case "Number":
case "Boolean":
return new Response2(
response.toString(),
set2
);
case "FormData":
return new Response2(response);
case "Cookie":
if (response instanceof Cookie)
return new Response2(response.value, set2);
return new Response2(response?.toString(), set2);
default:
if (response instanceof Response2)
return handleResponse(response, set2, request);
if (response instanceof Promise)
return response.then((x) => mapEarlyResponse(x, set2));
if (response instanceof Error)
return errorToResponse(response, set2);
if (response instanceof ElysiaCustomStatusResponse) {
set2.status = response.code;
return mapEarlyResponse(
response.response,
set2,
request
);
}
if (typeof response?.next === "function")
return handleStream(response, set2, request);
if (typeof response?.then === "function")
return response.then((x) => mapEarlyResponse(x, set2));
if (typeof response?.toResponse === "function")
return mapEarlyResponse(response.toResponse(), set2);
if ("charCodeAt" in response) {
const code = response.charCodeAt(0);
if (code === 123 || code === 91) {
if (!set2.headers["Content-Type"])
set2.headers["Content-Type"] = "application/json";
return new Response2(
JSON.stringify(response),
set2
);
}
}
return new Response2(response, set2);
}
} else
switch (response?.constructor?.name) {
case "String":
set2.headers["content-type"] = "text/plain";
return new Response2(response);
case "Array":
case "Object":
set2.headers["content-type"] = "application/json";
return new Response2(JSON.stringify(response), set2);
case "ElysiaFile":
return handleElysiaFile(response, set2);
case "File":
return handleFile(response, set2);
case "Blob":
return handleFile(response, set2);
case "ElysiaCustomStatusResponse":
set2.status = response.code;
return mapEarlyResponse(
response.response,
set2,
request
);
case "ReadableStream":
request?.signal?.addEventListener(
"abort",
{
handleEvent() {
if (request?.signal && !request?.signal?.aborted)
response.cancel();
}
},
{
once: true
}
);
return new Response2(response, {
headers: {
"Content-Type": "text/event-stream; charset=utf-8"
}
});
case void 0:
if (!response) return new Response2("");
return new Response2(JSON.stringify(response), {
headers: {
"content-type": "application/json"
}
});
case "Response":
if (!response.headers.has("content-length") && response.headers.get("transfer-encoding") === "chunked")
return handleStream(
streamResponse2(response),
responseToSetHeaders2(response),
request
);
return response;
case "Promise":
return response.then((x) => {
const r = mapEarlyResponse(x, set2);
if (r !== void 0) return r;
});
case "Error":
return errorToResponse(response, set2);
case "Function":
return mapCompactResponse(response(), request);
case "Number":
case "Boolean":
return new Response2(response.toString());
case "Cookie":
if (response instanceof Cookie)
return new Response2(response.value, set2);
return new Response2(response?.toString(), set2);
case "FormData":
return new Response2(response);
default:
if (response instanceof Response2) return response;
if (response instanceof Promise)
return response.then((x) => mapEarlyResponse(x, set2));
if (response instanceof Error)
return errorToResponse(response, set2);
if (response instanceof ElysiaCustomStatusResponse) {
set2.status = response.code;
return mapEarlyResponse(
response.response,
set2,
request
);
}
if (typeof response?.next === "function")
return handleStream(response, set2, request);
if (typeof response?.then === "function")
return response.then((x) => mapEarlyResponse(x, set2));
if (typeof response?.toResponse === "function")
return mapEarlyResponse(response.toResponse(), set2);
if ("charCodeAt" in response) {
const code = response.charCodeAt(0);
if (code === 123 || code === 91) {
if (!set2.headers["Content-Type"])
set2.headers["Content-Type"] = "application/json";
return new Response2(
JSON.stringify(response),
set2
);
}
}
return new Response2(response);
}
};
var mapCompactResponse = (response, request) => {
switch (response?.constructor?.name) {
case "String":
return new Response2(response, {
headers: {
"Content-Type": "text/plain"
}
});
case "Object":
case "Array":
return new Response2(JSON.stringify(response), {
headers: {
"Content-Type": "application/json"
}
});
case "ElysiaFile":
return handleElysiaFile(response);
case "File":
return handleFile(response);
case "Blob":
return handleFile(response);
case "ElysiaCustomStatusResponse":
return mapResponse(
response.response,
{
status: response.code,
headers: {}
}
);
case "ReadableStream":
request?.signal?.addEventListener(
"abort",
{
handleEvent() {
if (request?.signal && !request?.signal?.aborted)
response.cancel();
}
},
{
once: true
}
);
return new Response2(response, {
headers: {
"Content-Type": "text/event-stream; charset=utf-8"
}
});
case void 0:
if (!response) return new Response2("");
return new Response2(JSON.stringify(response), {
headers: {
"content-type": "application/json"
}
});
case "Response":
if (response.headers.get("transfer-encoding") === "chunked")
return handleStream(
streamResponse2(response),
responseToSetHeaders2(response),
request
);
return response;
case "Error":
return errorToResponse(response);
case "Promise":
return response.then(
(x) => mapCompactResponse(x, request)
);
// ? Maybe response or Blob
case "Function":
return mapCompactResponse(response(), request);
case "Number":
case "Boolean":
return new Response2(response.toString());
case "FormData":
return new Response2(response);
default:
if (response instanceof Response2) return response;
if (response instanceof Promise)
return response.then(
(x) => mapCompactResponse(x, request)
);
if (response instanceof Error)
return errorToResponse(response);
if (response instanceof ElysiaCustomStatusResponse)
return mapResponse(
response.response,
{
status: response.code,
headers: {}
}
);
if (typeof response?.next === "function")
return handleStream(response, void 0, request);
if (typeof response?.then === "function")
return response.then((x) => mapResponse(x, set));
if (typeof response?.toResponse === "function")
return mapCompactResponse(response.toResponse());
if ("charCodeAt" in response) {
const code = response.charCodeAt(0);
if (code === 123 || code === 91) {
return new Response2(JSON.stringify(response), {
headers: {
"Content-Type": "application/json"
}
});
}
}
return new Response2(response);
}
};
var errorToResponse = (error, set2) => new Response2(
JSON.stringify({
name: error?.name,
message: error?.message,
cause: error?.cause
}),
{
status: set2?.status !== 200 ? set2?.status ?? 500 : 500,
headers: set2?.headers
}
);
var createStaticHandler = (handle, hooks, setHeaders = {}) => {
if (typeof handle === "function") return;
const response = mapResponse(handle, {
headers: setHeaders
});
if (!hooks.parse?.length && !hooks.transform?.length && !hooks.beforeHandle?.length && !hooks.afterHandle?.length)
return response.clone.bind(response);
};
var handleResponse = createResponseHandler({
mapResponse,
mapCompactResponse
});
var handleStream = createStreamHandler2({
mapResponse,
mapCompactResponse
});
export {
createStaticHandler,
errorToResponse,
mapCompactResponse,
mapEarlyResponse,
mapResponse
};