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.
312 lines (298 loc) • 9.34 kB
JavaScript
// src/client/helpers/make-server-action-immutable-hook.ts
import useSWRImmutable from "swr/immutable";
// src/server/helpers/make-server-action.ts
import {
makeAsyncErrorState,
makeAsyncSuccessState
} from "async-xtate";
// src/server/helpers/parse-server-error.ts
import { StatusCodes as StatusCodes2 } from "http-status-codes";
// src/server/errors/server-error.ts
import { StatusCodes } from "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 ?? StatusCodes.INTERNAL_SERVER_ERROR;
this.uiMessage = props.uiMessage;
}
toJSON() {
return {
message: this.message,
code: this.code,
status: this.status,
uiMessage: this.uiMessage
};
}
};
// src/server/helpers/parse-server-error.ts
import { ZodError } from "zod";
var defaultOptions = {
defaultMessage: "An unexpected error occurred",
defaultCode: "GENERIC_ERROR" /* GenericError */,
defaultStatus: StatusCodes2.INTERNAL_SERVER_ERROR,
defaultUiMessage: "An unexpected error occurred"
};
// src/server/helpers/logger/log-levels.ts
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: false ? "INFO" /* INFO */ : "DEBUG" /* DEBUG */,
context: "SERVER"
});
var requestLogger = new request_logger_default(defaultLogger);
// src/server/helpers/make-api-controller.ts
import { StatusCodes as StatusCodes3 } from "http-status-codes";
import { NextResponse } from "next/server";
// src/client/helpers/make-server-action-immutable-hook.ts
function makeServerActionImmutableHook(options) {
const { key, action } = options;
const makeKey = (context) => ({
key,
...context ? { context } : {}
});
function useAction(hookOptions) {
const { context, options: swrOptions, skip = false } = hookOptions;
const currentKey = skip ? null : makeKey(context);
return useSWRImmutable(
currentKey,
async (args) => {
const result = await action(args.context);
if (result.status === "error") throw new ServerError(result.error);
if (result.status !== "success")
throw new ServerError({
message: "Unknown error",
code: "UNKNOWN_ERROR"
});
return result.data;
},
swrOptions
);
}
;
return {
useAction,
makeKey
};
}
var make_server_action_immutable_hook_default = makeServerActionImmutableHook;
// src/client/helpers/make-server-action-mutation-hook.ts
import useSWRMutation from "swr/mutation";
function makeServerActionMutationHook(options) {
const { key, action } = options;
const makeKey = (context) => ({
key,
...context ? { context } : {}
});
function useAction(hookOptions) {
const { context, options: swrOptions } = hookOptions || {};
const currentKey = makeKey(context);
return useSWRMutation(
currentKey,
async (_, { arg }) => {
const result = await action(arg);
if (result.status === "error") throw new ServerError(result.error);
if (result.status !== "success")
throw new ServerError({
message: "Unknown error",
code: "UNKNOWN_ERROR"
});
return result.data;
},
swrOptions
);
}
;
return {
useAction,
makeKey
};
}
var make_server_action_mutation_hook_default = makeServerActionMutationHook;
export {
make_server_action_immutable_hook_default as makeServerActionImmutableHook,
make_server_action_mutation_hook_default as makeServerActionMutationHook
};