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.

342 lines (326 loc) 10.9 kB
"use strict"; 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 __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/client/index.ts var client_exports = {}; __export(client_exports, { makeServerActionImmutableHook: () => make_server_action_immutable_hook_default, makeServerActionMutationHook: () => make_server_action_mutation_hook_default }); module.exports = __toCommonJS(client_exports); // src/client/helpers/make-server-action-immutable-hook.ts var import_immutable = __toESM(require("swr/immutable")); // 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; } toJSON() { return { message: this.message, code: this.code, status: this.status, uiMessage: this.uiMessage }; } }; // src/server/helpers/parse-server-error.ts var import_zod = require("zod"); 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" }; // 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 var import_http_status_codes3 = require("http-status-codes"); var import_server = require("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 (0, import_immutable.default)( 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 var import_mutation = __toESM(require("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 (0, import_mutation.default)( 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;