@hemia/core 0.0.4 → 0.0.6

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.
@@ -1,9 +1,10 @@
1
1
  import 'reflect-metadata';
2
2
  import express, { Router } from 'express';
3
- import { METADATA_KEYS, ParamType, isRedirectResponse, ApiResponse } from '@hemia/common';
3
+ import { METADATA_KEYS, ParamType, ApiResponse, isRedirectResponse, ControllerRegistry } from '@hemia/common';
4
4
  import { TRACE_METADATA_KEY } from '@hemia/trace-manager';
5
5
  import { traceMiddleware } from '@hemia/app-context';
6
6
  import { plainToInstance } from 'class-transformer';
7
+ import multer from 'multer';
7
8
  import { injectable, inject } from 'inversify';
8
9
  import { AUTH_SERVICE_ID, AuthService } from '@hemia/auth-sdk';
9
10
 
@@ -80,7 +81,7 @@ class ResponseSerializer {
80
81
  * @param container Instancia del Container de Inversify
81
82
  * @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
82
83
  */
83
- async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
84
+ async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback, multerOptions) {
84
85
  for (const identifier of controllerIdentifiers) {
85
86
  const instance = await container.getAsync(identifier);
86
87
  const prototype = Object.getPrototypeOf(instance);
@@ -102,6 +103,14 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
102
103
  // Leer metadata de headers y redirect
103
104
  const headersMetadata = Reflect.getMetadata(METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
104
105
  const redirectMetadata = Reflect.getMetadata(METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
106
+ const needsFile = paramsMetadata.some(p => p.type === ParamType.FILE);
107
+ const needsFiles = paramsMetadata.some(p => p.type === ParamType.FILES);
108
+ if (needsFiles) {
109
+ handlers.push(multer(multerOptions).array('files'));
110
+ }
111
+ else if (needsFile) {
112
+ handlers.push(multer(multerOptions).single('file'));
113
+ }
105
114
  const mainHandler = async (req, res, next) => {
106
115
  try {
107
116
  const args = [];
@@ -138,11 +147,59 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
138
147
  case ParamType.SESSION:
139
148
  args[param.index] = req.session;
140
149
  break;
150
+ case ParamType.COOKIES:
151
+ args[param.index] = param.data ? req.cookies?.[param.data] : req.cookies;
152
+ break;
153
+ case ParamType.FILE:
154
+ args[param.index] = req.file;
155
+ break;
156
+ case ParamType.FILES:
157
+ args[param.index] = req.files;
158
+ break;
159
+ case ParamType.USER:
160
+ args[param.index] = req.user;
161
+ break;
162
+ case ParamType.IP:
163
+ args[param.index] = req.ip;
164
+ break;
165
+ case ParamType.LOCALE:
166
+ args[param.index] = req.acceptsLanguages()[0];
167
+ break;
168
+ case ParamType.HOST:
169
+ args[param.index] = req.hostname;
170
+ break;
171
+ case ParamType.PERMISSIONS:
172
+ args[param.index] = req.permissions;
173
+ break;
174
+ case ParamType.CONTEXT:
175
+ args[param.index] = req.context;
176
+ break;
141
177
  default: args[param.index] = undefined;
142
178
  }
143
179
  });
144
180
  }
181
+ // CUSTOM PARAMETERS
182
+ const customs = Reflect.getMetadata(METADATA_KEYS.CUSTOMS, instance, route.methodName) || [];
183
+ for (const { index, key } of customs) {
184
+ args[index] = req.customData?.[key];
185
+ }
186
+ // TRANSFORMACIÓN DE PARÁMETROS
187
+ const transformers = Reflect.getMetadata(METADATA_KEYS.TRANSFORMERS, instance, route.methodName) || [];
188
+ for (const { index, transformer } of transformers) {
189
+ args[index] = transformer(args[index]);
190
+ }
191
+ // VALIDACIÓN DE PARÁMETROS
192
+ const validators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, instance, route.methodName) || [];
193
+ for (const { index, validator } of validators) {
194
+ const value = args[index];
195
+ const result = validator(value);
196
+ if (result === false) {
197
+ return res.status(400).json(ApiResponse.error('Validation failed', undefined, 400));
198
+ }
199
+ }
200
+ // EJECUCIÓN DEL CONTROLLER
145
201
  const result = await instance[route.methodName](...args);
202
+ // SERIALIZACIÓN DE RESULTADO
146
203
  const serializeDto = Reflect.getMetadata(METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(METADATA_KEYS.SERIALIZE, ControllerClass);
147
204
  let finalResult = result;
148
205
  if (serializeDto) {
@@ -262,10 +319,10 @@ class HemiaFactory {
262
319
  * @param controllers Lista de Clases de Controladores a registrar.
263
320
  * @param options Opciones de configuración.
264
321
  */
265
- static async create(container, controllers, options = {}) {
322
+ static async create(container, options = {}, controllers) {
266
323
  const app = express();
267
- app.use(express.json());
268
- app.use(express.urlencoded({ extended: true }));
324
+ app.use(express.json(options.jsonOptions));
325
+ app.use(express.urlencoded({ extended: true, ...options.urlencodedOptions }));
269
326
  if (options.middlewares && Array.isArray(options.middlewares)) {
270
327
  options.middlewares.forEach(middleware => {
271
328
  app.use(middleware);
@@ -291,14 +348,24 @@ class HemiaFactory {
291
348
  if (!container.isBound(Reflector)) {
292
349
  container.bind(Reflector).toSelf().inSingletonScope();
293
350
  }
294
- controllers.forEach(controllerClass => {
351
+ const controllerList = controllers && controllers.length > 0
352
+ ? controllers
353
+ : ControllerRegistry.getAll();
354
+ controllerList.forEach(controllerClass => {
295
355
  if (!container.isBound(controllerClass)) {
296
356
  container.bind(controllerClass).toSelf();
297
357
  }
298
358
  });
299
- await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
359
+ await registerRoutes(app, container, controllerList, options.onTraceFinish || (() => { }), options.multerOptions);
360
+ app.use((err, req, res, next) => {
361
+ console.error('[Hemia] Unhandled error:', err);
362
+ res.status(err.status || 500).json({
363
+ error: err.message || 'Internal Server Error',
364
+ details: err.details || undefined,
365
+ });
366
+ });
300
367
  if (options.logger !== false) {
301
- console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
368
+ console.log(`[Hemia] Application initialized with ${controllerList.length} controllers.`);
302
369
  }
303
370
  return app;
304
371
  }
@@ -6,6 +6,7 @@ var common = require('@hemia/common');
6
6
  var traceManager = require('@hemia/trace-manager');
7
7
  var appContext = require('@hemia/app-context');
8
8
  var classTransformer = require('class-transformer');
9
+ var multer = require('multer');
9
10
  var inversify = require('inversify');
10
11
  var authSdk = require('@hemia/auth-sdk');
11
12
 
@@ -82,7 +83,7 @@ class ResponseSerializer {
82
83
  * @param container Instancia del Container de Inversify
83
84
  * @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
84
85
  */
85
- async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
86
+ async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback, multerOptions) {
86
87
  for (const identifier of controllerIdentifiers) {
87
88
  const instance = await container.getAsync(identifier);
88
89
  const prototype = Object.getPrototypeOf(instance);
@@ -104,6 +105,14 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
104
105
  // Leer metadata de headers y redirect
105
106
  const headersMetadata = Reflect.getMetadata(common.METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
106
107
  const redirectMetadata = Reflect.getMetadata(common.METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
108
+ const needsFile = paramsMetadata.some(p => p.type === common.ParamType.FILE);
109
+ const needsFiles = paramsMetadata.some(p => p.type === common.ParamType.FILES);
110
+ if (needsFiles) {
111
+ handlers.push(multer(multerOptions).array('files'));
112
+ }
113
+ else if (needsFile) {
114
+ handlers.push(multer(multerOptions).single('file'));
115
+ }
107
116
  const mainHandler = async (req, res, next) => {
108
117
  try {
109
118
  const args = [];
@@ -140,11 +149,59 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
140
149
  case common.ParamType.SESSION:
141
150
  args[param.index] = req.session;
142
151
  break;
152
+ case common.ParamType.COOKIES:
153
+ args[param.index] = param.data ? req.cookies?.[param.data] : req.cookies;
154
+ break;
155
+ case common.ParamType.FILE:
156
+ args[param.index] = req.file;
157
+ break;
158
+ case common.ParamType.FILES:
159
+ args[param.index] = req.files;
160
+ break;
161
+ case common.ParamType.USER:
162
+ args[param.index] = req.user;
163
+ break;
164
+ case common.ParamType.IP:
165
+ args[param.index] = req.ip;
166
+ break;
167
+ case common.ParamType.LOCALE:
168
+ args[param.index] = req.acceptsLanguages()[0];
169
+ break;
170
+ case common.ParamType.HOST:
171
+ args[param.index] = req.hostname;
172
+ break;
173
+ case common.ParamType.PERMISSIONS:
174
+ args[param.index] = req.permissions;
175
+ break;
176
+ case common.ParamType.CONTEXT:
177
+ args[param.index] = req.context;
178
+ break;
143
179
  default: args[param.index] = undefined;
144
180
  }
145
181
  });
146
182
  }
183
+ // CUSTOM PARAMETERS
184
+ const customs = Reflect.getMetadata(common.METADATA_KEYS.CUSTOMS, instance, route.methodName) || [];
185
+ for (const { index, key } of customs) {
186
+ args[index] = req.customData?.[key];
187
+ }
188
+ // TRANSFORMACIÓN DE PARÁMETROS
189
+ const transformers = Reflect.getMetadata(common.METADATA_KEYS.TRANSFORMERS, instance, route.methodName) || [];
190
+ for (const { index, transformer } of transformers) {
191
+ args[index] = transformer(args[index]);
192
+ }
193
+ // VALIDACIÓN DE PARÁMETROS
194
+ const validators = Reflect.getMetadata(common.METADATA_KEYS.VALIDATORS, instance, route.methodName) || [];
195
+ for (const { index, validator } of validators) {
196
+ const value = args[index];
197
+ const result = validator(value);
198
+ if (result === false) {
199
+ return res.status(400).json(common.ApiResponse.error('Validation failed', undefined, 400));
200
+ }
201
+ }
202
+ // EJECUCIÓN DEL CONTROLLER
147
203
  const result = await instance[route.methodName](...args);
204
+ // SERIALIZACIÓN DE RESULTADO
148
205
  const serializeDto = Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, ControllerClass);
149
206
  let finalResult = result;
150
207
  if (serializeDto) {
@@ -264,10 +321,10 @@ class HemiaFactory {
264
321
  * @param controllers Lista de Clases de Controladores a registrar.
265
322
  * @param options Opciones de configuración.
266
323
  */
267
- static async create(container, controllers, options = {}) {
324
+ static async create(container, options = {}, controllers) {
268
325
  const app = express();
269
- app.use(express.json());
270
- app.use(express.urlencoded({ extended: true }));
326
+ app.use(express.json(options.jsonOptions));
327
+ app.use(express.urlencoded({ extended: true, ...options.urlencodedOptions }));
271
328
  if (options.middlewares && Array.isArray(options.middlewares)) {
272
329
  options.middlewares.forEach(middleware => {
273
330
  app.use(middleware);
@@ -293,14 +350,24 @@ class HemiaFactory {
293
350
  if (!container.isBound(exports.Reflector)) {
294
351
  container.bind(exports.Reflector).toSelf().inSingletonScope();
295
352
  }
296
- controllers.forEach(controllerClass => {
353
+ const controllerList = controllers && controllers.length > 0
354
+ ? controllers
355
+ : common.ControllerRegistry.getAll();
356
+ controllerList.forEach(controllerClass => {
297
357
  if (!container.isBound(controllerClass)) {
298
358
  container.bind(controllerClass).toSelf();
299
359
  }
300
360
  });
301
- await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
361
+ await registerRoutes(app, container, controllerList, options.onTraceFinish || (() => { }), options.multerOptions);
362
+ app.use((err, req, res, next) => {
363
+ console.error('[Hemia] Unhandled error:', err);
364
+ res.status(err.status || 500).json({
365
+ error: err.message || 'Internal Server Error',
366
+ details: err.details || undefined,
367
+ });
368
+ });
302
369
  if (options.logger !== false) {
303
- console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
370
+ console.log(`[Hemia] Application initialized with ${controllerList.length} controllers.`);
304
371
  }
305
372
  return app;
306
373
  }
@@ -7,6 +7,9 @@ export interface HemiaFactoryOptions {
7
7
  logger?: boolean;
8
8
  corsHeaders?: Record<string, string>;
9
9
  middlewares?: any[];
10
+ multerOptions?: any;
11
+ jsonOptions?: any;
12
+ urlencodedOptions?: any;
10
13
  }
11
14
  export declare class HemiaFactory {
12
15
  /**
@@ -15,5 +18,5 @@ export declare class HemiaFactory {
15
18
  * @param controllers Lista de Clases de Controladores a registrar.
16
19
  * @param options Opciones de configuración.
17
20
  */
18
- static create(container: Container, controllers: Type<any>[], options?: HemiaFactoryOptions): Promise<Express>;
21
+ static create(container: Container, options?: HemiaFactoryOptions, controllers?: Type<any>[]): Promise<Express>;
19
22
  }
@@ -7,4 +7,4 @@ export type TraceFinishCallback = (payloads: any, context: any, res: Response) =
7
7
  * @param container Instancia del Container de Inversify
8
8
  * @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
9
9
  */
10
- export declare function registerRoutes(app: Express, container: Container, controllerIdentifiers: any[], onTraceFinishCallback: TraceFinishCallback): Promise<void>;
10
+ export declare function registerRoutes(app: Express, container: Container, controllerIdentifiers: any[], onTraceFinishCallback: TraceFinishCallback, multerOptions?: any): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hemia/core",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Core utilities for Hemia projects",
5
5
  "main": "dist/hemia-core.js",
6
6
  "module": "dist/hemia-core.esm.js",
@@ -17,7 +17,7 @@
17
17
  "@rollup/plugin-commonjs": "^26.0.1",
18
18
  "@rollup/plugin-json": "^6.1.0",
19
19
  "@rollup/plugin-node-resolve": "^15.2.3",
20
- "@hemia/common": "^0.0.5",
20
+ "@hemia/common": "^0.0.8",
21
21
  "@hemia/app-context": "^0.0.6",
22
22
  "@hemia/trace-manager": "^0.0.9",
23
23
  "@hemia/auth-sdk": "^0.0.9",
@@ -27,6 +27,7 @@
27
27
  "@types/jest": "^29.5.14",
28
28
  "@types/node": "^22.3.0",
29
29
  "@typescript-eslint/eslint-plugin": "^8.5.0",
30
+ "@types/multer": "^2.0.0",
30
31
  "class-transformer": "^0.5.1",
31
32
  "events": "^3.3.0",
32
33
  "jest": "^29.7.0",
@@ -35,7 +36,8 @@
35
36
  "rollup-plugin-typescript2": "^0.36.0",
36
37
  "ts-jest": "^29.2.5",
37
38
  "ts-node": "^8.9.0",
38
- "typescript": "^5.5.4"
39
+ "typescript": "^5.5.4",
40
+ "multer": "^2.0.2"
39
41
  },
40
42
  "author": "",
41
43
  "license": "ISC",
@@ -46,6 +48,10 @@
46
48
  "class-transformer": "^0.5.1",
47
49
  "express": "^5.0.0",
48
50
  "inversify": "^7.0.0",
49
- "reflect-metadata": "^0.2.2"
51
+ "reflect-metadata": "^0.2.2",
52
+ "multer": "^2.0.0"
53
+ },
54
+ "dependencies": {
55
+
50
56
  }
51
57
  }