exstack
Version:
A utility library designed to simplify and enhance Express.js applications.
431 lines (424 loc) • 14.1 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
ApiRes: () => ApiRes,
BadRequestError: () => BadRequestError,
ConflictError: () => ConflictError,
ContentTooLargeError: () => ContentTooLargeError,
ForbiddenError: () => ForbiddenError,
HttpError: () => HttpError,
HttpStatus: () => HttpStatus,
InternalServerError: () => InternalServerError,
NotFoundError: () => NotFoundError,
UnAuthorizedError: () => UnAuthorizedError,
createHttpErrorClass: () => createHttpErrorClass,
errorHandler: () => errorHandler,
getErrorName: () => getErrorName,
handler: () => handler,
makePermission: () => makePermission,
notFound: () => notFound,
proxyWrapper: () => proxyWrapper
});
module.exports = __toCommonJS(index_exports);
// src/enums.ts
var HttpStatus = Object.freeze({
/** Continue with the request. */
CONTINUE: 100,
"100_NAME": "CONTINUE",
/** Switching protocols. */
SWITCHING_PROTOCOLS: 101,
"101_NAME": "SWITCHING_PROTOCOLS",
/** Request is being processed. */
PROCESSING: 102,
"102_NAME": "PROCESSING",
/** Early hints for the client. */
EARLYHINTS: 103,
"103_NAME": "EARLY_HINTS",
/** Request succeeded. */
OK: 200,
"200_NAME": "OK",
/** Resource created. */
CREATED: 201,
"201_NAME": "CREATED",
/** Request accepted for processing. */
ACCEPTED: 202,
"202_NAME": "ACCEPTED",
/** Non-authoritative information. */
NON_AUTHORITATIVE_INFORMATION: 203,
"203_NAME": "NON_AUTHORITATIVE_INFORMATION",
/** No content to send. */
NO_CONTENT: 204,
"204_NAME": "NO_CONTENT",
/** Content reset. */
RESET_CONTENT: 205,
"205_NAME": "RESET_CONTENT",
/** Partial content delivered. */
PARTIAL_CONTENT: 206,
"206_NAME": "PARTIAL_CONTENT",
/** Multiple choices available. */
AMBIGUOUS: 300,
"300_NAME": "AMBIGUOUS",
/** Resource moved permanently. */
MOVED_PERMANENTLY: 301,
"301_NAME": "MOVED_PERMANENTLY",
/** Resource found at another URI. */
FOUND: 302,
"302_NAME": "FOUND",
/** See other resource. */
SEE_OTHER: 303,
"303_NAME": "SEE_OTHER",
/** Resource not modified. */
NOT_MODIFIED: 304,
"304_NAME": "NOT_MODIFIED",
/** Temporary redirect. */
TEMPORARY_REDIRECT: 307,
"307_NAME": "TEMPORARY_REDIRECT",
/** Permanent redirect. */
PERMANENT_REDIRECT: 308,
"308_NAME": "PERMANENT_REDIRECT",
/** Bad request. */
BAD_REQUEST: 400,
"400_NAME": "BAD_REQUEST",
/** Authentication required. */
UNAUTHORIZED: 401,
"401_NAME": "UNAUTHORIZED",
/** Payment required. */
PAYMENT_REQUIRED: 402,
"402_NAME": "PAYMENT_REQUIRED",
/** Access forbidden. */
FORBIDDEN: 403,
"403_NAME": "FORBIDDEN",
/** Resource not found. */
NOT_FOUND: 404,
"404_NAME": "NOT_FOUND",
/** Method not allowed. */
METHOD_NOT_ALLOWED: 405,
"405_NAME": "METHOD_NOT_ALLOWED",
/** Not acceptable content. */
NOT_ACCEPTABLE: 406,
"406_NAME": "NOT_ACCEPTABLE",
/** Proxy authentication required. */
PROXY_AUTHENTICATION_REQUIRED: 407,
"407_NAME": "PROXY_AUTHENTICATION_REQUIRED",
/** Request timed out. */
REQUEST_TIMEOUT: 408,
"408_NAME": "REQUEST_TIMEOUT",
/** Conflict with current state. */
CONFLICT: 409,
"409_NAME": "CONFLICT",
/** Resource gone. */
GONE: 410,
"410_NAME": "GONE",
/** Length required. */
LENGTH_REQUIRED: 411,
"411_NAME": "LENGTH_REQUIRED",
/** Precondition failed. */
PRECONDITION_FAILED: 412,
"412_NAME": "PRECONDITION_FAILED",
/** Payload too large. */
PAYLOAD_TOO_LARGE: 413,
"413_NAME": "PAYLOAD_TOO_LARGE",
/** URI too long. */
URI_TOO_LONG: 414,
"414_NAME": "URI_TOO_LONG",
/** Unsupported media type. */
UNSUPPORTED_MEDIA_TYPE: 415,
"415_NAME": "UNSUPPORTED_MEDIA_TYPE",
/** Requested range not satisfiable. */
REQUESTED_RANGE_NOT_SATISFIABLE: 416,
"416_NAME": "REQUESTED_RANGE_NOT_SATISFIABLE",
/** Expectation failed. */
EXPECTATION_FAILED: 417,
"417_NAME": "EXPECTATION_FAILED",
/** I'm a teapot. */
I_AM_A_TEAPOT: 418,
"418_NAME": "I_AM_A_TEAPOT",
/** Misdirected request. */
MISDIRECTED: 421,
"421_NAME": "MISDIRECTED",
/** Unprocessable entity. */
UNPROCESSABLE_ENTITY: 422,
"422_NAME": "UNPROCESSABLE_ENTITY",
/** Locked. */
LOCKED: 423,
"423_NAME": "LOCKED",
/** Failed dependency. */
FAILED_DEPENDENCY: 424,
"424_NAME": "FAILED_DEPENDENCY",
/** Too early. */
TOO_EARLY: 425,
"425_NAME": "TOO_EARLY",
/** Upgrade required. */
UPGRADE_REQUIRED: 426,
"426_NAME": "UPGRADE_REQUIRED",
/** Precondition required. */
PRECONDITION_REQUIRED: 428,
"428_NAME": "PRECONDITION_REQUIRED",
/** Too many requests. */
TOO_MANY_REQUESTS: 429,
"429_NAME": "TOO_MANY_REQUESTS",
/** Request header fields too large. */
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
"431_NAME": "REQUEST_HEADER_FIELDS_TOO_LARGE",
/** Unavailable for legal reasons. */
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
"451_NAME": "UNAVAILABLE_FOR_LEGAL_REASONS",
/** Internal server error. */
INTERNAL_SERVER_ERROR: 500,
"500_NAME": "INTERNAL_SERVER_ERROR",
/** Not implemented. */
NOT_IMPLEMENTED: 501,
"501_NAME": "NOT_IMPLEMENTED",
/** Bad gateway. */
BAD_GATEWAY: 502,
"502_NAME": "BAD_GATEWAY",
/** Service unavailable. */
SERVICE_UNAVAILABLE: 503,
"503_NAME": "SERVICE_UNAVAILABLE",
/** Gateway timeout. */
GATEWAY_TIMEOUT: 504,
"504_NAME": "GATEWAY_TIMEOUT",
/** HTTP version not supported. */
HTTP_VERSION_NOT_SUPPORTED: 505,
"505_NAME": "HTTP_VERSION_NOT_SUPPORTED",
/** Variant also negotiates. */
VARIANT_ALSO_NEGOTIATES: 506,
"506_NAME": "VARIANT_ALSO_NEGOTIATES",
/** Insufficient storage. */
INSUFFICIENT_STORAGE: 507,
"507_NAME": "INSUFFICIENT_STORAGE",
/** Loop detected. */
LOOP_DETECTED: 508,
"508_NAME": "LOOP_DETECTED",
/** Bandwidth limit exceeded. */
BANDWIDTH_LIMIT_EXCEEDED: 509,
"509_NAME": "BANDWIDTH_LIMIT_EXCEEDED",
/** Not extended. */
NOT_EXTENDED: 510,
"510_NAME": "NOT_EXTENDED",
/** Network authentication required. */
NETWORK_AUTHENTICATION_REQUIRED: 511,
"511_NAME": "NETWORK_AUTHENTICATION_REQUIRED"
});
// src/errors.ts
var getErrorName = (status) => {
if (status < 400 || status > 511) return "HttpError";
const statusKey = HttpStatus[`${status}_NAME`];
if (!statusKey) return "HttpError";
const name = statusKey.toLowerCase().replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()).replace(/\s+/g, "");
return name.endsWith("Error") ? name : name.concat("Error");
};
var _HttpError = class _HttpError extends Error {
/**
* Creates an instance of `HTTPException`.
* @param status - HTTP status code for the exception. Defaults to 500.
* @param options - Additional options for the exception.
*/
constructor(status = HttpStatus.INTERNAL_SERVER_ERROR, options) {
super(typeof options.message === "string" ? options.message : getErrorName(status));
this.status = status;
this.options = options;
this.name = getErrorName(status);
Error.captureStackTrace(this, this.constructor);
}
/**
* Convert the HttpError instance to a Body object.
* @example
* const errorBody = new HttpError(404, {message: 'Not Found'}).body;
*/
get body() {
const { name: error, status } = this;
const { message, data = null } = this.options;
return { status, error, message, data };
}
/**
* Send the json of the error in an HTTP response.
* @param {Response} res - The Express response object.
*
* @example
* new HttpError(404, {message: 'Not Found'}).toJson(res);
*/
toJson(res) {
res.status(this.status).json(this.body);
}
};
/**
* Check if the given error is an instance of HttpError.
* @param {unknown} value - The error to check.
* @returns {boolean} - True if the error is an instance of HttpError, false otherwise.
*
* @example
* if (HttpError.isHttpError(error)) {
* // Handle the HttpError
* }
*/
_HttpError.isHttpError = (value) => value instanceof _HttpError;
var HttpError = _HttpError;
var createHttpErrorClass = (status) => class extends HttpError {
constructor(message, data, cause) {
super(status, { message, data, cause });
}
};
var BadRequestError = createHttpErrorClass(HttpStatus.BAD_REQUEST);
var ConflictError = createHttpErrorClass(HttpStatus.CONFLICT);
var ForbiddenError = createHttpErrorClass(HttpStatus.FORBIDDEN);
var NotFoundError = createHttpErrorClass(HttpStatus.NOT_FOUND);
var UnAuthorizedError = createHttpErrorClass(HttpStatus.UNAUTHORIZED);
var InternalServerError = createHttpErrorClass(HttpStatus.INTERNAL_SERVER_ERROR);
var ContentTooLargeError = createHttpErrorClass(HttpStatus.PAYLOAD_TOO_LARGE);
// src/utils.ts
var import_express = require("express");
var errorHandler = (isDev = true, logger = console.error) => (err, _req, res, _next) => {
if (HttpError.isHttpError(err)) {
if (err.options.cause) logger?.(`HttpError Cause: ${err.options.cause}`);
return err.toJson(res);
}
logger?.(`Unknown Error: ${err}`);
const unknown = {
status: HttpStatus.INTERNAL_SERVER_ERROR,
error: "InternalServerError",
message: isDev ? err.message || "Unexpected error" : "Something went wrong",
stack: isDev ? err.stack : void 0
};
res.status(unknown.status).json(unknown);
};
var notFound = (path = "*") => (0, import_express.Router)().all(
path,
(req, res) => new NotFoundError(`Cannot ${req.originalUrl} on ${req.method.toUpperCase()}`).toJson(res)
);
function makePermission(options) {
const { actions, subjects, filter } = options;
const map_data = subjects.flatMap(
(subject) => (filter?.[subject] ?? actions).map((action) => [
`${subject}_${action}`.toUpperCase(),
// Convert key to uppercase
`${subject.toLowerCase()}:${action.toLowerCase()}`
])
);
return Object.freeze(Object.fromEntries(map_data));
}
// src/api-res.ts
var _ApiRes = class _ApiRes {
/**
* Creates an instance of ApiRes.
* @param {any} result - The result of the operation
* @param {Status} status - The HTTP status code
* @param {string} message - The response message
*/
constructor(result = {}, status = HttpStatus.OK, message = "Operation successful") {
this.result = result;
this.status = status;
this.message = message;
}
/**
* Returns the Body (JSON) representation of the response.
* @returns The Body (JSON) representation of the response
*
* @example
* new ApiRes('Hello World', 200).body;
*/
get body() {
return {
status: this.status,
message: this.message,
result: this.result
};
}
/**
* Send the json of HTTP response.
* @param {Response} res - The Express response object.
*
* @example
* new ApiRes('Hello World', 200).toJson(res);
*/
toJson(res) {
res.status(this.status).json(this.body);
}
};
/**
* Creates an OK (200) response.
* @param {any} result - The result to be included in the response
* @param {string} [message='Request processed successfully'] - The response message
* @returns {ApiRes} An ApiRes instance with OK status
*/
_ApiRes.ok = (result, message = "Request processed successfully") => new _ApiRes(result, HttpStatus.OK, message);
/**
* Creates a Created (201) response.
* @param {any} result - The result to be included in the response
* @param {string} [message='Resource created successfully'] - The response message
* @returns {ApiRes} An ApiRes instance with Created status
*/
_ApiRes.created = (result, message = "Resource created successfully") => new _ApiRes(result, HttpStatus.CREATED, message);
/**
* Creates a paginated OK (200) response.
* @param {any} data - The paginated data
* @param {object} meta - Metadata for pagination
* @param {string} [message='Data retrieved successfully'] - The response message
* @returns {ApiRes} An ApiRes instance with OK status and paginated data
*/
_ApiRes.paginated = (data, meta, message = "Data retrieved successfully") => new _ApiRes({ ...meta, data }, HttpStatus.OK, message);
var ApiRes = _ApiRes;
// src/handler.ts
var handleResult = (result, res) => {
if (result instanceof ApiRes) result.toJson(res);
else if (result && result !== res) res.send(result);
};
var handler = (callback) => async (req, res, next) => {
try {
const result = callback(req, res, next);
if (result instanceof Promise) await result.then((value) => handleResult(value, res)).catch(next);
else handleResult(result, res);
} catch (error) {
next(error);
}
};
var proxyWrapper = (clsOrInstance, ...args) => {
const instance = typeof clsOrInstance === "function" ? new clsOrInstance(...args) : clsOrInstance;
return new Proxy(instance, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
return typeof value === "function" ? handler(value.bind(target)) : value;
},
set() {
throw new Error("Overriding methods and properties is not allowed.");
}
});
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ApiRes,
BadRequestError,
ConflictError,
ContentTooLargeError,
ForbiddenError,
HttpError,
HttpStatus,
InternalServerError,
NotFoundError,
UnAuthorizedError,
createHttpErrorClass,
errorHandler,
getErrorName,
handler,
makePermission,
notFound,
proxyWrapper
});