@velajs/vela 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,12 @@
1
- import { METADATA_KEYS } from "../constants.js";
2
- import { defineMetadata } from "../metadata.js";
3
- import { MetadataRegistry } from "../registry/metadata.registry.js";
4
1
  import { APP_INTERCEPTOR } from "../pipeline/tokens.js";
5
2
  import { CacheInterceptor } from "./cache.interceptor.js";
6
3
  import { CacheService } from "./cache.service.js";
7
4
  import { MemoryCacheStore } from "./cache.store.js";
8
5
  import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from "./cache.tokens.js";
9
- function makeCacheModuleClass(name) {
10
- const moduleClass = class {
11
- };
12
- Object.defineProperty(moduleClass, 'name', {
13
- value: name
14
- });
15
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
16
- return moduleClass;
17
- }
18
6
  export class CacheModule {
19
7
  static forRoot(options = {}) {
20
8
  const { ttl = 5, max = 100, isGlobal = false } = options;
21
9
  const store = new MemoryCacheStore(ttl, max);
22
- const moduleClass = makeCacheModuleClass('CacheModule');
23
- MetadataRegistry.setModuleOptions(moduleClass, {
24
- exports: [
25
- CACHE_MANAGER,
26
- CACHE_MODULE_OPTIONS,
27
- CacheService,
28
- CacheInterceptor
29
- ]
30
- });
31
10
  const providers = [
32
11
  {
33
12
  provide: CACHE_MANAGER,
@@ -47,21 +26,17 @@ export class CacheModule {
47
26
  });
48
27
  }
49
28
  return {
50
- module: moduleClass,
51
- providers
52
- };
53
- }
54
- static registerAsync(options) {
55
- const moduleClass = makeCacheModuleClass('CacheModule');
56
- MetadataRegistry.setModuleOptions(moduleClass, {
57
- imports: options.imports ?? [],
29
+ module: CacheModule,
30
+ providers,
58
31
  exports: [
59
32
  CACHE_MANAGER,
60
33
  CACHE_MODULE_OPTIONS,
61
34
  CacheService,
62
35
  CacheInterceptor
63
36
  ]
64
- });
37
+ };
38
+ }
39
+ static registerAsync(options) {
65
40
  const providers = [
66
41
  {
67
42
  provide: CACHE_MODULE_OPTIONS,
@@ -85,8 +60,15 @@ export class CacheModule {
85
60
  });
86
61
  }
87
62
  return {
88
- module: moduleClass,
89
- providers
63
+ module: CacheModule,
64
+ imports: options.imports ?? [],
65
+ providers,
66
+ exports: [
67
+ CACHE_MANAGER,
68
+ CACHE_MODULE_OPTIONS,
69
+ CacheService,
70
+ CacheInterceptor
71
+ ]
90
72
  };
91
73
  }
92
74
  }
@@ -1,26 +1,10 @@
1
- import { METADATA_KEYS } from "../constants.js";
2
- import { defineMetadata } from "../metadata.js";
3
- import { MetadataRegistry } from "../registry/metadata.registry.js";
4
1
  import { ConfigService } from "./config.service.js";
5
2
  import { CONFIG_OPTIONS } from "./config.tokens.js";
