@velajs/vela 0.4.1 → 0.4.2

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.
@@ -148,6 +148,8 @@ export class RouteManager {
148
148
  }
149
149
  createHandler(instance, route, controller, allParamMetadata) {
150
150
  const paramMetadata = (allParamMetadata.get(route.handlerName) || []).sort((a, b)=>a.index - b.index);
151
+ // Read param types once at build time for metatype population
152
+ const paramTypes = Reflect.getMetadata('design:paramtypes', instance.constructor.prototype, route.handlerName);
151
153
  // Pre-resolve controller + method level components at build time
152
154
  const methodGuards = ComponentManager.getComponents('guard', controller, route.handlerName);
153
155
  const methodPipes = ComponentManager.getComponents('pipe', controller, route.handlerName);
@@ -188,7 +190,7 @@ export class RouteManager {
188
190
  const executionContext = this.createExecutionContext(c, controller, route);
189
191
  try {
190
192
  // 1. Extract args + run pipes
191
- const args = await this.extractArguments(c, paramMetadata, pipes);
193
+ const args = await this.extractArguments(c, paramMetadata, pipes, paramTypes);
192
194
  // 2. Guards (fail-fast)
193
195
  for (const guard of guards){
194
196
  const canActivate = await guard.canActivate(executionContext);
@@ -245,7 +247,7 @@ export class RouteManager {
245
247
  }
246
248
  };
247
249
  }
248
- async extractArguments(c, paramMetadata, pipes) {
250
+ async extractArguments(c, paramMetadata, pipes, paramTypes) {
249
251
  if (paramMetadata.length === 0) {
250
252
  return [
251
253
  c
@@ -258,7 +260,7 @@ export class RouteManager {
258
260
  const metadata = {
259
261
  type: param.type,
260
262
  data: param.name,
261
- metatype: undefined
263
+ metatype: paramTypes?.[param.index]
262
264
  };
263
265
  // Run shared pipes (global + controller + method)
264
266
  for (const pipe of pipes){
package/dist/index.d.ts CHANGED
@@ -34,3 +34,6 @@ export { MetadataRegistry } from './registry/index';
34
34
  export { RouteManager } from './http/index';
35
35
  export { ModuleLoader } from './module/index';
36
36
  export { ComponentManager } from './pipeline/index';
37
+ export { createZodDto, ValidationPipe } from './validation/index';
38
+ export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from './serialization/index';
39
+ export { Test, TestingModule, TestingModuleBuilder } from './testing/index';
package/dist/index.js CHANGED
@@ -40,3 +40,9 @@ export { RouteManager } from "./http/index.js";
40
40
  export { ModuleLoader } from "./module/index.js";
41
41
  // Component Manager (for advanced usage)
42
42
  export { ComponentManager } from "./pipeline/index.js";
43
+ // Validation
44
+ export { createZodDto, ValidationPipe } from "./validation/index.js";
45
+ // Serialization
46
+ export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from "./serialization/index.js";
47
+ // Testing
48
+ export { Test, TestingModule, TestingModuleBuilder } from "./testing/index.js";
@@ -0,0 +1,2 @@
1
+ export { Serialize, SERIALIZE_METADATA } from './serialize.decorator';
2
+ export { SerializerInterceptor } from './serializer.interceptor';
@@ -0,0 +1,2 @@
1
+ export { Serialize, SERIALIZE_METADATA } from "./serialize.decorator.js";
2
+ export { SerializerInterceptor } from "./serializer.interceptor.js";
@@ -0,0 +1,6 @@
1
+ export declare const SERIALIZE_METADATA = "vela:serialize";
2
+ export declare function Serialize(dto: {
3
+ schema: {
4
+ parse(data: unknown): unknown;
5
+ };
6
+ }): MethodDecorator;
@@ -0,0 +1,7 @@
1
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
2
+ export const SERIALIZE_METADATA = 'vela:serialize';
3
+ export function Serialize(dto) {
4
+ return (target, propertyKey)=>{
5
+ MetadataRegistry.setCustomHandlerMeta(target.constructor, propertyKey, SERIALIZE_METADATA, dto);
6
+ };
7
+ }
@@ -0,0 +1,4 @@
1
+ import type { CallHandler, ExecutionContext, NestInterceptor } from '../pipeline/types';
2
+ export declare class SerializerInterceptor implements NestInterceptor {
3
+ intercept(context: ExecutionContext, next: CallHandler): Promise<unknown>;
4
+ }
@@ -0,0 +1,16 @@
1
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
2
+ import { SERIALIZE_METADATA } from "./serialize.decorator.js";
3
+ export class SerializerInterceptor {
4
+ async intercept(context, next) {
5
+ const result = await next.handle();
6
+ const controller = context.getClass();
7
+ const handler = context.getHandler();
8
+ const dto = MetadataRegistry.getCustomHandlerMeta(controller, handler, SERIALIZE_METADATA);
9
+ const schema = dto?.schema;
10
+ if (!schema?.parse) return result;
11
+ if (Array.isArray(result)) {
12
+ return result.map((item)=>schema.parse(item));
13
+ }
14
+ return schema.parse(result);
15
+ }
16
+ }
@@ -0,0 +1,2 @@
1
+ export { Test, TestingModuleBuilder, OverrideBy } from './testing.builder';
2
+ export { TestingModule } from './testing.module';
@@ -0,0 +1,2 @@
1
+ export { Test, TestingModuleBuilder, OverrideBy } from "./testing.builder.js";
2
+ export { TestingModule } from "./testing.module.js";
@@ -0,0 +1,29 @@
1
+ import type { Token, Type } from '../container/types';
2
+ import type { ModuleOptions } from '../module/types';
3
+ import { TestingModule } from './testing.module';
4
+ export declare class OverrideBy {
5
+ private readonly builder;
6
+ private readonly token;
7
+ constructor(builder: TestingModuleBuilder, token: Token);
8
+ useValue(value: unknown): TestingModuleBuilder;
9
+ useClass(cls: Type): TestingModuleBuilder;
10
+ useFactory(options: {
11
+ factory: (...args: unknown[]) => unknown;
12
+ inject?: Token[];
13
+ }): TestingModuleBuilder;
14
+ }
15
+ export declare class TestingModuleBuilder {
16
+ private readonly metadata;
17
+ private overrides;
18
+ constructor(metadata: ModuleOptions);
19
+ overrideProvider(token: Token): OverrideBy;
20
+ overrideGuard(guard: Type): OverrideBy;
21
+ overridePipe(pipe: Type): OverrideBy;
22
+ overrideInterceptor(interceptor: Type): OverrideBy;
23
+ overrideFilter(filter: Type): OverrideBy;
24
+ private addOverride;
25
+ compile(): Promise<TestingModule>;
26
+ }
27
+ export declare class Test {
28
+ static createTestingModule(metadata: ModuleOptions): TestingModuleBuilder;
29
+ }
@@ -0,0 +1,134 @@
1
+ import { VelaApplication } from "../application.js";
2
+ import { METADATA_KEYS } from "../constants.js";
3
+ import { Container } from "../container/container.js";
4
+ import { RouteManager } from "../http/route.manager.js";
5
+ import { defineMetadata } from "../metadata.js";
6
+ import { ModuleLoader } from "../module/module-loader.js";
7
+ import { ComponentManager } from "../pipeline/component.manager.js";
8
+ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
9
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
10
+ import { TestingModule } from "./testing.module.js";
11
+ export class OverrideBy {
12
+ builder;
13
+ token;
14
+ constructor(builder, token){
15
+ this.builder = builder;
16
+ this.token = token;
17
+ }
18
+ useValue(value) {
19
+ this.builder['addOverride']({
20
+ token: this.token,
21
+ provider: {
22
+ token: this.token,
23
+ useValue: value
24
+ }
25
+ });
26
+ return this.builder;
27
+ }
28
+ useClass(cls) {
29
+ this.builder['addOverride']({
30
+ token: this.token,
31
+ provider: {
32
+ token: this.token,
33
+ useFactory: ()=>new cls()
34
+ }
35
+ });
36
+ return this.builder;
37
+ }
38
+ useFactory(options) {
39
+ this.builder['addOverride']({
40
+ token: this.token,
41
+ provider: {
42
+ token: this.token,
43
+ useFactory: options.factory,
44
+ inject: options.inject
45
+ }
46
+ });
47
+ return this.builder;
48
+ }
49
+ }
50
+ export class TestingModuleBuilder {
51
+ metadata;
52
+ overrides = [];
53
+ constructor(metadata){
54
+ this.metadata = metadata;
55
+ }
56
+ overrideProvider(token) {
57
+ return new OverrideBy(this, token);
58
+ }
59
+ overrideGuard(guard) {
60
+ return new OverrideBy(this, guard);
61
+ }
62
+ overridePipe(pipe) {
63
+ return new OverrideBy(this, pipe);
64
+ }
65
+ overrideInterceptor(interceptor) {
66
+ return new OverrideBy(this, interceptor);
67
+ }
68
+ overrideFilter(filter) {
69
+ return new OverrideBy(this, filter);
70
+ }
71
+ addOverride(entry) {
72
+ // Replace existing override for same token if any
73
+ const idx = this.overrides.findIndex((o)=>o.token === entry.token);
74
+ if (idx !== -1) {
75
+ this.overrides[idx] = entry;
76
+ } else {
77
+ this.overrides.push(entry);
78
+ }
79
+ }
80
+ async compile() {
81
+ // Create a temporary module class with the provided metadata
82
+ let TestRootModule = class TestRootModule {
83
+ };
84
+ MetadataRegistry.setModuleOptions(TestRootModule, {
85
+ imports: this.metadata.imports,
86
+ providers: this.metadata.providers,
87
+ controllers: this.metadata.controllers,
88
+ exports: this.metadata.exports
89
+ });
90
+ defineMetadata(METADATA_KEYS.MODULE, true, TestRootModule);
91
+ // Bootstrap (mirrors VelaFactory.create)
92
+ const container = new Container();
93
+ container.register({
94
+ token: Container,
95
+ useValue: container
96
+ });
97
+ // Register overrides BEFORE module loading so ModuleLoader skips originals
98
+ for (const override of this.overrides){
99
+ container.register(override.provider);
100
+ }
101
+ const routeManager = new RouteManager(container);
102
+ ComponentManager.init(container);
103
+ const loader = new ModuleLoader(container, routeManager);
104
+ loader.load(TestRootModule);
105
+ // Resolve APP_* tokens
106
+ if (container.has(APP_GUARD)) {
107
+ routeManager.useGlobalGuards(container.resolve(APP_GUARD));
108
+ }
109
+ if (container.has(APP_PIPE)) {
110
+ routeManager.useGlobalPipes(container.resolve(APP_PIPE));
111
+ }
112
+ if (container.has(APP_INTERCEPTOR)) {
113
+ routeManager.useGlobalInterceptors(container.resolve(APP_INTERCEPTOR));
114
+ }
115
+ if (container.has(APP_FILTER)) {
116
+ routeManager.useGlobalFilters(container.resolve(APP_FILTER));
117
+ }
118
+ if (container.has(APP_MIDDLEWARE)) {
119
+ routeManager.useGlobalMiddleware(container.resolve(APP_MIDDLEWARE));
120
+ }
121
+ const app = new VelaApplication(container, routeManager);
122
+ const instances = loader.resolveAllInstances();
123
+ app.setInstances(instances);
124
+ await app.callOnModuleInit();
125
+ await app.callOnApplicationBootstrap();
126
+ await app.initRoutes();
127
+ return new TestingModule(app);
128
+ }
129
+ }
130
+ export class Test {
131
+ static createTestingModule(metadata) {
132
+ return new TestingModuleBuilder(metadata);
133
+ }
134
+ }
@@ -0,0 +1,9 @@
1
+ import type { VelaApplication } from '../application';
2
+ import type { Token } from '../container/types';
3
+ export declare class TestingModule {
4
+ private readonly app;
5
+ constructor(app: VelaApplication);
6
+ get<T>(token: Token<T>): T;
7
+ close(signal?: string): Promise<void>;
8
+ createNestApplication(): VelaApplication;
9
+ }
@@ -0,0 +1,15 @@
1
+ export class TestingModule {
2
+ app;
3
+ constructor(app){
4
+ this.app = app;
5
+ }
6
+ get(token) {
7
+ return this.app.get(token);
8
+ }
9
+ async close(signal) {
10
+ await this.app.close(signal);
11
+ }
12
+ createNestApplication() {
13
+ return this.app;
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ interface ZodSchema {
2
+ parse(data: unknown): unknown;
3
+ }
4
+ export declare function createZodDto<T extends ZodSchema>(schema: T): {
5
+ new (): unknown;
6
+ schema: T;
7
+ };
8
+ export {};
@@ -0,0 +1,6 @@
1
+ export function createZodDto(schema) {
2
+ let ZodDto = class ZodDto {
3
+ static schema = schema;
4
+ };
5
+ return ZodDto;
6
+ }
@@ -0,0 +1,2 @@
1
+ export { createZodDto } from './create-zod-dto';
2
+ export { ValidationPipe } from './validation.pipe';
@@ -0,0 +1,2 @@
1
+ export { createZodDto } from "./create-zod-dto.js";
2
+ export { ValidationPipe } from "./validation.pipe.js";
@@ -0,0 +1,4 @@
1
+ import type { ArgumentMetadata, PipeTransform } from '../pipeline/types';
2
+ export declare class ValidationPipe implements PipeTransform {
3
+ transform(value: unknown, metadata: ArgumentMetadata): unknown;
4
+ }
@@ -0,0 +1,19 @@
1
+ import { BadRequestException } from "../errors/http-exception.js";
2
+ export class ValidationPipe {
3
+ transform(value, metadata) {
4
+ const metatype = metadata.metatype;
5
+ if (!metatype?.schema?.parse) return value;
6
+ try {
7
+ return metatype.schema.parse(value);
8
+ } catch (error) {
9
+ if (error && typeof error === 'object' && 'issues' in error && Array.isArray(error.issues)) {
10
+ throw new BadRequestException('Validation failed', {
11
+ statusCode: 400,
12
+ message: 'Validation failed',
13
+ errors: error.issues
14
+ });
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -66,7 +66,8 @@
66
66
  "@types/bun": "latest",
67
67
  "typescript": "^5",
68
68
  "unplugin-swc": "^1.5.9",
69
- "vitest": "^4.0.18"
69
+ "vitest": "^4.0.18",
70
+ "zod": "^4.3.6"
70
71
  },
71
72
  "scripts": {
72
73
  "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",