tspace-spear
Version:
tspace-spear is a lightweight, high-performance API framework for Node.js that leverages the native HTTP server and supports uWebSockets.js (C++) for maximum speed and efficiency.
793 lines • 35 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParserFactory = void 0;
const busboy_1 = __importDefault(require("busboy"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const mime_types_1 = __importDefault(require("mime-types"));
const crypto_1 = __importDefault(require("crypto"));
const swagger_ui_dist_1 = __importDefault(require("swagger-ui-dist"));
const string_decoder_1 = require("string_decoder");
const fast_querystring_1 = __importDefault(require("fast-querystring"));
const utils_1 = require("../utils");
const uWS_1 = require("./uWS");
const net_1 = require("./net");
class ParserFactory {
uWebStockets = false;
net = false;
useAdater(adapter) {
if (adapter.kind === 'uWS') {
this.uWebStockets = true;
}
if (adapter.kind === 'net') {
this.net = true;
}
return this;
}
queryString(url) {
const i = url.indexOf("?");
if (i === -1)
return {};
return fast_querystring_1.default.parse(url.slice(i + 1));
}
async files({ req, res, options, }) {
if (this.uWebStockets) {
return (0, uWS_1.uWSfiles)({ req, res, options });
}
if (this.net) {
return (0, net_1.netFiles)(req, options);
}
const temp = options.tempFileDir;
if (!fs_1.default.existsSync(temp)) {
try {
fs_1.default.mkdirSync(temp, { recursive: true });
}
catch (err) { }
}
return new Promise((resolve, reject) => {
const body = {};
const files = {};
const fileWritePromises = [];
const bb = (0, busboy_1.default)({ headers: req.headers, defParamCharset: "utf8" });
const removeTemp = (fileTemp, ms) => {
const remove = () => {
try {
fs_1.default.unlinkSync(fileTemp);
}
catch (err) { }
};
setTimeout(remove, ms);
};
bb.on("file", (fieldName, fileData, info) => {
const { filename, mimeType } = info;
const extension = mime_types_1.default.extension(mimeType) ||
path_1.default.extname(filename).replace(".", "") ||
"bin";
const tempFilename = crypto_1.default.randomBytes(16).toString("hex");
const filePath = path_1.default.join(path_1.default.resolve(), `${temp}/${tempFilename}`);
const writeStream = fs_1.default.createWriteStream(filePath);
let fileSize = 0;
fileData.on("data", (data) => {
fileSize += data.length;
if (fileSize > options.limit) {
fileData.unpipe(writeStream);
writeStream.destroy();
return reject(new Error(`The file '${fieldName}' is too large to be uploaded. The limit is '${options.limit}' bytes.`));
}
});
const fileWritePromise = new Promise((resolve, reject) => {
fileData.pipe(writeStream);
writeStream.on("finish", () => {
const file = {
name: filename,
tempFilePath: filePath,
tempFileName: tempFilename,
mimetype: mimeType,
extension: extension,
size: fileSize,
sizes: {
bytes: fileSize,
kb: fileSize / 1024,
mb: fileSize / 1024 / 1024,
gb: fileSize / 1024 / 1024 / 1024,
},
write: (to) => {
return new Promise((resolve, reject) => {
fs_1.default
.createReadStream(filePath)
.pipe(fs_1.default.createWriteStream(to))
.on("finish", () => {
return resolve(null);
})
.on("error", (err) => {
return reject(err);
});
});
},
remove: () => {
return new Promise((resolve) => setTimeout(() => {
fs_1.default.unlinkSync(filePath);
return resolve(null);
}, 100));
},
};
if (files[fieldName] == null) {
files[fieldName] = [];
}
files[fieldName].push(file);
if (options.removeTempFile.remove) {
removeTemp(filePath, options.removeTempFile.ms);
}
return resolve(null);
});
writeStream.on("error", reject);
});
fileWritePromises.push(fileWritePromise);
});
bb.on("field", (name, value) => {
body[name] = value;
});
bb.on("finish", () => {
Promise.all(fileWritePromises)
.then(() => {
return resolve({
files,
body,
});
})
.catch((err) => {
return reject(err);
});
});
bb.on("error", (err) => {
return reject(err);
});
req.pipe(bb);
});
}
async body(req, res) {
if (this.uWebStockets) {
return (await (0, uWS_1.uWSBody)(req, res));
}
if (this.net) {
return (await (0, net_1.netBody)(req));
}
return new Promise((resolve, reject) => {
const decoder = new string_decoder_1.StringDecoder("utf-8");
let payload = "";
req.on("data", (data) => {
payload += decoder.write(data);
});
req.on("end", async () => {
payload += decoder.end();
const contentType = req.headers["content-type"]?.toLowerCase() || null;
try {
const body = await (0, utils_1.normalizeRequestBody)({ contentType, payload });
return resolve(body);
}
catch (err) {
return reject(err);
}
});
req.on("error", (err) => {
return reject(err);
});
});
}
cookies(req) {
const cookies = {};
const cookieString = req.headers?.cookie;
if (cookieString == null)
return null;
for (const cookie of cookieString.split(";")) {
const [name, value] = cookie.split("=").map((v) => v.trim());
cookies[name] = decodeURIComponent(value);
}
for (const name of Object.keys(cookies)) {
const cookie = cookies[name];
if (!cookie.startsWith("Expires="))
continue;
const expiresString = cookie.replace("Expires=", "");
const expiresDate = new Date(expiresString);
if (isNaN(expiresDate.getTime()) || expiresDate < new Date()) {
delete cookies[name];
}
}
return cookies;
}
async swagger(doc) {
const spec = {
openapi: "3.1.0",
info: doc.info ?? {
title: "API Documentation",
description: "Documentation",
version: "1.0.0",
},
components: {
securitySchemes: {
BearerToken: {
type: "http",
scheme: "bearer",
name: "Authorization",
description: "Enter your token in the format : 'Bearer {TOKEN}'",
},
cookies: {
type: "apiKey",
in: "header",
name: "Cookie",
description: "Enter your cookies in the headers",
},
},
},
servers: doc.servers,
tags: doc.tags,
paths: {},
};
const { appRoutes } = await Promise.resolve().then(() => __importStar(require("../compiler/pre-routes")));
const specPaths = (routes) => {
let paths = {};
const defaultSpecResponse = {
"200": {
description: "OK",
content: {
"application/json": {
schema: {
type: "object",
properties: {
message: {
example: "success",
},
},
},
},
},
},
};
for (const r of routes) {
if (r.path === "*")
continue;
const path = r.path.replace(/:(\w+)/g, "{$1}");
const method = r.method.toLowerCase();
const pathWithoutGlobalPrefix = r.path.replace(`/${doc.globalPrefix}/`, "/");
//@ts-ignore
const preRoute = appRoutes[pathWithoutGlobalPrefix]?.[r.method];
const swagger = (doc.specs ?? []).find((s) => {
return s.path === r.path && s.method.toLowerCase() === method;
});
const decoratedOnly = doc.options?.decoratedOnly ?? false;
if ((swagger == null && decoratedOnly) || swagger?.disabled) {
continue;
}
if (paths[path] == null) {
paths[path] = {
[method]: {},
};
}
const spec = {};
const tags = /\/api\/v\d+/.test(r.path)
? r.path.split("/")[3]
: /\/api/.test(r.path)
? r.path.split("/")[2]
: r.path.split("/")[1];
spec.parameters = [];
spec.responses = {};
spec.tags = [];
if (doc.responses != null) {
const responses = {};
for (const response of Array.from(doc.responses ?? [])) {
if (response == null || !Object.keys(response).length)
continue;
responses[`${response.status}`] = {
description: response.description,
content: {
"application/json": {
schema: {
type: "object",
properties: response.example == null
? {}
: Object.keys(response.example).reduce((prev, key) => {
prev[key] = {
example: (response?.example ?? {})[key] ?? {},
};
return prev;
}, {}),
},
},
},
};
}
spec.responses = {
...responses,
};
}
if (swagger == null) {
spec.tags = [
tags == null || tags === "" || /^:[^:]*$/.test(tags)
? "default"
: tags,
];
if (!Object.keys(spec.responses).length) {
spec.responses = defaultSpecResponse;
}
if (preRoute && Object.keys(preRoute.params ?? {}).length) {
const queryParams = Object.entries(preRoute.params ?? {}).map(([k, v]) => {
return {
name: k,
in: "path",
required: false,
description: `Params for '${k}'`,
example: v,
schema: {
type: v
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...queryParams,
];
}
else if (Array.isArray(r.params) && Array.from(r.params).length) {
spec.parameters = Array.from(r.params).map((p) => {
return {
name: p,
in: "path",
required: true,
schema: {
type: "string",
},
};
});
}
if (preRoute && Object.keys(preRoute.query ?? {}).length) {
const queryParams = Object.entries(preRoute.query ?? {}).map(([k, v]) => {
return {
name: k,
in: "query",
required: false,
description: `QueryParams for '${k}'`,
example: v,
schema: {
type: v,
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...queryParams,
];
}
if (preRoute && Object.keys(preRoute.body ?? {}).length) {
const properties = Object.fromEntries(Object.entries(preRoute.body ?? {}).map(([key, value]) => [
key,
{
type: value,
example: value,
},
]));
spec.requestBody = {
description: `Body payload`,
required: false,
content: {
"application/json": {
schema: {
type: "object",
properties: properties,
required: false
}
},
},
};
}
if (preRoute && Object.keys(preRoute.files ?? {}).length) {
const properties = Object.fromEntries(Object.entries(preRoute.files ?? {}).map(([key, value]) => [
key,
{
type: value,
format: "binary",
example: value,
},
]));
spec.requestBody = {
description: `File Upload payload`,
required: false,
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: properties,
},
},
},
};
}
if (preRoute && Object.keys(preRoute.response ?? {}).length) {
const responses = {};
responses["200"] = {
description: null,
content: {
"application/json": {
schema: {
type: "object",
properties: Object.keys(preRoute.response ?? {}).reduce((prev, key) => {
prev[key] = {
example: (preRoute.response ?? {})[key] ?? {},
};
return prev;
}, {})
},
},
},
};
spec.responses = {
...responses,
};
}
paths[path][method] = spec;
continue;
}
/** Load from Swagger */
spec.tags = [
swagger.tags == null
? tags == null || tags === "" || /^:[^:]*$/.test(tags)
? "default"
: tags
: swagger.tags,
];
if (swagger.bearerToken) {
spec.security = [{ BearerToken: [] }];
}
if (swagger.summary != null) {
spec.summary = swagger.summary;
}
if (swagger.description != null) {
spec.description = swagger.description;
}
if (swagger.params != null) {
const params = Object.entries(swagger.params).map(([k, v]) => {
return {
name: k,
in: "path",
required: v?.required === true,
description: v?.description,
example: v?.example || v?.enum,
schema: {
type: v?.type ?? "string",
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...params,
];
}
else if (preRoute && Object.keys(preRoute.params ?? {}).length) {
const queryParams = Object.entries(preRoute.params ?? {}).map(([k, v]) => {
return {
name: k,
in: "path",
required: false,
description: `Params for '${k}'`,
example: v,
schema: {
type: v
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...queryParams,
];
}
else if (Array.isArray(r.params) && Array.from(r.params).length) {
spec.parameters = Array.from(r?.params).map((p) => {
return {
name: p,
in: "path",
required: true,
schema: {
type: "string",
},
};
});
}
if (swagger.query != null) {
const queryParams = Object.entries(swagger.query).map(([k, v]) => {
return {
name: k,
in: "query",
required: v?.required === true,
description: v?.description,
example: v?.example || v?.enum,
schema: {
type: v?.type ?? "string",
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...queryParams,
];
}
else if (preRoute && Object.keys(preRoute.query ?? {}).length) {
const queryParams = Object.entries(preRoute.query ?? {}).map(([k, v]) => {
return {
name: k,
in: "query",
required: false,
description: `QueryParams for '${k}'`,
example: v,
schema: {
type: v,
}
};
});
spec.parameters = [
...(spec.parameters ?? []),
...queryParams,
];
}
if (swagger.cookies != null) {
spec.parameters = [
...spec.parameters,
...[
{
name: "Cookie",
in: "header",
required: swagger.cookies.required === true,
schema: {
type: "string",
},
example: swagger.cookies.names
.map((v, i) => `${v}={value${i + 1}}`)
.join(" ; "),
description: swagger.cookies?.description,
},
],
];
}
if (swagger.body != null) {
spec.requestBody = {
description: swagger.body.description,
required: swagger.body?.required === true,
content: {
"application/json": {
schema: {
type: "object",
properties: swagger.body.properties,
required: Object.entries(swagger.body.properties)
.filter(([_, v]) => v.required)
.map(([key]) => key)
},
},
},
};
}
else if (preRoute && Object.keys(preRoute.body ?? {}).length) {
const properties = Object.fromEntries(Object.entries(preRoute.body ?? {}).map(([key, value]) => [
key,
{
type: value,
example: value,
},
]));
spec.requestBody = {
description: `Body payload`,
required: false,
content: {
"application/json": {
schema: {
type: "object",
properties: properties,
required: false
}
},
},
};
}
if (swagger.files != null) {
spec.requestBody = {
description: swagger.files.description,
required: swagger.files?.required === true,
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: swagger.files.properties,
},
},
},
};
}
else if (preRoute && Object.keys(preRoute.files ?? {}).length) {
const properties = Object.fromEntries(Object.entries(preRoute.files ?? {}).map(([key, value]) => [
key,
{
type: value,
format: "binary",
example: value,
},
]));
spec.requestBody = {
description: `File Upload payload`,
required: false,
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: properties,
},
},
},
};
}
if (swagger.responses != null) {
const responses = {};
for (const response of swagger.responses) {
if (response == null || !Object.keys(response).length)
continue;
responses[`${response.status}`] = {
description: response.description,
content: {
"application/json": {
schema: {
type: "object",
properties: response.example == null
? {}
: Object.keys(response.example).reduce((prev, key) => {
prev[key] = {
example: (response?.example ?? {})[key] ?? {},
};
return prev;
}, {}),
},
},
},
};
}
spec.responses = {
...responses,
};
}
else if (preRoute && Object.keys(preRoute.response ?? {}).length) {
const responses = {};
responses["200"] = {
description: null,
content: {
"application/json": {
schema: {
type: "object",
properties: Object.keys(preRoute.response ?? {}).reduce((prev, key) => {
prev[key] = {
example: (preRoute.response ?? {})[key] ?? {},
};
return prev;
}, {})
},
},
},
};
spec.responses = {
...responses
};
}
if (!Object.keys(spec.responses).length) {
spec.responses = defaultSpecResponse;
}
paths[path][method] = spec;
}
return paths;
};
spec.paths = specPaths(doc.routes ?? []);
const normalizePath = (...paths) => {
const path = paths.join("/").replace(/\/+/g, "/").replace(/\/+$/, "");
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
return /\/api\/api/.test(normalizedPath)
? normalizedPath.replace(/\/api\/api\//, "/api/")
: normalizedPath;
};
const STATIC_URL = "/swagger-ui";
const iconURL = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/favicon-32x32.png`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
const cssURL = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui.css`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
const scriptBundle = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui-bundle.js`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
const scriptStandalonePreset = normalizePath(doc.staticUrl ?? "", `${STATIC_URL}/swagger-ui-standalone-preset.js`).replace(/^\/(http[s]?:\/{0,2})/, "$1");
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="SwaggerUI" />
<title>SwaggerUI</title>
<link rel="icon" href="${iconURL}">
<link rel="stylesheet" href="${cssURL}" />
<style>
.swagger-ui .topbar .download-url-wrapper {
visibility: hidden;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
</body>
<script src="${scriptBundle}"></script>
<script src="${scriptStandalonePreset}"></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
spec : ${JSON.stringify(spec)},
withCredentials: ${doc.options?.withCredentials ?? "true"},
layout: "${doc.options?.layout ?? "StandaloneLayout"}",
filter: "${doc.options?.filter ?? "false"}",
docExpansion: "${doc.options?.docExpansion ?? "list"}",
deepLinking: "${doc.options?.deepLinking ?? "true"}",
displayOperationId: "${doc.options?.displayOperationId ?? "false"}",
displayRequestDuration: "${doc.options?.displayRequestDuration ?? "false"}"
});
};
</script>
</html>
`;
const staticSwaggerHandler = (req, res, params) => {
try {
const swaggerUiPath = swagger_ui_dist_1.default.getAbsoluteFSPath();
const mimeTypes = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".json": "application/json",
};
const requestedFilePath = params["*"];
const filePath = path_1.default.join(swaggerUiPath, requestedFilePath);
const extname = path_1.default.extname(filePath);
const contentType = mimeTypes[extname] || "text/html";
const content = fs_1.default.readFileSync(filePath);
res.writeHead(200, { "Content-Type": contentType });
return res.end(content, "utf-8");
}
catch (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
return res.end("Not found");
}
};
return {
path: doc.path,
staticUrl: `${STATIC_URL}/*`,
staticSwaggerHandler,
html,
};
}
}
exports.ParserFactory = ParserFactory;
//# sourceMappingURL=parser-factory.js.map