@hemia/core 0.0.1
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/README.md +36 -0
- package/dist/hemia-core.esm.js +319 -0
- package/dist/hemia-core.js +325 -0
- package/dist/types/context/execution-context.d.ts +14 -0
- package/dist/types/context/index.d.ts +1 -0
- package/dist/types/guards/auth.guard.d.ts +7 -0
- package/dist/types/guards/guards-consumer.d.ts +10 -0
- package/dist/types/guards/index.d.ts +2 -0
- package/dist/types/hemia-factory.d.ts +17 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/router/index.d.ts +2 -0
- package/dist/types/router/response-serializer.d.ts +4 -0
- package/dist/types/router/routes-registry.d.ts +10 -0
- package/dist/types/services/index.d.ts +1 -0
- package/dist/types/services/reflector.service.d.ts +19 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
# hemia-core
|
|
3
|
+
|
|
4
|
+
**@hemia/hemia-core**
|
|
5
|
+
Paquete generado con [Hemia CLI](https://www.npmjs.com/package/@hemia/cli)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Instalación
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @hemia/hemia-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🛠️ Scripts disponibles
|
|
18
|
+
|
|
19
|
+
| Script | Descripción |
|
|
20
|
+
|--------------|----------------------------------|
|
|
21
|
+
| `npm run build` | Compila el paquete con Rollup |
|
|
22
|
+
| `npm run test` | Ejecuta pruebas con Jest |
|
|
23
|
+
| `npm run clean` | Limpia la carpeta `dist/` |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🔍 Archivos generados
|
|
28
|
+
|
|
29
|
+
- `dist/hemia-core.js`
|
|
30
|
+
- `dist/hemia-core.esm.js`
|
|
31
|
+
- `dist/types/`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ✨ Generado con Hemia CLI
|
|
36
|
+
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import express, { Router } from 'express';
|
|
3
|
+
import { METADATA_KEYS, ParamType, isRedirectResponse, ApiResponse } from '@hemia/common';
|
|
4
|
+
import { TRACE_METADATA_KEY } from '@hemia/trace-manager';
|
|
5
|
+
import { traceMiddleware } from '@hemia/app-context';
|
|
6
|
+
import { plainToInstance } from 'class-transformer';
|
|
7
|
+
import { injectable } from 'inversify';
|
|
8
|
+
|
|
9
|
+
class GuardsConsumer {
|
|
10
|
+
/**
|
|
11
|
+
* Ejecuta una lista de guards secuencialmente.
|
|
12
|
+
* Lanza error si alguno falla.
|
|
13
|
+
*/
|
|
14
|
+
static async tryActivate(guards, context, container) {
|
|
15
|
+
if (!guards || guards.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
for (const GuardClass of guards) {
|
|
18
|
+
let guardInstance;
|
|
19
|
+
if (container.isBound(GuardClass)) {
|
|
20
|
+
guardInstance = await container.getAsync(GuardClass);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
container.bind(GuardClass).toSelf().inTransientScope();
|
|
24
|
+
guardInstance = await container.getAsync(GuardClass);
|
|
25
|
+
}
|
|
26
|
+
const canActivate = await guardInstance.canActivate(context);
|
|
27
|
+
if (!canActivate) {
|
|
28
|
+
const error = new Error('Forbidden resource');
|
|
29
|
+
error.statusCode = 403;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class HemiaExecutionContext {
|
|
37
|
+
constructor(args, constructorRef, handler, contextType = 'http') {
|
|
38
|
+
this.args = args;
|
|
39
|
+
this.constructorRef = constructorRef;
|
|
40
|
+
this.handler = handler;
|
|
41
|
+
this.contextType = contextType;
|
|
42
|
+
}
|
|
43
|
+
getType() {
|
|
44
|
+
return this.contextType;
|
|
45
|
+
}
|
|
46
|
+
getArgs() {
|
|
47
|
+
return this.args;
|
|
48
|
+
}
|
|
49
|
+
getArgByIndex(index) {
|
|
50
|
+
return this.args[index];
|
|
51
|
+
}
|
|
52
|
+
switchToHttp() {
|
|
53
|
+
return {
|
|
54
|
+
getRequest: () => this.args[0],
|
|
55
|
+
getResponse: () => this.args[1],
|
|
56
|
+
getNext: () => this.args[2],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
getClass() {
|
|
60
|
+
return this.constructorRef;
|
|
61
|
+
}
|
|
62
|
+
getHandler() {
|
|
63
|
+
return this.handler;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class ResponseSerializer {
|
|
68
|
+
static transform(entityOrArray, dto) {
|
|
69
|
+
return plainToInstance(dto, entityOrArray, {
|
|
70
|
+
excludeExtraneousValues: true,
|
|
71
|
+
enableImplicitConversion: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Registra los controladores resolviéndolos desde el contenedor de Inversify.
|
|
78
|
+
* @param app Instancia de Express
|
|
79
|
+
* @param container Instancia del Container de Inversify
|
|
80
|
+
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
81
|
+
*/
|
|
82
|
+
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
|
|
83
|
+
for (const identifier of controllerIdentifiers) {
|
|
84
|
+
const instance = await container.getAsync(identifier);
|
|
85
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
86
|
+
const ControllerClass = instance.constructor;
|
|
87
|
+
const basePath = Reflect.getMetadata(METADATA_KEYS.BASE_PATH, ControllerClass) || '/';
|
|
88
|
+
const routes = Reflect.getMetadata(METADATA_KEYS.ROUTES, ControllerClass) || [];
|
|
89
|
+
const expressRouter = Router();
|
|
90
|
+
routes.forEach((route) => {
|
|
91
|
+
const methodHandler = instance[route.methodName];
|
|
92
|
+
const traceOptions = Reflect.getMetadata(TRACE_METADATA_KEY, prototype, route.methodName);
|
|
93
|
+
const handlers = [];
|
|
94
|
+
if (traceOptions) {
|
|
95
|
+
handlers.push(traceMiddleware(traceOptions.name || ControllerClass.name || 'UnnamedController', traceOptions.kind || route.methodName, onTraceFinishCallback, undefined, traceOptions.tags));
|
|
96
|
+
}
|
|
97
|
+
const classGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, ControllerClass) || [];
|
|
98
|
+
const methodGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, methodHandler) || [];
|
|
99
|
+
const allGuards = [...classGuards, ...methodGuards];
|
|
100
|
+
const paramsMetadata = Reflect.getMetadata(METADATA_KEYS.PARAMS, instance, route.methodName) || [];
|
|
101
|
+
// Leer metadata de headers y redirect
|
|
102
|
+
const headersMetadata = Reflect.getMetadata(METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
|
|
103
|
+
const redirectMetadata = Reflect.getMetadata(METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
|
|
104
|
+
const mainHandler = async (req, res, next) => {
|
|
105
|
+
try {
|
|
106
|
+
const args = [];
|
|
107
|
+
const executionContext = new HemiaExecutionContext([req, res, next], ControllerClass, methodHandler);
|
|
108
|
+
await GuardsConsumer.tryActivate(allGuards, executionContext, container);
|
|
109
|
+
if (paramsMetadata.length === 0) {
|
|
110
|
+
args.push(req, res, next);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
paramsMetadata.sort((a, b) => a.index - b.index);
|
|
114
|
+
paramsMetadata.forEach((param) => {
|
|
115
|
+
switch (param.type) {
|
|
116
|
+
case ParamType.REQUEST:
|
|
117
|
+
args[param.index] = req;
|
|
118
|
+
break;
|
|
119
|
+
case ParamType.RESPONSE:
|
|
120
|
+
args[param.index] = res;
|
|
121
|
+
break;
|
|
122
|
+
case ParamType.NEXT:
|
|
123
|
+
args[param.index] = next;
|
|
124
|
+
break;
|
|
125
|
+
case ParamType.BODY:
|
|
126
|
+
args[param.index] = param.data ? req.body?.[param.data] : req.body;
|
|
127
|
+
break;
|
|
128
|
+
case ParamType.QUERY:
|
|
129
|
+
args[param.index] = param.data ? req.query?.[param.data] : req.query;
|
|
130
|
+
break;
|
|
131
|
+
case ParamType.PARAM:
|
|
132
|
+
args[param.index] = param.data ? req.params?.[param.data] : req.params;
|
|
133
|
+
break;
|
|
134
|
+
case ParamType.HEADERS:
|
|
135
|
+
args[param.index] = param.data ? req.headers?.[param.data.toLowerCase()] : req.headers;
|
|
136
|
+
break;
|
|
137
|
+
case ParamType.SESSION:
|
|
138
|
+
args[param.index] = req.session;
|
|
139
|
+
break;
|
|
140
|
+
default: args[param.index] = undefined;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const result = await instance[route.methodName](...args);
|
|
145
|
+
const serializeDto = Reflect.getMetadata(METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(METADATA_KEYS.SERIALIZE, ControllerClass);
|
|
146
|
+
let finalResult = result;
|
|
147
|
+
if (serializeDto) {
|
|
148
|
+
finalResult = ResponseSerializer.transform(result, serializeDto);
|
|
149
|
+
}
|
|
150
|
+
if (headersMetadata.length > 0) {
|
|
151
|
+
headersMetadata.forEach((header) => {
|
|
152
|
+
res.setHeader(header.name, header.value);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (isRedirectResponse(result)) {
|
|
156
|
+
const statusCode = result.statusCode || redirectMetadata?.statusCode || 302;
|
|
157
|
+
return res.redirect(statusCode, result.url);
|
|
158
|
+
}
|
|
159
|
+
if (redirectMetadata) {
|
|
160
|
+
return res.redirect(redirectMetadata.statusCode, redirectMetadata.url);
|
|
161
|
+
}
|
|
162
|
+
if (!res.headersSent) {
|
|
163
|
+
const response = ApiResponse.success(finalResult);
|
|
164
|
+
const { status, ...responseData } = response;
|
|
165
|
+
res.status(status).json(responseData);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error(`Error en ${route.method.toUpperCase()} ${basePath}${route.path}:`, error);
|
|
170
|
+
const status = error.statusCode || error.status || 500;
|
|
171
|
+
const message = error.message || 'Internal Server Error';
|
|
172
|
+
const errorResponse = ApiResponse.error(message, error.stack, status);
|
|
173
|
+
const { status: errStatus, ...errorData } = errorResponse;
|
|
174
|
+
res.status(errStatus).json(errorData);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
handlers.push(mainHandler);
|
|
178
|
+
const method = route.method;
|
|
179
|
+
expressRouter[method](route.path, ...handlers);
|
|
180
|
+
});
|
|
181
|
+
app.use(basePath, expressRouter);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/******************************************************************************
|
|
186
|
+
Copyright (c) Microsoft Corporation.
|
|
187
|
+
|
|
188
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
189
|
+
purpose with or without fee is hereby granted.
|
|
190
|
+
|
|
191
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
192
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
193
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
194
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
195
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
196
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
197
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
198
|
+
***************************************************************************** */
|
|
199
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
function __decorate(decorators, target, key, desc) {
|
|
203
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
204
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
205
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
206
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function __metadata(metadataKey, metadataValue) {
|
|
210
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
214
|
+
var e = new Error(message);
|
|
215
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let Reflector = class Reflector {
|
|
219
|
+
/**
|
|
220
|
+
* Obtiene metadata de una clase o un método
|
|
221
|
+
*/
|
|
222
|
+
get(metadataKey, target) {
|
|
223
|
+
return Reflect.getMetadata(metadataKey, target);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Obtiene metadata con prioridad: Método > Clase.
|
|
227
|
+
* Si el método tiene metadata, retorna esa. Si no, busca en la clase.
|
|
228
|
+
* @param metadataKey La clave de la metadata.
|
|
229
|
+
* @param targets Tupla con [Handler (Método), Class (Controlador)].
|
|
230
|
+
*/
|
|
231
|
+
getAllAndOverride(metadataKey, targets) {
|
|
232
|
+
const [methodHandler, controllerClass] = targets;
|
|
233
|
+
const methodMetadata = Reflect.getMetadata(metadataKey, methodHandler);
|
|
234
|
+
if (methodMetadata !== undefined) {
|
|
235
|
+
return methodMetadata;
|
|
236
|
+
}
|
|
237
|
+
return Reflect.getMetadata(metadataKey, controllerClass);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Obtiene metadata combinada (merge) de Método y Clase (opcional, útil para arrays acumulativos)
|
|
241
|
+
*/
|
|
242
|
+
getAllAndMerge(metadataKey, targets) {
|
|
243
|
+
const [methodHandler, controllerClass] = targets;
|
|
244
|
+
const methodMetadata = Reflect.getMetadata(metadataKey, methodHandler) || [];
|
|
245
|
+
const classMetadata = Reflect.getMetadata(metadataKey, controllerClass) || [];
|
|
246
|
+
return [...classMetadata, ...methodMetadata];
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
Reflector = __decorate([
|
|
250
|
+
injectable()
|
|
251
|
+
], Reflector);
|
|
252
|
+
|
|
253
|
+
class HemiaFactory {
|
|
254
|
+
/**
|
|
255
|
+
* Inicializa la aplicación Hemia conectando Express con Inversify.
|
|
256
|
+
* @param container El contenedor de Inversify con tus servicios ya bindeados.
|
|
257
|
+
* @param controllers Lista de Clases de Controladores a registrar.
|
|
258
|
+
* @param options Opciones de configuración.
|
|
259
|
+
*/
|
|
260
|
+
static async create(container, controllers, options = {}) {
|
|
261
|
+
const app = express();
|
|
262
|
+
app.use(express.json());
|
|
263
|
+
app.use(express.urlencoded({ extended: true }));
|
|
264
|
+
if (!container.isBound(Reflector)) {
|
|
265
|
+
container.bind(Reflector).toSelf().inSingletonScope();
|
|
266
|
+
}
|
|
267
|
+
controllers.forEach(controllerClass => {
|
|
268
|
+
if (!container.isBound(controllerClass)) {
|
|
269
|
+
container.bind(controllerClass).toSelf();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
|
|
273
|
+
if (options.logger !== false) {
|
|
274
|
+
console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
|
|
275
|
+
}
|
|
276
|
+
return app;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let AuthGuard = class AuthGuard {
|
|
281
|
+
constructor(reflector) {
|
|
282
|
+
this.reflector = reflector;
|
|
283
|
+
}
|
|
284
|
+
canActivate(context) {
|
|
285
|
+
const targets = [
|
|
286
|
+
context.getHandler(),
|
|
287
|
+
context.getClass()
|
|
288
|
+
];
|
|
289
|
+
const requiredRoles = this.reflector.getAllAndOverride(METADATA_KEYS.ROLES, targets);
|
|
290
|
+
const requiredPermissions = this.reflector.getAllAndOverride(METADATA_KEYS.PERMISSIONS, targets);
|
|
291
|
+
if (!requiredRoles && !requiredPermissions) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
const request = context.switchToHttp().getRequest();
|
|
295
|
+
const user = request.user;
|
|
296
|
+
if (!user || !user.roles) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
if (requiredRoles) {
|
|
300
|
+
const userRoleNames = user.roles.map((r) => r.name);
|
|
301
|
+
const hasRole = requiredRoles.some((role) => userRoleNames.includes(role));
|
|
302
|
+
if (!hasRole)
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (requiredPermissions) {
|
|
306
|
+
const userPermissions = user.roles.flatMap((r) => r.permissions || []);
|
|
307
|
+
const hasPermission = requiredPermissions.some((perm) => userPermissions.includes(perm));
|
|
308
|
+
if (!hasPermission)
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
AuthGuard = __decorate([
|
|
315
|
+
injectable(),
|
|
316
|
+
__metadata("design:paramtypes", [Reflector])
|
|
317
|
+
], AuthGuard);
|
|
318
|
+
|
|
319
|
+
export { AuthGuard, GuardsConsumer, HemiaExecutionContext, HemiaFactory, Reflector, ResponseSerializer, registerRoutes };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('reflect-metadata');
|
|
4
|
+
var express = require('express');
|
|
5
|
+
var common = require('@hemia/common');
|
|
6
|
+
var traceManager = require('@hemia/trace-manager');
|
|
7
|
+
var appContext = require('@hemia/app-context');
|
|
8
|
+
var classTransformer = require('class-transformer');
|
|
9
|
+
var inversify = require('inversify');
|
|
10
|
+
|
|
11
|
+
class GuardsConsumer {
|
|
12
|
+
/**
|
|
13
|
+
* Ejecuta una lista de guards secuencialmente.
|
|
14
|
+
* Lanza error si alguno falla.
|
|
15
|
+
*/
|
|
16
|
+
static async tryActivate(guards, context, container) {
|
|
17
|
+
if (!guards || guards.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
for (const GuardClass of guards) {
|
|
20
|
+
let guardInstance;
|
|
21
|
+
if (container.isBound(GuardClass)) {
|
|
22
|
+
guardInstance = await container.getAsync(GuardClass);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
container.bind(GuardClass).toSelf().inTransientScope();
|
|
26
|
+
guardInstance = await container.getAsync(GuardClass);
|
|
27
|
+
}
|
|
28
|
+
const canActivate = await guardInstance.canActivate(context);
|
|
29
|
+
if (!canActivate) {
|
|
30
|
+
const error = new Error('Forbidden resource');
|
|
31
|
+
error.statusCode = 403;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class HemiaExecutionContext {
|
|
39
|
+
constructor(args, constructorRef, handler, contextType = 'http') {
|
|
40
|
+
this.args = args;
|
|
41
|
+
this.constructorRef = constructorRef;
|
|
42
|
+
this.handler = handler;
|
|
43
|
+
this.contextType = contextType;
|
|
44
|
+
}
|
|
45
|
+
getType() {
|
|
46
|
+
return this.contextType;
|
|
47
|
+
}
|
|
48
|
+
getArgs() {
|
|
49
|
+
return this.args;
|
|
50
|
+
}
|
|
51
|
+
getArgByIndex(index) {
|
|
52
|
+
return this.args[index];
|
|
53
|
+
}
|
|
54
|
+
switchToHttp() {
|
|
55
|
+
return {
|
|
56
|
+
getRequest: () => this.args[0],
|
|
57
|
+
getResponse: () => this.args[1],
|
|
58
|
+
getNext: () => this.args[2],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
getClass() {
|
|
62
|
+
return this.constructorRef;
|
|
63
|
+
}
|
|
64
|
+
getHandler() {
|
|
65
|
+
return this.handler;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class ResponseSerializer {
|
|
70
|
+
static transform(entityOrArray, dto) {
|
|
71
|
+
return classTransformer.plainToInstance(dto, entityOrArray, {
|
|
72
|
+
excludeExtraneousValues: true,
|
|
73
|
+
enableImplicitConversion: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Registra los controladores resolviéndolos desde el contenedor de Inversify.
|
|
80
|
+
* @param app Instancia de Express
|
|
81
|
+
* @param container Instancia del Container de Inversify
|
|
82
|
+
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
83
|
+
*/
|
|
84
|
+
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
|
|
85
|
+
for (const identifier of controllerIdentifiers) {
|
|
86
|
+
const instance = await container.getAsync(identifier);
|
|
87
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
88
|
+
const ControllerClass = instance.constructor;
|
|
89
|
+
const basePath = Reflect.getMetadata(common.METADATA_KEYS.BASE_PATH, ControllerClass) || '/';
|
|
90
|
+
const routes = Reflect.getMetadata(common.METADATA_KEYS.ROUTES, ControllerClass) || [];
|
|
91
|
+
const expressRouter = express.Router();
|
|
92
|
+
routes.forEach((route) => {
|
|
93
|
+
const methodHandler = instance[route.methodName];
|
|
94
|
+
const traceOptions = Reflect.getMetadata(traceManager.TRACE_METADATA_KEY, prototype, route.methodName);
|
|
95
|
+
const handlers = [];
|
|
96
|
+
if (traceOptions) {
|
|
97
|
+
handlers.push(appContext.traceMiddleware(traceOptions.name || ControllerClass.name || 'UnnamedController', traceOptions.kind || route.methodName, onTraceFinishCallback, undefined, traceOptions.tags));
|
|
98
|
+
}
|
|
99
|
+
const classGuards = Reflect.getMetadata(common.METADATA_KEYS.GUARDS, ControllerClass) || [];
|
|
100
|
+
const methodGuards = Reflect.getMetadata(common.METADATA_KEYS.GUARDS, methodHandler) || [];
|
|
101
|
+
const allGuards = [...classGuards, ...methodGuards];
|
|
102
|
+
const paramsMetadata = Reflect.getMetadata(common.METADATA_KEYS.PARAMS, instance, route.methodName) || [];
|
|
103
|
+
// Leer metadata de headers y redirect
|
|
104
|
+
const headersMetadata = Reflect.getMetadata(common.METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
|
|
105
|
+
const redirectMetadata = Reflect.getMetadata(common.METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
|
|
106
|
+
const mainHandler = async (req, res, next) => {
|
|
107
|
+
try {
|
|
108
|
+
const args = [];
|
|
109
|
+
const executionContext = new HemiaExecutionContext([req, res, next], ControllerClass, methodHandler);
|
|
110
|
+
await GuardsConsumer.tryActivate(allGuards, executionContext, container);
|
|
111
|
+
if (paramsMetadata.length === 0) {
|
|
112
|
+
args.push(req, res, next);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
paramsMetadata.sort((a, b) => a.index - b.index);
|
|
116
|
+
paramsMetadata.forEach((param) => {
|
|
117
|
+
switch (param.type) {
|
|
118
|
+
case common.ParamType.REQUEST:
|
|
119
|
+
args[param.index] = req;
|
|
120
|
+
break;
|
|
121
|
+
case common.ParamType.RESPONSE:
|
|
122
|
+
args[param.index] = res;
|
|
123
|
+
break;
|
|
124
|
+
case common.ParamType.NEXT:
|
|
125
|
+
args[param.index] = next;
|
|
126
|
+
break;
|
|
127
|
+
case common.ParamType.BODY:
|
|
128
|
+
args[param.index] = param.data ? req.body?.[param.data] : req.body;
|
|
129
|
+
break;
|
|
130
|
+
case common.ParamType.QUERY:
|
|
131
|
+
args[param.index] = param.data ? req.query?.[param.data] : req.query;
|
|
132
|
+
break;
|
|
133
|
+
case common.ParamType.PARAM:
|
|
134
|
+
args[param.index] = param.data ? req.params?.[param.data] : req.params;
|
|
135
|
+
break;
|
|
136
|
+
case common.ParamType.HEADERS:
|
|
137
|
+
args[param.index] = param.data ? req.headers?.[param.data.toLowerCase()] : req.headers;
|
|
138
|
+
break;
|
|
139
|
+
case common.ParamType.SESSION:
|
|
140
|
+
args[param.index] = req.session;
|
|
141
|
+
break;
|
|
142
|
+
default: args[param.index] = undefined;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const result = await instance[route.methodName](...args);
|
|
147
|
+
const serializeDto = Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, ControllerClass);
|
|
148
|
+
let finalResult = result;
|
|
149
|
+
if (serializeDto) {
|
|
150
|
+
finalResult = ResponseSerializer.transform(result, serializeDto);
|
|
151
|
+
}
|
|
152
|
+
if (headersMetadata.length > 0) {
|
|
153
|
+
headersMetadata.forEach((header) => {
|
|
154
|
+
res.setHeader(header.name, header.value);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (common.isRedirectResponse(result)) {
|
|
158
|
+
const statusCode = result.statusCode || redirectMetadata?.statusCode || 302;
|
|
159
|
+
return res.redirect(statusCode, result.url);
|
|
160
|
+
}
|
|
161
|
+
if (redirectMetadata) {
|
|
162
|
+
return res.redirect(redirectMetadata.statusCode, redirectMetadata.url);
|
|
163
|
+
}
|
|
164
|
+
if (!res.headersSent) {
|
|
165
|
+
const response = common.ApiResponse.success(finalResult);
|
|
166
|
+
const { status, ...responseData } = response;
|
|
167
|
+
res.status(status).json(responseData);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error(`Error en ${route.method.toUpperCase()} ${basePath}${route.path}:`, error);
|
|
172
|
+
const status = error.statusCode || error.status || 500;
|
|
173
|
+
const message = error.message || 'Internal Server Error';
|
|
174
|
+
const errorResponse = common.ApiResponse.error(message, error.stack, status);
|
|
175
|
+
const { status: errStatus, ...errorData } = errorResponse;
|
|
176
|
+
res.status(errStatus).json(errorData);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
handlers.push(mainHandler);
|
|
180
|
+
const method = route.method;
|
|
181
|
+
expressRouter[method](route.path, ...handlers);
|
|
182
|
+
});
|
|
183
|
+
app.use(basePath, expressRouter);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/******************************************************************************
|
|
188
|
+
Copyright (c) Microsoft Corporation.
|
|
189
|
+
|
|
190
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
191
|
+
purpose with or without fee is hereby granted.
|
|
192
|
+
|
|
193
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
194
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
195
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
196
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
197
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
198
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
199
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
200
|
+
***************************************************************************** */
|
|
201
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
function __decorate(decorators, target, key, desc) {
|
|
205
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
206
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
207
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
208
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function __metadata(metadataKey, metadataValue) {
|
|
212
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
216
|
+
var e = new Error(message);
|
|
217
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
exports.Reflector = class Reflector {
|
|
221
|
+
/**
|
|
222
|
+
* Obtiene metadata de una clase o un método
|
|
223
|
+
*/
|
|
224
|
+
get(metadataKey, target) {
|
|
225
|
+
return Reflect.getMetadata(metadataKey, target);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Obtiene metadata con prioridad: Método > Clase.
|
|
229
|
+
* Si el método tiene metadata, retorna esa. Si no, busca en la clase.
|
|
230
|
+
* @param metadataKey La clave de la metadata.
|
|
231
|
+
* @param targets Tupla con [Handler (Método), Class (Controlador)].
|
|
232
|
+
*/
|
|
233
|
+
getAllAndOverride(metadataKey, targets) {
|
|
234
|
+
const [methodHandler, controllerClass] = targets;
|
|
235
|
+
const methodMetadata = Reflect.getMetadata(metadataKey, methodHandler);
|
|
236
|
+
if (methodMetadata !== undefined) {
|
|
237
|
+
return methodMetadata;
|
|
238
|
+
}
|
|
239
|
+
return Reflect.getMetadata(metadataKey, controllerClass);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Obtiene metadata combinada (merge) de Método y Clase (opcional, útil para arrays acumulativos)
|
|
243
|
+
*/
|
|
244
|
+
getAllAndMerge(metadataKey, targets) {
|
|
245
|
+
const [methodHandler, controllerClass] = targets;
|
|
246
|
+
const methodMetadata = Reflect.getMetadata(metadataKey, methodHandler) || [];
|
|
247
|
+
const classMetadata = Reflect.getMetadata(metadataKey, controllerClass) || [];
|
|
248
|
+
return [...classMetadata, ...methodMetadata];
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
exports.Reflector = __decorate([
|
|
252
|
+
inversify.injectable()
|
|
253
|
+
], exports.Reflector);
|
|
254
|
+
|
|
255
|
+
class HemiaFactory {
|
|
256
|
+
/**
|
|
257
|
+
* Inicializa la aplicación Hemia conectando Express con Inversify.
|
|
258
|
+
* @param container El contenedor de Inversify con tus servicios ya bindeados.
|
|
259
|
+
* @param controllers Lista de Clases de Controladores a registrar.
|
|
260
|
+
* @param options Opciones de configuración.
|
|
261
|
+
*/
|
|
262
|
+
static async create(container, controllers, options = {}) {
|
|
263
|
+
const app = express();
|
|
264
|
+
app.use(express.json());
|
|
265
|
+
app.use(express.urlencoded({ extended: true }));
|
|
266
|
+
if (!container.isBound(exports.Reflector)) {
|
|
267
|
+
container.bind(exports.Reflector).toSelf().inSingletonScope();
|
|
268
|
+
}
|
|
269
|
+
controllers.forEach(controllerClass => {
|
|
270
|
+
if (!container.isBound(controllerClass)) {
|
|
271
|
+
container.bind(controllerClass).toSelf();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
|
|
275
|
+
if (options.logger !== false) {
|
|
276
|
+
console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
|
|
277
|
+
}
|
|
278
|
+
return app;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
exports.AuthGuard = class AuthGuard {
|
|
283
|
+
constructor(reflector) {
|
|
284
|
+
this.reflector = reflector;
|
|
285
|
+
}
|
|
286
|
+
canActivate(context) {
|
|
287
|
+
const targets = [
|
|
288
|
+
context.getHandler(),
|
|
289
|
+
context.getClass()
|
|
290
|
+
];
|
|
291
|
+
const requiredRoles = this.reflector.getAllAndOverride(common.METADATA_KEYS.ROLES, targets);
|
|
292
|
+
const requiredPermissions = this.reflector.getAllAndOverride(common.METADATA_KEYS.PERMISSIONS, targets);
|
|
293
|
+
if (!requiredRoles && !requiredPermissions) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
const request = context.switchToHttp().getRequest();
|
|
297
|
+
const user = request.user;
|
|
298
|
+
if (!user || !user.roles) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
if (requiredRoles) {
|
|
302
|
+
const userRoleNames = user.roles.map((r) => r.name);
|
|
303
|
+
const hasRole = requiredRoles.some((role) => userRoleNames.includes(role));
|
|
304
|
+
if (!hasRole)
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
if (requiredPermissions) {
|
|
308
|
+
const userPermissions = user.roles.flatMap((r) => r.permissions || []);
|
|
309
|
+
const hasPermission = requiredPermissions.some((perm) => userPermissions.includes(perm));
|
|
310
|
+
if (!hasPermission)
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
exports.AuthGuard = __decorate([
|
|
317
|
+
inversify.injectable(),
|
|
318
|
+
__metadata("design:paramtypes", [exports.Reflector])
|
|
319
|
+
], exports.AuthGuard);
|
|
320
|
+
|
|
321
|
+
exports.GuardsConsumer = GuardsConsumer;
|
|
322
|
+
exports.HemiaExecutionContext = HemiaExecutionContext;
|
|
323
|
+
exports.HemiaFactory = HemiaFactory;
|
|
324
|
+
exports.ResponseSerializer = ResponseSerializer;
|
|
325
|
+
exports.registerRoutes = registerRoutes;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ExecutionContext, HttpArgumentsHost, Type } from "@hemia/common";
|
|
2
|
+
export declare class HemiaExecutionContext implements ExecutionContext {
|
|
3
|
+
private readonly args;
|
|
4
|
+
private readonly constructorRef;
|
|
5
|
+
private readonly handler;
|
|
6
|
+
private readonly contextType;
|
|
7
|
+
constructor(args: any[], constructorRef: Type<any>, handler: Function, contextType?: string);
|
|
8
|
+
getType<TContext extends string = string>(): TContext;
|
|
9
|
+
getArgs<T extends Array<any> = any[]>(): T;
|
|
10
|
+
getArgByIndex<T = any>(index: number): T;
|
|
11
|
+
switchToHttp(): HttpArgumentsHost;
|
|
12
|
+
getClass<T = any>(): Type<T>;
|
|
13
|
+
getHandler(): Function;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./execution-context";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@hemia/common';
|
|
2
|
+
import { Reflector } from '../services';
|
|
3
|
+
export declare class AuthGuard implements CanActivate {
|
|
4
|
+
private reflector;
|
|
5
|
+
constructor(reflector: Reflector);
|
|
6
|
+
canActivate(context: ExecutionContext): boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Container } from 'inversify';
|
|
2
|
+
import { CanActivate, Type } from '@hemia/common';
|
|
3
|
+
import { HemiaExecutionContext } from '../context/execution-context';
|
|
4
|
+
export declare class GuardsConsumer {
|
|
5
|
+
/**
|
|
6
|
+
* Ejecuta una lista de guards secuencialmente.
|
|
7
|
+
* Lanza error si alguno falla.
|
|
8
|
+
*/
|
|
9
|
+
static tryActivate(guards: Type<CanActivate>[], context: HemiaExecutionContext, container: Container): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Container } from "inversify";
|
|
2
|
+
import { TraceFinishCallback } from "./router";
|
|
3
|
+
import { Type } from "@hemia/common";
|
|
4
|
+
import { Express } from "express";
|
|
5
|
+
export interface HemiaFactoryOptions {
|
|
6
|
+
onTraceFinish?: TraceFinishCallback;
|
|
7
|
+
logger?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class HemiaFactory {
|
|
10
|
+
/**
|
|
11
|
+
* Inicializa la aplicación Hemia conectando Express con Inversify.
|
|
12
|
+
* @param container El contenedor de Inversify con tus servicios ya bindeados.
|
|
13
|
+
* @param controllers Lista de Clases de Controladores a registrar.
|
|
14
|
+
* @param options Opciones de configuración.
|
|
15
|
+
*/
|
|
16
|
+
static create(container: Container, controllers: Type<any>[], options?: HemiaFactoryOptions): Promise<Express>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Container } from 'inversify';
|
|
2
|
+
import { Express, Response } from 'express';
|
|
3
|
+
export type TraceFinishCallback = (payloads: any, context: any, res: Response) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Registra los controladores resolviéndolos desde el contenedor de Inversify.
|
|
6
|
+
* @param app Instancia de Express
|
|
7
|
+
* @param container Instancia del Container de Inversify
|
|
8
|
+
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerRoutes(app: Express, container: Container, controllerIdentifiers: any[], onTraceFinishCallback: TraceFinishCallback): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./reflector.service";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type } from '@hemia/common';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
export declare class Reflector {
|
|
4
|
+
/**
|
|
5
|
+
* Obtiene metadata de una clase o un método
|
|
6
|
+
*/
|
|
7
|
+
get<TResult = any, TKey = any>(metadataKey: TKey, target: Type<any> | Function): TResult;
|
|
8
|
+
/**
|
|
9
|
+
* Obtiene metadata con prioridad: Método > Clase.
|
|
10
|
+
* Si el método tiene metadata, retorna esa. Si no, busca en la clase.
|
|
11
|
+
* @param metadataKey La clave de la metadata.
|
|
12
|
+
* @param targets Tupla con [Handler (Método), Class (Controlador)].
|
|
13
|
+
*/
|
|
14
|
+
getAllAndOverride<TResult = any, TKey = any>(metadataKey: TKey, targets: [Function, Type<any>]): TResult | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Obtiene metadata combinada (merge) de Método y Clase (opcional, útil para arrays acumulativos)
|
|
17
|
+
*/
|
|
18
|
+
getAllAndMerge<TResult extends any[] = any[], TKey = any>(metadataKey: TKey, targets: [Function, Type<any>]): TResult;
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hemia/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core utilities for Hemia projects",
|
|
5
|
+
"main": "dist/hemia-core.js",
|
|
6
|
+
"module": "dist/hemia-core.esm.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"clean": "rimraf dist",
|
|
10
|
+
"tscBuild": "rollup -c",
|
|
11
|
+
"build": "npm run clean && npm run tscBuild",
|
|
12
|
+
"test": "jest --detectOpenHandles",
|
|
13
|
+
"test:coverage": "jest --coverage",
|
|
14
|
+
"test:watch": "jest --watch"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
18
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
19
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
20
|
+
"@hemia/common": "^0.0.4",
|
|
21
|
+
"@hemia/app-context": "^0.0.6",
|
|
22
|
+
"@hemia/trace-manager": "^0.0.9",
|
|
23
|
+
"@types/express": "^5.0.5",
|
|
24
|
+
"express": "^5.1.0",
|
|
25
|
+
"inversify": "^7.10.4",
|
|
26
|
+
"@types/jest": "^29.5.14",
|
|
27
|
+
"@types/node": "^22.3.0",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
|
29
|
+
"class-transformer": "^0.5.1",
|
|
30
|
+
"events": "^3.3.0",
|
|
31
|
+
"jest": "^29.7.0",
|
|
32
|
+
"rimraf": "^6.0.1",
|
|
33
|
+
"rollup": "^4.20.0",
|
|
34
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
35
|
+
"ts-jest": "^29.2.5",
|
|
36
|
+
"ts-node": "^8.9.0",
|
|
37
|
+
"typescript": "^5.5.4"
|
|
38
|
+
},
|
|
39
|
+
"author": "",
|
|
40
|
+
"license": "ISC",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"class-transformer": "^0.5.1",
|
|
46
|
+
"express": "^5.0.0",
|
|
47
|
+
"inversify": "^7.0.0",
|
|
48
|
+
"reflect-metadata": "^0.1.13"
|
|
49
|
+
}
|
|
50
|
+
}
|