@velajs/vela 0.4.3 → 0.5.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.
- package/dist/cache/cache.module.d.ts +4 -1
- package/dist/cache/cache.module.js +51 -9
- package/dist/config/config.module.d.ts +4 -1
- package/dist/config/config.module.js +40 -3
- package/dist/config/config.types.d.ts +1 -0
- package/dist/constants.d.ts +6 -1
- package/dist/constants.js +5 -0
- package/dist/container/container.d.ts +2 -0
- package/dist/container/container.js +47 -15
- package/dist/container/decorators.d.ts +3 -1
- package/dist/container/decorators.js +27 -6
- package/dist/container/index.d.ts +4 -2
- package/dist/container/index.js +4 -2
- package/dist/container/mixin.d.ts +24 -0
- package/dist/container/mixin.js +26 -0
- package/dist/container/module-ref.d.ts +21 -0
- package/dist/container/module-ref.js +48 -0
- package/dist/container/types.d.ts +9 -3
- package/dist/container/types.js +9 -0
- package/dist/cors/cors.module.js +2 -2
- package/dist/factory.js +11 -2
- package/dist/fetch/fetch.module.d.ts +6 -0
- package/dist/fetch/fetch.module.js +77 -0
- package/dist/fetch/fetch.service.d.ts +24 -0
- package/dist/fetch/fetch.service.js +145 -0
- package/dist/fetch/fetch.types.d.ts +24 -0
- package/dist/fetch/fetch.types.js +1 -0
- package/dist/fetch/index.d.ts +3 -0
- package/dist/fetch/index.js +2 -0
- package/dist/http/decorators.d.ts +64 -0
- package/dist/http/decorators.js +89 -1
- package/dist/http/index.d.ts +3 -1
- package/dist/http/index.js +2 -1
- package/dist/http/middleware-consumer.d.ts +36 -0
- package/dist/http/middleware-consumer.js +71 -0
- package/dist/http/route.manager.d.ts +4 -0
- package/dist/http/route.manager.js +75 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +7 -4
- package/dist/metadata.d.ts +1 -1
- package/dist/module/decorators.d.ts +1 -0
- package/dist/module/decorators.js +24 -8
- package/dist/module/index.d.ts +2 -2
- package/dist/module/index.js +1 -1
- package/dist/module/module-loader.d.ts +5 -1
- package/dist/module/module-loader.js +54 -10
- package/dist/module/types.d.ts +13 -3
- package/dist/pipeline/index.d.ts +3 -2
- package/dist/pipeline/index.js +1 -1
- package/dist/pipeline/pipes.d.ts +22 -0
- package/dist/pipeline/pipes.js +50 -0
- package/dist/pipeline/tokens.d.ts +1 -1
- package/dist/pipeline/tokens.js +1 -1
- package/dist/pipeline/types.d.ts +16 -0
- package/dist/registry/metadata.registry.d.ts +7 -6
- package/dist/registry/metadata.registry.js +13 -0
- package/dist/registry/types.d.ts +1 -0
- package/dist/schedule/schedule.module.d.ts +4 -1
- package/dist/schedule/schedule.module.js +39 -7
- package/dist/testing/testing.builder.js +5 -5
- package/dist/throttler/throttler.module.d.ts +2 -1
- package/dist/throttler/throttler.module.js +47 -9
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { HttpMethod, ParamType } from "../constants.js";
|
|
3
3
|
import { getMetadata } from "../metadata.js";
|
|
4
|
+
import { RequestMethod } from "./middleware-consumer.js";
|
|
4
5
|
import { ForbiddenException, HttpException } from "../errors/http-exception.js";
|
|
5
6
|
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
6
7
|
import { shouldFilterCatch } from "../pipeline/decorators.js";
|
|
@@ -27,9 +28,14 @@ export class RouteManager {
|
|
|
27
28
|
globalInterceptors = [];
|
|
28
29
|
globalFilters = [];
|
|
29
30
|
globalPrefix = '';
|
|
31
|
+
consumerMiddlewareDefinitions = [];
|
|
30
32
|
constructor(container){
|
|
31
33
|
this.container = container;
|
|
32
34
|
}
|
|
35
|
+
registerConsumerMiddleware(definitions) {
|
|
36
|
+
this.consumerMiddlewareDefinitions.push(...definitions);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
33
39
|
setGlobalPrefix(prefix) {
|
|
34
40
|
this.globalPrefix = prefix && !prefix.startsWith('/') ? `/${prefix}` : prefix;
|
|
35
41
|
return this;
|
|
@@ -125,6 +131,37 @@ export class RouteManager {
|
|
|
125
131
|
return resolved.use(c, next);
|
|
126
132
|
});
|
|
127
133
|
}
|
|
134
|
+
// Register MiddlewareConsumer-configured middleware
|
|
135
|
+
for (const def of this.consumerMiddlewareDefinitions){
|
|
136
|
+
app.use('*', (c, next)=>{
|
|
137
|
+
const reqPath = c.req.path;
|
|
138
|
+
const reqMethod = c.req.method;
|
|
139
|
+
const matches = def.routes.some((route)=>{
|
|
140
|
+
if (!this.matchesConsumerPath(reqPath, route.path)) return false;
|
|
141
|
+
if (route.method && route.method !== RequestMethod.ALL) {
|
|
142
|
+
if (reqMethod !== route.method) return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
if (!matches) return next();
|
|
147
|
+
const excluded = def.excludes.some((exclude)=>{
|
|
148
|
+
if (!this.matchesConsumerPath(reqPath, exclude.path)) return false;
|
|
149
|
+
if (exclude.method && exclude.method !== RequestMethod.ALL) {
|
|
150
|
+
if (reqMethod !== exclude.method) return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
if (excluded) return next();
|
|
155
|
+
const requestContainer = this.getRequestContainer(c);
|
|
156
|
+
const runChain = (index)=>{
|
|
157
|
+
if (index >= def.middleware.length) return next();
|
|
158
|
+
const instance = this.instantiate(def.middleware[index], requestContainer);
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
return instance.use(c, ()=>runChain(index + 1)).then(()=>{});
|
|
161
|
+
};
|
|
162
|
+
return runChain(0);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
128
165
|
// First pass: register all custom routes (must come before CRUD /:id routes)
|
|
129
166
|
for (const { controller, metadata, routes } of this.controllers){
|
|
130
167
|
if (routes.length > 0) {
|
|
@@ -171,10 +208,15 @@ export class RouteManager {
|
|
|
171
208
|
}
|
|
172
209
|
createExecutionContext(c, controller, route) {
|
|
173
210
|
return {
|
|
211
|
+
getType: ()=>'http',
|
|
174
212
|
getClass: ()=>controller,
|
|
175
213
|
getHandler: ()=>route.handlerName,
|
|
176
214
|
getContext: ()=>c,
|
|
177
|
-
getRequest: ()=>c.req.raw
|
|
215
|
+
getRequest: ()=>c.req.raw,
|
|
216
|
+
switchToHttp: ()=>({
|
|
217
|
+
getRequest: ()=>c.req.raw,
|
|
218
|
+
getResponse: ()=>c
|
|
219
|
+
})
|
|
178
220
|
};
|
|
179
221
|
}
|
|
180
222
|
createHandler(route, controller, allParamMetadata) {
|
|
@@ -326,6 +368,28 @@ export class RouteManager {
|
|
|
326
368
|
return headers;
|
|
327
369
|
case ParamType.REQUEST:
|
|
328
370
|
return c;
|
|
371
|
+
case ParamType.RESPONSE:
|
|
372
|
+
return c;
|
|
373
|
+
case ParamType.IP:
|
|
374
|
+
return c.req.raw.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? c.req.raw.headers.get('x-real-ip') ?? null;
|
|
375
|
+
case ParamType.COOKIE:
|
|
376
|
+
{
|
|
377
|
+
const cookieHeader = c.req.raw.headers.get('cookie') ?? '';
|
|
378
|
+
const cookies = {};
|
|
379
|
+
for (const pair of cookieHeader.split(';')){
|
|
380
|
+
const eqIdx = pair.indexOf('=');
|
|
381
|
+
if (eqIdx === -1) continue;
|
|
382
|
+
const name = pair.slice(0, eqIdx).trim();
|
|
383
|
+
const value = pair.slice(eqIdx + 1).trim();
|
|
384
|
+
if (name) cookies[name] = decodeURIComponent(value);
|
|
385
|
+
}
|
|
386
|
+
return param.name ? cookies[param.name] ?? undefined : cookies;
|
|
387
|
+
}
|
|
388
|
+
case ParamType.RAW_BODY:
|
|
389
|
+
{
|
|
390
|
+
const buffer = await c.req.arrayBuffer();
|
|
391
|
+
return new Uint8Array(buffer);
|
|
392
|
+
}
|
|
329
393
|
default:
|
|
330
394
|
// Custom param decorator — use factory if available
|
|
331
395
|
if (param.factory) {
|
|
@@ -393,6 +457,16 @@ export class RouteManager {
|
|
|
393
457
|
const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;
|
|
394
458
|
return `${cleanPrefix}${cleanPath}` || '/';
|
|
395
459
|
}
|
|
460
|
+
matchesConsumerPath(reqPath, routePath) {
|
|
461
|
+
if (routePath === '*') return true;
|
|
462
|
+
const normalized = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
463
|
+
if (reqPath === normalized) return true;
|
|
464
|
+
if (reqPath.startsWith(`${normalized}/`)) return true;
|
|
465
|
+
if (normalized.endsWith('*')) {
|
|
466
|
+
return reqPath.startsWith(normalized.slice(0, -1));
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
396
470
|
getControllers() {
|
|
397
471
|
return [
|
|
398
472
|
...this.controllers
|
package/dist/index.d.ts
CHANGED
|
@@ -2,14 +2,16 @@ import './metadata';
|
|
|
2
2
|
export { defineMetadata, getMetadata } from './metadata';
|
|
3
3
|
export { VelaFactory } from './factory';
|
|
4
4
|
export { VelaApplication } from './application';
|
|
5
|
-
export { Container, Injectable, Inject, InjectionToken } from './container/index';
|
|
5
|
+
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from './container/index';
|
|
6
6
|
export type { Type, Token, InjectableOptions, ProviderOptions, } from './container/index';
|
|
7
7
|
export { METADATA_KEYS, HttpMethod, ParamType, Scope } from './constants';
|
|
8
|
-
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, Sse, Param, Query, Body, Headers, Req, HttpCode, Header, Redirect, createParamDecorator, } from './http/index';
|
|
8
|
+
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, } from './http/index';
|
|
9
9
|
export { Logger, LogLevel } from './services/index';
|
|
10
10
|
export type { LoggerService } from './services/index';
|
|
11
11
|
export { ConfigModule, ConfigService, CONFIG_OPTIONS } from './config/index';
|
|
12
12
|
export type { ConfigModuleOptions } from './config/index';
|
|
13
|
+
export { HttpModule, HttpService, HTTP_MODULE_OPTIONS, HttpRequestException } from './fetch/index';
|
|
14
|
+
export type { HttpModuleOptions, HttpResponse, HttpRequestConfig } from './fetch/index';
|
|
13
15
|
export { CorsModule, CORS_OPTIONS } from './cors/index';
|
|
14
16
|
export type { CorsOptions } from './cors/index';
|
|
15
17
|
export { CacheModule, CacheService, CacheInterceptor, MemoryCacheStore, CacheKey, CacheTTL, CACHE_MANAGER, CACHE_MODULE_OPTIONS, CACHE_KEY_METADATA, CACHE_TTL_METADATA, } from './cache/index';
|
|
@@ -22,12 +24,15 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
|
|
|
22
24
|
export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
|
|
23
25
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
|
|
24
26
|
export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerStorageRecord, RateLimitInfo, } from './throttler/index';
|
|
25
|
-
export { Module } from './module/index';
|
|
26
|
-
export type { ModuleOptions, DynamicModule } from './module/index';
|
|
27
|
+
export { Global, Module } from './module/index';
|
|
28
|
+
export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport } from './module/index';
|
|
29
|
+
export { RequestMethod } from './http/index';
|
|
30
|
+
export type { MiddlewareConsumer, NestModule, RouteInfo } from './http/index';
|
|
27
31
|
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE, } from './pipeline/index';
|
|
28
|
-
export type { ExecutionContext, CanActivate, CallHandler, NestInterceptor, NestMiddleware, PipeTransform, ExceptionFilter, ArgumentMetadata, ReflectableDecorator, CreateDecoratorOptions, } from './pipeline/index';
|
|
32
|
+
export type { HttpArgumentsHost, ExecutionContext, CanActivate, CallHandler, NestInterceptor, NestMiddleware, PipeTransform, ExceptionFilter, ArgumentMetadata, ReflectableDecorator, CreateDecoratorOptions, } from './pipeline/index';
|
|
29
33
|
export type { Constructor, MiddlewareType, GuardType, PipeType, InterceptorType, FilterType, } from './registry/index';
|
|
30
|
-
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipeline/index';
|
|
34
|
+
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipeline/index';
|
|
35
|
+
export type { ParseUUIDPipeOptions, ParseArrayPipeOptions } from './pipeline/index';
|
|
31
36
|
export { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, GoneException, PayloadTooLargeException, UnsupportedMediaTypeException, UnprocessableEntityException, TooManyRequestsException, InternalServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, GatewayTimeoutException, } from './errors/index';
|
|
32
37
|
export type { OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, OnApplicationShutdown, BeforeApplicationShutdown, } from './lifecycle/index';
|
|
33
38
|
export { MetadataRegistry } from './registry/index';
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,17 @@ export { defineMetadata, getMetadata } from "./metadata.js";
|
|
|
4
4
|
export { VelaFactory } from "./factory.js";
|
|
5
5
|
export { VelaApplication } from "./application.js";
|
|
6
6
|
// DI Container
|
|
7
|
-
export { Container, Injectable, Inject, InjectionToken } from "./container/index.js";
|
|
7
|
+
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from "./container/index.js";
|
|
8
8
|
// Constants
|
|
9
9
|
export { METADATA_KEYS, HttpMethod, ParamType, Scope } from "./constants.js";
|
|
10
10
|
// HTTP Decorators
|
|
11
|
-
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, Sse, Param, Query, Body, Headers, Req, HttpCode, Header, Redirect, createParamDecorator } from "./http/index.js";
|
|
11
|
+
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators } from "./http/index.js";
|
|
12
12
|
// Services
|
|
13
13
|
export { Logger, LogLevel } from "./services/index.js";
|
|
14
14
|
// Config
|
|
15
15
|
export { ConfigModule, ConfigService, CONFIG_OPTIONS } from "./config/index.js";
|
|
16
|
+
// HTTP Client
|
|
17
|
+
export { HttpModule, HttpService, HTTP_MODULE_OPTIONS, HttpRequestException } from "./fetch/index.js";
|
|
16
18
|
// CORS
|
|
17
19
|
export { CorsModule, CORS_OPTIONS } from "./cors/index.js";
|
|
18
20
|
// Cache
|
|
@@ -26,11 +28,12 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
|
|
|
26
28
|
// Throttler
|
|
27
29
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
|
|
28
30
|
// Module
|
|
29
|
-
export { Module } from "./module/index.js";
|
|
31
|
+
export { Global, Module } from "./module/index.js";
|
|
32
|
+
export { RequestMethod } from "./http/index.js";
|
|
30
33
|
// Pipeline Decorators
|
|
31
34
|
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/index.js";
|
|
32
35
|
// Built-in Pipes
|
|
33
|
-
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe } from "./pipeline/index.js";
|
|
36
|
+
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe } from "./pipeline/index.js";
|
|
34
37
|
// Errors
|
|
35
38
|
export { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, GoneException, PayloadTooLargeException, UnsupportedMediaTypeException, UnprocessableEntityException, TooManyRequestsException, InternalServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, GatewayTimeoutException } from "./errors/index.js";
|
|
36
39
|
// Registry (for advanced usage)
|
package/dist/metadata.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ declare global {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
export declare function defineMetadata(key: string, value: unknown, target: object, propertyKey?: string | symbol): void;
|
|
10
|
-
export declare function getMetadata(key: string, target: object, propertyKey?: string | symbol):
|
|
10
|
+
export declare function getMetadata<T = unknown>(key: string, target: object, propertyKey?: string | symbol): T | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Constructor } from '../registry/types';
|
|
2
2
|
import type { ModuleMetadata, ModuleOptions } from './types';
|
|
3
|
+
export declare function Global(): ClassDecorator;
|
|
3
4
|
export declare function Module(options?: ModuleOptions): ClassDecorator;
|
|
4
5
|
export declare function isModule(target: Constructor): boolean;
|
|
5
6
|
export declare function getModuleMetadata(target: Constructor): ModuleMetadata | undefined;
|
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
import { METADATA_KEYS } from "../constants.js";
|
|
2
|
-
import { getMetadata } from "../metadata.js";
|
|
2
|
+
import { defineMetadata, getMetadata } from "../metadata.js";
|
|
3
3
|
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
4
|
-
export function
|
|
4
|
+
export function Global() {
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
6
6
|
return (target)=>{
|
|
7
|
+
const existing = MetadataRegistry.getModuleOptions(target);
|
|
7
8
|
MetadataRegistry.setModuleOptions(target, {
|
|
9
|
+
...existing,
|
|
10
|
+
isGlobal: true
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function Module(options = {}) {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
16
|
+
return (target)=>{
|
|
17
|
+
const normalized = {
|
|
8
18
|
imports: options.imports,
|
|
9
19
|
providers: options.providers,
|
|
10
20
|
controllers: options.controllers,
|
|
11
|
-
exports: options.exports
|
|
12
|
-
|
|
21
|
+
exports: options.exports,
|
|
22
|
+
isGlobal: options.isGlobal
|
|
23
|
+
};
|
|
24
|
+
// WeakMap store survives MetadataRegistry.clear() — needed for framework modules
|
|
25
|
+
// decorated at import time (e.g. HttpModule, CorsModule).
|
|
26
|
+
defineMetadata(METADATA_KEYS.MODULE_OPTIONS, normalized, target);
|
|
27
|
+
MetadataRegistry.setModuleOptions(target, normalized);
|
|
13
28
|
};
|
|
14
29
|
}
|
|
15
30
|
export function isModule(target) {
|
|
16
|
-
return MetadataRegistry.getModuleOptions(target) !== undefined || getMetadata(METADATA_KEYS.MODULE, target) === true;
|
|
31
|
+
return MetadataRegistry.getModuleOptions(target) !== undefined || getMetadata(METADATA_KEYS.MODULE, target) === true || getMetadata(METADATA_KEYS.MODULE_OPTIONS, target) !== undefined;
|
|
17
32
|
}
|
|
18
33
|
export function getModuleMetadata(target) {
|
|
19
34
|
if (!isModule(target)) {
|
|
20
35
|
return undefined;
|
|
21
36
|
}
|
|
22
|
-
const options = MetadataRegistry.getModuleOptions(target);
|
|
23
|
-
if (!options) {
|
|
37
|
+
const options = MetadataRegistry.getModuleOptions(target) ?? getMetadata(METADATA_KEYS.MODULE_OPTIONS, target);
|
|
38
|
+
if (!options || typeof options !== 'object') {
|
|
24
39
|
return undefined;
|
|
25
40
|
}
|
|
26
41
|
return {
|
|
27
42
|
providers: options.providers ?? [],
|
|
28
43
|
controllers: options.controllers ?? [],
|
|
29
44
|
imports: options.imports ?? [],
|
|
30
|
-
exports: options.exports ?? []
|
|
45
|
+
exports: options.exports ?? [],
|
|
46
|
+
isGlobal: options.isGlobal === true
|
|
31
47
|
};
|
|
32
48
|
}
|
package/dist/module/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Module, isModule, getModuleMetadata } from './decorators';
|
|
1
|
+
export { Global, Module, isModule, getModuleMetadata } from './decorators';
|
|
2
2
|
export { ModuleLoader } from './module-loader';
|
|
3
|
-
export type { ModuleOptions, ModuleMetadata, DynamicModule } from './types';
|
|
3
|
+
export type { ModuleOptions, ModuleMetadata, DynamicModule, AsyncModuleOptions, ModuleImport } from './types';
|
package/dist/module/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Module, isModule, getModuleMetadata } from "./decorators.js";
|
|
1
|
+
export { Global, Module, isModule, getModuleMetadata } from "./decorators.js";
|
|
2
2
|
export { ModuleLoader } from "./module-loader.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Container } from '../container/container';
|
|
2
2
|
import type { Token, Type } from '../container/types';
|
|
3
|
+
import type { MiddlewareRouteDefinition } from '../http/middleware-consumer';
|
|
3
4
|
import type { RouteManager } from '../http/route.manager';
|
|
4
5
|
export declare class ModuleLoader {
|
|
5
6
|
private container;
|
|
@@ -9,6 +10,8 @@ export declare class ModuleLoader {
|
|
|
9
10
|
private collectedControllers;
|
|
10
11
|
private registeredProviders;
|
|
11
12
|
private moduleExportsCache;
|
|
13
|
+
private globalExports;
|
|
14
|
+
private consumerMiddlewareDefinitions;
|
|
12
15
|
private appProviderCounter;
|
|
13
16
|
private appProviderTokens;
|
|
14
17
|
constructor(container: Container, router: RouteManager);
|
|
@@ -20,5 +23,6 @@ export declare class ModuleLoader {
|
|
|
20
23
|
getControllers(): Type[];
|
|
21
24
|
getRegisteredProviders(): Token[];
|
|
22
25
|
getAppProviderTokens(token: Token): Token[];
|
|
23
|
-
|
|
26
|
+
getConsumerMiddlewareDefinitions(): MiddlewareRouteDefinition[];
|
|
27
|
+
resolveAllInstances(): Promise<unknown[]>;
|
|
24
28
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Scope } from "../constants.js";
|
|
2
|
-
import { InjectionToken } from "../container/types.js";
|
|
2
|
+
import { ForwardRef, InjectionToken } from "../container/types.js";
|
|
3
|
+
import { MiddlewareBuilder } from "../http/middleware-consumer.js";
|
|
3
4
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
5
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
4
6
|
import { getModuleMetadata, isModule } from "./decorators.js";
|
|
5
7
|
function isDynamicModule(value) {
|
|
6
8
|
return typeof value === 'object' && value !== null && 'module' in value && typeof value.module === 'function';
|
|
@@ -13,6 +15,8 @@ export class ModuleLoader {
|
|
|
13
15
|
collectedControllers = [];
|
|
14
16
|
registeredProviders = [];
|
|
15
17
|
moduleExportsCache = new Map();
|
|
18
|
+
globalExports = new Set();
|
|
19
|
+
consumerMiddlewareDefinitions = [];
|
|
16
20
|
appProviderCounter = 0;
|
|
17
21
|
appProviderTokens = new Map([
|
|
18
22
|
[
|
|
@@ -47,12 +51,14 @@ export class ModuleLoader {
|
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
processModule(moduleClassOrDynamic) {
|
|
50
|
-
// Handle dynamic modules ({ module, controllers, providers })
|
|
54
|
+
// Handle dynamic modules ({ module, imports, controllers, providers })
|
|
51
55
|
let moduleClass;
|
|
56
|
+
let extraImports = [];
|
|
52
57
|
let extraControllers = [];
|
|
53
58
|
let extraProviders = [];
|
|
54
59
|
if (isDynamicModule(moduleClassOrDynamic)) {
|
|
55
60
|
moduleClass = moduleClassOrDynamic.module;
|
|
61
|
+
extraImports = moduleClassOrDynamic.imports ?? [];
|
|
56
62
|
extraControllers = moduleClassOrDynamic.controllers ?? [];
|
|
57
63
|
extraProviders = moduleClassOrDynamic.providers ?? [];
|
|
58
64
|
} else {
|
|
@@ -83,8 +89,20 @@ export class ModuleLoader {
|
|
|
83
89
|
}
|
|
84
90
|
this.processingStack.add(moduleClass);
|
|
85
91
|
try {
|
|
86
|
-
const importedProviders = new Set();
|
|
87
|
-
for (const
|
|
92
|
+
const importedProviders = new Set(this.globalExports);
|
|
93
|
+
for (const entry of [
|
|
94
|
+
...metadata.imports,
|
|
95
|
+
...extraImports
|
|
96
|
+
]){
|
|
97
|
+
// Unwrap forwardRef(() => Module) — resolves lazy circular references
|
|
98
|
+
const isForwardRef = entry instanceof ForwardRef;
|
|
99
|
+
const importedModule = isForwardRef ? entry.factory() : entry;
|
|
100
|
+
const importedModuleClass = isDynamicModule(importedModule) ? importedModule.module : importedModule;
|
|
101
|
+
// If this forwardRef-wrapped import is currently being processed, skip it to
|
|
102
|
+
// break the circular chain. Non-forwardRef circular imports still throw.
|
|
103
|
+
if (isForwardRef && this.processingStack.has(importedModuleClass)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
88
106
|
const exportedTokens = this.processModule(importedModule);
|
|
89
107
|
for (const token of exportedTokens){
|
|
90
108
|
importedProviders.add(token);
|
|
@@ -108,9 +126,30 @@ export class ModuleLoader {
|
|
|
108
126
|
this.collectedControllers.push(controller);
|
|
109
127
|
}
|
|
110
128
|
}
|
|
129
|
+
// Propagate module-level Use* decorators (@UseGuards, @UseInterceptors, etc.) to each controller
|
|
130
|
+
for (const controller of allControllers){
|
|
131
|
+
MetadataRegistry.propagateControllerComponents(moduleClass, controller);
|
|
132
|
+
}
|
|
111
133
|
this.processedModules.add(moduleClass);
|
|
112
134
|
const exports = this.buildExportSet(metadata.exports, allProviders, importedProviders);
|
|
113
135
|
this.moduleExportsCache.set(moduleClass, exports);
|
|
136
|
+
const isGlobal = metadata.isGlobal || isDynamicModule(moduleClassOrDynamic) && moduleClassOrDynamic.global === true;
|
|
137
|
+
if (isGlobal) {
|
|
138
|
+
for (const token of exports){
|
|
139
|
+
this.globalExports.add(token);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Call configure() if the module implements NestModule
|
|
143
|
+
if (typeof moduleClass.prototype?.configure === 'function') {
|
|
144
|
+
try {
|
|
145
|
+
const instance = new moduleClass();
|
|
146
|
+
const builder = new MiddlewareBuilder();
|
|
147
|
+
instance.configure(builder);
|
|
148
|
+
this.consumerMiddlewareDefinitions.push(...builder.getDefinitions());
|
|
149
|
+
} catch {
|
|
150
|
+
// Module has constructor dependencies — configure() skipped
|
|
151
|
+
}
|
|
152
|
+
}
|
|
114
153
|
return exports;
|
|
115
154
|
} finally{
|
|
116
155
|
this.processingStack.delete(moduleClass);
|
|
@@ -123,7 +162,7 @@ export class ModuleLoader {
|
|
|
123
162
|
this.registeredProviders.push(provider);
|
|
124
163
|
}
|
|
125
164
|
} else {
|
|
126
|
-
const token = provider.
|
|
165
|
+
const token = provider.provide;
|
|
127
166
|
if (!token) {
|
|
128
167
|
this.container.register(provider);
|
|
129
168
|
return;
|
|
@@ -132,7 +171,7 @@ export class ModuleLoader {
|
|
|
132
171
|
const syntheticToken = new InjectionToken(`${token.toString()}:${this.appProviderCounter++}`);
|
|
133
172
|
this.container.register({
|
|
134
173
|
...provider,
|
|
135
|
-
|
|
174
|
+
provide: syntheticToken
|
|
136
175
|
});
|
|
137
176
|
this.registeredProviders.push(syntheticToken);
|
|
138
177
|
this.appProviderTokens.get(token).push(syntheticToken);
|
|
@@ -154,7 +193,7 @@ export class ModuleLoader {
|
|
|
154
193
|
if (typeof p === 'function') {
|
|
155
194
|
return p === exported;
|
|
156
195
|
}
|
|
157
|
-
return p.
|
|
196
|
+
return p.provide === exported;
|
|
158
197
|
});
|
|
159
198
|
const isImportedProvider = importedProviders.has(exported);
|
|
160
199
|
if (!isLocalProvider && !isImportedProvider) {
|
|
@@ -180,14 +219,19 @@ export class ModuleLoader {
|
|
|
180
219
|
...this.appProviderTokens.get(token) ?? []
|
|
181
220
|
];
|
|
182
221
|
}
|
|
183
|
-
|
|
222
|
+
getConsumerMiddlewareDefinitions() {
|
|
223
|
+
return [
|
|
224
|
+
...this.consumerMiddlewareDefinitions
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
async resolveAllInstances() {
|
|
184
228
|
const instances = [];
|
|
185
229
|
for (const token of this.registeredProviders){
|
|
186
230
|
try {
|
|
187
231
|
if (this.container.getProviderScope(token) === Scope.REQUEST) {
|
|
188
232
|
continue;
|
|
189
233
|
}
|
|
190
|
-
const instance = this.container.
|
|
234
|
+
const instance = await this.container.resolveAsync(token);
|
|
191
235
|
instances.push(instance);
|
|
192
236
|
} catch {
|
|
193
237
|
// Skip unresolvable tokens
|
|
@@ -198,7 +242,7 @@ export class ModuleLoader {
|
|
|
198
242
|
if (this.container.getProviderScope(controller) === Scope.REQUEST) {
|
|
199
243
|
continue;
|
|
200
244
|
}
|
|
201
|
-
const instance = this.container.
|
|
245
|
+
const instance = await this.container.resolveAsync(controller);
|
|
202
246
|
if (!instances.includes(instance)) {
|
|
203
247
|
instances.push(instance);
|
|
204
248
|
}
|
package/dist/module/types.d.ts
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
import type { InjectionToken, ProviderOptions, Type } from '../container/types';
|
|
1
|
+
import type { ForwardRef, InjectionToken, ProviderOptions, Token, Type } from '../container/types';
|
|
2
|
+
export type ModuleImport = Type | DynamicModule | ForwardRef;
|
|
3
|
+
export interface AsyncModuleOptions<T = unknown> {
|
|
4
|
+
imports?: ModuleImport[];
|
|
5
|
+
useFactory: (...args: any[]) => T | Promise<T>;
|
|
6
|
+
inject?: Token[];
|
|
7
|
+
}
|
|
2
8
|
export interface DynamicModule {
|
|
3
9
|
module: Type;
|
|
10
|
+
imports?: ModuleImport[];
|
|
4
11
|
providers?: Array<Type | ProviderOptions>;
|
|
5
12
|
controllers?: Type[];
|
|
6
13
|
exports?: Array<Type | InjectionToken>;
|
|
14
|
+
global?: boolean;
|
|
7
15
|
}
|
|
8
16
|
export interface ModuleOptions {
|
|
9
17
|
providers?: Array<Type | ProviderOptions>;
|
|
10
18
|
controllers?: Type[];
|
|
11
|
-
imports?:
|
|
19
|
+
imports?: ModuleImport[];
|
|
12
20
|
exports?: Array<Type | InjectionToken>;
|
|
21
|
+
isGlobal?: boolean;
|
|
13
22
|
}
|
|
14
23
|
export interface ModuleMetadata {
|
|
15
24
|
providers: Array<Type | ProviderOptions>;
|
|
16
25
|
controllers: Type[];
|
|
17
|
-
imports:
|
|
26
|
+
imports: ModuleImport[];
|
|
18
27
|
exports: Array<Type | InjectionToken>;
|
|
28
|
+
isGlobal: boolean;
|
|
19
29
|
}
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch,
|
|
|
3
3
|
export { SetMetadata, Reflector } from './reflector';
|
|
4
4
|
export type { ReflectableDecorator, CreateDecoratorOptions } from './reflector';
|
|
5
5
|
export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from './tokens';
|
|
6
|
-
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipes';
|
|
7
|
-
export type {
|
|
6
|
+
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipes';
|
|
7
|
+
export type { ParseUUIDPipeOptions, ParseArrayPipeOptions } from './pipes';
|
|
8
|
+
export type { HttpArgumentsHost, ExecutionContext, CanActivate, CallHandler, NestInterceptor, NestMiddleware, PipeTransform, ExceptionFilter, ArgumentMetadata, } from './types';
|
package/dist/pipeline/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { ComponentManager } from "./component.manager.js";
|
|
|
2
2
|
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, getCatchTypes, shouldFilterCatch } from "./decorators.js";
|
|
3
3
|
export { SetMetadata, Reflector } from "./reflector.js";
|
|
4
4
|
export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./tokens.js";
|
|
5
|
-
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe } from "./pipes.js";
|
|
5
|
+
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe } from "./pipes.js";
|
package/dist/pipeline/pipes.d.ts
CHANGED
|
@@ -23,3 +23,25 @@ export declare class ZodValidationPipe implements PipeTransform {
|
|
|
23
23
|
});
|
|
24
24
|
transform(value: unknown, _metadata: ArgumentMetadata): unknown;
|
|
25
25
|
}
|
|
26
|
+
export interface ParseUUIDPipeOptions {
|
|
27
|
+
version?: '3' | '4' | '5';
|
|
28
|
+
}
|
|
29
|
+
export declare class ParseUUIDPipe implements PipeTransform<string, string> {
|
|
30
|
+
private readonly options?;
|
|
31
|
+
constructor(options?: ParseUUIDPipeOptions | undefined);
|
|
32
|
+
transform(value: string, metadata: ArgumentMetadata): string;
|
|
33
|
+
}
|
|
34
|
+
export declare class ParseEnumPipe<T extends Record<string, string | number>> implements PipeTransform<string, T[keyof T]> {
|
|
35
|
+
private readonly enumType;
|
|
36
|
+
constructor(enumType: T);
|
|
37
|
+
transform(value: string, metadata: ArgumentMetadata): T[keyof T];
|
|
38
|
+
}
|
|
39
|
+
export interface ParseArrayPipeOptions {
|
|
40
|
+
separator?: string;
|
|
41
|
+
optional?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare class ParseArrayPipe implements PipeTransform {
|
|
44
|
+
private readonly options?;
|
|
45
|
+
constructor(options?: ParseArrayPipeOptions | undefined);
|
|
46
|
+
transform(value: unknown, metadata: ArgumentMetadata): unknown[];
|
|
47
|
+
}
|
package/dist/pipeline/pipes.js
CHANGED
|
@@ -50,3 +50,53 @@ export class ZodValidationPipe {
|
|
|
50
50
|
return this.schema.parse(value);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
54
|
+
const UUID_VERSION_REGEX = {
|
|
55
|
+
'3': /^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
56
|
+
'4': /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
57
|
+
'5': /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
58
|
+
};
|
|
59
|
+
export class ParseUUIDPipe {
|
|
60
|
+
options;
|
|
61
|
+
constructor(options){
|
|
62
|
+
this.options = options;
|
|
63
|
+
}
|
|
64
|
+
transform(value, metadata) {
|
|
65
|
+
const regex = this.options?.version ? UUID_VERSION_REGEX[this.options.version] : UUID_REGEX;
|
|
66
|
+
if (!regex.test(value)) {
|
|
67
|
+
throw new BadRequestException(`Validation failed (uuid${this.options?.version ? ` v${this.options.version}` : ''} expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class ParseEnumPipe {
|
|
73
|
+
enumType;
|
|
74
|
+
constructor(enumType){
|
|
75
|
+
this.enumType = enumType;
|
|
76
|
+
}
|
|
77
|
+
transform(value, metadata) {
|
|
78
|
+
const enumValues = Object.values(this.enumType);
|
|
79
|
+
if (!enumValues.includes(value)) {
|
|
80
|
+
throw new BadRequestException(`Validation failed (${enumValues.join(', ')} expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class ParseArrayPipe {
|
|
86
|
+
options;
|
|
87
|
+
constructor(options){
|
|
88
|
+
this.options = options;
|
|
89
|
+
}
|
|
90
|
+
transform(value, metadata) {
|
|
91
|
+
if (value === undefined || value === null || value === '') {
|
|
92
|
+
if (this.options?.optional) return [];
|
|
93
|
+
throw new BadRequestException(`Validation failed (array expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(value)) return value;
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
const sep = this.options?.separator ?? ',';
|
|
98
|
+
return value.split(sep).map((v)=>v.trim()).filter((v)=>v.length > 0);
|
|
99
|
+
}
|
|
100
|
+
throw new BadRequestException(`Validation failed (array expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -8,7 +8,7 @@ import type { CanActivate, ExceptionFilter, NestInterceptor, NestMiddleware, Pip
|
|
|
8
8
|
* @Module({
|
|
9
9
|
* providers: [
|
|
10
10
|
* AuthGuard,
|
|
11
|
-
* {
|
|
11
|
+
* { provide: APP_GUARD, useClass: AuthGuard },
|
|
12
12
|
* ],
|
|
13
13
|
* })
|
|
14
14
|
* class AppModule {}
|
package/dist/pipeline/tokens.js
CHANGED
package/dist/pipeline/types.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { Context } from 'hono';
|
|
2
2
|
import type { Type } from '../container/types';
|
|
3
|
+
/**
|
|
4
|
+
* HTTP-specific arguments host returned by `ExecutionContext.switchToHttp()`.
|
|
5
|
+
* In Vela/Hono, the `Context` object holds both request and response state.
|
|
6
|
+
*
|
|
7
|
+
* - `getRequest()` — the Web `Request` object
|
|
8
|
+
* - `getResponse()` — the Hono `Context` (use `c.header()`, `c.setCookie()`, etc.)
|
|
9
|
+
*/
|
|
10
|
+
export interface HttpArgumentsHost {
|
|
11
|
+
getRequest<T = Request>(): T;
|
|
12
|
+
getResponse<T = Context>(): T;
|
|
13
|
+
}
|
|
3
14
|
export interface ExecutionContext {
|
|
15
|
+
getType<T extends string = 'http'>(): T;
|
|
4
16
|
getClass(): Type;
|
|
5
17
|
getHandler(): string | symbol;
|
|
18
|
+
/** Returns the Hono `Context` directly. */
|
|
6
19
|
getContext<T = Context>(): T;
|
|
20
|
+
/** Shorthand for `switchToHttp().getRequest()` — returns the Web `Request`. */
|
|
7
21
|
getRequest(): Request;
|
|
22
|
+
/** Switch to the HTTP arguments host for NestJS-style `getRequest()` / `getResponse()` access. */
|
|
23
|
+
switchToHttp(): HttpArgumentsHost;
|
|
8
24
|
}
|
|
9
25
|
export interface CanActivate {
|
|
10
26
|
canActivate(context: ExecutionContext): boolean | Promise<boolean>;
|