@hemia/core 0.0.4 → 0.0.5

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 } 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) {
@@ -264,8 +321,8 @@ class HemiaFactory {
264
321
  */
265
322
  static async create(container, controllers, options = {}) {
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);
@@ -296,7 +353,14 @@ class HemiaFactory {
296
353
  container.bind(controllerClass).toSelf();
297
354
  }
298
355
  });
299
- await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
356
+ await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }), options.multerOptions);
357
+ app.use((err, req, res, next) => {
358
+ console.error('[Hemia] Unhandled error:', err);
359
+ res.status(err.status || 500).json({
360
+ error: err.message || 'Internal Server Error',
361
+ details: err.details || undefined,
362
+ });
363
+ });
300
364
  if (options.logger !== false) {
301
365
  console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
302
366
  }
@@ -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) {
@@ -266,8 +323,8 @@ class HemiaFactory {
266
323
  */
267
324
  static async create(container, controllers, options = {}) {
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);
@@ -298,7 +355,14 @@ class HemiaFactory {
298
355
  container.bind(controllerClass).toSelf();
299
356
  }
300
357
  });
301
- await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
358
+ await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }), options.multerOptions);
359
+ app.use((err, req, res, next) => {
360
+ console.error('[Hemia] Unhandled error:', err);
361
+ res.status(err.status || 500).json({
362
+ error: err.message || 'Internal Server Error',
363
+ details: err.details || undefined,
364
+ });
365
+ });
302
366
  if (options.logger !== false) {
303
367
  console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
304
368
  }
@@ -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
  /**
@@ -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.5",
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.7",
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
  }