@zenofolio/hyper-decor 1.0.68 → 1.0.71
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/__internals/constants.d.ts +2 -2
- package/dist/__internals/constants.js +2 -2
- package/dist/__internals/creators/request.creator.d.ts +1 -1
- package/dist/__internals/creators/request.creator.js +3 -1
- package/dist/__internals/decorator-base.js +1 -1
- package/dist/__internals/helpers/prepare.helper.js +83 -51
- package/dist/decorators/File.d.ts +4 -32
- package/dist/decorators/File.js +62 -75
- package/dist/decorators/Http.d.ts +20 -13
- package/dist/decorators/Http.js +77 -31
- package/dist/decorators/Output.d.ts +9 -0
- package/dist/decorators/Output.js +18 -0
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +1 -1
- package/dist/decorators/types.d.ts +2 -0
- package/dist/exeptions/HyperException.d.ts +2 -1
- package/dist/exeptions/HyperException.js +2 -1
- package/dist/exeptions/HyperFileException.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/openapi/collectors/method.collector.js +25 -12
- package/dist/lib/openapi/collectors/param.collector.d.ts +2 -3
- package/dist/lib/openapi/collectors/param.collector.js +49 -12
- package/dist/lib/openapi/decorators/api-parameter.decorator.d.ts +2 -2
- package/dist/lib/openapi/decorators/api-response.decorator.d.ts +2 -2
- package/dist/lib/openapi/decorators/api-tag.decorator.d.ts +1 -1
- package/dist/lib/openapi/decorators/api-tag.decorator.js +3 -0
- package/dist/lib/openapi/helpers/parameter.helper.d.ts +2 -2
- package/dist/lib/openapi/helpers/response.helper.d.ts +2 -2
- package/dist/lib/openapi/types.d.ts +8 -8
- package/package.json +2 -2
|
@@ -14,8 +14,8 @@ export declare const KEY_PARAMS_MIDDLEWARES = "hyper:type:middlewares";
|
|
|
14
14
|
export declare const KEY_PARAMS_SCOPE = "hyper:type:scope";
|
|
15
15
|
export declare const KEY_PARAMS_ROLE = "hyper:type:role";
|
|
16
16
|
export declare const KEY_PARAMS_PASS = "hyper:type:pass";
|
|
17
|
-
export declare const
|
|
18
|
-
export type KeyParams = typeof KEY_PARAMS_APP | typeof KEY_PARAMS_CONTROLLER | typeof KEY_PARAMS_MODULE | typeof KEY_PARAMS_ROUTE | typeof KEY_PARAMS_PARAM | typeof KEY_PARAMS_MIDDLEWARES | typeof KEY_PARAMS_SCOPE | typeof KEY_PARAMS_ROLE | typeof KEY_PARAMS_PASS | typeof
|
|
17
|
+
export declare const KEY_OUTPUT_SCHEMA = "hyper:output:schema";
|
|
18
|
+
export type KeyParams = typeof KEY_PARAMS_APP | typeof KEY_PARAMS_CONTROLLER | typeof KEY_PARAMS_MODULE | typeof KEY_PARAMS_ROUTE | typeof KEY_PARAMS_PARAM | typeof KEY_PARAMS_MIDDLEWARES | typeof KEY_PARAMS_SCOPE | typeof KEY_PARAMS_ROLE | typeof KEY_PARAMS_PASS | typeof KEY_OUTPUT_SCHEMA;
|
|
19
19
|
export declare const KEY_STATE_UPDATED = "hyper:state:updated";
|
|
20
20
|
export declare const KEY_STATE_CREATED = "hyper:state:created";
|
|
21
21
|
export declare const KEY_STATE_PREPARED = "hyper:state:prepared";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FULL_ACCESS = exports.METADATA_STATE_KEYS = exports.METADATA_PARAMS_KEYS = exports.METADATA_METHOD_KEYS = exports.METADATA_STORE_KEYS = exports.METADATA_KEYS = exports.METADATA_HYPER_TYPE = exports.DESIGN_TYPE = exports.DESIGN_RETURNTYPE = exports.DESIGN_PARAMTYPES = exports.KEY_STATE_BY_PASS = exports.KEY_STATE_PREPARED = exports.KEY_STATE_CREATED = exports.KEY_STATE_UPDATED = exports.
|
|
3
|
+
exports.FULL_ACCESS = exports.METADATA_STATE_KEYS = exports.METADATA_PARAMS_KEYS = exports.METADATA_METHOD_KEYS = exports.METADATA_STORE_KEYS = exports.METADATA_KEYS = exports.METADATA_HYPER_TYPE = exports.DESIGN_TYPE = exports.DESIGN_RETURNTYPE = exports.DESIGN_PARAMTYPES = exports.KEY_STATE_BY_PASS = exports.KEY_STATE_PREPARED = exports.KEY_STATE_CREATED = exports.KEY_STATE_UPDATED = exports.KEY_OUTPUT_SCHEMA = exports.KEY_PARAMS_PASS = exports.KEY_PARAMS_ROLE = exports.KEY_PARAMS_SCOPE = exports.KEY_PARAMS_MIDDLEWARES = exports.KEY_PARAMS_PARAM = exports.KEY_PARAMS_ROUTE = exports.KEY_PARAMS_MODULE = exports.KEY_PARAMS_CONTROLLER = exports.KEY_PARAMS_APP = exports.KEY_TYPE_GUARD = exports.KEY_TYPE_SERVICE = exports.KEY_TYPE_ROUTE = exports.KEY_TYPE_MODULE = exports.KEY_TYPE_CONTROLLER = exports.KEY_TYPE_APP = void 0;
|
|
4
4
|
//////////////////////////////
|
|
5
5
|
/// Types constants
|
|
6
6
|
//////////////////////////////
|
|
@@ -22,7 +22,7 @@ exports.KEY_PARAMS_MIDDLEWARES = "hyper:type:middlewares";
|
|
|
22
22
|
exports.KEY_PARAMS_SCOPE = "hyper:type:scope";
|
|
23
23
|
exports.KEY_PARAMS_ROLE = "hyper:type:role";
|
|
24
24
|
exports.KEY_PARAMS_PASS = "hyper:type:pass";
|
|
25
|
-
exports.
|
|
25
|
+
exports.KEY_OUTPUT_SCHEMA = "hyper:output:schema";
|
|
26
26
|
//////////////////////////////
|
|
27
27
|
/// State constants
|
|
28
28
|
//////////////////////////////
|
|
@@ -8,4 +8,4 @@ import { ParameterResolver } from "../../decorators";
|
|
|
8
8
|
* @param {IParamsResolver} resolver - Resolver function to handle the parameter.
|
|
9
9
|
* @returns {ParameterDecorator} - The parameter decorator function.
|
|
10
10
|
*/
|
|
11
|
-
export default function createParamDecorator(key: keyof Request | "req" | "res", decoratorName: string, resolver: ParameterResolver): ParameterDecorator;
|
|
11
|
+
export default function createParamDecorator(key: keyof Request | "req" | "res", decoratorName: string, resolver: ParameterResolver, schema?: any, isWholeSource?: boolean): ParameterDecorator;
|
|
@@ -17,7 +17,7 @@ const function_util_1 = require("../utils/function.util");
|
|
|
17
17
|
* @param {IParamsResolver} resolver - Resolver function to handle the parameter.
|
|
18
18
|
* @returns {ParameterDecorator} - The parameter decorator function.
|
|
19
19
|
*/
|
|
20
|
-
function createParamDecorator(key, decoratorName, resolver) {
|
|
20
|
+
function createParamDecorator(key, decoratorName, resolver, schema, isWholeSource) {
|
|
21
21
|
const _key = key;
|
|
22
22
|
return (0, decorator_base_1.DecoratorHelper)({
|
|
23
23
|
type: constants_1.KEY_TYPE_CONTROLLER,
|
|
@@ -42,6 +42,8 @@ function createParamDecorator(key, decoratorName, resolver) {
|
|
|
42
42
|
key: _key,
|
|
43
43
|
method: propertyKey.toString(),
|
|
44
44
|
resolver,
|
|
45
|
+
schema,
|
|
46
|
+
isWholeSource,
|
|
45
47
|
});
|
|
46
48
|
// sort by index
|
|
47
49
|
saved.params[propertyKey].sort((a, b) => a.index - b.index);
|
|
@@ -61,7 +61,7 @@ function DecoratorHelper({ key, type, options, targetResolver, onDefineData }, .
|
|
|
61
61
|
const METADATA_CACHE = new WeakMap();
|
|
62
62
|
const defineDecorData = (key, options, target, property, descriptor) => {
|
|
63
63
|
let value = options;
|
|
64
|
-
if (typeof options === "function") {
|
|
64
|
+
if (typeof options === "function" && !(options.prototype && options.prototype.constructor === options)) {
|
|
65
65
|
const old = (0, exports.getDecorData)(key, target, property);
|
|
66
66
|
value = Object.assign(Object.assign({}, old), options(old, target, property, descriptor));
|
|
67
67
|
}
|
|
@@ -24,7 +24,6 @@ const middleware_transform_1 = __importDefault(require("../transform/middleware.
|
|
|
24
24
|
const tsyringe_1 = require("tsyringe");
|
|
25
25
|
const path_util_1 = require("../utils/path.util");
|
|
26
26
|
const imports_helper_1 = require("./imports.helper");
|
|
27
|
-
const constants_2 = require("../constants");
|
|
28
27
|
const message_bus_1 = require("../../common/message-bus");
|
|
29
28
|
const transport_1 = require("../../common/transport");
|
|
30
29
|
const transform_registry_1 = require("../transform/transform.registry");
|
|
@@ -243,11 +242,66 @@ function prepareTarget(_a) {
|
|
|
243
242
|
};
|
|
244
243
|
});
|
|
245
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Resolves method parameters and applies adaptive transformations.
|
|
247
|
+
* Optimized: Metadata is resolved once and passed here.
|
|
248
|
+
*/
|
|
249
|
+
function resolveMethodParams(req, res, params) {
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const len = params.length;
|
|
252
|
+
const args = new Array(len);
|
|
253
|
+
for (let i = 0; i < len; i++) {
|
|
254
|
+
const param = params[i];
|
|
255
|
+
let val = yield param.resolver(req, res);
|
|
256
|
+
if (param.schema) {
|
|
257
|
+
val = yield transform_registry_1.transformRegistry.resolve({
|
|
258
|
+
data: val,
|
|
259
|
+
schema: param.schema,
|
|
260
|
+
options: { isWholeSource: param.isWholeSource },
|
|
261
|
+
req,
|
|
262
|
+
res,
|
|
263
|
+
from: param.key,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
args[i] = val;
|
|
267
|
+
}
|
|
268
|
+
return args;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Handles output transformation and sends the response.
|
|
273
|
+
* Optimized: outputSchema is pre-resolved outside the request hotpath.
|
|
274
|
+
*/
|
|
275
|
+
function handleResponse(req, res, result, outputSchema) {
|
|
276
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
277
|
+
if (result === undefined || res.completed)
|
|
278
|
+
return;
|
|
279
|
+
if (outputSchema && outputSchema !== Object && outputSchema !== Promise) {
|
|
280
|
+
const transformed = yield transform_registry_1.transformRegistry.resolve({
|
|
281
|
+
data: result,
|
|
282
|
+
schema: outputSchema,
|
|
283
|
+
options: {},
|
|
284
|
+
req,
|
|
285
|
+
res,
|
|
286
|
+
from: "response",
|
|
287
|
+
});
|
|
288
|
+
if (transformed !== undefined && !res.completed) {
|
|
289
|
+
res.json(transformed);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!res.completed) {
|
|
294
|
+
if (typeof result === "object" && result !== null) {
|
|
295
|
+
res.json(result);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
res.send(result);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
246
303
|
/**
|
|
247
304
|
* Prepare the routes for the target class.
|
|
248
|
-
*
|
|
249
|
-
* @param param0
|
|
250
|
-
* @returns
|
|
251
305
|
*/
|
|
252
306
|
function prepareRoutes(_a) {
|
|
253
307
|
return __awaiter(this, arguments, void 0, function* ({ target, router, route, instance, namespace, log, }) {
|
|
@@ -256,63 +310,41 @@ function prepareRoutes(_a) {
|
|
|
256
310
|
const metadata = getData(handler);
|
|
257
311
|
const params = (_d = (_c = (_b = metadata.params) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c[propertyKey]) !== null && _d !== void 0 ? _d : [];
|
|
258
312
|
const $fn = Reflect.get(router, method);
|
|
259
|
-
const hasParams = params.length > 0;
|
|
260
313
|
if (!$fn)
|
|
261
314
|
return;
|
|
262
315
|
const middlewares = [...metadata.middlewares];
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
const from = transform.options.from || "body";
|
|
269
|
-
let data;
|
|
270
|
-
if (from === "body") {
|
|
271
|
-
data = req.body !== undefined ? req.body : yield req.json();
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
data = req[from];
|
|
275
|
-
}
|
|
276
|
-
// Resolve through the chain of responsibility
|
|
277
|
-
const transformed = yield transform_registry_1.transformRegistry.resolve({
|
|
278
|
-
data,
|
|
279
|
-
schema: transform.schema,
|
|
280
|
-
options: transform.options,
|
|
281
|
-
req,
|
|
282
|
-
res,
|
|
283
|
-
from
|
|
284
|
-
});
|
|
285
|
-
// Update the request object
|
|
286
|
-
req[from] = transformed;
|
|
287
|
-
next();
|
|
288
|
-
}
|
|
289
|
-
catch (err) {
|
|
290
|
-
next(err);
|
|
291
|
-
}
|
|
292
|
-
}));
|
|
293
|
-
}
|
|
316
|
+
const proto = target.prototype || target;
|
|
317
|
+
// Pre-resolve Output-Metadata
|
|
318
|
+
const outputSchema = Reflect.getMetadata(constants_1.KEY_OUTPUT_SCHEMA, proto, propertyKey) ||
|
|
319
|
+
Reflect.getMetadata(constants_1.DESIGN_RETURNTYPE, proto, propertyKey);
|
|
294
320
|
(0, role_transform_1.default)(metadata.roles, (middleware) => middlewares.push(middleware));
|
|
295
321
|
(0, scope_transfrom_1.default)(metadata.scopes, (middleware, scopes) => {
|
|
296
322
|
middlewares.push(middleware);
|
|
297
323
|
stores_1.ScopeStore.addAll(scopes);
|
|
298
324
|
});
|
|
299
|
-
log("routes", `${namespace}/${propertyKey} ${method.toUpperCase()} { ${path} }`);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
325
|
+
log("routes", `${namespace}/${propertyKey.toString()} ${method.toUpperCase()} { ${path} }`);
|
|
326
|
+
const routeHandler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
327
|
+
try {
|
|
328
|
+
const args = params.length > 0
|
|
329
|
+
? yield resolveMethodParams(req, res, params)
|
|
330
|
+
: [req, res];
|
|
331
|
+
const result = yield handler.apply(instance, args);
|
|
332
|
+
yield handleResponse(req, res, result, outputSchema);
|
|
304
333
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
args[i] = yield params[i].resolver(req, res);
|
|
334
|
+
catch (err) {
|
|
335
|
+
if (!res.completed) {
|
|
336
|
+
const error = err;
|
|
337
|
+
res.status(error.status || 500).json({
|
|
338
|
+
error: error.message || "Internal Server Error",
|
|
339
|
+
code: error.code,
|
|
340
|
+
});
|
|
313
341
|
}
|
|
314
|
-
|
|
315
|
-
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
if (method === "ws" && options) {
|
|
345
|
+
router.ws(path, options, handler.bind(instance));
|
|
346
|
+
return;
|
|
316
347
|
}
|
|
348
|
+
$fn.call(router, path, ...middlewares, routeHandler);
|
|
317
349
|
});
|
|
318
350
|
}
|
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
import { Request } from "hyper-express/types";
|
|
2
2
|
/**
|
|
3
3
|
* File upload restrictions
|
|
4
|
-
*
|
|
5
|
-
* @param allowedMimeTypes Allowed MIME types
|
|
6
|
-
* @param maxFileSize Maximum file size in bytes
|
|
7
|
-
*
|
|
8
4
|
*/
|
|
9
|
-
export interface
|
|
5
|
+
export interface FileUploadRestrictions {
|
|
10
6
|
allowedMimeTypes: string[];
|
|
11
7
|
maxFileSize: number;
|
|
12
8
|
}
|
|
13
9
|
/**
|
|
14
10
|
* File restrictions resolver
|
|
15
|
-
*
|
|
16
|
-
* @param request Request object
|
|
17
|
-
* @returns File restrictions
|
|
18
|
-
*
|
|
19
11
|
*/
|
|
20
|
-
export type FileRestrictions =
|
|
12
|
+
export type FileRestrictions = FileUploadRestrictions | ((request: Request) => FileUploadRestrictions | Promise<FileUploadRestrictions>);
|
|
21
13
|
/**
|
|
22
14
|
* File decorator options
|
|
23
|
-
*
|
|
24
|
-
* @param fieldName Field name to extract from the request
|
|
25
|
-
* @param restrictions File restrictions
|
|
26
|
-
* @param required If the file is required
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
15
|
*/
|
|
30
16
|
export interface FileOptions {
|
|
31
17
|
fieldName: string | string[];
|
|
@@ -33,33 +19,19 @@ export interface FileOptions {
|
|
|
33
19
|
required?: boolean;
|
|
34
20
|
}
|
|
35
21
|
export interface UploadedFile {
|
|
22
|
+
field: string;
|
|
36
23
|
name: string;
|
|
37
24
|
filename: string;
|
|
38
25
|
mimeType: string;
|
|
39
26
|
size: number;
|
|
40
|
-
|
|
27
|
+
extension: string;
|
|
41
28
|
buffer: Buffer;
|
|
42
29
|
}
|
|
43
30
|
/**
|
|
44
31
|
* Decorator to extract file from the request
|
|
45
|
-
*
|
|
46
|
-
* @param param0
|
|
47
32
|
*/
|
|
48
33
|
export declare const File: {
|
|
49
34
|
(options: FileOptions | string): ParameterDecorator;
|
|
50
|
-
/**
|
|
51
|
-
*
|
|
52
|
-
* Helper function to create a file decorator with options
|
|
53
|
-
*
|
|
54
|
-
* @param options
|
|
55
|
-
* @returns
|
|
56
|
-
*/
|
|
57
35
|
options(options: FileOptions, required?: boolean): (fieldName: string | string[]) => ParameterDecorator;
|
|
58
|
-
/**
|
|
59
|
-
* Helper function to create a file decorator with restrictions
|
|
60
|
-
*
|
|
61
|
-
* @param restrictions
|
|
62
|
-
* @returns
|
|
63
|
-
*/
|
|
64
36
|
restrictions(restrictions: FileRestrictions): (fieldName: string | string[], required?: boolean) => ParameterDecorator;
|
|
65
37
|
};
|
package/dist/decorators/File.js
CHANGED
|
@@ -8,46 +8,37 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
11
18
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
19
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
20
|
};
|
|
14
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
22
|
exports.File = void 0;
|
|
16
23
|
const Http_1 = require("./Http");
|
|
17
|
-
const consumers_1 = require("stream/consumers");
|
|
18
24
|
const HyperFileException_1 = __importDefault(require("../exeptions/HyperFileException"));
|
|
19
25
|
/**
|
|
20
26
|
* Decorator to extract file from the request
|
|
21
|
-
*
|
|
22
|
-
* @param param0
|
|
23
27
|
*/
|
|
24
28
|
const File = (options) => {
|
|
25
29
|
const _options = typeof options === "string" ? { fieldName: options } : options;
|
|
26
30
|
return (0, Http_1.createCustomRequestDecorator)("File", (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
-
var _a
|
|
31
|
+
var _a;
|
|
28
32
|
const { allowedMimeTypes, maxFileSize } = yield resolveRestrictions(request, _options.restrictions);
|
|
29
33
|
const passTypes = allowedMimeTypes.includes("*") || allowedMimeTypes.length === 0;
|
|
30
34
|
const passSize = maxFileSize === Infinity;
|
|
31
35
|
const contentType = (_a = request.headers) === null || _a === void 0 ? void 0 : _a["content-type"];
|
|
32
|
-
const fileSize = Number((_b = request.headers) === null || _b === void 0 ? void 0 : _b["content-length"]);
|
|
33
36
|
const isMultipart = contentType === null || contentType === void 0 ? void 0 : contentType.includes("multipart/form-data");
|
|
34
37
|
if (!isMultipart) {
|
|
35
38
|
throw new HyperFileException_1.default("Invalid request, expected multipart form data", {
|
|
36
39
|
fieldName: _options.fieldName,
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
|
-
if (isNaN(fileSize) || fileSize <= 0) {
|
|
40
|
-
throw new HyperFileException_1.default(`File ${_options.fieldName} is missing or empty`, {
|
|
41
|
-
fieldName: _options.fieldName,
|
|
42
|
-
fileSize,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
if (!passSize && fileSize > maxFileSize) {
|
|
46
|
-
throw new HyperFileException_1.default(`File ${_options.fieldName} is too large. Max size is ${maxFileSize} bytes`, {
|
|
47
|
-
fieldName: _options.fieldName,
|
|
48
|
-
maxFileSize,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
42
|
const files = yield extractFiles(request, {
|
|
52
43
|
maxSize: passSize ? undefined : maxFileSize,
|
|
53
44
|
mimeTypes: passTypes ? undefined : allowedMimeTypes,
|
|
@@ -64,35 +55,16 @@ const File = (options) => {
|
|
|
64
55
|
}));
|
|
65
56
|
};
|
|
66
57
|
exports.File = File;
|
|
67
|
-
/**
|
|
68
|
-
*
|
|
69
|
-
* Helper function to create a file decorator with options
|
|
70
|
-
*
|
|
71
|
-
* @param options
|
|
72
|
-
* @returns
|
|
73
|
-
*/
|
|
74
58
|
exports.File.options = (options, required = false) => {
|
|
75
59
|
return (fieldName) => {
|
|
76
60
|
return (0, exports.File)(Object.assign(Object.assign({}, options), { required, fieldName }));
|
|
77
61
|
};
|
|
78
62
|
};
|
|
79
|
-
/**
|
|
80
|
-
* Helper function to create a file decorator with restrictions
|
|
81
|
-
*
|
|
82
|
-
* @param restrictions
|
|
83
|
-
* @returns
|
|
84
|
-
*/
|
|
85
63
|
exports.File.restrictions = (restrictions) => {
|
|
86
64
|
return (fieldName, required = false) => {
|
|
87
65
|
return (0, exports.File)({ fieldName, restrictions, required });
|
|
88
66
|
};
|
|
89
67
|
};
|
|
90
|
-
/////////////////////////////
|
|
91
|
-
// Types
|
|
92
|
-
/////////////////////////////
|
|
93
|
-
/////////////////////////////
|
|
94
|
-
/// Utility
|
|
95
|
-
/////////////////////////////
|
|
96
68
|
const resolveRestrictions = (request, restrictions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
69
|
if (typeof restrictions === "function") {
|
|
98
70
|
return yield restrictions(request);
|
|
@@ -103,63 +75,78 @@ const resolveRestrictions = (request, restrictions) => __awaiter(void 0, void 0,
|
|
|
103
75
|
});
|
|
104
76
|
});
|
|
105
77
|
/**
|
|
106
|
-
* Extract files from a request with validation
|
|
107
|
-
*
|
|
108
|
-
* @param request
|
|
109
|
-
* @param options Validation and transformation options
|
|
110
|
-
* @returns Transformed or raw file details
|
|
78
|
+
* Extract files from a request with validation and secure streaming.
|
|
111
79
|
*/
|
|
112
80
|
const extractFiles = (request, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
81
|
var _a;
|
|
114
|
-
const contentType = (_a = request.headers) === null || _a === void 0 ? void 0 : _a["content-type"];
|
|
115
|
-
if (!(contentType === null || contentType === void 0 ? void 0 : contentType.includes("multipart/form-data"))) {
|
|
116
|
-
throw new Error("Invalid request, expected multipart form data");
|
|
117
|
-
}
|
|
118
82
|
const filesMap = {};
|
|
83
|
+
// Track if we are already over total limit (Content-Length check as first line of defense)
|
|
84
|
+
const totalLength = Number((_a = request.headers) === null || _a === void 0 ? void 0 : _a["content-length"]);
|
|
85
|
+
if ((options === null || options === void 0 ? void 0 : options.maxSize) && !isNaN(totalLength) && totalLength > options.maxSize + 1024 * 10) { // Buffer for boundaries
|
|
86
|
+
// Content-Length is significantly larger than allowed file size
|
|
87
|
+
// We don't throw yet because multipart might have multiple files or large fields
|
|
88
|
+
}
|
|
119
89
|
yield request.multipart((field) => __awaiter(void 0, void 0, void 0, function* () {
|
|
120
|
-
var _a, _b, _c
|
|
90
|
+
var _a, e_1, _b, _c;
|
|
91
|
+
var _d, _e;
|
|
121
92
|
if (field.file && field.file.stream) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
93
|
+
const chunks = [];
|
|
94
|
+
let totalRead = 0;
|
|
95
|
+
try {
|
|
96
|
+
// Safe stream reading with early exit
|
|
97
|
+
for (var _f = true, _g = __asyncValues(field.file.stream), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
|
|
98
|
+
_c = _h.value;
|
|
99
|
+
_f = false;
|
|
100
|
+
const chunk = _c;
|
|
101
|
+
totalRead += chunk.length;
|
|
102
|
+
if ((options === null || options === void 0 ? void 0 : options.maxSize) && totalRead > options.maxSize) {
|
|
103
|
+
throw new HyperFileException_1.default(`File '${field.name}' exceeds the maximum limit of ${options.maxSize} bytes`, { field: field.name, maxSize: options.maxSize });
|
|
104
|
+
}
|
|
105
|
+
chunks.push(chunk);
|
|
106
|
+
}
|
|
128
107
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
allowedMimeTypes: options.mimeTypes,
|
|
136
|
-
mimeType,
|
|
137
|
-
});
|
|
108
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
109
|
+
finally {
|
|
110
|
+
try {
|
|
111
|
+
if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
|
|
112
|
+
}
|
|
113
|
+
finally { if (e_1) throw e_1.error; }
|
|
138
114
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
115
|
+
const bufferFile = Buffer.concat(chunks);
|
|
116
|
+
const { fileTypeFromBuffer } = yield import("file-type");
|
|
117
|
+
const type = yield fileTypeFromBuffer(bufferFile);
|
|
118
|
+
const mimeType = (type === null || type === void 0 ? void 0 : type.mime) || "unknown/unrecognized";
|
|
119
|
+
const extension = (type === null || type === void 0 ? void 0 : type.ext) || "bin";
|
|
120
|
+
// Validate MIME type early if unrecognized
|
|
121
|
+
if (!type && (options === null || options === void 0 ? void 0 : options.mimeTypes) && options.mimeTypes.length > 0) {
|
|
122
|
+
throw new HyperFileException_1.default(`Invalid file type for field '${field.name}'. Could not recognize file signature. Expected one of: ${options.mimeTypes.join(", ")}`, { field: field.name, allowedMimeTypes: options.mimeTypes });
|
|
143
123
|
}
|
|
144
|
-
|
|
145
|
-
|
|
124
|
+
// Validate MIME type if recognized
|
|
125
|
+
if ((options === null || options === void 0 ? void 0 : options.mimeTypes) && !options.mimeTypes.includes(mimeType)) {
|
|
126
|
+
throw new HyperFileException_1.default(`Invalid file type for field '${field.name}'. Expected: ${options.mimeTypes.join(", ")}, Received: ${mimeType}`, {
|
|
146
127
|
field: field.name,
|
|
147
|
-
|
|
128
|
+
allowedMimeTypes: options.mimeTypes,
|
|
129
|
+
receivedMimeType: mimeType,
|
|
148
130
|
});
|
|
149
131
|
}
|
|
150
|
-
if ((options === null || options === void 0 ? void 0 : options.requireName) && !((
|
|
151
|
-
throw new HyperFileException_1.default(`File name is required`, {
|
|
132
|
+
if ((options === null || options === void 0 ? void 0 : options.requireName) && !((_d = field.file) === null || _d === void 0 ? void 0 : _d.name)) {
|
|
133
|
+
throw new HyperFileException_1.default(`File name is required for field '${field.name}'`, {
|
|
152
134
|
field: field.name,
|
|
153
135
|
});
|
|
154
136
|
}
|
|
155
|
-
//
|
|
156
|
-
const
|
|
157
|
-
|
|
137
|
+
// Secure filename resolution
|
|
138
|
+
const rawName = ((_e = field.file) === null || _e === void 0 ? void 0 : _e.name) || field.name;
|
|
139
|
+
// Sanitize: remove null bytes, path traversal sequences, and extract base name
|
|
140
|
+
const sanitizedBase = rawName.replace(/\0/g, "").split(/[\\/]/).pop() || "file";
|
|
141
|
+
const nameOnly = sanitizedBase.includes(".")
|
|
142
|
+
? sanitizedBase.substring(0, sanitizedBase.lastIndexOf("."))
|
|
143
|
+
: sanitizedBase;
|
|
144
|
+
const filename = `${nameOnly}.${extension}`;
|
|
158
145
|
filesMap[field.name] = {
|
|
159
146
|
field: field.name,
|
|
160
|
-
name:
|
|
147
|
+
name: nameOnly,
|
|
161
148
|
filename: filename,
|
|
162
|
-
|
|
149
|
+
extension: extension,
|
|
163
150
|
size: bufferFile.byteLength,
|
|
164
151
|
mimeType: mimeType,
|
|
165
152
|
buffer: bufferFile,
|
|
@@ -5,28 +5,35 @@ import { ParameterResolver } from "./types";
|
|
|
5
5
|
* @param key
|
|
6
6
|
* @returns
|
|
7
7
|
*/
|
|
8
|
-
export declare const Query: (key?: string, transfrom?: (data: any) => any) => ParameterDecorator;
|
|
9
8
|
/**
|
|
10
|
-
* Get the
|
|
9
|
+
* Get the value of a key from the request object or transform the entire query.
|
|
11
10
|
*
|
|
12
|
-
* @
|
|
13
|
-
*
|
|
11
|
+
* @example
|
|
12
|
+
* \@Query('id')
|
|
13
|
+
* \@Query('id', IntSchema)
|
|
14
|
+
* \@Query(UserQueryDto)
|
|
14
15
|
*/
|
|
15
|
-
export declare
|
|
16
|
+
export declare function Query(keyOrSchema?: string | any, schemaOrTransform?: any): any;
|
|
16
17
|
/**
|
|
17
|
-
* Get the
|
|
18
|
+
* Get the body of the request or transform it via a schema.
|
|
18
19
|
*
|
|
19
|
-
* @
|
|
20
|
-
*
|
|
20
|
+
* @example
|
|
21
|
+
* \@Body()
|
|
22
|
+
* \@Body(CreateUserDto)
|
|
21
23
|
*/
|
|
22
|
-
export declare
|
|
24
|
+
export declare function Body(schemaOrResolver?: any): any;
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
26
|
+
* Get the params from the request or transform via schema.
|
|
25
27
|
*
|
|
26
|
-
* @
|
|
27
|
-
*
|
|
28
|
+
* @example
|
|
29
|
+
* \@Param('id')
|
|
30
|
+
* \@Param('id', IntSchema)
|
|
31
|
+
*/
|
|
32
|
+
export declare function Param(keyOrSchema: string | any, schemaOrValidator?: any): any;
|
|
33
|
+
/**
|
|
34
|
+
* Get the headers from the request.
|
|
28
35
|
*/
|
|
29
|
-
export declare
|
|
36
|
+
export declare function Headers(keyOrSchema?: string | any, schema?: any): any;
|
|
30
37
|
export declare const Req: () => ParameterDecorator;
|
|
31
38
|
export declare const Res: () => ParameterDecorator;
|
|
32
39
|
/**
|
package/dist/decorators/Http.js
CHANGED
|
@@ -12,7 +12,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.createCustomRequestDecorator = exports.Res = exports.Req =
|
|
15
|
+
exports.createCustomRequestDecorator = exports.Res = exports.Req = void 0;
|
|
16
|
+
exports.Query = Query;
|
|
17
|
+
exports.Body = Body;
|
|
18
|
+
exports.Param = Param;
|
|
19
|
+
exports.Headers = Headers;
|
|
16
20
|
const request_creator_1 = __importDefault(require("../__internals/creators/request.creator"));
|
|
17
21
|
const object_util_1 = require("../__internals/utils/object.util");
|
|
18
22
|
/**
|
|
@@ -21,45 +25,87 @@ const object_util_1 = require("../__internals/utils/object.util");
|
|
|
21
25
|
* @param key
|
|
22
26
|
* @returns
|
|
23
27
|
*/
|
|
24
|
-
const Query = (key, transfrom) => (0, request_creator_1.default)("query", "Query", (request) => {
|
|
25
|
-
const value = (0, object_util_1.$get)(request.query, key, request.query);
|
|
26
|
-
if (!transfrom)
|
|
27
|
-
return value;
|
|
28
|
-
return value;
|
|
29
|
-
});
|
|
30
|
-
exports.Query = Query;
|
|
31
28
|
/**
|
|
32
|
-
* Get the
|
|
29
|
+
* Get the value of a key from the request object or transform the entire query.
|
|
33
30
|
*
|
|
34
|
-
* @
|
|
35
|
-
*
|
|
31
|
+
* @example
|
|
32
|
+
* \@Query('id')
|
|
33
|
+
* \@Query('id', IntSchema)
|
|
34
|
+
* \@Query(UserQueryDto)
|
|
36
35
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
function Query(keyOrSchema, schemaOrTransform) {
|
|
37
|
+
// Case: @Query(UserQueryDto)
|
|
38
|
+
if (typeof keyOrSchema === 'function' && !schemaOrTransform) {
|
|
39
|
+
return (0, request_creator_1.default)("query", "Query", (request) => request.query, keyOrSchema, true);
|
|
40
|
+
}
|
|
41
|
+
// Case: @Query('id', IntSchema) or @Query('id', data => ...)
|
|
42
|
+
const key = keyOrSchema;
|
|
43
|
+
const resolver = (request) => (0, object_util_1.$get)(request.query, key, request.query);
|
|
44
|
+
if (typeof schemaOrTransform === 'function' && schemaOrTransform.prototype === undefined) {
|
|
45
|
+
// It's a legacy transform function: data => ...
|
|
46
|
+
return (0, request_creator_1.default)("query", "Query", (request) => {
|
|
47
|
+
const value = resolver(request);
|
|
48
|
+
return schemaOrTransform(value);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// It's either a schema/DTO class or just a key
|
|
52
|
+
return (0, request_creator_1.default)("query", "Query", resolver, schemaOrTransform, false);
|
|
53
|
+
}
|
|
42
54
|
/**
|
|
43
|
-
* Get the
|
|
55
|
+
* Get the body of the request or transform it via a schema.
|
|
44
56
|
*
|
|
45
|
-
* @
|
|
46
|
-
*
|
|
57
|
+
* @example
|
|
58
|
+
* \@Body()
|
|
59
|
+
* \@Body(CreateUserDto)
|
|
47
60
|
*/
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
function Body(schemaOrResolver) {
|
|
62
|
+
const resolver = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
return request.body !== undefined ? request.body : yield request.json();
|
|
64
|
+
});
|
|
65
|
+
if (typeof schemaOrResolver === 'function' && schemaOrResolver.prototype !== undefined) {
|
|
66
|
+
// Case: @Body(CreateUserDto)
|
|
67
|
+
return (0, request_creator_1.default)("req", "BODY", resolver, schemaOrResolver, true);
|
|
68
|
+
}
|
|
69
|
+
// Case: @Body(data => ...) (Legacy) or @Body()
|
|
70
|
+
return (0, request_creator_1.default)("req", "BODY", (request) => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const value = yield resolver(request);
|
|
72
|
+
return typeof schemaOrResolver === 'function' ? schemaOrResolver(value) : value;
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
55
75
|
/**
|
|
56
|
-
*
|
|
76
|
+
* Get the params from the request or transform via schema.
|
|
57
77
|
*
|
|
58
|
-
* @
|
|
59
|
-
*
|
|
78
|
+
* @example
|
|
79
|
+
* \@Param('id')
|
|
80
|
+
* \@Param('id', IntSchema)
|
|
60
81
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
82
|
+
function Param(keyOrSchema, schemaOrValidator) {
|
|
83
|
+
// Case: @Param(ParamsDto)
|
|
84
|
+
if (typeof keyOrSchema === 'function' && !schemaOrValidator) {
|
|
85
|
+
return (0, request_creator_1.default)("params", "Param", (req) => req.params, keyOrSchema, true);
|
|
86
|
+
}
|
|
87
|
+
const key = keyOrSchema;
|
|
88
|
+
const resolver = (req) => (0, object_util_1.$get)(req.params, key, req.params);
|
|
89
|
+
if (typeof schemaOrValidator === 'function' && schemaOrValidator.prototype === undefined) {
|
|
90
|
+
// Legacy validator function
|
|
91
|
+
return (0, request_creator_1.default)("params", "Param", (req) => {
|
|
92
|
+
const value = resolver(req);
|
|
93
|
+
return schemaOrValidator(value);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return (0, request_creator_1.default)("params", "Param", resolver, schemaOrValidator, false);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get the headers from the request.
|
|
100
|
+
*/
|
|
101
|
+
function Headers(keyOrSchema, schema) {
|
|
102
|
+
if (typeof keyOrSchema === 'function' && !schema) {
|
|
103
|
+
return (0, request_creator_1.default)("headers", "Headers", (req) => req.headers, keyOrSchema, true);
|
|
104
|
+
}
|
|
105
|
+
const key = keyOrSchema;
|
|
106
|
+
const resolver = (req) => (0, object_util_1.$get)(req.headers, key, req.headers);
|
|
107
|
+
return (0, request_creator_1.default)("headers", "Headers", resolver, schema, false);
|
|
108
|
+
}
|
|
63
109
|
const Req = () => (0, request_creator_1.default)("req", "Req", (req) => req);
|
|
64
110
|
exports.Req = Req;
|
|
65
111
|
const Res = () => (0, request_creator_1.default)("res", "Res", (req, res) => res);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
/**
|
|
3
|
+
* Decorator to explicitly mark a method for response transformation and OpenAPI documentation.
|
|
4
|
+
*
|
|
5
|
+
* @param schema The schema or DTO class for the successful response (200).
|
|
6
|
+
* @example
|
|
7
|
+
* \@Output(UserDto)
|
|
8
|
+
*/
|
|
9
|
+
export declare function Output(schema?: any): (target: any, propertyKey?: any, descriptor?: PropertyDescriptor) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Output = Output;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const decorator_base_1 = require("../__internals/decorator-base");
|
|
6
|
+
const constants_1 = require("../__internals/constants");
|
|
7
|
+
/**
|
|
8
|
+
* Decorator to explicitly mark a method for response transformation and OpenAPI documentation.
|
|
9
|
+
*
|
|
10
|
+
* @param schema The schema or DTO class for the successful response (200).
|
|
11
|
+
* @example
|
|
12
|
+
* \@Output(UserDto)
|
|
13
|
+
*/
|
|
14
|
+
function Output(schema) {
|
|
15
|
+
return (target, propertyKey, descriptor) => {
|
|
16
|
+
(0, decorator_base_1.defineDecorData)(constants_1.KEY_OUTPUT_SCHEMA, schema, target, propertyKey);
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/decorators/index.js
CHANGED
|
@@ -27,4 +27,4 @@ __exportStar(require("./Http"), exports);
|
|
|
27
27
|
__exportStar(require("./Pass"), exports);
|
|
28
28
|
__exportStar(require("./File"), exports);
|
|
29
29
|
__exportStar(require("./Messaging"), exports);
|
|
30
|
-
__exportStar(require("./
|
|
30
|
+
__exportStar(require("./Output"), exports);
|
|
@@ -74,6 +74,8 @@ export type HyperParameterMetadata = {
|
|
|
74
74
|
name: string;
|
|
75
75
|
method: string;
|
|
76
76
|
resolver: ParameterResolver;
|
|
77
|
+
schema?: any;
|
|
78
|
+
isWholeSource?: boolean;
|
|
77
79
|
}[]>;
|
|
78
80
|
};
|
|
79
81
|
export type HyperParamDecorator = (key: string) => (target: any, key: string, index: number) => void;
|
|
@@ -2,6 +2,7 @@ import { ExceptionType } from "./types";
|
|
|
2
2
|
export default class HyperException extends Error {
|
|
3
3
|
code: ExceptionType;
|
|
4
4
|
additionalInfo: any;
|
|
5
|
-
|
|
5
|
+
status: number;
|
|
6
|
+
constructor(message: string, code?: ExceptionType, additionalInfo?: any, status?: number);
|
|
6
7
|
static throw(message: string, code?: ExceptionType, additionalInfo?: {}): void;
|
|
7
8
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
class HyperException extends Error {
|
|
4
|
-
constructor(message, code = "HyperException", additionalInfo = {}) {
|
|
4
|
+
constructor(message, code = "HyperException", additionalInfo = {}, status = 500) {
|
|
5
5
|
super(message);
|
|
6
6
|
this.code = code;
|
|
7
7
|
this.additionalInfo = additionalInfo;
|
|
8
|
+
this.status = status;
|
|
8
9
|
}
|
|
9
10
|
static throw(message, code, additionalInfo = {}) {
|
|
10
11
|
throw new this(message, code, additionalInfo);
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const HyperException_1 = __importDefault(require("./HyperException"));
|
|
7
7
|
class HyperFileException extends HyperException_1.default {
|
|
8
8
|
constructor(message, additional) {
|
|
9
|
-
super(message, "HyperFileException", additional);
|
|
9
|
+
super(message, "HyperFileException", additional, 400);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
exports.default = HyperFileException;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -27,3 +27,5 @@ __exportStar(require("./stores"), exports);
|
|
|
27
27
|
__exportStar(require("./type"), exports);
|
|
28
28
|
__exportStar(require("./common/transport"), exports);
|
|
29
29
|
__exportStar(require("./common/message-bus"), exports);
|
|
30
|
+
__exportStar(require("./__internals/transform/transform.registry"), exports);
|
|
31
|
+
__exportStar(require("./lib/openapi"), exports);
|
|
@@ -4,7 +4,6 @@ exports.collectMethodMetadata = collectMethodMetadata;
|
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
5
|
const param_collector_1 = require("./param.collector");
|
|
6
6
|
const constants_2 = require("../../../__internals/constants");
|
|
7
|
-
const decorator_base_1 = require("../../../__internals/decorator-base");
|
|
8
7
|
const transform_registry_1 = require("../../../__internals/transform/transform.registry");
|
|
9
8
|
function collectMethodMetadata(target, methodName) {
|
|
10
9
|
const methodMetadata = {};
|
|
@@ -22,20 +21,34 @@ function collectMethodMetadata(target, methodName) {
|
|
|
22
21
|
description: Reflect.getMetadata(constants_1.REQUEST_BODY_DESCRIPTION, target, methodName),
|
|
23
22
|
content: Reflect.getMetadata(constants_1.REQUEST_BODY_CONTENT, target, methodName),
|
|
24
23
|
};
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
// Bridge @Body to OpenAPI
|
|
25
|
+
const hyperParams = Reflect.getMetadata(constants_2.KEY_PARAMS_PARAM, target[methodName]);
|
|
26
|
+
if (hyperParams && hyperParams.params[methodName]) {
|
|
27
|
+
const bodyParam = hyperParams.params[methodName].find(p => ['body', 'BODY', 'req'].includes(p.key));
|
|
28
|
+
if (bodyParam) {
|
|
29
|
+
const targetSchema = bodyParam.schema;
|
|
30
|
+
if (targetSchema) {
|
|
31
|
+
const bodySchema = transform_registry_1.transformRegistry.getOpenApiSchema(targetSchema);
|
|
32
|
+
if (bodySchema) {
|
|
33
|
+
requestBody.content = requestBody.content || {};
|
|
34
|
+
requestBody.content['application/json'] = {
|
|
35
|
+
schema: bodySchema
|
|
36
|
+
};
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
// Bridging @Output / return type to OpenAPI
|
|
42
|
+
const outputSchema = Reflect.getMetadata(constants_2.KEY_OUTPUT_SCHEMA, target, methodName)
|
|
43
|
+
|| Reflect.getMetadata(constants_2.DESIGN_RETURNTYPE, target, methodName);
|
|
44
|
+
if (outputSchema && outputSchema !== Object && outputSchema !== Promise) {
|
|
45
|
+
const schema = transform_registry_1.transformRegistry.getOpenApiSchema(outputSchema);
|
|
46
|
+
if (schema) {
|
|
47
|
+
responses['200'] = responses['200'] || { description: 'Success' };
|
|
48
|
+
responses['200'].content = responses['200'].content || {};
|
|
49
|
+
responses['200'].content['application/json'] = { schema };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
// Asignamos las propiedades al objeto de metadata solo si existen
|
|
40
53
|
if (summary)
|
|
41
54
|
methodMetadata.summary = summary;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import "
|
|
2
|
-
|
|
3
|
-
export declare function collectParameterMetadata(target: any, methodName: string): Parameter[];
|
|
1
|
+
import { OpenApiParameter } from "../types";
|
|
2
|
+
export declare function collectParameterMetadata(target: any, methodName: string): OpenApiParameter[];
|
|
@@ -1,26 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.collectParameterMetadata = collectParameterMetadata;
|
|
4
|
-
require("reflect-metadata");
|
|
5
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const constants_2 = require("../../../__internals/constants");
|
|
6
|
+
const transform_registry_1 = require("../../../__internals/transform/transform.registry");
|
|
6
7
|
const function_util_1 = require("../../../__internals/utils/function.util");
|
|
7
8
|
function collectParameterMetadata(target, methodName) {
|
|
8
9
|
const parameters = Reflect.getMetadata(constants_1.PARAMETERS, target, methodName) || [];
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const hyperParams = Reflect.getMetadata(constants_2.KEY_PARAMS_PARAM, target[methodName]);
|
|
11
|
+
if (hyperParams && hyperParams.params[methodName]) {
|
|
12
|
+
hyperParams.params[methodName].forEach((p) => {
|
|
13
|
+
// Ignore body, req, res as they are not standard "parameters" in OpenAPI terms (body is separate)
|
|
14
|
+
if (['req', 'res', 'body', 'BODY'].includes(p.key))
|
|
15
|
+
return;
|
|
16
|
+
const locationMap = {
|
|
17
|
+
'query': 'query',
|
|
18
|
+
'params': 'path',
|
|
19
|
+
'headers': 'header',
|
|
20
|
+
'cookie': 'cookie'
|
|
21
|
+
};
|
|
22
|
+
const location = locationMap[p.key] || 'query';
|
|
23
|
+
if (p.isWholeSource && p.schema) {
|
|
24
|
+
const schema = transform_registry_1.transformRegistry.getOpenApiSchema(p.schema);
|
|
25
|
+
if (schema && schema.properties) {
|
|
26
|
+
Object.keys(schema.properties).forEach((propKey) => {
|
|
27
|
+
parameters.push({
|
|
28
|
+
name: propKey,
|
|
29
|
+
in: location,
|
|
30
|
+
required: (schema.required || []).includes(propKey),
|
|
31
|
+
schema: schema.properties[propKey]
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
parameters.push({
|
|
38
|
+
name: p.name,
|
|
39
|
+
in: location,
|
|
40
|
+
required: true, // TODO: detect optionality from design:paramtypes or metadata
|
|
41
|
+
schema: p.schema ? transform_registry_1.transformRegistry.getOpenApiSchema(p.schema) : { type: 'string' }
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Fallback to design:paramtypes if no hyper-decor metadata found
|
|
47
|
+
if (parameters.length === 0) {
|
|
48
|
+
const methodParams = Reflect.getMetadata("design:paramtypes", target, methodName) || [];
|
|
49
|
+
const paramNames = (0, function_util_1.extractArgsNames)(target[methodName]);
|
|
14
50
|
methodParams.forEach((paramType, index) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
51
|
+
var _a;
|
|
52
|
+
const name = paramNames && paramNames[index] ? paramNames[index] : `param${index}`;
|
|
53
|
+
parameters.push({
|
|
54
|
+
name,
|
|
55
|
+
in: "query",
|
|
18
56
|
required: true,
|
|
19
57
|
schema: {
|
|
20
|
-
type: paramType.name.toLowerCase()
|
|
58
|
+
type: ((_a = paramType === null || paramType === void 0 ? void 0 : paramType.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'number' ? 'number' : 'string',
|
|
21
59
|
},
|
|
22
|
-
};
|
|
23
|
-
parameters.push(param);
|
|
60
|
+
});
|
|
24
61
|
});
|
|
25
62
|
}
|
|
26
63
|
return parameters;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import {
|
|
3
|
-
export declare function ApiParameter(options:
|
|
2
|
+
import { OpenApiParameter } from '../types';
|
|
3
|
+
export declare function ApiParameter(options: OpenApiParameter): (target: any, propertyKey: any) => void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
-
import {
|
|
3
|
-
export declare function ApiResponse(options:
|
|
2
|
+
import { OpenApiResponses } from "../types";
|
|
3
|
+
export declare function ApiResponse(options: OpenApiResponses): (target: any, propertyKey?: any, descriptor?: any) => any;
|
|
@@ -5,6 +5,9 @@ require("reflect-metadata");
|
|
|
5
5
|
const tag_helper_1 = require("../helpers/tag.helper");
|
|
6
6
|
function ApiTag(options) {
|
|
7
7
|
return (target) => {
|
|
8
|
+
if (typeof options === 'string') {
|
|
9
|
+
options = { name: options };
|
|
10
|
+
}
|
|
8
11
|
(0, tag_helper_1.apiTag)(target, options);
|
|
9
12
|
};
|
|
10
13
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import {
|
|
3
|
-
export declare function apiParameter(target: any, propertyKey: string, options:
|
|
2
|
+
import { OpenApiParameter } from '../types';
|
|
3
|
+
export declare function apiParameter(target: any, propertyKey: string, options: OpenApiParameter): void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
-
import {
|
|
3
|
-
export declare function apiResponse(target: any, propertyKey: string | symbol, options:
|
|
2
|
+
import { OpenApiResponses } from "../types";
|
|
3
|
+
export declare function apiResponse(target: any, propertyKey: string | symbol, options: OpenApiResponses): void;
|
|
@@ -41,12 +41,12 @@ export interface Operation {
|
|
|
41
41
|
description?: string;
|
|
42
42
|
operationId?: string;
|
|
43
43
|
tags?: Tag[];
|
|
44
|
-
parameters?:
|
|
44
|
+
parameters?: OpenApiParameter[];
|
|
45
45
|
requestBody?: RequestBody;
|
|
46
|
-
responses:
|
|
46
|
+
responses: OpenApiResponses;
|
|
47
47
|
security?: SecurityRequirement[];
|
|
48
48
|
}
|
|
49
|
-
export interface
|
|
49
|
+
export interface OpenApiParameter {
|
|
50
50
|
name: string;
|
|
51
51
|
in: "query" | "header" | "path" | "cookie";
|
|
52
52
|
description?: string;
|
|
@@ -64,10 +64,10 @@ export interface RequestBody {
|
|
|
64
64
|
export interface MediaType {
|
|
65
65
|
schema: Schema;
|
|
66
66
|
}
|
|
67
|
-
export interface
|
|
68
|
-
[statusCode: string]:
|
|
67
|
+
export interface OpenApiResponses {
|
|
68
|
+
[statusCode: string]: OpenApiResponse;
|
|
69
69
|
}
|
|
70
|
-
export interface
|
|
70
|
+
export interface OpenApiResponse {
|
|
71
71
|
description: string;
|
|
72
72
|
content?: {
|
|
73
73
|
[mediaType: string]: MediaType;
|
|
@@ -103,10 +103,10 @@ export interface Components {
|
|
|
103
103
|
[schemaName: string]: Schema;
|
|
104
104
|
};
|
|
105
105
|
responses?: {
|
|
106
|
-
[responseName: string]:
|
|
106
|
+
[responseName: string]: OpenApiResponse;
|
|
107
107
|
};
|
|
108
108
|
parameters?: {
|
|
109
|
-
[parameterName: string]:
|
|
109
|
+
[parameterName: string]: OpenApiParameter;
|
|
110
110
|
};
|
|
111
111
|
securitySchemes?: {
|
|
112
112
|
[schemeName: string]: SecurityScheme;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenofolio/hyper-decor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.71",
|
|
4
4
|
"description": "Project core with utilities and features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "zenozaga",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"@vitest/ui": "^4.0.18",
|
|
22
22
|
"nodemon": "3.1.7",
|
|
23
23
|
"typescript": "5.6.2",
|
|
24
|
+
"undici": "^7.24.0",
|
|
24
25
|
"unplugin-swc": "^1.5.9",
|
|
25
26
|
"vitest": "^4.0.18"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"core-decorators": "^0.20.0",
|
|
29
|
-
"delay": "^7.0.0",
|
|
30
30
|
"eventemitter3": "^5.0.4",
|
|
31
31
|
"file-type": "^19.5.0",
|
|
32
32
|
"reflect-metadata": "^0.2.2",
|