yup-server
Version:
A lightweight server that uses the amazing Yup library for validation
173 lines (172 loc) • 7.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createServer = exports.useConnectMiddleware = void 0;
const http_errors_1 = require("http-errors");
function executeRequestHandler({ requestHandler, req, res, pattern, ctx, }) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const { handler, bodySchema, paramsSchema, querySchema, responseSchema } = requestHandler;
if (bodySchema) {
let body = "";
req.on("data", function (chunk) {
body += chunk;
});
yield new Promise((resolve) => req.on("end", resolve));
let validatedBody;
try {
validatedBody = yield bodySchema.validate(JSON.parse(body));
}
catch (err) {
throw new http_errors_1.UnprocessableEntity(err.message);
}
ctx.body = validatedBody;
}
if (paramsSchema) {
const regexResult = pattern.exec(((_a = req.url) === null || _a === void 0 ? void 0 : _a.split("?")[0]) || "");
if (!regexResult) {
throw new http_errors_1.UnprocessableEntity("no url parameters found");
}
let validatedParams;
try {
validatedParams = yield paramsSchema.validate(regexResult.groups);
}
catch (err) {
throw new http_errors_1.UnprocessableEntity(err.message);
}
ctx.params = validatedParams;
}
if (querySchema) {
const queryString = ((_b = req.url) === null || _b === void 0 ? void 0 : _b.split("?")[1]) || "";
const query = Object.fromEntries(new URLSearchParams(queryString).entries());
let validatedQuery;
try {
validatedQuery = yield querySchema.validate(query);
}
catch (err) {
throw new http_errors_1.UnprocessableEntity(err.message);
}
ctx.query = validatedQuery;
}
let handlerRes = yield handler({ ctx, req, res });
// the handler took care of the response
if (res.headersSent)
return;
// the handler didn't return any data to send so we don't return anything
// this handler was likely middleware
if (handlerRes === undefined)
return;
if (responseSchema) {
// data from response shouldn't need to be parsed/coerced
handlerRes = yield responseSchema.validate(handlerRes, { strict: true });
}
// promisify and await so this function doesn't return until after response
// is sent
yield new Promise((resolve) => {
res
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(handlerRes), resolve);
});
});
}
/**
* Generate a request handler function suitable for use with http.createServer()
*/
function createServer(routes) {
return function requestHandler(req, res) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
try {
const url = (_a = req.url) === null || _a === void 0 ? void 0 : _a.split("?")[0];
const method = (_b = req.method) === null || _b === void 0 ? void 0 : _b.toUpperCase();
if (!url || !method)
throw new http_errors_1.ImATeapot(); // this should never happen
const ctx = {
body: undefined,
user: undefined,
query: undefined,
params: undefined,
};
for (let route of routes) {
if (!route.pattern.test(url))
continue;
const allRequestMethodsHandler = route["*"];
if (allRequestMethodsHandler) {
yield executeRequestHandler({
requestHandler: allRequestMethodsHandler,
req,
res,
pattern: route.pattern,
ctx,
});
}
if (res.headersSent)
return;
const specificRequestMethodHandler = route[method];
if (!specificRequestMethodHandler)
continue;
yield executeRequestHandler({
requestHandler: specificRequestMethodHandler,
req,
res,
pattern: route.pattern,
ctx,
});
if (res.headersSent)
return;
}
throw new http_errors_1.NotFound(`${method} ${url}`);
}
catch (err) {
if (process.env.NODE_ENV !== "production")
console.log(err);
if (res.headersSent)
return;
let message = err.message;
const statusCode = err.statusCode || 500;
if (statusCode >= 500 && process.env.NODE_ENV === "production") {
message = "internal server error";
}
res
.writeHead(statusCode, { "Content-Type": "application/json" })
.end(JSON.stringify({ message }));
}
});
};
}
exports.createServer = createServer;
/**
* Generate a request handler function that will execute connect-style middleware.
*
* @param connectMiddleware The middleware to execute
* @param errorHandler The error handler to execute if the middleware returns an error
*
* @returns A request handler function
*
*/
function useConnectMiddleware(connectMiddleware, errorHandler) {
return function ({ req, res }) {
return __awaiter(this, void 0, void 0, function* () {
let middlewareError = yield new Promise((resolve) => {
connectMiddleware(req, res, (err) => {
resolve(err);
});
});
if (middlewareError) {
if (!errorHandler) {
throw new http_errors_1.InternalServerError(`Middleware error: ${middlewareError}`);
}
yield errorHandler(middlewareError);
}
});
};
}
exports.useConnectMiddleware = useConnectMiddleware;