UNPKG

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
// 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 };