@nano-app/backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Endpoint } from "@all";
2
+ type NanoAppConfig = {
3
+ port?: number;
4
+ endpoints: Endpoint[];
5
+ };
6
+ export declare class NanoApp {
7
+ private app;
8
+ private router;
9
+ private config;
10
+ constructor(config: NanoAppConfig);
11
+ start(listener?: () => void): Promise<void>;
12
+ }
13
+ export {};
package/dist/app.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NanoApp = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const dotenv_1 = __importDefault(require("dotenv"));
9
+ const _all_1 = require("@all");
10
+ dotenv_1.default.config();
11
+ class NanoApp {
12
+ app = (0, express_1.default)();
13
+ router;
14
+ config = {
15
+ port: 3000,
16
+ endpoints: [],
17
+ };
18
+ constructor(config) {
19
+ // Inicializar el router
20
+ this.router = new _all_1.Router(config.endpoints);
21
+ // Inicializar la configuración
22
+ this.config = {
23
+ ...this.config,
24
+ ...config,
25
+ };
26
+ }
27
+ async start(listener) {
28
+ this.app.use(express_1.default.json());
29
+ // Registrar las rutas
30
+ this.app.use('/', this.router.getRoutes());
31
+ // Iniciar el servidor
32
+ this.app.listen(Number(this.config.port), listener || (() => console.log(`Servidor corriendo en el puerto ${this.config.port}`)));
33
+ }
34
+ }
35
+ exports.NanoApp = NanoApp;
@@ -0,0 +1,22 @@
1
+ import { ApiResponse } from "@nano-app/core";
2
+ import { Request, Response } from "express";
3
+ export declare abstract class Endpoint<BeforeResult = any> {
4
+ private methodMap;
5
+ endpointName: string;
6
+ constructor(endpointName?: string);
7
+ /** Metodos envoltorios */
8
+ protected BEFORE(_req: Request): Promise<BeforeResult>;
9
+ protected AFTER(_result: ApiResponse): Promise<ApiResponse>;
10
+ /**
11
+ * Métodos HTTP opcionales - las clases hijas solo implementan los que necesiten
12
+ * Si no se implementan, se lanza un error por defecto
13
+ */
14
+ protected GET(_req: Request, _res: Response, _beforeParams: BeforeResult): Promise<ApiResponse>;
15
+ protected POST(_req: Request, _res: Response, _beforeParams: BeforeResult): Promise<ApiResponse>;
16
+ protected PUT(_req: Request, _res: Response, _beforeParams: BeforeResult): Promise<ApiResponse>;
17
+ protected DELETE(_req: Request, _res: Response, _beforeParams: BeforeResult): Promise<ApiResponse>;
18
+ /**
19
+ * Ejecuta la lógica específica del endpoint
20
+ */
21
+ execute(req: Request, res: Response): void;
22
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Endpoint = void 0;
4
+ class Endpoint {
5
+ // protected prismaClient = new PrismaClient();
6
+ methodMap = {};
7
+ endpointName = '';
8
+ constructor(endpointName) {
9
+ this.endpointName = endpointName || 'default';
10
+ this.methodMap = {
11
+ 'GET': this.GET.bind(this),
12
+ 'POST': this.POST.bind(this),
13
+ 'PUT': this.PUT.bind(this),
14
+ 'DELETE': this.DELETE.bind(this),
15
+ };
16
+ }
17
+ /** Metodos envoltorios */
18
+ BEFORE(_req) {
19
+ return Promise.resolve({});
20
+ }
21
+ AFTER(_result) {
22
+ return Promise.resolve(_result);
23
+ }
24
+ /**
25
+ * Métodos HTTP opcionales - las clases hijas solo implementan los que necesiten
26
+ * Si no se implementan, se lanza un error por defecto
27
+ */
28
+ async GET(_req, _res, _beforeParams) {
29
+ throw { message: `Método GET no implementado para ${this.endpointName}` };
30
+ }
31
+ async POST(_req, _res, _beforeParams) {
32
+ throw { message: `Método POST no implementado para ${this.endpointName}` };
33
+ }
34
+ async PUT(_req, _res, _beforeParams) {
35
+ throw { message: `Método PUT no implementado para ${this.endpointName}` };
36
+ }
37
+ async DELETE(_req, _res, _beforeParams) {
38
+ throw { message: `Método DELETE no implementado para ${this.endpointName}` };
39
+ }
40
+ /**
41
+ * Ejecuta la lógica específica del endpoint
42
+ */
43
+ execute(req, res) {
44
+ try {
45
+ const method = req.method;
46
+ const handler = this.methodMap[method];
47
+ if (!handler) {
48
+ throw { message: `Método ${method} no soportado` };
49
+ }
50
+ this.BEFORE(req)
51
+ .then((beforeParams) => handler(req, res, beforeParams))
52
+ .then(result => this.AFTER(result))
53
+ .then(result => res.json(result));
54
+ }
55
+ catch (error) {
56
+ res.status(500).json({
57
+ success: false,
58
+ message: error.message ? error.message : error
59
+ });
60
+ }
61
+ }
62
+ }
63
+ exports.Endpoint = Endpoint;
@@ -0,0 +1,10 @@
1
+ import { Endpoint } from "@all";
2
+ import { Request, Response } from "express";
3
+ export declare class Handler {
4
+ private endpoints;
5
+ basePath: string;
6
+ /** Agrega los endpoints al handler */
7
+ constructor(basePath: string, dinamicPath: string, endpoints: Endpoint[]);
8
+ /** Maneja la petición HTTP */
9
+ handlerRequest(req: Request, res: Response): Promise<Response>;
10
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Handler = void 0;
4
+ class Handler {
5
+ endpoints = {};
6
+ basePath;
7
+ /** Agrega los endpoints al handler */
8
+ constructor(basePath, dinamicPath, endpoints) {
9
+ this.basePath = basePath;
10
+ endpoints.forEach(endpoint => {
11
+ this.endpoints[endpoint.endpointName] = endpoint;
12
+ });
13
+ }
14
+ /** Maneja la petición HTTP */
15
+ async handlerRequest(req, res) {
16
+ try {
17
+ // Obtiene el path del endpoint
18
+ const path = (typeof req.params.path === "string" ? req.params.path : req.params.path[0]).slice(1);
19
+ // Obtiene el endpoint correspondiente al path
20
+ const endpoint = this.endpoints[path] || this.endpoints['default'];
21
+ // Lanza error si el endpoint no existe
22
+ if (!endpoint) {
23
+ throw { message: `Endpoint ${path} no encontrado` };
24
+ }
25
+ // Ejecuta el handler específico
26
+ return await endpoint.execute(req, res)
27
+ .then(result => res.json(result)).catch(error => res.status(500).json({
28
+ success: false,
29
+ message: error.message ? error.message : error
30
+ }));
31
+ }
32
+ catch (error) {
33
+ return res.status(500).json({
34
+ success: false,
35
+ message: error.message ? error.message : error
36
+ });
37
+ }
38
+ }
39
+ }
40
+ exports.Handler = Handler;
@@ -0,0 +1,6 @@
1
+ import { Endpoint } from '@all';
2
+ export declare class Router {
3
+ private router;
4
+ constructor(endpoints: Endpoint[]);
5
+ getRoutes(): import("express-serve-static-core").Router;
6
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Router = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ class Router {
9
+ router = express_1.default.Router();
10
+ constructor(endpoints) {
11
+ // Registrar todos los endpoints
12
+ endpoints.forEach(endpoint => {
13
+ this.router.all(`${endpoint.endpointName}`, endpoint.execute.bind(endpoint));
14
+ });
15
+ }
16
+ getRoutes() {
17
+ return this.router;
18
+ }
19
+ }
20
+ exports.Router = Router;
@@ -0,0 +1,3 @@
1
+ export * from "./http/Endpoint";
2
+ export * from "./http/Router";
3
+ export * from "./app";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./http/Endpoint"), exports);
18
+ __exportStar(require("./http/Router"), exports);
19
+ __exportStar(require("./app"), exports);
@@ -0,0 +1,6 @@
1
+ export interface LoadedEndpoint<T = any> {
2
+ path: string;
3
+ EndpointClass: new (...args: any[]) => T;
4
+ endpointName: string;
5
+ }
6
+ export declare function loadEndpoints<T = any>(endpointsDir: string): Promise<LoadedEndpoint<T>[]>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadEndpoints = loadEndpoints;
37
+ const promises_1 = require("fs/promises");
38
+ const fs_1 = require("fs");
39
+ const path_1 = require("path");
40
+ const url_1 = require("url");
41
+ const EXT = [".js", ".ts"];
42
+ function slugFromClass(name) {
43
+ const base = name.replace(/Endpoint$/i, "") || "endpoint";
44
+ return base.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
45
+ }
46
+ async function* listFiles(dir) {
47
+ for (const e of await (0, promises_1.readdir)(dir, { withFileTypes: true })) {
48
+ const full = (0, path_1.join)(dir, e.name);
49
+ if (e.isDirectory())
50
+ yield* listFiles(full);
51
+ else if (e.isFile() && EXT.some((x) => e.name.endsWith(x)))
52
+ yield full;
53
+ }
54
+ }
55
+ function getEndpointClass(mod) {
56
+ const C = mod?.default ?? Object.values(mod).find((v) => typeof v === "function");
57
+ return typeof C === "function" ? C : null;
58
+ }
59
+ async function loadEndpoints(endpointsDir) {
60
+ const results = [];
61
+ const baseDir = (0, path_1.resolve)(process.cwd(), endpointsDir);
62
+ if (!(0, fs_1.existsSync)(baseDir)) {
63
+ throw new Error(`Carpeta de endpoints no encontrada: ${baseDir} (cwd: ${process.cwd()})`);
64
+ }
65
+ for await (const file of listFiles(baseDir)) {
66
+ const rel = (0, path_1.relative)(baseDir, file);
67
+ const dirs = (0, path_1.dirname)(rel).split(path_1.sep).filter((s) => s && s !== ".");
68
+ try {
69
+ const fileUrl = (0, url_1.pathToFileURL)(file).href;
70
+ const mod = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
71
+ const EndpointClass = getEndpointClass(mod);
72
+ if (!EndpointClass)
73
+ continue;
74
+ const endpointName = slugFromClass(EndpointClass.name ?? "endpoint");
75
+ const path = "/" + [...dirs, endpointName].join("/");
76
+ results.push({ path, EndpointClass: EndpointClass, endpointName });
77
+ }
78
+ catch (err) {
79
+ if (process.env.NODE_ENV !== "production") {
80
+ console.warn(`[nano-app] No se pudo cargar ${rel}:`, err?.message ?? err);
81
+ }
82
+ }
83
+ }
84
+ return results;
85
+ }
@@ -0,0 +1,44 @@
1
+ import { DiscoveredEndpoint } from "./discover-endpoints";
2
+ /**
3
+ * Clase que expone los paths de los endpoints descubiertos a partir de la
4
+ * estructura de carpetas del proyecto donde está instalado el backend.
5
+ *
6
+ * Se inicializa con Paths.init(projectRoot, endpointsDir) antes de usar
7
+ * (normalmente lo hace NanoApp al arrancar). Luego se puede usar
8
+ * Paths.health, Paths.greetingHello, etc., según los archivos en src/endpoints.
9
+ */
10
+ export declare class Paths {
11
+ private static _map;
12
+ private static _endpoints;
13
+ private static _instances;
14
+ private static _initialized;
15
+ /**
16
+ * Inicializa Paths leyendo la estructura de carpetas del proyecto y
17
+ * cargando las instancias de las clases Endpoint.
18
+ * Debe llamarse con la raíz del proyecto (p. ej. process.cwd()) y el
19
+ * directorio de endpoints (p. ej. "src/endpoints").
20
+ */
21
+ static init(projectRoot: string, endpointsDir: string): Promise<void>;
22
+ /**
23
+ * Obtiene el path HTTP por clave (camelCase). Ej: Paths.get("greetingHello") => "/greeting/hello"
24
+ */
25
+ static get(key: string): string;
26
+ /**
27
+ * Devuelve todos los paths descubiertos (clave -> path).
28
+ */
29
+ static getAll(): Record<string, string>;
30
+ /**
31
+ * Lista completa de endpoints descubiertos (path, key, filePath).
32
+ */
33
+ static getEndpoints(): DiscoveredEndpoint[];
34
+ /**
35
+ * Obtiene la instancia de Endpoint por clave (camelCase).
36
+ * Ej: Paths.getInstance("greetingHello") => instancia de HelloEndpoint
37
+ */
38
+ static getInstance(key: string): any;
39
+ /**
40
+ * Devuelve todas las instancias de Endpoint cargadas (clave -> instancia).
41
+ */
42
+ static getAllInstances(): Record<string, any>;
43
+ static get initialized(): boolean;
44
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Paths = void 0;
4
+ const discover_endpoints_1 = require("./discover-endpoints");
5
+ /**
6
+ * Clase que expone los paths de los endpoints descubiertos a partir de la
7
+ * estructura de carpetas del proyecto donde está instalado el backend.
8
+ *
9
+ * Se inicializa con Paths.init(projectRoot, endpointsDir) antes de usar
10
+ * (normalmente lo hace NanoApp al arrancar). Luego se puede usar
11
+ * Paths.health, Paths.greetingHello, etc., según los archivos en src/endpoints.
12
+ */
13
+ class Paths {
14
+ static _map = {};
15
+ static _endpoints = [];
16
+ static _instances = {};
17
+ static _initialized = false;
18
+ /**
19
+ * Inicializa Paths leyendo la estructura de carpetas del proyecto y
20
+ * cargando las instancias de las clases Endpoint.
21
+ * Debe llamarse con la raíz del proyecto (p. ej. process.cwd()) y el
22
+ * directorio de endpoints (p. ej. "src/endpoints").
23
+ */
24
+ static async init(projectRoot, endpointsDir) {
25
+ this._endpoints = (0, discover_endpoints_1.discoverEndpoints)(projectRoot, endpointsDir);
26
+ this._map = (0, discover_endpoints_1.discoverPathsMap)(projectRoot, endpointsDir);
27
+ // Cargar instancias de las clases Endpoint
28
+ this._instances = await (0, discover_endpoints_1.loadEndpointInstances)(this._endpoints, projectRoot, endpointsDir);
29
+ // Exponer cada path como propiedad estática en la clase
30
+ for (const [key, value] of Object.entries(this._map)) {
31
+ this[key] = value;
32
+ }
33
+ // Exponer cada instancia como propiedad estática (ej: Paths.healthInstance)
34
+ for (const [key, instance] of Object.entries(this._instances)) {
35
+ this[`${key}Instance`] = instance;
36
+ }
37
+ this._initialized = true;
38
+ }
39
+ /**
40
+ * Obtiene el path HTTP por clave (camelCase). Ej: Paths.get("greetingHello") => "/greeting/hello"
41
+ */
42
+ static get(key) {
43
+ return this._map[key] ?? "";
44
+ }
45
+ /**
46
+ * Devuelve todos los paths descubiertos (clave -> path).
47
+ */
48
+ static getAll() {
49
+ return { ...this._map };
50
+ }
51
+ /**
52
+ * Lista completa de endpoints descubiertos (path, key, filePath).
53
+ */
54
+ static getEndpoints() {
55
+ return [...this._endpoints];
56
+ }
57
+ /**
58
+ * Obtiene la instancia de Endpoint por clave (camelCase).
59
+ * Ej: Paths.getInstance("greetingHello") => instancia de HelloEndpoint
60
+ */
61
+ static getInstance(key) {
62
+ return this._instances[key] ?? null;
63
+ }
64
+ /**
65
+ * Devuelve todas las instancias de Endpoint cargadas (clave -> instancia).
66
+ */
67
+ static getAllInstances() {
68
+ return { ...this._instances };
69
+ }
70
+ static get initialized() {
71
+ return this._initialized;
72
+ }
73
+ }
74
+ exports.Paths = Paths;
@@ -0,0 +1,33 @@
1
+ export interface DiscoveredEndpoint {
2
+ /** Ruta HTTP del endpoint, ej: "/health", "/greeting/hello" */
3
+ path: string;
4
+ /** Nombre en camelCase para usar como propiedad, ej: "health", "greetingHello" */
5
+ key: string;
6
+ /** Ruta del archivo relativa al directorio de endpoints */
7
+ filePath: string;
8
+ /** Ruta absoluta completa del archivo */
9
+ absolutePath: string;
10
+ }
11
+ /**
12
+ * Lee la estructura de la carpeta de endpoints del proyecto y devuelve
13
+ * la lista de endpoints con su path HTTP y clave para la clase Paths.
14
+ *
15
+ * Reglas:
16
+ * - Carpetas cuyo nombre empieza con _ no forman parte del path (ej: _invisible_path).
17
+ * - El path es: /carpeta/subcarpeta/nombrearchivo en minúsculas.
18
+ */
19
+ export declare function discoverEndpoints(projectRoot: string, endpointsDir: string): DiscoveredEndpoint[];
20
+ /**
21
+ * Devuelve un objeto con las claves en camelCase y los paths como valores.
22
+ * Ej: { health: "/health", greetingHello: "/greeting/hello" }
23
+ */
24
+ export declare function discoverPathsMap(projectRoot: string, endpointsDir: string): Record<string, string>;
25
+ /**
26
+ * Tipo para representar una clase Endpoint (clase abstracta de @nano-app/core)
27
+ */
28
+ export type EndpointClass = new (...args: any[]) => any;
29
+ /**
30
+ * Carga todas las instancias de Endpoint desde los archivos descubiertos.
31
+ * Retorna un mapa de clave (camelCase) -> instancia de Endpoint.
32
+ */
33
+ export declare function loadEndpointInstances(endpoints: DiscoveredEndpoint[], projectRoot: string, endpointsDir: string): Promise<Record<string, any>>;
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.discoverEndpoints = discoverEndpoints;
37
+ exports.discoverPathsMap = discoverPathsMap;
38
+ exports.loadEndpointInstances = loadEndpointInstances;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const url_1 = require("url");
42
+ /**
43
+ * Convierte un path de endpoint a nombre en camelCase para usar como propiedad.
44
+ * Ej: "greeting/hello" -> "greetingHello", "health" -> "health"
45
+ */
46
+ function pathToCamelCase(routePath) {
47
+ const segments = routePath
48
+ .split("/")
49
+ .filter(Boolean)
50
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase());
51
+ if (segments.length === 0)
52
+ return "";
53
+ const first = segments[0].toLowerCase();
54
+ const rest = segments.slice(1).join("");
55
+ return first + rest;
56
+ }
57
+ /**
58
+ * Recorre recursivamente un directorio y devuelve rutas relativas de archivos .ts
59
+ */
60
+ function walkDir(dir, baseDir, relativePrefix, result) {
61
+ if (!fs.existsSync(dir))
62
+ return;
63
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
64
+ for (const entry of entries) {
65
+ const fullPath = path.join(dir, entry.name);
66
+ const relativeFromBase = path.relative(baseDir, fullPath);
67
+ if (entry.isDirectory()) {
68
+ // Carpetas que empiezan con _ no forman parte del path HTTP
69
+ const segment = entry.name.startsWith("_") ? [] : [entry.name];
70
+ walkDir(fullPath, baseDir, [...relativePrefix, ...segment], result);
71
+ }
72
+ else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
73
+ const nameWithoutExt = path.basename(entry.name, ".ts");
74
+ const pathSegments = [...relativePrefix, nameWithoutExt];
75
+ const routePath = "/" + pathSegments.map((s) => s.toLowerCase()).join("/");
76
+ result.push({ relativePath: routePath, fullPath: relativeFromBase });
77
+ }
78
+ }
79
+ }
80
+ /**
81
+ * Lee la estructura de la carpeta de endpoints del proyecto y devuelve
82
+ * la lista de endpoints con su path HTTP y clave para la clase Paths.
83
+ *
84
+ * Reglas:
85
+ * - Carpetas cuyo nombre empieza con _ no forman parte del path (ej: _invisible_path).
86
+ * - El path es: /carpeta/subcarpeta/nombrearchivo en minúsculas.
87
+ */
88
+ function discoverEndpoints(projectRoot, endpointsDir) {
89
+ const absoluteDir = path.resolve(projectRoot, endpointsDir);
90
+ const collected = [];
91
+ walkDir(absoluteDir, absoluteDir, [], collected);
92
+ const seen = new Set();
93
+ return collected.map(({ relativePath, fullPath }) => {
94
+ const key = pathToCamelCase(relativePath);
95
+ const uniqueKey = key || "index";
96
+ let finalKey = uniqueKey;
97
+ let n = 1;
98
+ while (seen.has(finalKey)) {
99
+ finalKey = uniqueKey + n++;
100
+ }
101
+ seen.add(finalKey);
102
+ const absolutePath = path.resolve(absoluteDir, fullPath);
103
+ return {
104
+ path: relativePath,
105
+ key: finalKey,
106
+ filePath: fullPath,
107
+ absolutePath,
108
+ };
109
+ });
110
+ }
111
+ /**
112
+ * Devuelve un objeto con las claves en camelCase y los paths como valores.
113
+ * Ej: { health: "/health", greetingHello: "/greeting/hello" }
114
+ */
115
+ function discoverPathsMap(projectRoot, endpointsDir) {
116
+ const endpoints = discoverEndpoints(projectRoot, endpointsDir);
117
+ const map = {};
118
+ for (const ep of endpoints) {
119
+ map[ep.key] = ep.path;
120
+ }
121
+ return map;
122
+ }
123
+ /**
124
+ * Carga dinámicamente un módulo y busca la clase Endpoint exportada.
125
+ * Busca tanto exportaciones por defecto como exportaciones nombradas.
126
+ * Intenta cargar primero archivos .js compilados, luego .ts si están disponibles.
127
+ */
128
+ async function loadEndpointClass(filePath, projectRoot, endpointsDir) {
129
+ // Construir rutas posibles:
130
+ // 1. Archivo .js compilado en la misma ubicación
131
+ const jsPath = filePath.replace(/\.ts$/, ".js");
132
+ // 2. Archivo .js en dist/ manteniendo la estructura relativa
133
+ const relativeFromEndpoints = path.relative(path.resolve(projectRoot, endpointsDir), filePath);
134
+ const distPath = path.resolve(projectRoot, "dist", endpointsDir.replace(/^src\//, ""), relativeFromEndpoints.replace(/\.ts$/, ".js"));
135
+ const pathsToTry = [jsPath, distPath, filePath];
136
+ for (const tryPath of pathsToTry) {
137
+ if (!fs.existsSync(tryPath) && tryPath.endsWith(".js")) {
138
+ continue; // Saltar .js si no existe
139
+ }
140
+ try {
141
+ // Convertir ruta absoluta a URL para import dinámico
142
+ const fileUrl = (0, url_1.pathToFileURL)(tryPath).href;
143
+ // Intentar importar el módulo
144
+ const module = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
145
+ // Buscar la clase Endpoint en las exportaciones
146
+ // 1. Primero buscar exportación por defecto
147
+ if (module.default) {
148
+ const DefaultClass = module.default;
149
+ if (typeof DefaultClass === "function" && DefaultClass.prototype) {
150
+ return DefaultClass;
151
+ }
152
+ }
153
+ // 2. Buscar en exportaciones nombradas (buscar clases que tengan "Endpoint" en el nombre)
154
+ for (const [key, value] of Object.entries(module)) {
155
+ if (typeof value === "function" &&
156
+ value.prototype &&
157
+ (key.includes("Endpoint") || key.match(/^[A-Z]/))) {
158
+ return value;
159
+ }
160
+ }
161
+ // 3. Si no encontramos nada específico, buscar cualquier clase exportada
162
+ for (const [key, value] of Object.entries(module)) {
163
+ if (typeof value === "function" && value.prototype) {
164
+ // Verificar que no sea una función helper (empieza con minúscula generalmente)
165
+ if (key.charAt(0) === key.charAt(0).toUpperCase()) {
166
+ return value;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ catch (error) {
172
+ // Continuar al siguiente path si este falla
173
+ continue;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ /**
179
+ * Carga todas las instancias de Endpoint desde los archivos descubiertos.
180
+ * Retorna un mapa de clave (camelCase) -> instancia de Endpoint.
181
+ */
182
+ async function loadEndpointInstances(endpoints, projectRoot, endpointsDir) {
183
+ const instances = {};
184
+ for (const endpoint of endpoints) {
185
+ const EndpointClass = await loadEndpointClass(endpoint.absolutePath, projectRoot, endpointsDir);
186
+ if (EndpointClass) {
187
+ try {
188
+ // Crear instancia de la clase (sin argumentos por ahora)
189
+ const instance = new EndpointClass();
190
+ instances[endpoint.key] = instance;
191
+ }
192
+ catch (error) {
193
+ console.error(`Error al crear instancia de ${endpoint.key} desde ${endpoint.filePath}:`, error);
194
+ }
195
+ }
196
+ else {
197
+ console.warn(`No se encontró clase Endpoint exportada en ${endpoint.filePath}. ` +
198
+ `Asegúrate de que el archivo exporte una clase que extienda Endpoint ` +
199
+ `o que los archivos estén compilados a JavaScript en dist/`);
200
+ }
201
+ }
202
+ return instances;
203
+ }
@@ -0,0 +1,7 @@
1
+ import "dotenv/config";
2
+ export default class PrismaClient {
3
+ private prisma;
4
+ getModel(entityName: string): any;
5
+ /** Para consultas que comparan columnas u otras condiciones no soportadas en findMany */
6
+ queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: any[]): Promise<T>;
7
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("dotenv/config");
4
+ const adapter_pg_1 = require("@prisma/adapter-pg");
5
+ const client_1 = require("@prisma-client/client");
6
+ if (!process.env.DATABASE_URL) {
7
+ throw { message: 'DATABASE_URL no está configurada' };
8
+ }
9
+ const adapter = new adapter_pg_1.PrismaPg({
10
+ connectionString: process.env.DATABASE_URL
11
+ });
12
+ class PrismaClient {
13
+ prisma = new client_1.PrismaClient({ adapter });
14
+ getModel(entityName) {
15
+ return this.prisma[entityName.toLowerCase()];
16
+ }
17
+ /** Para consultas que comparan columnas u otras condiciones no soportadas en findMany */
18
+ async queryRaw(strings, ...values) {
19
+ return this.prisma.$queryRaw(strings, ...values);
20
+ }
21
+ }
22
+ exports.default = PrismaClient;
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const _all_1 = require("@all");
4
+ class HealthEndpoint extends _all_1.Endpoint {
5
+ async GET(_ctx) {
6
+ return { success: true, message: "Health!" };
7
+ }
8
+ }
9
+ const app = new _all_1.NanoApp({
10
+ endpoints: [
11
+ new HealthEndpoint("/service/health"),
12
+ new HealthEndpoint("/:entity{/:id}"),
13
+ ]
14
+ });
15
+ app.start();
@@ -0,0 +1,5 @@
1
+ import { Handler } from "@all";
2
+ export type NanoAppConfig = {
3
+ handlers: Handler[];
4
+ port?: number;
5
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@nano-app/backend",
3
+ "version": "0.1.0",
4
+ "description": "Backend for nano-app architecture",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "bin"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "architecture",
17
+ "typescript",
18
+ "framework",
19
+ "core"
20
+ ],
21
+ "author": "Cristian Serrano",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@nano-app/core": "^0.1.0",
25
+ "@prisma/adapter-pg": "^7.4.0",
26
+ "@prisma/client": "^7.4.0",
27
+ "express": "^5.2.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/dotenv": "^8.2.3",
31
+ "@types/express": "^5.0.0",
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.3.0"
34
+ }
35
+ }