6
3
  export class ConfigModule {
7
4
  static forRoot(options) {
8
5
  const config = options.validate ? options.validate(options.config) : options.config;
9
- const moduleClass = class ConfigDynamicModule {
10
- };
11
- Object.defineProperty(moduleClass, 'name', {
12
- value: 'ConfigModule'
13
- });
14
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
15
- MetadataRegistry.setModuleOptions(moduleClass, {
16
- exports: [
17
- ConfigService,
18
- CONFIG_OPTIONS
19
- ],
20
- isGlobal: options.isGlobal
21
- });
22
6
  return {
23
- module: moduleClass,
7
+ module: ConfigModule,
24
8
  providers: [
25
9
  {
26
10
  provide: CONFIG_OPTIONS,
@@ -28,28 +12,19 @@ export class ConfigModule {
28
12
  },
29
13
  ConfigService
30
14
  ],
15
+ exports: [
16
+ ConfigService,
17
+ CONFIG_OPTIONS
18
+ ],
31
19
  ...options.isGlobal ? {
32
20
  global: true
33
21
  } : {}
34
22
  };
35
23
  }
36
24
  static forRootAsync(options) {
37
- const moduleClass = class ConfigAsyncDynamicModule {
38
- };
39
- Object.defineProperty(moduleClass, 'name', {
40
- value: 'ConfigModule'
41
- });
42
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
43
- MetadataRegistry.setModuleOptions(moduleClass, {
44
- imports: options.imports ?? [],
45
- exports: [
46
- ConfigService,
47
- CONFIG_OPTIONS
48
- ],
49
- isGlobal: options.isGlobal
50
- });
51
25
  return {
52
- module: moduleClass,
26
+ module: ConfigModule,
27
+ imports: options.imports ?? [],
53
28
  providers: [
54
29
  {
55
30
  provide: CONFIG_OPTIONS,
@@ -61,6 +36,10 @@ export class ConfigModule {
61
36
  },
62
37
  ConfigService
63
38
  ],
39
+ exports: [
40
+ ConfigService,
41
+ CONFIG_OPTIONS
42
+ ],
64
43
  ...options.isGlobal ? {
65
44
  global: true
66
45
  } : {}
@@ -1,6 +1,7 @@
1
1
  import { Scope } from "../constants.js";
2
2
  import { getConstructorDependencies, getInjectMetadata, getScope, isInjectable } from "./decorators.js";
3
3
  import { ForwardRef, InjectionToken } from "./types.js";
4
+ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips type-only imports at ' + 'runtime and `design:paramtypes` emits `Object`/`undefined` for their ' + 'positions. Use a runtime `import { X }` for DI tokens.';
4
5
  export class Container {
5
6
  providers = new Map();
6
7
  resolutionStack = new Set();
@@ -52,6 +53,12 @@ export class Container {
52
53
  resolve(token) {
53
54
  const registration = this.providers.get(token);
54
55
  if (!registration) {
56
+ // `Object`/undefined at a token position is the fingerprint of a
57
+ // type-only import that TypeScript stripped — emit the hint BEFORE
58
+ // auto-registering Object as a provider (which would silently "succeed").
59
+ if (token === Object || token == null) {
60
+ throw new Error(`No provider found for token: ${this.tokenToString(token)}. ` + IMPORT_TYPE_HINT);
61
+ }
55
62
  if (typeof token === 'function') {
56
63
  this.register(token);
57
64
  return this.resolve(token);
@@ -155,7 +162,7 @@ export class Container {
155
162
  const token = isForwardRef ? rawToken.factory() : rawToken ?? paramType;
156
163
  if (!token || token === Object) {
157
164
  if (meta?.optional) return undefined;
158
- throw new Error(`Cannot resolve dependency at index ${index} for ${target.name}. ` + `Parameter type is undefined or Object. ` + `Use @Inject() to specify the token explicitly.`);
165
+ throw new Error(`Cannot resolve dependency at index ${index} for ${target.name}. ` + `Parameter type is undefined or Object. ` + IMPORT_TYPE_HINT + ` Alternatively, use \`@Inject()\` to specify the token explicitly.`);
159
166
  }
160
167
  if (meta?.optional && !this.has(token)) {
161
168
  return undefined;
@@ -1,7 +1,4 @@
1
1
  import { cors } from "hono/cors";
2
- import { METADATA_KEYS } from "../constants.js";
3
- import { defineMetadata } from "../metadata.js";
4
- import { MetadataRegistry } from "../registry/metadata.registry.js";
5
2
  import { APP_MIDDLEWARE } from "../pipeline/tokens.js";
6
3
  import { CORS_OPTIONS } from "./cors.tokens.js";
7
4
  export class CorsModule {
@@ -17,19 +14,8 @@ export class CorsModule {
17
14
  const middleware = {
18
15
  use: (c, next)=>corsMiddleware(c, next)
19
16
  };
20
- const moduleClass = class CorsDynamicModule {
21
- };
22
- Object.defineProperty(moduleClass, 'name', {
23
- value: 'CorsModule'
24
- });
25
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
26
- MetadataRegistry.setModuleOptions(moduleClass, {
27
- exports: [
28
- CORS_OPTIONS
29
- ]
30
- });
31
17
  return {
32
- module: moduleClass,
18
+ module: CorsModule,
33
19
  providers: [
34
20
  {
35
21
  provide: CORS_OPTIONS,
@@ -39,6 +25,9 @@ export class CorsModule {
39
25
  provide: APP_MIDDLEWARE,
40
26
  useValue: middleware
41
27
  }
28
+ ],
29
+ exports: [
30
+ CORS_OPTIONS
42
31
  ]
43
32
  };
44
33
  }
package/dist/factory.d.ts CHANGED
@@ -4,3 +4,4 @@ import type { RouteManagerOptions } from './http/route.manager';
4
4
  export declare const VelaFactory: {
5
5
  create(rootModule: Type, options?: RouteManagerOptions): Promise<VelaApplication>;
6
6
  };
7
+ export declare const createApplication: typeof VelaFactory.create;
package/dist/factory.js CHANGED
@@ -73,3 +73,4 @@ export const VelaFactory = {
73
73
  return app;
74
74
  }
75
75
  };
76
+ export const createApplication = VelaFactory.create.bind(VelaFactory);
@@ -4,52 +4,30 @@ function _ts_decorate(decorators, target, key, desc) {
4
4
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
- import { METADATA_KEYS } from "../constants.js";
8
- import { defineMetadata } from "../metadata.js";
9
- import { MetadataRegistry } from "../registry/metadata.registry.js";
7
+ import { createModuleRef } from "../module/decorators.js";
10
8
  import { Module } from "../module/decorators.js";
11
9
  import { HttpService, HTTP_MODULE_OPTIONS } from "./fetch.service.js";
12
10
  export class HttpModule {
13
11
  static register(options = {}) {
14
- const moduleClass = class HttpDynamicModule {
15
- };
16
- Object.defineProperty(moduleClass, 'name', {
17
- value: 'HttpModule'
18
- });
19
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
20
- MetadataRegistry.setModuleOptions(moduleClass, {
21
- exports: [
22
- HttpService,
23
- HTTP_MODULE_OPTIONS
24
- ]
25
- });
26
12
  return {
27
- module: moduleClass,
13
+ module: createModuleRef('HttpModule'),
28
14
  providers: [
29
15
  {
30
16
  provide: HTTP_MODULE_OPTIONS,
31
17
  useValue: options
32
18
  },
33
19
  HttpService
34
- ]
35
- };
36
- }
37
- static registerAsync(options) {
38
- const moduleClass = class HttpAsyncDynamicModule {
39
- };
40
- Object.defineProperty(moduleClass, 'name', {
41
- value: 'HttpModule'
42
- });
43
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
44
- MetadataRegistry.setModuleOptions(moduleClass, {
45
- imports: options.imports ?? [],
20
+ ],
46
21
  exports: [
47
22
  HttpService,
48
23
  HTTP_MODULE_OPTIONS
49
24
  ]
50
- });
25
+ };
26
+ }
27
+ static registerAsync(options) {
51
28
  return {
52
- module: moduleClass,
29
+ module: createModuleRef('HttpModule'),
30
+ imports: options.imports ?? [],
53
31
  providers: [
54
32
  {
55
33
  provide: HTTP_MODULE_OPTIONS,
@@ -57,6 +35,10 @@ export class HttpModule {
57
35
  inject: options.inject ?? []
58
36
  },
59
37
  HttpService
38
+ ],
39
+ exports: [
40
+ HttpService,
41
+ HTTP_MODULE_OPTIONS
60
42
  ]
61
43
  };
62
44
  }
@@ -18,9 +18,12 @@ export interface MiddlewareRouteDefinition {
18
18
  middleware: Array<Type<NestMiddleware> | NestMiddleware>;
19
19
  routes: RouteInfo[];
20
20
  excludes: RouteInfo[];
21
+ /** Stable-sort key; lower runs first. Default 0. */
22
+ priority?: number;
21
23
  }
22
24
  export interface MiddlewareConfigProxy {
23
25
  exclude(...routes: Array<string | RouteInfo>): MiddlewareConfigProxy;
26
+ withPriority(priority: number): MiddlewareConfigProxy;
24
27
  forRoutes(...routes: Array<string | Function | RouteInfo>): MiddlewareConsumer;
25
28
  }
26
29
  export interface MiddlewareConsumer {
@@ -17,11 +17,16 @@ export class MiddlewareBuilder {
17
17
  ...middleware
18
18
  ];
19
19
  let currentExcludes = [];
20
+ let currentPriority;
20
21
  const proxy = {
21
22
  exclude: (...routes)=>{
22
23
  currentExcludes = routes.map(normalizeRouteArg);
23
24
  return proxy;
24
25
  },
26
+ withPriority: (priority)=>{
27
+ currentPriority = priority;
28
+ return proxy;
29
+ },
25
30
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
26
31
  forRoutes: (...routes)=>{
27
32
  this.definitions.push({
@@ -29,9 +34,13 @@ export class MiddlewareBuilder {
29
34
  routes: routes.flatMap(resolveRouteArg),
30
35
  excludes: [
31
36
  ...currentExcludes
32
- ]
37
+ ],
38
+ ...currentPriority !== undefined ? {
39
+ priority: currentPriority
40
+ } : {}
33
41
  });
34
42
  currentExcludes = [];
43
+ currentPriority = undefined;
35
44
  return this;
36
45
  }
37
46
  };
@@ -38,6 +38,7 @@ export declare class RouteManager {
38
38
  useGlobalFilterTokens(...filterTokens: Array<Token<ExceptionFilter>>): this;
39
39
  private instantiate;
40
40
  private instantiateMany;
41
+ private getMiddlewarePriority;
41
42
  private getRequestContainer;
42
43
  registerController(controller: Type): this;
43
44
  build(): Promise<Hono>;
@@ -175,6 +175,35 @@ export class RouteManager {
175
175
  instantiateMany(items, container) {
176
176
  return items.map((item)=>this.instantiate(item, container));
177
177
  }
178
+ // Resolve middleware priority at build time. Supports three sources:
179
+ // 1. static priority on the class constructor
180
+ // 2. priority property on the instance
181
+ // 3. for container tokens, resolve and inspect the instance (+ its ctor)
182
+ // Falls back to 0 on any error or when no priority is set.
183
+ getMiddlewarePriority(entry) {
184
+ if (entry == null) return 0;
185
+ if (typeof entry === 'function') {
186
+ const p = entry.priority;
187
+ if (typeof p === 'number') return p;
188
+ }
189
+ if (typeof entry === 'object') {
190
+ const inst = entry;
191
+ if (typeof inst.priority === 'number') return inst.priority;
192
+ if (typeof inst.constructor?.priority === 'number') return inst.constructor.priority;
193
+ }
194
+ try {
195
+ const resolved = this.instantiate(entry, this.container);
196
+ if (resolved && typeof resolved === 'object') {
197
+ const p = resolved.priority;
198
+ if (typeof p === 'number') return p;
199
+ const cp = resolved.constructor?.priority;
200
+ if (typeof cp === 'number') return cp;
201
+ }
202
+ } catch {
203
+ // Unresolvable at build time (e.g. request-scoped) — default 0
204
+ }
205
+ return 0;
206
+ }
178
207
  getRequestContainer(c) {
179
208
  const existing = c.get('container');
180
209
  if (existing) {
@@ -203,16 +232,29 @@ export class RouteManager {
203
232
  }
204
233
  async build() {
205
234
  const app = new Hono();
235
+ // Sort global middleware by priority (lower runs first) with insertion
236
+ // index as tiebreaker so equal priorities preserve registration order.
237
+ const sortedGlobal = this.globalMiddleware.map((entry, index)=>({
238
+ entry,
239
+ index,
240
+ priority: this.getMiddlewarePriority(entry)
241
+ })).sort((a, b)=>a.priority - b.priority || a.index - b.index);
206
242
  // Register global middleware on the Hono app
207
- for (const mw of this.globalMiddleware){
243
+ for (const { entry } of sortedGlobal){
208
244
  app.use('*', (c, next)=>{
209
245
  const requestContainer = this.getRequestContainer(c);
210
- const resolved = this.instantiate(mw, requestContainer);
246
+ const resolved = this.instantiate(entry, requestContainer);
211
247
  return resolved.use(c, next);
212
248
  });
213
249
  }
250
+ // Sort consumer middleware with the same stable-by-index rule.
251
+ const sortedConsumer = this.consumerMiddlewareDefinitions.map((def, index)=>({
252
+ def,
253
+ index,
254
+ priority: def.priority ?? 0
255
+ })).sort((a, b)=>a.priority - b.priority || a.index - b.index);
214
256
  // Register MiddlewareConsumer-configured middleware
215
- for (const def of this.consumerMiddlewareDefinitions){
257
+ for (const { def } of sortedConsumer){
216
258
  const matchRoute = this.compileRouteMatcher(def.routes);
217
259
  const matchExclude = this.compileRouteMatcher(def.excludes);
218
260
  app.use('*', (c, next)=>{
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import './metadata';
2
2
  export { defineMetadata, getMetadata } from './metadata';
3
- export { VelaFactory } from './factory';
3
+ export { VelaFactory, createApplication } from './factory';
4
4
  export { VelaApplication } from './application';
5
+ export { createOpenApiDocument, ApiDoc, ApiTags, zodToJsonSchema } from './openapi/index';
6
+ export type { OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiRequestBody, OpenApiResponse, ApiDocMetadata, CreateOpenApiDocumentOptions, HttpVerb, JsonSchema, } from './openapi/index';
5
7
  export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from './container/index';
6
8
  export type { Type, Token, InjectableOptions, ProviderOptions, } from './container/index';
7
9
  export { METADATA_KEYS, HttpMethod, ParamType, Scope } from './constants';
@@ -24,7 +26,7 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
24
26
  export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
25
27
  export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
26
28
  export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerStorageRecord, RateLimitInfo, } from './throttler/index';
27
- export { Global, Module } from './module/index';
29
+ export { Global, Module, createModuleRef } from './module/index';
28
30
  export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport } from './module/index';
29
31
  export { RequestMethod } from './http/index';
30
32
  export type { MiddlewareConsumer, NestModule, RouteInfo } from './http/index';
@@ -41,6 +43,7 @@ export type { RouteManagerOptions } from './http/index';
41
43
  export { ModuleLoader } from './module/index';
42
44
  export { ComponentManager } from './pipeline/index';
43
45
  export { createZodDto, ValidationPipe } from './validation/index';
46
+ export type { CreateZodDtoOptions } from './validation/index';
44
47
  export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from './serialization/index';
45
48
  export { Test, TestingModule, TestingModuleBuilder } from './testing/index';
46
49
  export { getRuntimeKey, env } from 'hono/adapter';
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import "./metadata.js";
2
2
  export { defineMetadata, getMetadata } from "./metadata.js";
3
3
  // Factory & Application
4
- export { VelaFactory } from "./factory.js";
4
+ export { VelaFactory, createApplication } from "./factory.js";
5
5
  export { VelaApplication } from "./application.js";
6
+ // OpenAPI
7
+ export { createOpenApiDocument, ApiDoc, ApiTags, zodToJsonSchema } from "./openapi/index.js";
6
8
  // DI Container
7
9
  export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from "./container/index.js";
8
10
  // Constants
@@ -28,7 +30,7 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
28
30
  // Throttler
29
31
  export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
30
32
  // Module
31
- export { Global, Module } from "./module/index.js";
33
+ export { Global, Module, createModuleRef } from "./module/index.js";
32
34
  export { RequestMethod } from "./http/index.js";
33
35
  // Pipeline Decorators
34
36
  export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/index.js";
@@ -1,6 +1,8 @@
1
1
  import type { Constructor } from '../registry/types';
2
+ import type { Type } from '../container/types';
2
3
  import type { ModuleMetadata, ModuleOptions } from './types';
3
4
  export declare function Global(): ClassDecorator;
4
5
  export declare function Module(options?: ModuleOptions): ClassDecorator;
5
6
  export declare function isModule(target: Constructor): boolean;
7
+ export declare function createModuleRef(name: string): Type;
6
8
  export declare function getModuleMetadata(target: Constructor): ModuleMetadata | undefined;
@@ -30,6 +30,14 @@ export function Module(options = {}) {
30
30
  export function isModule(target) {
31
31
  return MetadataRegistry.getModuleOptions(target) !== undefined || getMetadata(METADATA_KEYS.MODULE, target) === true || getMetadata(METADATA_KEYS.MODULE_OPTIONS, target) !== undefined;
32
32
  }
33
+ export function createModuleRef(name) {
34
+ const moduleClass = class {
35
+ };
36
+ Object.defineProperty(moduleClass, 'name', {
37
+ value: name
38
+ });
39
+ return moduleClass;
40
+ }
33
41
  export function getModuleMetadata(target) {
34
42
  if (!isModule(target)) {
35
43
  return undefined;
@@ -1,3 +1,3 @@
1
- export { Global, Module, isModule, getModuleMetadata } from './decorators';
1
+ export { Global, Module, isModule, getModuleMetadata, createModuleRef } from './decorators';
2
2
  export { ModuleLoader } from './module-loader';
3
3
  export type { ModuleOptions, ModuleMetadata, DynamicModule, AsyncModuleOptions, ModuleImport } from './types';
@@ -1,2 +1,2 @@
1
- export { Global, Module, isModule, getModuleMetadata } from "./decorators.js";
1
+ export { Global, Module, isModule, getModuleMetadata, createModuleRef } from "./decorators.js";
2
2
  export { ModuleLoader } from "./module-loader.js";
@@ -63,11 +63,13 @@ export class ModuleLoader {
63
63
  let extraImports = [];
64
64
  let extraControllers = [];
65
65
  let extraProviders = [];
66
+ let extraExports = [];
66
67
  if (isDynamicModule(moduleClassOrDynamic)) {
67
68
  moduleClass = moduleClassOrDynamic.module;
68
69
  extraImports = moduleClassOrDynamic.imports ?? [];
69
70
  extraControllers = moduleClassOrDynamic.controllers ?? [];
70
71
  extraProviders = moduleClassOrDynamic.providers ?? [];
72
+ extraExports = moduleClassOrDynamic.exports ?? [];
71
73
  } else {
72
74
  moduleClass = moduleClassOrDynamic;
73
75
  }
@@ -86,7 +88,11 @@ export class ModuleLoader {
86
88
  throw new Error(`Circular module dependency detected: ${chain}`);
87
89
  }
88
90
  if (!isModule(moduleClass)) {
89
- throw new Error(`${moduleClass.name} is not a module. Add @Module() decorator to the class.`);
91
+ if (isDynamicModule(moduleClassOrDynamic)) {
92
+ MetadataRegistry.setModuleOptions(moduleClass, {});
93
+ } else {
94
+ throw new Error(`${moduleClass.name} is not a module. Add @Module() decorator to the class.`);
95
+ }
90
96
  }
91
97
  const metadata = getModuleMetadata(moduleClass);
92
98
  if (!metadata) {
@@ -134,7 +140,11 @@ export class ModuleLoader {
134
140
  MetadataRegistry.propagateControllerComponents(moduleClass, controller);
135
141
  }
136
142
  this.processedModules.add(moduleClass);
137
- const exports = this.buildExportSet(metadata.exports, allProviders, importedProviders);
143
+ const allExports = [
144
+ ...metadata.exports,
145
+ ...extraExports
146
+ ];
147
+ const exports = this.buildExportSet(allExports, allProviders, importedProviders);
138
148
  this.moduleExportsCache.set(moduleClass, exports);
139
149
  const isGlobal = metadata.isGlobal || isDynamicModule(moduleClassOrDynamic) && moduleClassOrDynamic.global === true;
140
150
  if (isGlobal) {
@@ -0,0 +1,20 @@
1
+ import type { ApiDocMetadata } from './types';
2
+ export declare const API_DOC_METADATA = "vela:openapi:doc";
3
+ export declare const API_TAGS_METADATA = "vela:openapi:tags";
4
+ /**
5
+ * Attach OpenAPI documentation to a route handler (or controller).
6
+ *
7
+ * ```ts
8
+ * @Get('/:id')
9
+ * @ApiDoc({ summary: 'Get a user', description: '...', operationId: 'getUser' })
10
+ * findOne(@Param('id') id: string) { ... }
11
+ * ```
12
+ */
13
+ export declare function ApiDoc(metadata: ApiDocMetadata): MethodDecorator & ClassDecorator;
14
+ /**
15
+ * Attach OpenAPI tags to a controller or handler. Handler-level tags merge
16
+ * with controller-level tags; duplicates are de-duplicated in the output.
17
+ */
18
+ export declare function ApiTags(...tags: string[]): MethodDecorator & ClassDecorator;
19
+ export declare function getApiDoc(target: object, propertyKey?: string | symbol): ApiDocMetadata | undefined;
20
+ export declare function getApiTags(target: object, propertyKey?: string | symbol): string[] | undefined;
@@ -0,0 +1,37 @@
1
+ export const API_DOC_METADATA = 'vela:openapi:doc';
2
+ export const API_TAGS_METADATA = 'vela:openapi:tags';
3
+ /**
4
+ * Attach OpenAPI documentation to a route handler (or controller).
5
+ *
6
+ * ```ts
7
+ * @Get('/:id')
8
+ * @ApiDoc({ summary: 'Get a user', description: '...', operationId: 'getUser' })
9
+ * findOne(@Param('id') id: string) { ... }
10
+ * ```
11
+ */ export function ApiDoc(metadata) {
12
+ return (target, propertyKey)=>{
13
+ if (propertyKey !== undefined) {
14
+ Reflect.defineMetadata(API_DOC_METADATA, metadata, target, propertyKey);
15
+ } else {
16
+ Reflect.defineMetadata(API_DOC_METADATA, metadata, target);
17
+ }
18
+ };
19
+ }
20
+ /**
21
+ * Attach OpenAPI tags to a controller or handler. Handler-level tags merge
22
+ * with controller-level tags; duplicates are de-duplicated in the output.
23
+ */ export function ApiTags(...tags) {
24
+ return (target, propertyKey)=>{
25
+ if (propertyKey !== undefined) {
26
+ Reflect.defineMetadata(API_TAGS_METADATA, tags, target, propertyKey);
27
+ } else {
28
+ Reflect.defineMetadata(API_TAGS_METADATA, tags, target);
29
+ }
30
+ };
31
+ }
32
+ export function getApiDoc(target, propertyKey) {
33
+ return propertyKey !== undefined ? Reflect.getMetadata(API_DOC_METADATA, target, propertyKey) : Reflect.getMetadata(API_DOC_METADATA, target);
34
+ }
35
+ export function getApiTags(target, propertyKey) {
36
+ return propertyKey !== undefined ? Reflect.getMetadata(API_TAGS_METADATA, target, propertyKey) : Reflect.getMetadata(API_TAGS_METADATA, target);
37
+ }
@@ -0,0 +1,3 @@
1
+ import type { Type } from '../container/types';
2
+ import type { CreateOpenApiDocumentOptions, OpenApiDocument } from './types';
3
+ export declare function createOpenApiDocument(rootModule: Type, options?: CreateOpenApiDocumentOptions): OpenApiDocument;
@@ -0,0 +1,187 @@
1
+ import { getModuleMetadata } from "../module/decorators.js";
2
+ import { ForwardRef } from "../container/types.js";
3
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
4
+ import { ParamType } from "../constants.js";
5
+ import { getApiDoc, getApiTags } from "./decorators.js";
6
+ import { isOptional, zodToJsonSchema } from "./zod-to-json-schema.js";
7
+ function isDynamicModuleLike(v) {
8
+ return !!v && typeof v === 'object' && 'module' in v && typeof v.module === 'function';
9
+ }
10
+ function collectControllers(rootModule) {
11
+ const visited = new Set();
12
+ const controllers = new Set();
13
+ function visit(entry) {
14
+ const unwrapped = entry instanceof ForwardRef ? entry.factory() : entry;
15
+ const moduleClass = isDynamicModuleLike(unwrapped) ? unwrapped.module : unwrapped;
16
+ const extraControllers = isDynamicModuleLike(unwrapped) ? unwrapped.controllers ?? [] : [];
17
+ const extraImports = isDynamicModuleLike(unwrapped) ? unwrapped.imports ?? [] : [];
18
+ if (typeof moduleClass !== 'function' || visited.has(moduleClass)) return;
19
+ visited.add(moduleClass);
20
+ const metadata = getModuleMetadata(moduleClass);
21
+ if (metadata) {
22
+ for (const controller of metadata.controllers)controllers.add(controller);
23
+ for (const imp of metadata.imports)visit(imp);
24
+ }
25
+ for (const controller of extraControllers)controllers.add(controller);
26
+ for (const imp of extraImports)visit(imp);
27
+ }
28
+ visit(rootModule);
29
+ return [
30
+ ...controllers
31
+ ];
32
+ }
33
+ function normalizePath(path) {
34
+ // Hono/Nest style `:id` → OpenAPI `{id}`
35
+ return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
36
+ }
37
+ function joinPath(a, b) {
38
+ const left = a.endsWith('/') ? a.slice(0, -1) : a;
39
+ const right = b && !b.startsWith('/') ? `/${b}` : b;
40
+ const joined = `${left}${right}`;
41
+ return joined || '/';
42
+ }
43
+ function getParamSchema(param, paramtypes) {
44
+ const metatype = paramtypes?.[param.index];
45
+ if (metatype?.schema) {
46
+ return zodToJsonSchema(metatype.schema);
47
+ }
48
+ return undefined;
49
+ }
50
+ function isParamOptional(param, paramtypes) {
51
+ const metatype = paramtypes?.[param.index];
52
+ if (metatype?.schema) {
53
+ return isOptional(metatype.schema);
54
+ }
55
+ return true;
56
+ }
57
+ function buildOperation(controller, route, pathString) {
58
+ const handlerName = route.handlerName;
59
+ const paramMetadata = MetadataRegistry.getParameters(controller).get(handlerName) ?? [];
60
+ const paramtypes = Reflect.getMetadata('design:paramtypes', controller.prototype, handlerName);
61
+ const parameters = [];
62
+ let requestBody;
63
+ // Path params declared in the URL but not listed as @Param() get a string default.
64
+ const declaredPathParams = new Set();
65
+ const pathParamNames = [
66
+ ...pathString.matchAll(/\{([^}]+)\}/g)
67
+ ].map((m)=>m[1]);
68
+ for (const param of paramMetadata){
69
+ if (param.type === ParamType.PARAM && param.name) {
70
+ declaredPathParams.add(param.name);
71
+ parameters.push({
72
+ name: param.name,
73
+ in: 'path',
74
+ required: true,
75
+ schema: getParamSchema(param, paramtypes) ?? {
76
+ type: 'string'
77
+ }
78
+ });
79
+ } else if (param.type === ParamType.QUERY && param.name) {
80
+ parameters.push({
81
+ name: param.name,
82
+ in: 'query',
83
+ required: !isParamOptional(param, paramtypes),
84
+ schema: getParamSchema(param, paramtypes) ?? {
85
+ type: 'string'
86
+ }
87
+ });
88
+ } else if (param.type === ParamType.HEADERS && param.name) {
89
+ parameters.push({
90
+ name: param.name,
91
+ in: 'header',
92
+ required: !isParamOptional(param, paramtypes),
93
+ schema: getParamSchema(param, paramtypes) ?? {
94
+ type: 'string'
95
+ }
96
+ });
97
+ } else if (param.type === ParamType.BODY) {
98
+ const schema = getParamSchema(param, paramtypes);
99
+ if (schema) {
100
+ requestBody = {
101
+ required: !isParamOptional(param, paramtypes),
102
+ content: {
103
+ 'application/json': {
104
+ schema
105
+ }
106
+ }
107
+ };
108
+ }
109
+ }
110
+ }
111
+ for (const name of pathParamNames){
112
+ if (!declaredPathParams.has(name)) {
113
+ parameters.push({
114
+ name,
115
+ in: 'path',
116
+ required: true,
117
+ schema: {
118
+ type: 'string'
119
+ }
120
+ });
121
+ }
122
+ }
123
+ const docMeta = getApiDoc(controller.prototype, handlerName);
124
+ const controllerTags = getApiTags(controller) ?? [];
125
+ const handlerTags = getApiTags(controller.prototype, handlerName) ?? [];
126
+ const docTags = docMeta?.tags ?? [];
127
+ const mergedTags = [
128
+ ...new Set([
129
+ ...controllerTags,
130
+ ...handlerTags,
131
+ ...docTags
132
+ ])
133
+ ];
134
+ const operation = {
135
+ responses: {
136
+ '200': {
137
+ description: 'OK'
138
+ }
139
+ }
140
+ };
141
+ if (parameters.length > 0) operation.parameters = parameters;
142
+ if (requestBody) operation.requestBody = requestBody;
143
+ if (docMeta?.summary) operation.summary = docMeta.summary;
144
+ if (docMeta?.description) operation.description = docMeta.description;
145
+ if (docMeta?.operationId) operation.operationId = docMeta.operationId;
146
+ if (docMeta?.deprecated) operation.deprecated = docMeta.deprecated;
147
+ if (mergedTags.length > 0) operation.tags = mergedTags;
148
+ return operation;
149
+ }
150
+ const VERB_WHITELIST = new Set([
151
+ 'get',
152
+ 'post',
153
+ 'put',
154
+ 'patch',
155
+ 'delete',
156
+ 'options',
157
+ 'head'
158
+ ]);
159
+ export function createOpenApiDocument(rootModule, options = {}) {
160
+ const paths = {};
161
+ const globalPrefix = options.globalPrefix ?? '';
162
+ for (const controller of collectControllers(rootModule)){
163
+ const controllerPath = MetadataRegistry.getControllerPath(controller);
164
+ const routes = MetadataRegistry.getRoutes(controller);
165
+ for (const route of routes){
166
+ const method = route.method.toLowerCase();
167
+ if (!VERB_WHITELIST.has(method)) continue;
168
+ const rawPath = joinPath(joinPath(globalPrefix, controllerPath), route.path);
169
+ const pathString = normalizePath(rawPath);
170
+ const operation = buildOperation(controller, route, pathString);
171
+ const pathItem = paths[pathString] ?? {};
172
+ pathItem[method] = operation;
173
+ paths[pathString] = pathItem;
174
+ }
175
+ }
176
+ return {
177
+ openapi: '3.1.0',
178
+ info: {
179
+ title: options.info?.title ?? 'Vela API',
180
+ version: options.info?.version ?? '1.0.0',
181
+ ...options.info?.description ? {
182
+ description: options.info.description
183
+ } : {}
184
+ },
185
+ paths
186
+ };
187
+ }
@@ -0,0 +1,4 @@
1
+ export { createOpenApiDocument } from './document';
2
+ export { ApiDoc, ApiTags, API_DOC_METADATA, API_TAGS_METADATA } from './decorators';
3
+ export { zodToJsonSchema } from './zod-to-json-schema';
4
+ export type { ApiDocMetadata, CreateOpenApiDocumentOptions, HttpVerb, JsonSchema, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiRequestBody, OpenApiResponse, } from './types';
@@ -0,0 +1,3 @@
1
+ export { createOpenApiDocument } from "./document.js";
2
+ export { ApiDoc, ApiTags, API_DOC_METADATA, API_TAGS_METADATA } from "./decorators.js";
3
+ export { zodToJsonSchema } from "./zod-to-json-schema.js";
@@ -0,0 +1,85 @@
1
+ export interface OpenApiInfo {
2
+ title: string;
3
+ version: string;
4
+ description?: string;
5
+ }
6
+ export interface JsonSchema {
7
+ type?: string | string[];
8
+ format?: string;
9
+ enum?: Array<string | number | boolean | null>;
10
+ const?: unknown;
11
+ description?: string;
12
+ nullable?: boolean;
13
+ default?: unknown;
14
+ items?: JsonSchema;
15
+ properties?: Record<string, JsonSchema>;
16
+ required?: string[];
17
+ additionalProperties?: boolean | JsonSchema;
18
+ oneOf?: JsonSchema[];
19
+ anyOf?: JsonSchema[];
20
+ allOf?: JsonSchema[];
21
+ minimum?: number;
22
+ maximum?: number;
23
+ minLength?: number;
24
+ maxLength?: number;
25
+ pattern?: string;
26
+ $ref?: string;
27
+ [key: string]: unknown;
28
+ }
29
+ export interface OpenApiParameter {
30
+ name: string;
31
+ in: 'path' | 'query' | 'header' | 'cookie';
32
+ required?: boolean;
33
+ description?: string;
34
+ schema?: JsonSchema;
35
+ }
36
+ export interface OpenApiRequestBody {
37
+ description?: string;
38
+ required?: boolean;
39
+ content?: Record<string, {
40
+ schema: JsonSchema;
41
+ }>;
42
+ }
43
+ export interface OpenApiResponse {
44
+ description: string;
45
+ content?: Record<string, {
46
+ schema: JsonSchema;
47
+ }>;
48
+ }
49
+ export interface OpenApiOperation {
50
+ summary?: string;
51
+ description?: string;
52
+ operationId?: string;
53
+ deprecated?: boolean;
54
+ tags?: string[];
55
+ parameters?: OpenApiParameter[];
56
+ requestBody?: OpenApiRequestBody;
57
+ responses: Record<string, OpenApiResponse>;
58
+ }
59
+ export type HttpVerb = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options' | 'head';
60
+ export type OpenApiPathItem = {
61
+ [verb in HttpVerb]?: OpenApiOperation;
62
+ };
63
+ export interface OpenApiDocument {
64
+ openapi: '3.1.0';
65
+ info: OpenApiInfo;
66
+ paths: Record<string, OpenApiPathItem>;
67
+ components?: {
68
+ schemas?: Record<string, JsonSchema>;
69
+ };
70
+ tags?: Array<{
71
+ name: string;
72
+ description?: string;
73
+ }>;
74
+ }
75
+ export interface ApiDocMetadata {
76
+ summary?: string;
77
+ description?: string;
78
+ operationId?: string;
79
+ deprecated?: boolean;
80
+ tags?: string[];
81
+ }
82
+ export interface CreateOpenApiDocumentOptions {
83
+ info?: Partial<OpenApiInfo>;
84
+ globalPrefix?: string;
85
+ }
@@ -0,0 +1,4 @@
1
+ // Minimal OpenAPI 3.1 subset Vela emits. Kept intentionally loose (index
2
+ // signatures on schemas, open tags array) so users can extend without
3
+ // fighting the types. Covers what createOpenApiDocument actually produces.
4
+ export { };
@@ -0,0 +1,4 @@
1
+ import type { JsonSchema } from './types';
2
+ export declare function zodToJsonSchema(schema: unknown): JsonSchema;
3
+ export declare function isOptional(schema: unknown): boolean;
4
+ export declare function isNullable(schema: unknown): boolean;
@@ -0,0 +1,57 @@
1
+ // Zod v4 exposes `schema.toJSONSchema()` on every schema instance — an
2
+ // authoritative, zero-cost converter. We delegate to it when present and
3
+ // strip the `$schema` meta so the result embeds cleanly into OpenAPI
4
+ // components. For non-Zod schemas (anything that doesn't expose the
5
+ // method) we return an empty (permissive) schema.
6
+ function stripOpenApiIncompatible(schema) {
7
+ const out = {
8
+ ...schema
9
+ };
10
+ delete out.$schema;
11
+ // Zod v4 emits `additionalProperties: false` by default; users rarely
12
+ // want that constraint in their published OpenAPI doc, so leave it in
13
+ // — it's a faithful reflection of the schema. No change here.
14
+ if (Array.isArray(out.properties)) {
15
+ // shouldn't happen, but guard
16
+ } else if (out.properties && typeof out.properties === 'object') {
17
+ const props = out.properties;
18
+ const cleaned = {};
19
+ for (const [key, val] of Object.entries(props)){
20
+ cleaned[key] = stripOpenApiIncompatible(val);
21
+ }
22
+ out.properties = cleaned;
23
+ }
24
+ if (out.items && typeof out.items === 'object') {
25
+ out.items = stripOpenApiIncompatible(out.items);
26
+ }
27
+ return out;
28
+ }
29
+ export function zodToJsonSchema(schema) {
30
+ if (!schema || typeof schema !== 'object') return {};
31
+ const maybe = schema.toJSONSchema;
32
+ if (typeof maybe === 'function') {
33
+ try {
34
+ const result = maybe.call(schema);
35
+ if (result && typeof result === 'object') {
36
+ return stripOpenApiIncompatible(result);
37
+ }
38
+ } catch {
39
+ // Zod may refuse to convert certain schemas; fall through.
40
+ }
41
+ }
42
+ return {};
43
+ }
44
+ // Zod v4 and v3 both name constructors `ZodOptional` / `ZodDefault` /
45
+ // `ZodNullable`. Checking the constructor name is cheap and avoids
46
+ // depending on internal `_def` shape.
47
+ function ctorName(schema) {
48
+ if (!schema || typeof schema !== 'object') return undefined;
49
+ return schema.constructor?.name;
50
+ }
51
+ export function isOptional(schema) {
52
+ const name = ctorName(schema);
53
+ return name === 'ZodOptional' || name === 'ZodDefault';
54
+ }
55
+ export function isNullable(schema) {
56
+ return ctorName(schema) === 'ZodNullable';
57
+ }
@@ -1,22 +1,9 @@
1
- import { METADATA_KEYS } from "../constants.js";
2
- import { defineMetadata } from "../metadata.js";
3
- import { MetadataRegistry } from "../registry/metadata.registry.js";
4
1
  import { ScheduleExecutor } from "./schedule.executor.js";
5
2
  import { ScheduleRegistry } from "./schedule.registry.js";
6
3
  import { SCHEDULE_MODULE_OPTIONS } from "./schedule.tokens.js";
7
- function makeScheduleModuleClass() {
8
- const moduleClass = class ScheduleDynamicModule {
9
- };
10
- Object.defineProperty(moduleClass, 'name', {
11
- value: 'ScheduleModule'
12
- });
13
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
14
- return moduleClass;
15
- }
16
4
  export class ScheduleModule {
17
5
  static forRoot(options = {}) {
18
6
  const { enableTimers = false } = options;
19
- const moduleClass = makeScheduleModuleClass();
20
7
  const providers = [
21
8
  {
22
9
  provide: SCHEDULE_MODULE_OPTIONS,
@@ -32,17 +19,14 @@ export class ScheduleModule {
32
19
  providers.push(ScheduleExecutor);
33
20
  exports.push(ScheduleExecutor);
34
21
  }
35
- MetadataRegistry.setModuleOptions(moduleClass, {
36
- exports
37
- });
38
22
  return {
39
- module: moduleClass,
40
- providers
23
+ module: ScheduleModule,
24
+ providers,
25
+ exports
41
26
  };
42
27
  }
43
28
  static forRootAsync(options) {
44
29
  const { enableTimers = false } = options;
45
- const moduleClass = makeScheduleModuleClass();
46
30
  const providers = [
47
31
  {
48
32
  provide: SCHEDULE_MODULE_OPTIONS,
@@ -59,13 +43,11 @@ export class ScheduleModule {
59
43
  providers.push(ScheduleExecutor);
60
44
  exports.push(ScheduleExecutor);
61
45
  }
62
- MetadataRegistry.setModuleOptions(moduleClass, {
46
+ return {
47
+ module: ScheduleModule,
63
48
  imports: options.imports ?? [],
49
+ providers,
64
50
  exports
65
- });
66
- return {
67
- module: moduleClass,
68
- providers
69
51
  };
70
52
  }
71
53
  }
@@ -1,29 +1,9 @@
1
- import { METADATA_KEYS } from "../constants.js";
2
- import { defineMetadata } from "../metadata.js";
3
- import { MetadataRegistry } from "../registry/metadata.registry.js";
4
1
  import { APP_GUARD } from "../pipeline/tokens.js";
5
2
  import { ThrottlerGuard } from "./throttler.guard.js";
6
3
  import { ThrottlerStorage } from "./throttler.storage.js";
7
4
  import { THROTTLER_OPTIONS, THROTTLER_STORAGE } from "./throttler.tokens.js";
8
- function makeThrottlerModuleClass() {
9
- const moduleClass = class ThrottlerDynamicModule {
10
- };
11
- Object.defineProperty(moduleClass, 'name', {
12
- value: 'ThrottlerModule'
13
- });
14
- defineMetadata(METADATA_KEYS.MODULE, true, moduleClass);
15
- return moduleClass;
16
- }
17
5
  export class ThrottlerModule {
18
6
  static forRoot(options) {
19
- const moduleClass = makeThrottlerModuleClass();
20
- MetadataRegistry.setModuleOptions(moduleClass, {
21
- exports: [
22
- THROTTLER_OPTIONS,
23
- THROTTLER_STORAGE,
24
- ThrottlerGuard
25
- ]
26
- });
27
7
  const providers = [
28
8
  {
29
9
  provide: THROTTLER_OPTIONS,
@@ -40,20 +20,16 @@ export class ThrottlerModule {
40
20
  }
41
21
  ];
42
22
  return {
43
- module: moduleClass,
44
- providers
45
- };
46
- }
47
- static forRootAsync(options) {
48
- const moduleClass = makeThrottlerModuleClass();
49
- MetadataRegistry.setModuleOptions(moduleClass, {
50
- imports: options.imports ?? [],
23
+ module: ThrottlerModule,
24
+ providers,
51
25
  exports: [
52
26
  THROTTLER_OPTIONS,
53
27
  THROTTLER_STORAGE,
54
28
  ThrottlerGuard
55
29
  ]
56
- });
30
+ };
31
+ }
32
+ static forRootAsync(options) {
57
33
  const providers = [
58
34
  {
59
35
  provide: THROTTLER_OPTIONS,
@@ -74,8 +50,14 @@ export class ThrottlerModule {
74
50
  }
75
51
  ];
76
52
  return {
77
- module: moduleClass,
78
- providers
53
+ module: ThrottlerModule,
54
+ imports: options.imports ?? [],
55
+ providers,
56
+ exports: [
57
+ THROTTLER_OPTIONS,
58
+ THROTTLER_STORAGE,
59
+ ThrottlerGuard
60
+ ]
79
61
  };
80
62
  }
81
63
  }
@@ -1,8 +1,13 @@
1
- interface ZodSchema {
1
+ interface ZodLikeSchema {
2
2
  parse(data: unknown): unknown;
3
3
  }
4
- export declare function createZodDto<T extends ZodSchema>(schema: T): {
5
- new (): unknown;
4
+ type InferOutput<T extends ZodLikeSchema> = ReturnType<T['parse']>;
5
+ export interface CreateZodDtoOptions {
6
+ /** Override the generated class name (useful for debugging and OpenAPI). */
7
+ name?: string;
8
+ }
9
+ export declare function createZodDto<T extends ZodLikeSchema>(schema: T, options?: CreateZodDtoOptions): {
10
+ new (initial?: InferOutput<T>): InferOutput<T>;
6
11
  schema: T;
7
12
  };
8
13
  export {};
@@ -1,6 +1,17 @@
1
- export function createZodDto(schema) {
1
+ export function createZodDto(schema, options = {}) {
2
2
  let ZodDto = class ZodDto {
3
3
  static schema = schema;
4
+ constructor(initial){
5
+ if (initial && typeof initial === 'object') {
6
+ Object.assign(this, initial);
7
+ }
8
+ }
4
9
  };
10
+ if (options.name) {
11
+ Object.defineProperty(ZodDto, 'name', {
12
+ value: options.name,
13
+ configurable: true
14
+ });
15
+ }
5
16
  return ZodDto;
6
17
  }
@@ -1,2 +1,3 @@
1
1
  export { createZodDto } from './create-zod-dto';
2
+ export type { CreateZodDtoOptions } from './create-zod-dto';
2
3
  export { ValidationPipe } from './validation.pipe';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",