@nano-app/backend 0.4.0 → 0.6.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.
@@ -0,0 +1,9 @@
1
+ import { Endpoint, type ApiResponse, type BeforeResult } from "../index";
2
+ import type { Request, Response } from 'express';
3
+ export declare class Entities extends Endpoint<BeforeResult> {
4
+ protected BEFORE({ params: { endpoint, id }, path }: Request): Promise<BeforeResult>;
5
+ protected GET(req: Request, _res: Response, { entity, id }: BeforeResult): Promise<ApiResponse>;
6
+ protected POST({ body }: Request, _res: Response, { entity }: BeforeResult): Promise<ApiResponse>;
7
+ protected PUT({ body }: Request, _res: Response, { entity, id }: BeforeResult): Promise<ApiResponse>;
8
+ protected DELETE(_req: Request, _res: Response, { entity, id }: BeforeResult): Promise<ApiResponse>;
9
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Entities = void 0;
4
+ const index_1 = require("../index");
5
+ class Entities extends index_1.Endpoint {
6
+ BEFORE({ params: { endpoint, id }, path }) {
7
+ // Valida que la entidad exista
8
+ const entity = this.prismaClient.getModel(endpoint);
9
+ if (!entity) {
10
+ throw { message: `Endpoint ${path} no encontrado` };
11
+ }
12
+ return Promise.resolve({ entity, id: Number(id) });
13
+ }
14
+ async GET(req, _res, { entity, id }) {
15
+ const query = req.query;
16
+ let result;
17
+ // Si se proporciona un ID, se busca un registro único
18
+ if (id) {
19
+ query.id = id.toString();
20
+ result = await entity.findUnique(index_1.QueryBuilder.build(query));
21
+ }
22
+ else {
23
+ result = await entity.findMany(index_1.QueryBuilder.build(query));
24
+ }
25
+ return { result };
26
+ }
27
+ async POST({ body }, _res, { entity }) {
28
+ const result = await entity.create({ data: body });
29
+ return { result, message: `Registro creado correctamente` };
30
+ }
31
+ async PUT({ body }, _res, { entity, id }) {
32
+ if (!id) {
33
+ throw { message: "ID es requerido para actualizar" };
34
+ }
35
+ const result = await entity.update({
36
+ where: { id },
37
+ data: body
38
+ });
39
+ return { result, message: `Registro actualizado correctamente` };
40
+ }
41
+ async DELETE(_req, _res, { entity, id }) {
42
+ let result;
43
+ if (!id) {
44
+ throw { message: "ID es requerido para eliminar" };
45
+ }
46
+ result = await entity.delete({ where: { id } });
47
+ return { result, message: `Registro eliminado correctamente` };
48
+ }
49
+ }
50
+ exports.Entities = Entities;
@@ -0,0 +1,33 @@
1
+ import type { QueryParams } from "../index";
2
+ export declare class QueryBuilder {
3
+ private static readonly OPERATORS;
4
+ private static parseValue;
5
+ /**
6
+ * Construye el objeto expand
7
+ */
8
+ private static buildExpand;
9
+ /**
10
+ * Parsea una expresión de filtro individual
11
+ */
12
+ private static parseExpression;
13
+ /**
14
+ * Construye el objeto filter
15
+ */
16
+ private static buildFilter;
17
+ /**
18
+ * Construye un objeto de ordenamiento
19
+ * Soporta propiedades encadenadas como "subscription.description"
20
+ */
21
+ private static buildOrderBy;
22
+ /**
23
+ * Construye el objeto query
24
+ */
25
+ static build(query: QueryParams): {
26
+ where: {
27
+ [x: string]: any;
28
+ };
29
+ include: Record<string, boolean> | undefined;
30
+ take: number;
31
+ orderBy: any;
32
+ };
33
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueryBuilder = void 0;
4
+ class QueryBuilder {
5
+ static OPERATORS = {
6
+ eq: (f, v) => ({ [f]: v }),
7
+ ne: (f, v) => ({ [f]: { not: v } }),
8
+ ge: (f, v) => ({ [f]: { gte: v } }),
9
+ le: (f, v) => ({ [f]: { lte: v } }),
10
+ contains: (f, v) => ({
11
+ [f]: { contains: v, mode: 'insensitive' }
12
+ }),
13
+ startswith: (f, v) => ({
14
+ [f]: { startsWith: v, mode: 'insensitive' }
15
+ }),
16
+ endswith: (f, v) => ({
17
+ [f]: { endsWith: v, mode: 'insensitive' }
18
+ }),
19
+ };
20
+ static parseValue(value) {
21
+ if (value === undefined)
22
+ return value;
23
+ if (!isNaN(Number(value)))
24
+ return Number(value);
25
+ if (value === 'true')
26
+ return true;
27
+ if (value === 'false')
28
+ return false;
29
+ return value;
30
+ }
31
+ /**
32
+ * Construye el objeto expand
33
+ */
34
+ static buildExpand(expand) {
35
+ return expand?.split(',').reduce((acc, path) => {
36
+ acc[path] = true;
37
+ return acc;
38
+ }, {});
39
+ }
40
+ /**
41
+ * Parsea una expresión de filtro individual
42
+ */
43
+ static parseExpression(expr) {
44
+ const [field, op, ...rest] = expr.trim().split(' ');
45
+ if (!this.OPERATORS[op])
46
+ throw { message: `Operador '${op}' no soportado` };
47
+ const value = this.parseValue(rest.join(' '));
48
+ return this.OPERATORS[op](field, value);
49
+ }
50
+ /**
51
+ * Construye el objeto filter
52
+ */
53
+ static buildFilter(filter) {
54
+ if (!filter)
55
+ return {};
56
+ if (filter.includes(' or ')) {
57
+ return {
58
+ OR: filter.split(' or ').map(this.parseExpression.bind(this))
59
+ };
60
+ }
61
+ if (filter.includes(' and ')) {
62
+ return {
63
+ AND: filter.split(' and ').map(this.parseExpression.bind(this))
64
+ };
65
+ }
66
+ return this.parseExpression.bind(this)(filter);
67
+ }
68
+ /**
69
+ * Construye un objeto de ordenamiento
70
+ * Soporta propiedades encadenadas como "subscription.description"
71
+ */
72
+ static buildOrderBy(orderby) {
73
+ if (!orderby)
74
+ return undefined;
75
+ // Obtiene el campo y la dirección
76
+ const [field, direction] = orderby.split(' ');
77
+ // Solo acepta asc o desc
78
+ if (direction !== 'asc' && direction !== 'desc')
79
+ return undefined;
80
+ // Construye el objeto de ordenamiento
81
+ const parts = field.split(".");
82
+ let result = {};
83
+ let current = result;
84
+ for (let i = 0; i < parts.length; i++) {
85
+ if (i === parts.length - 1) {
86
+ current[parts[i]] = direction;
87
+ }
88
+ else {
89
+ current[parts[i]] = {};
90
+ current = current[parts[i]];
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ /**
96
+ * Construye el objeto query
97
+ */
98
+ static build(query) {
99
+ return {
100
+ where: {
101
+ ...this.buildFilter(query.filter)
102
+ },
103
+ include: this.buildExpand(query.expand),
104
+ take: Number(query.top || 10) + Number(query.skip || 0),
105
+ orderBy: this.buildOrderBy(query.orderby),
106
+ };
107
+ }
108
+ }
109
+ exports.QueryBuilder = QueryBuilder;
@@ -0,0 +1,12 @@
1
+ import "dotenv/config";
2
+ declare const Prisma: any;
3
+ type GeneratedPrismaClient = typeof Prisma;
4
+ export type AppModels = Omit<GeneratedPrismaClient, `$${string}` | symbol>;
5
+ /** Cliente Prisma. getModel retorna el tipo correcto según el nombre del modelo. */
6
+ export declare class PrismaClient {
7
+ private prisma;
8
+ getModel<K extends keyof AppModels>(entityName: K): AppModels[K];
9
+ /** Para consultas que comparan columnas u otras condiciones no soportadas en findMany */
10
+ queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: any[]): Promise<T>;
11
+ }
12
+ export {};
@@ -0,0 +1,34 @@
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.PrismaClient = void 0;
7
+ require("dotenv/config");
8
+ const path_1 = __importDefault(require("path"));
9
+ const adapter_pg_1 = require("@prisma/adapter-pg");
10
+ // Crea el adaptador a la base de datos
11
+ if (!process.env.DATABASE_URL) {
12
+ throw { message: 'DATABASE_URL no está configurada' };
13
+ }
14
+ const adapter = new adapter_pg_1.PrismaPg({
15
+ connectionString: process.env.DATABASE_URL
16
+ });
17
+ // Carga el cliente generado por el binario
18
+ const clientPath = path_1.default.join(process.cwd(), "node_modules/.prisma/prisma-client/client");
19
+ const client = require(clientPath);
20
+ const Prisma = client.PrismaClient ?? client.default?.PrismaClient ?? client;
21
+ // Crea una instancia de Prisma
22
+ const prismaInstance = new Prisma({ adapter });
23
+ /** Cliente Prisma. getModel retorna el tipo correcto según el nombre del modelo. */
24
+ class PrismaClient {
25
+ prisma = prismaInstance;
26
+ getModel(entityName) {
27
+ return this.prisma[entityName];
28
+ }
29
+ /** Para consultas que comparan columnas u otras condiciones no soportadas en findMany */
30
+ async queryRaw(strings, ...values) {
31
+ return this.prisma.$queryRaw(strings, ...values);
32
+ }
33
+ }
34
+ exports.PrismaClient = PrismaClient;
@@ -0,0 +1,6 @@
1
+ export type ApiResponse<T = any> = {
2
+ success?: boolean;
3
+ result?: T;
4
+ message?: string;
5
+ warning?: boolean;
6
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import type { Endpoint } from "../http/Endpoint";
2
+ import type { ErrorRequestHandler, RequestHandler } from "express";
3
+ import type { Options } from "express-rate-limit";
4
+ export type NanoAppConfig = {
5
+ endpoints: Endpoint[];
6
+ enableEntities?: boolean;
7
+ errorMiddleware?: ErrorRequestHandler;
8
+ middlewares?: RequestHandler[];
9
+ port?: number;
10
+ rateLimit?: Partial<Options>;
11
+ expressConfig?: Partial<Express.Application>;
12
+ setters?: Record<string, any>;
13
+ morgan?: {
14
+ tokens?: Record<string, any>;
15
+ format?: string;
16
+ };
17
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
1
+ export type QueryParams = {
2
+ id?: string;
3
+ filter?: string;
4
+ expand?: string;
5
+ top?: string;
6
+ skip?: string;
7
+ orderby?: string;
8
+ };
9
+ export type BeforeResult = {
10
+ entity: any;
11
+ id: number;
12
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export declare class Formatter {
2
+ static price(value: number): string;
3
+ static logDate(date?: Date): string;
4
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Formatter = void 0;
4
+ class Formatter {
5
+ static price(value) {
6
+ return `$${new Intl.NumberFormat("de-DE", { maximumFractionDigits: 0 }).format(value || 0)}`;
7
+ }
8
+ static logDate(date = new Date()) {
9
+ return new Intl.DateTimeFormat("es-ES", {
10
+ year: "2-digit",
11
+ month: "2-digit",
12
+ day: "2-digit",
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ second: "2-digit",
16
+ }).format(date);
17
+ }
18
+ }
19
+ exports.Formatter = Formatter;
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@nano-app/backend",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Backend for nano-app architecture",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
- "*.js",
9
- "*.d.ts",
10
- "http"
8
+ "dist"
11
9
  ],
12
10
  "scripts": {
13
11
  "build": "tsc",