@carno.js/core 0.2.8 → 0.2.9
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/Carno.d.ts +9 -7
- package/dist/Carno.js +14 -1
- package/dist/commons/decorators/index.d.ts +1 -0
- package/dist/commons/decorators/index.js +1 -0
- package/dist/commons/decorators/validation.decorator.d.ts +32 -0
- package/dist/commons/decorators/validation.decorator.js +40 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -1
- package/dist/container/InjectorService.d.ts +3 -1
- package/dist/container/InjectorService.js +4 -3
- package/dist/container/MethodInvoker.d.ts +3 -2
- package/dist/container/MethodInvoker.js +4 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/route/ParamResolverFactory.d.ts +0 -5
- package/dist/route/ParamResolverFactory.js +0 -40
- package/dist/route/RouteCompiler.d.ts +3 -3
- package/dist/route/RouteCompiler.js +2 -11
- package/dist/utils/ValidationCache.d.ts +2 -0
- package/dist/utils/ValidationCache.js +10 -2
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/validation/ValidatorAdapter.d.ts +66 -0
- package/dist/validation/ValidatorAdapter.js +20 -0
- package/dist/validation/adapters/ClassValidatorAdapter.d.ts +23 -0
- package/dist/validation/adapters/ClassValidatorAdapter.js +47 -0
- package/dist/validation/adapters/ZodAdapter.d.ts +14 -0
- package/dist/validation/adapters/ZodAdapter.js +56 -0
- package/dist/validation/adapters/index.d.ts +4 -0
- package/dist/validation/adapters/index.js +7 -0
- package/dist/validation/index.d.ts +3 -0
- package/dist/validation/index.js +20 -0
- package/package.json +17 -6
- package/dist/Cheetah.d.ts +0 -65
- package/dist/Cheetah.js +0 -307
- package/dist/default-routes-cheetah.d.ts +0 -3
- package/dist/default-routes-cheetah.js +0 -29
- package/dist/domain/CheetahClosure.d.ts +0 -1
- package/dist/domain/CheetahClosure.js +0 -2
- package/dist/domain/CheetahMiddleware.d.ts +0 -5
- package/dist/domain/CheetahMiddleware.js +0 -2
- package/dist/services/request-logger.service.d.ts +0 -15
- package/dist/services/request-logger.service.js +0 -50
- package/dist/utils/isClassValidator.d.ts +0 -6
- package/dist/utils/isClassValidator.js +0 -13
package/dist/Carno.d.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { Server } from "bun";
|
|
2
|
-
import { ValidatorOptions } from "class-validator";
|
|
3
2
|
import * as pino from "pino";
|
|
3
|
+
import type { ValidationConfig, ValidatorAdapterConstructor } from "./validation/ValidatorAdapter";
|
|
4
4
|
import { TokenRouteWithProvider } from "./container/ContainerConfiguration";
|
|
5
5
|
import { CorsConfig } from "./domain/cors-config";
|
|
6
6
|
import Memoirist from "./route/memoirist";
|
|
7
7
|
import { type CompiledRoute } from "./route/CompiledRoute";
|
|
8
|
-
export interface ApplicationConfig {
|
|
9
|
-
validation?:
|
|
8
|
+
export interface ApplicationConfig<TAdapter extends ValidatorAdapterConstructor = ValidatorAdapterConstructor> {
|
|
9
|
+
validation?: ValidationConfig<TAdapter>;
|
|
10
10
|
logger?: pino.LoggerOptions;
|
|
11
11
|
exports?: any[];
|
|
12
12
|
providers?: any[];
|
|
13
13
|
cors?: CorsConfig;
|
|
14
14
|
globalMiddlewares?: any[];
|
|
15
15
|
}
|
|
16
|
-
export declare class Carno {
|
|
17
|
-
config: ApplicationConfig
|
|
16
|
+
export declare class Carno<TAdapter extends ValidatorAdapterConstructor = ValidatorAdapterConstructor> {
|
|
17
|
+
config: ApplicationConfig<TAdapter>;
|
|
18
18
|
router: Memoirist<CompiledRoute | TokenRouteWithProvider>;
|
|
19
19
|
private injector;
|
|
20
20
|
private corsCache?;
|
|
21
21
|
private readonly emptyLocals;
|
|
22
|
+
private validatorAdapter;
|
|
22
23
|
private fetch;
|
|
23
24
|
private server;
|
|
24
|
-
constructor(config?: ApplicationConfig);
|
|
25
|
+
constructor(config?: ApplicationConfig<TAdapter>);
|
|
26
|
+
private resolveValidatorAdapter;
|
|
25
27
|
/**
|
|
26
28
|
* Use the Carno plugin.
|
|
27
29
|
*
|
|
@@ -49,7 +51,7 @@ export declare class Carno {
|
|
|
49
51
|
listen(port?: number): Promise<void>;
|
|
50
52
|
private registerShutdownHandlers;
|
|
51
53
|
getHttpServer(): Server<any>;
|
|
52
|
-
getInjector(): import("
|
|
54
|
+
getInjector(): import("./container").InjectorService;
|
|
53
55
|
private createHttpServer;
|
|
54
56
|
private fetcher;
|
|
55
57
|
private catcher;
|
package/dist/Carno.js
CHANGED
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Carno = void 0;
|
|
7
7
|
const node_process_1 = __importDefault(require("node:process"));
|
|
8
|
+
const ZodAdapter_1 = require("./validation/adapters/ZodAdapter");
|
|
9
|
+
const ValidationCache_1 = require("./utils/ValidationCache");
|
|
8
10
|
const index_1 = require("./commons/index");
|
|
9
11
|
const constants_1 = require("./constants");
|
|
10
12
|
const createContainer_1 = require("./container/createContainer");
|
|
@@ -56,11 +58,21 @@ class Carno {
|
|
|
56
58
|
console.error("Unhandled error:", error);
|
|
57
59
|
return new Response("Internal Server Error", { status: 500 });
|
|
58
60
|
};
|
|
61
|
+
this.validatorAdapter = this.resolveValidatorAdapter();
|
|
59
62
|
if (config.cors) {
|
|
60
63
|
this.corsCache = new cors_headers_cache_1.CorsHeadersCache(config.cors);
|
|
61
64
|
}
|
|
62
65
|
void this.bootstrapApplication();
|
|
63
66
|
}
|
|
67
|
+
resolveValidatorAdapter() {
|
|
68
|
+
const config = this.config.validation;
|
|
69
|
+
if (!config?.adapter) {
|
|
70
|
+
return new ZodAdapter_1.ZodAdapter();
|
|
71
|
+
}
|
|
72
|
+
const AdapterClass = config.adapter;
|
|
73
|
+
const options = config.options || {};
|
|
74
|
+
return new AdapterClass(options);
|
|
75
|
+
}
|
|
64
76
|
/**
|
|
65
77
|
* Use the Carno plugin.
|
|
66
78
|
*
|
|
@@ -148,8 +160,9 @@ class Carno {
|
|
|
148
160
|
};
|
|
149
161
|
}
|
|
150
162
|
async init() {
|
|
163
|
+
(0, ValidationCache_1.setValidatorAdapter)(this.validatorAdapter);
|
|
151
164
|
this.loadProvidersAndControllers();
|
|
152
|
-
await this.injector.loadModule((0, createContainer_1.createContainer)(), this.config, this.router);
|
|
165
|
+
await this.injector.loadModule((0, createContainer_1.createContainer)(), this.config, this.router, this.validatorAdapter);
|
|
153
166
|
}
|
|
154
167
|
async listen(port = 3000) {
|
|
155
168
|
this.registerShutdownHandlers();
|
|
@@ -19,3 +19,4 @@ __exportStar(require("./http.decorators"), exports);
|
|
|
19
19
|
__exportStar(require("./service.decorator"), exports);
|
|
20
20
|
__exportStar(require("./middleware.decorator"), exports);
|
|
21
21
|
__exportStar(require("./Injectable.decorator"), exports);
|
|
22
|
+
__exportStar(require("./validation.decorator"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Decorator to attach a Zod schema to a DTO class
|
|
4
|
+
* The schema will be used for validation when the DTO is used in route handlers
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const CreateUserSchema = z.object({
|
|
9
|
+
* name: z.string().min(3),
|
|
10
|
+
* email: z.string().email()
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* @ZodSchema(CreateUserSchema)
|
|
14
|
+
* class CreateUserDto {
|
|
15
|
+
* name: string;
|
|
16
|
+
* email: string;
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* @Controller('/users')
|
|
20
|
+
* class UserController {
|
|
21
|
+
* @Post()
|
|
22
|
+
* create(@Body() dto: CreateUserDto) {
|
|
23
|
+
* // dto is validated and typed
|
|
24
|
+
* return dto;
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param schema - The Zod schema to use for validation
|
|
30
|
+
* @returns Class decorator
|
|
31
|
+
*/
|
|
32
|
+
export declare function ZodSchema<T extends z.ZodType>(schema: T): ClassDecorator;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZodSchema = ZodSchema;
|
|
4
|
+
const Metadata_1 = require("../../domain/Metadata");
|
|
5
|
+
const constants_1 = require("../../constants");
|
|
6
|
+
/**
|
|
7
|
+
* Decorator to attach a Zod schema to a DTO class
|
|
8
|
+
* The schema will be used for validation when the DTO is used in route handlers
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const CreateUserSchema = z.object({
|
|
13
|
+
* name: z.string().min(3),
|
|
14
|
+
* email: z.string().email()
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* @ZodSchema(CreateUserSchema)
|
|
18
|
+
* class CreateUserDto {
|
|
19
|
+
* name: string;
|
|
20
|
+
* email: string;
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* @Controller('/users')
|
|
24
|
+
* class UserController {
|
|
25
|
+
* @Post()
|
|
26
|
+
* create(@Body() dto: CreateUserDto) {
|
|
27
|
+
* // dto is validated and typed
|
|
28
|
+
* return dto;
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @param schema - The Zod schema to use for validation
|
|
34
|
+
* @returns Class decorator
|
|
35
|
+
*/
|
|
36
|
+
function ZodSchema(schema) {
|
|
37
|
+
return (target) => {
|
|
38
|
+
Metadata_1.Metadata.set(constants_1.VALIDATION_ZOD_SCHEMA, schema, target);
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export declare const CONTROLLER_EVENTS = "carno:controller:events";
|
|
|
5
5
|
export declare const ROUTE_PARAM = "carno:route:param";
|
|
6
6
|
export declare const ROUTE_MIDDLEWARES = "carno:route:middlewares";
|
|
7
7
|
export declare const PROVIDER = "carno:provider";
|
|
8
|
+
export declare const VALIDATION_ZOD_SCHEMA = "carno:validation:zod";
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PROVIDER = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PARAM = exports.CONTROLLER_EVENTS = exports.CONTROLLER_MIDDLEWARES = exports.CONTROLLER_ROUTES = exports.CONTROLLER = void 0;
|
|
3
|
+
exports.VALIDATION_ZOD_SCHEMA = exports.PROVIDER = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PARAM = exports.CONTROLLER_EVENTS = exports.CONTROLLER_MIDDLEWARES = exports.CONTROLLER_ROUTES = exports.CONTROLLER = void 0;
|
|
4
4
|
exports.CONTROLLER = "carno:controller";
|
|
5
5
|
exports.CONTROLLER_ROUTES = "carno:controller:routes";
|
|
6
6
|
exports.CONTROLLER_MIDDLEWARES = "carno:controller:middlewares";
|
|
@@ -8,3 +8,4 @@ exports.CONTROLLER_EVENTS = "carno:controller:events";
|
|
|
8
8
|
exports.ROUTE_PARAM = "carno:route:param";
|
|
9
9
|
exports.ROUTE_MIDDLEWARES = "carno:route:middlewares";
|
|
10
10
|
exports.PROVIDER = "carno:provider";
|
|
11
|
+
exports.VALIDATION_ZOD_SCHEMA = "carno:validation:zod";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApplicationConfig } from "../Carno";
|
|
2
2
|
import { TokenProvider } from "../commons/registries/ProviderControl";
|
|
3
|
+
import type { ValidatorAdapter } from "../validation/ValidatorAdapter";
|
|
3
4
|
import { Context } from "../domain/Context";
|
|
4
5
|
import { LocalsContainer } from "../domain/LocalsContainer";
|
|
5
6
|
import { Provider } from "../domain/provider";
|
|
@@ -20,7 +21,8 @@ export declare class InjectorService {
|
|
|
20
21
|
private routeResolver;
|
|
21
22
|
private dependencyResolver;
|
|
22
23
|
private methodInvoker;
|
|
23
|
-
|
|
24
|
+
private validatorAdapter;
|
|
25
|
+
loadModule(container: Container, applicationConfig: ApplicationConfig, router: Memoirist<any>, validatorAdapter: ValidatorAdapter): Promise<void>;
|
|
24
26
|
private initializeResolvers;
|
|
25
27
|
private ensureProvider;
|
|
26
28
|
get(token: TokenProvider): Provider | undefined;
|
|
@@ -40,10 +40,11 @@ let InjectorService = InjectorService_1 = class InjectorService {
|
|
|
40
40
|
this._hasOnResponseHook = false;
|
|
41
41
|
this.controllerScopes = new Map();
|
|
42
42
|
}
|
|
43
|
-
async loadModule(container, applicationConfig, router) {
|
|
43
|
+
async loadModule(container, applicationConfig, router, validatorAdapter) {
|
|
44
44
|
this.container = container;
|
|
45
45
|
this.router = router;
|
|
46
46
|
this.applicationConfig = applicationConfig;
|
|
47
|
+
this.validatorAdapter = validatorAdapter;
|
|
47
48
|
this.initializeResolvers();
|
|
48
49
|
this.removeUnknownProviders();
|
|
49
50
|
this.saveInjector();
|
|
@@ -57,7 +58,7 @@ let InjectorService = InjectorService_1 = class InjectorService {
|
|
|
57
58
|
initializeResolvers() {
|
|
58
59
|
this.routeResolver = new RouteResolver_1.RouteResolver(this.router, this.applicationConfig.globalMiddlewares);
|
|
59
60
|
this.dependencyResolver = new DependencyResolver_1.DependencyResolver(this.container);
|
|
60
|
-
this.methodInvoker = new MethodInvoker_1.MethodInvoker(this.applicationConfig);
|
|
61
|
+
this.methodInvoker = new MethodInvoker_1.MethodInvoker(this.applicationConfig, this.validatorAdapter);
|
|
61
62
|
}
|
|
62
63
|
ensureProvider(token) {
|
|
63
64
|
if (!this.container.has(token) && ProviderControl_1.GlobalProvider.has(token)) {
|
|
@@ -175,7 +176,7 @@ let InjectorService = InjectorService_1 = class InjectorService {
|
|
|
175
176
|
const compiler = new RouteCompiler_1.RouteCompiler({
|
|
176
177
|
container: this.container,
|
|
177
178
|
controllerScopes: this.controllerScopes,
|
|
178
|
-
|
|
179
|
+
validatorAdapter: this.validatorAdapter,
|
|
179
180
|
hasOnRequestHook: this._hasOnRequestHook,
|
|
180
181
|
hasOnResponseHook: this._hasOnResponseHook,
|
|
181
182
|
});
|
|
@@ -2,10 +2,12 @@ import { ApplicationConfig } from "../Carno";
|
|
|
2
2
|
import { TokenProvider } from "../commons/registries/ProviderControl";
|
|
3
3
|
import { Context } from "../domain/Context";
|
|
4
4
|
import { LocalsContainer } from "../domain/LocalsContainer";
|
|
5
|
+
import type { ValidatorAdapter } from "../validation/ValidatorAdapter";
|
|
5
6
|
export declare class MethodInvoker {
|
|
6
7
|
private applicationConfig;
|
|
8
|
+
private validatorAdapter;
|
|
7
9
|
private historyMethods;
|
|
8
|
-
constructor(applicationConfig: ApplicationConfig);
|
|
10
|
+
constructor(applicationConfig: ApplicationConfig, validatorAdapter: ValidatorAdapter);
|
|
9
11
|
invoke(instance: any, methodName: string, locals: LocalsContainer, context: Context, invokeCallback: (token: TokenProvider, locals: LocalsContainer) => any): Promise<any>;
|
|
10
12
|
private getMethodInfo;
|
|
11
13
|
private getCachedMethod;
|
|
@@ -16,5 +18,4 @@ export declare class MethodInvoker {
|
|
|
16
18
|
private hasBodyParam;
|
|
17
19
|
private isBodyParam;
|
|
18
20
|
private resolveService;
|
|
19
|
-
private validateAndTransform;
|
|
20
21
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MethodInvoker = void 0;
|
|
4
|
-
const class_transformer_1 = require("class-transformer");
|
|
5
|
-
const class_validator_1 = require("class-validator");
|
|
6
4
|
const Metadata_1 = require("../domain/Metadata");
|
|
7
|
-
const HttpException_1 = require("../exceptions/HttpException");
|
|
8
5
|
const getClassOrSymbol_1 = require("../utils/getClassOrSymbol");
|
|
9
6
|
const getMethodArgTypes_1 = require("../utils/getMethodArgTypes");
|
|
10
|
-
const isClassValidator_1 = require("../utils/isClassValidator");
|
|
11
7
|
class MethodInvoker {
|
|
12
|
-
constructor(applicationConfig) {
|
|
8
|
+
constructor(applicationConfig, validatorAdapter) {
|
|
13
9
|
this.applicationConfig = applicationConfig;
|
|
10
|
+
this.validatorAdapter = validatorAdapter;
|
|
14
11
|
this.historyMethods = new WeakMap();
|
|
15
12
|
}
|
|
16
13
|
async invoke(instance, methodName, locals, context, invokeCallback) {
|
|
@@ -78,19 +75,9 @@ class MethodInvoker {
|
|
|
78
75
|
return invokeCallback((0, getClassOrSymbol_1.getClassOrSymbol)(token), locals);
|
|
79
76
|
}
|
|
80
77
|
const value = param.fun(context, param.param);
|
|
81
|
-
return
|
|
82
|
-
? this.validateAndTransform(token, value)
|
|
78
|
+
return this.validatorAdapter.hasValidation(token)
|
|
79
|
+
? this.validatorAdapter.validateAndTransform(token, value)
|
|
83
80
|
: value;
|
|
84
81
|
}
|
|
85
|
-
validateAndTransform(token, value) {
|
|
86
|
-
const obj = (0, class_transformer_1.plainToInstance)(token, value);
|
|
87
|
-
const errors = (0, class_validator_1.validateSync)(obj, this.applicationConfig.validation);
|
|
88
|
-
// todo: deve retornar apenas os erros e no o objeto class-validator intei
|
|
89
|
-
// ro.
|
|
90
|
-
if (errors.length > 0) {
|
|
91
|
-
throw new HttpException_1.HttpException(errors, 400);
|
|
92
|
-
}
|
|
93
|
-
return obj;
|
|
94
|
-
}
|
|
95
82
|
}
|
|
96
83
|
exports.MethodInvoker = MethodInvoker;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,3 +29,4 @@ __exportStar(require("./services/logger.service"), exports);
|
|
|
29
29
|
__exportStar(require("./cache/cache.service"), exports);
|
|
30
30
|
__exportStar(require("./cache/bento-cache.driver"), exports);
|
|
31
31
|
__exportStar(require("./testing"), exports);
|
|
32
|
+
__exportStar(require("./validation"), exports);
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { type ValidatorOptions } from 'class-validator';
|
|
2
1
|
import type { Context } from '../domain/Context';
|
|
3
|
-
import type { ParamResolver, AsyncParamResolver } from './CompiledRoute';
|
|
4
2
|
export type ParamDecoratorType = 'body' | 'query' | 'param' | 'headers' | 'req' | 'locals';
|
|
5
3
|
export interface ParamDecoratorMeta {
|
|
6
4
|
fun: (context: Context, data?: any) => any;
|
|
@@ -14,6 +12,3 @@ export interface ParamInfo {
|
|
|
14
12
|
token?: any;
|
|
15
13
|
}
|
|
16
14
|
export declare function analyzeParamDecorator(decoratorMeta: ParamDecoratorMeta | undefined, token: any): ParamInfo;
|
|
17
|
-
export declare function createParamResolver(decoratorMeta: ParamDecoratorMeta | undefined, token: any, validationConfig?: ValidatorOptions): ParamResolver | AsyncParamResolver | null;
|
|
18
|
-
export declare function createParamResolvers(paramMetas: Record<number, ParamDecoratorMeta> | undefined, argTypes: any[], validationConfig?: ValidatorOptions): (ParamResolver | AsyncParamResolver | null)[];
|
|
19
|
-
export declare function hasAnyDIParam(resolvers: (ParamResolver | AsyncParamResolver | null)[]): boolean;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.analyzeParamDecorator = analyzeParamDecorator;
|
|
4
|
-
exports.createParamResolver = createParamResolver;
|
|
5
|
-
exports.createParamResolvers = createParamResolvers;
|
|
6
|
-
exports.hasAnyDIParam = hasAnyDIParam;
|
|
7
|
-
const class_transformer_1 = require("class-transformer");
|
|
8
|
-
const class_validator_1 = require("class-validator");
|
|
9
|
-
const HttpException_1 = require("../exceptions/HttpException");
|
|
10
4
|
const ValidationCache_1 = require("../utils/ValidationCache");
|
|
11
5
|
function analyzeParamDecorator(decoratorMeta, token) {
|
|
12
6
|
if (!decoratorMeta) {
|
|
@@ -53,37 +47,3 @@ function inferTypeFromSource(resolver) {
|
|
|
53
47
|
}
|
|
54
48
|
return null;
|
|
55
49
|
}
|
|
56
|
-
function createValidationResolver(extractFn, token, validationConfig) {
|
|
57
|
-
return (context) => {
|
|
58
|
-
const value = extractFn(context);
|
|
59
|
-
const obj = (0, class_transformer_1.plainToInstance)(token, value);
|
|
60
|
-
const errors = (0, class_validator_1.validateSync)(obj, validationConfig);
|
|
61
|
-
if (errors.length > 0) {
|
|
62
|
-
throw new HttpException_1.HttpException(errors, 400);
|
|
63
|
-
}
|
|
64
|
-
return obj;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function createParamResolver(decoratorMeta, token, validationConfig) {
|
|
68
|
-
if (!decoratorMeta) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
const extractFn = (context) => decoratorMeta.fun(context, decoratorMeta.param);
|
|
72
|
-
const needsValidation = typeof token === 'function' && (0, ValidationCache_1.isValidatable)(token);
|
|
73
|
-
if (needsValidation) {
|
|
74
|
-
return createValidationResolver(extractFn, token, validationConfig);
|
|
75
|
-
}
|
|
76
|
-
return extractFn;
|
|
77
|
-
}
|
|
78
|
-
function createParamResolvers(paramMetas, argTypes, validationConfig) {
|
|
79
|
-
const resolvers = [];
|
|
80
|
-
for (let i = 0; i < argTypes.length; i++) {
|
|
81
|
-
const meta = paramMetas?.[i];
|
|
82
|
-
const token = argTypes[i];
|
|
83
|
-
resolvers.push(createParamResolver(meta, token, validationConfig));
|
|
84
|
-
}
|
|
85
|
-
return resolvers;
|
|
86
|
-
}
|
|
87
|
-
function hasAnyDIParam(resolvers) {
|
|
88
|
-
return resolvers.some((r) => r === null);
|
|
89
|
-
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { type ValidatorOptions } from 'class-validator';
|
|
2
1
|
import type { TokenRouteWithProvider } from '../container/ContainerConfiguration';
|
|
2
|
+
import type { ValidatorAdapter } from '../validation/ValidatorAdapter';
|
|
3
3
|
import type { Container } from '../container/container';
|
|
4
4
|
import { ProviderScope } from '../domain/provider-scope';
|
|
5
5
|
import { type CompiledRoute } from './CompiledRoute';
|
|
6
6
|
export interface RouteCompilerOptions {
|
|
7
7
|
container: Container;
|
|
8
8
|
controllerScopes: Map<any, ProviderScope>;
|
|
9
|
-
|
|
9
|
+
validatorAdapter: ValidatorAdapter;
|
|
10
10
|
hasOnRequestHook: boolean;
|
|
11
11
|
hasOnResponseHook: boolean;
|
|
12
12
|
}
|
|
13
13
|
export declare class RouteCompiler {
|
|
14
14
|
private container;
|
|
15
15
|
private controllerScopes;
|
|
16
|
-
private
|
|
16
|
+
private validatorAdapter;
|
|
17
17
|
private hasOnRequestHook;
|
|
18
18
|
private hasOnResponseHook;
|
|
19
19
|
constructor(options: RouteCompilerOptions);
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RouteCompiler = void 0;
|
|
4
|
-
const class_transformer_1 = require("class-transformer");
|
|
5
|
-
const class_validator_1 = require("class-validator");
|
|
6
4
|
const provider_scope_1 = require("../domain/provider-scope");
|
|
7
5
|
const Metadata_1 = require("../domain/Metadata");
|
|
8
|
-
const HttpException_1 = require("../exceptions/HttpException");
|
|
9
6
|
const getMethodArgTypes_1 = require("../utils/getMethodArgTypes");
|
|
10
7
|
const CompiledRoute_1 = require("./CompiledRoute");
|
|
11
8
|
const ParamResolverFactory_1 = require("./ParamResolverFactory");
|
|
@@ -14,7 +11,7 @@ class RouteCompiler {
|
|
|
14
11
|
constructor(options) {
|
|
15
12
|
this.container = options.container;
|
|
16
13
|
this.controllerScopes = options.controllerScopes;
|
|
17
|
-
this.
|
|
14
|
+
this.validatorAdapter = options.validatorAdapter;
|
|
18
15
|
this.hasOnRequestHook = options.hasOnRequestHook;
|
|
19
16
|
this.hasOnResponseHook = options.hasOnResponseHook;
|
|
20
17
|
}
|
|
@@ -158,7 +155,6 @@ class RouteCompiler {
|
|
|
158
155
|
}
|
|
159
156
|
createValidatedHandler(instance, methodName, paramInfos, hasBodyParam) {
|
|
160
157
|
const handler = instance[methodName].bind(instance);
|
|
161
|
-
const config = this.validationConfig;
|
|
162
158
|
const resolveArg = (ctx, param) => {
|
|
163
159
|
let value;
|
|
164
160
|
switch (param.type) {
|
|
@@ -184,12 +180,7 @@ class RouteCompiler {
|
|
|
184
180
|
value = undefined;
|
|
185
181
|
}
|
|
186
182
|
if (param.needsValidation && param.token) {
|
|
187
|
-
|
|
188
|
-
const errors = (0, class_validator_1.validateSync)(obj, config);
|
|
189
|
-
if (errors.length > 0) {
|
|
190
|
-
throw new HttpException_1.HttpException(errors, 400);
|
|
191
|
-
}
|
|
192
|
-
return obj;
|
|
183
|
+
return this.validatorAdapter.validateAndTransform(param.token, value);
|
|
193
184
|
}
|
|
194
185
|
return value;
|
|
195
186
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ValidatorAdapter } from '../validation/ValidatorAdapter';
|
|
2
|
+
export declare function setValidatorAdapter(adapter: ValidatorAdapter): void;
|
|
1
3
|
export declare function isValidatable(token: Function): boolean;
|
|
2
4
|
export declare function preloadValidationForParams(args: any[]): number[];
|
|
3
5
|
export declare function clearValidationCache(): void;
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setValidatorAdapter = setValidatorAdapter;
|
|
3
4
|
exports.isValidatable = isValidatable;
|
|
4
5
|
exports.preloadValidationForParams = preloadValidationForParams;
|
|
5
6
|
exports.clearValidationCache = clearValidationCache;
|
|
6
|
-
|
|
7
|
+
let currentAdapter = null;
|
|
7
8
|
const cache = new Map();
|
|
9
|
+
function setValidatorAdapter(adapter) {
|
|
10
|
+
currentAdapter = adapter;
|
|
11
|
+
cache.clear();
|
|
12
|
+
}
|
|
8
13
|
function isValidatable(token) {
|
|
14
|
+
if (!currentAdapter) {
|
|
15
|
+
throw new Error('Validator adapter not initialized. Call setValidatorAdapter() first.');
|
|
16
|
+
}
|
|
9
17
|
let result = cache.get(token);
|
|
10
18
|
if (result === undefined) {
|
|
11
|
-
result =
|
|
19
|
+
result = currentAdapter.hasValidation(token);
|
|
12
20
|
cache.set(token, result);
|
|
13
21
|
}
|
|
14
22
|
return result;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export * from './isString';
|
|
|
4
4
|
export * from './isFunction';
|
|
5
5
|
export * from './getConstructorArgNames';
|
|
6
6
|
export * from './getValue';
|
|
7
|
-
export * from './isClassValidator';
|
|
8
7
|
export * from './getMethodArgTypes';
|
|
9
8
|
export * from './getClassOrSymbol';
|
|
10
9
|
export * from './isRequestScope';
|
package/dist/utils/index.js
CHANGED
|
@@ -20,7 +20,6 @@ __exportStar(require("./isString"), exports);
|
|
|
20
20
|
__exportStar(require("./isFunction"), exports);
|
|
21
21
|
__exportStar(require("./getConstructorArgNames"), exports);
|
|
22
22
|
__exportStar(require("./getValue"), exports);
|
|
23
|
-
__exportStar(require("./isClassValidator"), exports);
|
|
24
23
|
__exportStar(require("./getMethodArgTypes"), exports);
|
|
25
24
|
__exportStar(require("./getClassOrSymbol"), exports);
|
|
26
25
|
__exportStar(require("./isRequestScope"), exports);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for validation adapters
|
|
3
|
+
* Adapters provide validation capabilities for different libraries (Zod, class-validator, etc.)
|
|
4
|
+
*/
|
|
5
|
+
export interface ValidatorAdapter<TOptions = any> {
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a class/function has validation metadata
|
|
8
|
+
* Used by ValidationCache for detection and optimization
|
|
9
|
+
*
|
|
10
|
+
* @param target - The class/function to check
|
|
11
|
+
* @returns true if validation metadata exists
|
|
12
|
+
*/
|
|
13
|
+
hasValidation(target: Function): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Validates and transforms a value to the target type
|
|
16
|
+
*
|
|
17
|
+
* @param target - The DTO class to validate against
|
|
18
|
+
* @param value - The raw value to validate (usually from request body/query/params)
|
|
19
|
+
* @returns Transformed and validated instance
|
|
20
|
+
* @throws HttpException with formatted errors on validation failure
|
|
21
|
+
*/
|
|
22
|
+
validateAndTransform(target: Function, value: any): any;
|
|
23
|
+
/**
|
|
24
|
+
* Get the name of the validator (for debugging/logging)
|
|
25
|
+
*
|
|
26
|
+
* @returns The validator name (e.g., "ZodAdapter", "ClassValidatorAdapter")
|
|
27
|
+
*/
|
|
28
|
+
getName(): string;
|
|
29
|
+
/**
|
|
30
|
+
* Get the adapter options (for internal use)
|
|
31
|
+
*/
|
|
32
|
+
getOptions(): TOptions;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Constructor type for validator adapters
|
|
36
|
+
*/
|
|
37
|
+
export type ValidatorAdapterConstructor<TOptions = any> = new (options?: TOptions) => ValidatorAdapter<TOptions>;
|
|
38
|
+
/**
|
|
39
|
+
* Type to extract options type from a ValidatorAdapter constructor
|
|
40
|
+
*/
|
|
41
|
+
export type ValidatorAdapterOptions<TAdapter> = TAdapter extends new (options?: infer TOptions) => any ? TOptions : never;
|
|
42
|
+
/**
|
|
43
|
+
* Validation configuration with adapter and options
|
|
44
|
+
*/
|
|
45
|
+
export interface ValidationConfig<TAdapter extends ValidatorAdapterConstructor = ValidatorAdapterConstructor> {
|
|
46
|
+
adapter?: TAdapter;
|
|
47
|
+
options?: ValidatorAdapterOptions<TAdapter>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Helper function to create a strongly-typed validation config.
|
|
51
|
+
* Use this to get proper autocomplete for adapter options.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const app = new Carno({
|
|
56
|
+
* validation: defineValidation({
|
|
57
|
+
* adapter: ClassValidatorAdapter,
|
|
58
|
+
* options: { whitelist: true } // ✓ Autocomplete works!
|
|
59
|
+
* })
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function defineValidation<TAdapter extends ValidatorAdapterConstructor>(config: {
|
|
64
|
+
adapter: TAdapter;
|
|
65
|
+
options?: ValidatorAdapterOptions<TAdapter>;
|
|
66
|
+
}): ValidationConfig<TAdapter>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defineValidation = defineValidation;
|
|
4
|
+
/**
|
|
5
|
+
* Helper function to create a strongly-typed validation config.
|
|
6
|
+
* Use this to get proper autocomplete for adapter options.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const app = new Carno({
|
|
11
|
+
* validation: defineValidation({
|
|
12
|
+
* adapter: ClassValidatorAdapter,
|
|
13
|
+
* options: { whitelist: true } // ✓ Autocomplete works!
|
|
14
|
+
* })
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function defineValidation(config) {
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ValidatorAdapter } from "../ValidatorAdapter";
|
|
2
|
+
export interface ClassValidatorAdapterOptions {
|
|
3
|
+
skipMissingProperties?: boolean;
|
|
4
|
+
whitelist?: boolean;
|
|
5
|
+
forbidNonWhitelisted?: boolean;
|
|
6
|
+
forbidUnknownValues?: boolean;
|
|
7
|
+
disableErrorMessages?: boolean;
|
|
8
|
+
errorHttpStatusCode?: number;
|
|
9
|
+
dismissDefaultMessages?: boolean;
|
|
10
|
+
validationError?: {
|
|
11
|
+
target?: boolean;
|
|
12
|
+
value?: boolean;
|
|
13
|
+
};
|
|
14
|
+
stopAtFirstError?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class ClassValidatorAdapter implements ValidatorAdapter<ClassValidatorAdapterOptions> {
|
|
17
|
+
private options;
|
|
18
|
+
constructor(options?: ClassValidatorAdapterOptions);
|
|
19
|
+
getName(): string;
|
|
20
|
+
getOptions(): ClassValidatorAdapterOptions;
|
|
21
|
+
hasValidation(target: Function): boolean;
|
|
22
|
+
validateAndTransform(target: Function, value: any): any;
|
|
23
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClassValidatorAdapter = void 0;
|
|
4
|
+
const HttpException_1 = require("../../exceptions/HttpException");
|
|
5
|
+
const formatValidationErrors_1 = require("../../utils/formatValidationErrors");
|
|
6
|
+
let classValidator;
|
|
7
|
+
let classTransformer;
|
|
8
|
+
try {
|
|
9
|
+
classValidator = require("class-validator");
|
|
10
|
+
classTransformer = require("class-transformer");
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
// Will be checked in constructor
|
|
14
|
+
}
|
|
15
|
+
class ClassValidatorAdapter {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
if (!classValidator || !classTransformer) {
|
|
19
|
+
throw new Error("ClassValidatorAdapter requires class-validator and class-transformer. " +
|
|
20
|
+
"Install with: npm install class-validator class-transformer");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
getName() {
|
|
24
|
+
return "ClassValidatorAdapter";
|
|
25
|
+
}
|
|
26
|
+
getOptions() {
|
|
27
|
+
return this.options;
|
|
28
|
+
}
|
|
29
|
+
hasValidation(target) {
|
|
30
|
+
const { getMetadataStorage } = classValidator;
|
|
31
|
+
const metadataStorage = getMetadataStorage();
|
|
32
|
+
const validationMetadata = metadataStorage.getTargetValidationMetadatas(target, "", false, false, []);
|
|
33
|
+
return validationMetadata.length > 0;
|
|
34
|
+
}
|
|
35
|
+
validateAndTransform(target, value) {
|
|
36
|
+
const { validateSync } = classValidator;
|
|
37
|
+
const { plainToInstance } = classTransformer;
|
|
38
|
+
const instance = plainToInstance(target, value);
|
|
39
|
+
const errors = validateSync(instance, this.options);
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
const formatted = (0, formatValidationErrors_1.formatValidationErrors)(errors);
|
|
42
|
+
throw new HttpException_1.HttpException(formatted, 400);
|
|
43
|
+
}
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ClassValidatorAdapter = ClassValidatorAdapter;
|