nexting
Version:
A comprehensive, type-safe full-stack library for TypeScript/JavaScript applications. Provides server actions, API controllers, React hooks, error handling, and professional logging - all with complete type safety and inference.
467 lines (452 loc) • 16.3 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
LogLevel: () => LogLevel,
Logger: () => logger_default,
ParseServerErrorCode: () => ParseServerErrorCode,
RequestLogger: () => request_logger_default,
ServerError: () => ServerError,
createLogger: () => createLogger,
createRequestLogger: () => createRequestLogger,
defaultLogger: () => defaultLogger,
handleError: () => handleError,
makeApiController: () => make_api_controller_default,
makeServerAction: () => make_server_action_default,
parseServerError: () => parse_server_error_default,
requestLogger: () => requestLogger,
serverLogger: () => server_logger_default,
validateInput: () => validateInput,
zod: () => zod
});
module.exports = __toCommonJS(index_exports);
__reExport(index_exports, require("async-xtate"), module.exports);
__reExport(index_exports, require("http-status-codes"), module.exports);
var zod = __toESM(require("zod"));
// src/server/helpers/make-server-action.ts
var import_async_xtate = require("async-xtate");
// src/server/helpers/parse-server-error.ts
var import_http_status_codes2 = require("http-status-codes");
// src/server/errors/server-error.ts
var import_http_status_codes = require("http-status-codes");
var ServerError = class extends Error {
constructor(props) {
super(props.message);
this.name = "ServerError";
this.code = props.code ?? "GENERIC_ERROR";
this.status = props.status ?? import_http_status_codes.StatusCodes.INTERNAL_SERVER_ERROR;
this.uiMessage = props.uiMessage;
this.meta = props.meta;
}
toJSON() {
return {
message: this.message,
code: this.code,
status: this.status,
uiMessage: this.uiMessage,
meta: this.meta
};
}
};
// src/server/helpers/parse-server-error.ts
var import_zod = require("zod");
var ParseServerErrorCode = /* @__PURE__ */ ((ParseServerErrorCode2) => {
ParseServerErrorCode2["ValidationError"] = "VALIDATION_ERROR";
ParseServerErrorCode2["GenericError"] = "GENERIC_ERROR";
return ParseServerErrorCode2;
})(ParseServerErrorCode || {});
var defaultOptions = {
defaultMessage: "An unexpected error occurred",
defaultCode: "GENERIC_ERROR" /* GenericError */,
defaultStatus: import_http_status_codes2.StatusCodes.INTERNAL_SERVER_ERROR,
defaultUiMessage: "An unexpected error occurred"
};
var parseServerError = (error, {
defaultMessage = defaultOptions.defaultMessage,
defaultCode = defaultOptions.defaultCode,
defaultStatus = defaultOptions.defaultStatus,
defaultUiMessage = defaultOptions.defaultUiMessage,
parser
} = defaultOptions) => {
const customError = parser == null ? void 0 : parser(error);
if (customError instanceof ServerError) return customError;
if (error instanceof ServerError) return error;
if (error instanceof import_zod.ZodError) {
return new ServerError({
message: error.message,
code: "VALIDATION_ERROR" /* ValidationError */,
status: import_http_status_codes2.StatusCodes.BAD_REQUEST,
uiMessage: defaultUiMessage
});
}
if (error instanceof Error) {
return new ServerError({
message: error.message,
code: defaultCode,
status: defaultStatus,
uiMessage: defaultUiMessage
});
}
return new ServerError({
message: defaultMessage,
code: defaultCode,
status: defaultStatus,
uiMessage: defaultUiMessage
});
};
var parse_server_error_default = parseServerError;
// src/server/helpers/logger/log-levels.ts
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
LogLevel2["ERROR"] = "ERROR";
LogLevel2["WARN"] = "WARN";
LogLevel2["INFO"] = "INFO";
LogLevel2["DEBUG"] = "DEBUG";
LogLevel2["TRACE"] = "TRACE";
return LogLevel2;
})(LogLevel || {});
var LOG_LEVEL_PRIORITY = {
["ERROR" /* ERROR */]: 0,
["WARN" /* WARN */]: 1,
["INFO" /* INFO */]: 2,
["DEBUG" /* DEBUG */]: 3,
["TRACE" /* TRACE */]: 4
};
var LOG_LEVEL_COLORS = {
["ERROR" /* ERROR */]: "\x1B[31m",
// Red
["WARN" /* WARN */]: "\x1B[33m",
// Yellow
["INFO" /* INFO */]: "\x1B[36m",
// Cyan
["DEBUG" /* DEBUG */]: "\x1B[35m",
// Magenta
["TRACE" /* TRACE */]: "\x1B[37m"
// White
};
var RESET_COLOR = "\x1B[0m";
// src/server/helpers/logger/transports/console-transport.ts
var ConsoleTransport = class {
log(formattedMessage, entry) {
switch (entry.level) {
case "ERROR" /* ERROR */:
console.error(formattedMessage);
break;
case "WARN" /* WARN */:
console.warn(formattedMessage);
break;
case "INFO" /* INFO */:
console.info(formattedMessage);
break;
case "DEBUG" /* DEBUG */:
case "TRACE" /* TRACE */:
console.debug(formattedMessage);
break;
default:
console.log(formattedMessage);
}
}
};
var console_transport_default = ConsoleTransport;
// src/server/helpers/logger/formatters/pretty-formatter.ts
var PrettyFormatter = class {
format(entry) {
const timestamp = entry.timestamp.toISOString();
const color = LOG_LEVEL_COLORS[entry.level];
const levelText = `[${entry.level}]`.padEnd(7);
let message = `${color}${timestamp} ${levelText}${RESET_COLOR}`;
if (entry.context) {
message += ` [${entry.context}]`;
}
if (entry.requestId) {
message += ` [${entry.requestId}]`;
}
message += ` ${entry.message}`;
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
message += `
Metadata: ${JSON.stringify(entry.metadata, null, 2)}`;
}
return message;
}
};
var pretty_formatter_default = PrettyFormatter;
// src/server/helpers/logger/logger.ts
var Logger = class _Logger {
constructor(config = {}) {
this.config = {
level: config.level ?? "INFO" /* INFO */,
context: config.context ?? "APP",
formatter: config.formatter ?? new pretty_formatter_default(),
transports: config.transports ?? [new console_transport_default()],
includeTimestamp: config.includeTimestamp ?? true,
includeContext: config.includeContext ?? true,
includeMetadata: config.includeMetadata ?? true
};
}
shouldLog(level) {
return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.config.level];
}
async writeLog(level, message, metadata, requestId) {
if (!this.shouldLog(level)) {
return;
}
const entry = {
level,
message,
timestamp: /* @__PURE__ */ new Date(),
...this.config.includeContext && { context: this.config.context },
...this.config.includeMetadata && metadata && { metadata },
...requestId && { requestId }
};
const formattedMessage = this.config.formatter.format(entry);
const logPromises = this.config.transports.map(
(transport) => Promise.resolve(transport.log(formattedMessage, entry))
);
await Promise.all(logPromises);
}
error(message, metadata, requestId) {
return this.writeLog("ERROR" /* ERROR */, message, metadata, requestId);
}
warn(message, metadata, requestId) {
return this.writeLog("WARN" /* WARN */, message, metadata, requestId);
}
info(message, metadata, requestId) {
return this.writeLog("INFO" /* INFO */, message, metadata, requestId);
}
debug(message, metadata, requestId) {
return this.writeLog("DEBUG" /* DEBUG */, message, metadata, requestId);
}
trace(message, metadata, requestId) {
return this.writeLog("TRACE" /* TRACE */, message, metadata, requestId);
}
child(context) {
return new _Logger({
...this.config,
context: `${this.config.context}:${context}`
});
}
setLevel(level) {
this.config.level = level;
}
setFormatter(formatter) {
this.config.formatter = formatter;
}
addTransport(transport) {
this.config.transports.push(transport);
}
};
var logger_default = Logger;
// src/server/helpers/logger/request-logger.ts
var RequestLogger = class {
constructor(logger) {
this.logger = logger.child("REQUEST");
}
extractRequestData(request) {
const url = new URL(request.url);
const userAgent = request.headers.get("user-agent") || void 0;
const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || void 0;
return {
method: request.method,
url: url.pathname + url.search,
userAgent,
ip,
headers: Object.fromEntries(request.headers.entries())
};
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async logRequest(request) {
const requestId = this.generateRequestId();
const requestData = this.extractRequestData(request);
await this.logger.info(
`Incoming ${requestData.method} ${requestData.url}`,
{ requestData },
requestId
);
return requestId;
}
async logResponse(requestId, statusCode, duration, additionalData) {
const message = `Response ${statusCode} (${duration}ms)`;
const metadata = { statusCode, duration, ...additionalData };
if (statusCode >= 400) {
await this.logger.warn(message, metadata, requestId);
} else {
await this.logger.info(message, metadata, requestId);
}
}
async logError(requestId, error, additionalData) {
await this.logger.error(
`Request error: ${error.message}`,
{
error: error.name,
stack: error.stack,
...additionalData
},
requestId
);
}
};
var request_logger_default = RequestLogger;
// src/server/helpers/server-logger.ts
var defaultLogger = new logger_default({
level: process.env.NODE_ENV === "production" ? "INFO" /* INFO */ : "DEBUG" /* DEBUG */,
context: "SERVER"
});
var requestLogger = new request_logger_default(defaultLogger);
var serverLogger = async (request) => {
return await requestLogger.logRequest(request);
};
var createLogger = (config) => {
return new logger_default(config);
};
var createRequestLogger = (logger) => {
return new request_logger_default(logger || defaultLogger);
};
var server_logger_default = serverLogger;
// src/server/helpers/common-handler.ts
var validateInput = (input, schema) => {
if (!schema) {
return { isValid: true, data: input };
}
try {
const data = schema.parse(input);
return { isValid: true, data };
} catch (error) {
const parsedError = parse_server_error_default(error).toJSON();
return { isValid: false, error: parsedError };
}
};
var handleError = (error, options) => {
const parsedError = parse_server_error_default(error, options == null ? void 0 : options.error).toJSON();
((options == null ? void 0 : options.logger) || defaultLogger).error(parsedError.message, parsedError);
return parsedError;
};
// src/server/helpers/make-server-action.ts
function makeServerAction(actionFn, options) {
return async (props) => {
try {
if (options && "validationSchema" in options && options.validationSchema) {
const validation = validateInput(props, options.validationSchema);
if (!validation.isValid) {
return (0, import_async_xtate.makeAsyncErrorState)(validation.error);
}
const result2 = await actionFn(
validation.data
);
return (0, import_async_xtate.makeAsyncSuccessState)(result2);
}
const result = await actionFn();
return (0, import_async_xtate.makeAsyncSuccessState)(result);
} catch (error) {
const parsedError = handleError(error, options);
return (0, import_async_xtate.makeAsyncErrorState)(parsedError);
}
};
}
var make_server_action_default = makeServerAction;
// src/server/helpers/make-api-controller.ts
var import_http_status_codes3 = require("http-status-codes");
var import_server = require("next/server");
function makeApiController(controller, options) {
return async (request, props) => {
var _a, _b, _c;
try {
const context = { request };
const hasBody = !!(options && typeof options === "object" && "bodySchema" in options && options.bodySchema);
const hasQuery = !!(options && typeof options === "object" && "querySchema" in options && options.querySchema);
const hasParams = !!(options && typeof options === "object" && "paramsSchema" in options && options.paramsSchema);
const args = {};
if (hasQuery) {
const url = new URL(request.url);
const rawQuery = {};
for (const [key, value] of url.searchParams.entries()) {
if (rawQuery[key]) {
rawQuery[key] = Array.isArray(rawQuery[key]) ? [...rawQuery[key], value] : [rawQuery[key], value];
} else {
rawQuery[key] = value;
}
}
const validated = validateInput(
rawQuery,
options.querySchema
);
if (!validated.isValid) {
return import_server.NextResponse.json(validated.error, {
status: ((_a = validated.error) == null ? void 0 : _a.status) || import_http_status_codes3.StatusCodes.BAD_REQUEST
});
}
args.query = validated.data;
}
if (hasBody) {
const rawBody = await request.json();
const validated = validateInput(
rawBody,
options.bodySchema
);
if (!validated.isValid) {
return import_server.NextResponse.json(validated.error, {
status: ((_b = validated.error) == null ? void 0 : _b.status) || import_http_status_codes3.StatusCodes.BAD_REQUEST
});
}
args.body = validated.data;
}
if (hasParams) {
const validated = validateInput(
(props == null ? void 0 : props.params) ?? {},
options.paramsSchema
);
if (!validated.isValid) {
return import_server.NextResponse.json(validated.error, {
status: ((_c = validated.error) == null ? void 0 : _c.status) || import_http_status_codes3.StatusCodes.BAD_REQUEST
});
}
args.params = validated.data;
}
let result;
if (hasBody || hasQuery || hasParams) {
result = await controller(args, context);
} else {
result = await controller(context);
}
const responseStatus = result.status || import_http_status_codes3.StatusCodes.OK;
if (responseStatus === import_http_status_codes3.StatusCodes.NO_CONTENT) {
return new import_server.NextResponse(null, { status: responseStatus });
}
return import_server.NextResponse.json(result.data, { status: responseStatus });
} catch (error) {
const parsedError = handleError(error, options);
return import_server.NextResponse.json(parsedError, {
status: parsedError.status || import_http_status_codes3.StatusCodes.INTERNAL_SERVER_ERROR
});
}
};
}
var make_api_controller_default = makeApiController;