@velajs/vela 0.5.2 → 0.6.1
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/application.d.ts +3 -21
- package/dist/application.js +5 -43
- package/dist/event-emitter/event-emitter.service.d.ts +4 -4
- package/dist/event-emitter/event-emitter.service.js +91 -56
- package/dist/factory.d.ts +2 -1
- package/dist/factory.js +11 -2
- package/dist/http/index.d.ts +1 -0
- package/dist/http/route.manager.d.ts +12 -3
- package/dist/http/route.manager.js +114 -116
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/module/module-loader.d.ts +4 -4
- package/dist/module/module-loader.js +27 -24
- package/dist/pipeline/component.manager.js +1 -2
- package/dist/pipeline/pipes.d.ts +2 -1
- package/dist/pipeline/pipes.js +7 -5
- package/dist/registry/types.d.ts +2 -1
- package/package.json +1 -1
package/dist/application.d.ts
CHANGED
|
@@ -1,45 +1,27 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import type { Container } from './container/container';
|
|
3
3
|
import type { Token } from './container/types';
|
|
4
|
-
import type { CorsOptions } from './cors/cors.types';
|
|
5
4
|
import type { RouteManager } from './http/route.manager';
|
|
6
|
-
import type { FilterType, GuardType, InterceptorType,
|
|
5
|
+
import type { FilterType, GuardType, InterceptorType, PipeType } from './registry/types';
|
|
7
6
|
export declare class VelaApplication {
|
|
8
7
|
private readonly container;
|
|
9
8
|
private readonly routeManager;
|
|
10
9
|
private instances;
|
|
11
10
|
private honoApp;
|
|
12
|
-
private routesBuilt;
|
|
13
11
|
constructor(container: Container, routeManager: RouteManager);
|
|
14
12
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
|
|
15
13
|
initRoutes(): Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Rebuild routes. Call after registering global middleware/guards/etc.
|
|
18
|
-
* Not needed if you don't use useGlobalMiddleware() post-create.
|
|
19
|
-
*/
|
|
20
|
-
rebuild(): Promise<void>;
|
|
21
14
|
private getApp;
|
|
22
15
|
get fetch(): Hono['fetch'];
|
|
23
16
|
getInstances(): unknown[];
|
|
24
17
|
getContainer(): Container;
|
|
25
18
|
setInstances(instances: unknown[]): void;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* Must be called before getHonoApp()/fetch, or call rebuild() after.
|
|
29
|
-
*/
|
|
30
|
-
setGlobalPrefix(prefix: string): this;
|
|
31
|
-
/**
|
|
32
|
-
* Register global middleware. Call rebuild() after if called post-create.
|
|
33
|
-
* Controller/method-level @UseMiddleware works without rebuild.
|
|
34
|
-
*/
|
|
35
|
-
useGlobalMiddleware(...middleware: MiddlewareType[]): this;
|
|
19
|
+
get<T>(token: Token<T>): T;
|
|
20
|
+
getHonoApp(): Hono;
|
|
36
21
|
useGlobalPipes(...pipes: PipeType[]): this;
|
|
37
22
|
useGlobalGuards(...guards: GuardType[]): this;
|
|
38
23
|
useGlobalInterceptors(...interceptors: InterceptorType[]): this;
|
|
39
24
|
useGlobalFilters(...filters: FilterType[]): this;
|
|
40
|
-
enableCors(options?: CorsOptions): this;
|
|
41
|
-
get<T>(token: Token<T>): T;
|
|
42
|
-
getHonoApp(): Hono;
|
|
43
25
|
callOnModuleInit(): Promise<void>;
|
|
44
26
|
callOnApplicationBootstrap(): Promise<void>;
|
|
45
27
|
close(signal?: string): Promise<void>;
|
package/dist/application.js
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
import { cors } from "hono/cors";
|
|
2
1
|
import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
|
|
3
2
|
export class VelaApplication {
|
|
4
3
|
container;
|
|
5
4
|
routeManager;
|
|
6
5
|
instances = [];
|
|
7
6
|
honoApp = null;
|
|
8
|
-
routesBuilt = false;
|
|
9
7
|
constructor(container, routeManager){
|
|
10
8
|
this.container = container;
|
|
11
9
|
this.routeManager = routeManager;
|
|
12
10
|
}
|
|
13
11
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */ async initRoutes() {
|
|
14
12
|
this.honoApp = await this.routeManager.build();
|
|
15
|
-
this.routesBuilt = true;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Rebuild routes. Call after registering global middleware/guards/etc.
|
|
19
|
-
* Not needed if you don't use useGlobalMiddleware() post-create.
|
|
20
|
-
*/ async rebuild() {
|
|
21
|
-
this.honoApp = await this.routeManager.build();
|
|
22
|
-
this.routesBuilt = true;
|
|
23
13
|
}
|
|
24
14
|
getApp() {
|
|
25
15
|
if (!this.honoApp) {
|
|
@@ -39,20 +29,13 @@ export class VelaApplication {
|
|
|
39
29
|
setInstances(instances) {
|
|
40
30
|
this.instances = instances;
|
|
41
31
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* Must be called before getHonoApp()/fetch, or call rebuild() after.
|
|
45
|
-
*/ setGlobalPrefix(prefix) {
|
|
46
|
-
this.routeManager.setGlobalPrefix(prefix);
|
|
47
|
-
return this;
|
|
32
|
+
get(token) {
|
|
33
|
+
return this.container.resolve(token);
|
|
48
34
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* Controller/method-level @UseMiddleware works without rebuild.
|
|
52
|
-
*/ useGlobalMiddleware(...middleware) {
|
|
53
|
-
this.routeManager.useGlobalMiddleware(...middleware);
|
|
54
|
-
return this;
|
|
35
|
+
getHonoApp() {
|
|
36
|
+
return this.getApp();
|
|
55
37
|
}
|
|
38
|
+
// Pipeline components — applied at request time, no rebuild needed
|
|
56
39
|
useGlobalPipes(...pipes) {
|
|
57
40
|
this.routeManager.useGlobalPipes(...pipes);
|
|
58
41
|
return this;
|
|
@@ -69,27 +52,6 @@ export class VelaApplication {
|
|
|
69
52
|
this.routeManager.useGlobalFilters(...filters);
|
|
70
53
|
return this;
|
|
71
54
|
}
|
|
72
|
-
enableCors(options = {}) {
|
|
73
|
-
const corsMiddleware = cors({
|
|
74
|
-
origin: options.origin ?? '*',
|
|
75
|
-
allowMethods: options.allowMethods,
|
|
76
|
-
allowHeaders: options.allowHeaders,
|
|
77
|
-
exposeHeaders: options.exposeHeaders,
|
|
78
|
-
credentials: options.credentials,
|
|
79
|
-
maxAge: options.maxAge
|
|
80
|
-
});
|
|
81
|
-
const mw = {
|
|
82
|
-
use: (c, next)=>corsMiddleware(c, next)
|
|
83
|
-
};
|
|
84
|
-
this.routeManager.useGlobalMiddleware(mw);
|
|
85
|
-
return this;
|
|
86
|
-
}
|
|
87
|
-
get(token) {
|
|
88
|
-
return this.container.resolve(token);
|
|
89
|
-
}
|
|
90
|
-
getHonoApp() {
|
|
91
|
-
return this.getApp();
|
|
92
|
-
}
|
|
93
55
|
// Lifecycle hooks
|
|
94
56
|
async callOnModuleInit() {
|
|
95
57
|
for (const instance of this.instances){
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { EventHandler } from './event-emitter.types';
|
|
2
2
|
export declare class EventEmitter {
|
|
3
|
-
private
|
|
4
|
-
private
|
|
3
|
+
private exactListeners;
|
|
4
|
+
private exactOnceListeners;
|
|
5
|
+
private wildcardListeners;
|
|
5
6
|
on(event: string, handler: EventHandler): this;
|
|
6
7
|
once(event: string, handler: EventHandler): this;
|
|
7
8
|
off(event: string, handler: EventHandler): this;
|
|
8
9
|
emit(event: string, ...args: unknown[]): Promise<void>;
|
|
9
10
|
removeAllListeners(event?: string): this;
|
|
10
11
|
listenerCount(event: string): number;
|
|
11
|
-
private
|
|
12
|
-
private matchPattern;
|
|
12
|
+
private getOrCreateWildcard;
|
|
13
13
|
}
|
|
@@ -5,86 +5,121 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
}
|
|
7
7
|
import { Injectable } from "../container/index.js";
|
|
8
|
+
function isWildcard(pattern) {
|
|
9
|
+
return pattern.includes('*');
|
|
10
|
+
}
|
|
11
|
+
function compilePattern(pattern) {
|
|
12
|
+
const regexStr = pattern.split('.').map((segment)=>{
|
|
13
|
+
if (segment === '**') return '.*';
|
|
14
|
+
if (segment === '*') return '[^.]+';
|
|
15
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16
|
+
}).join('\\.');
|
|
17
|
+
return new RegExp(`^${regexStr}$`);
|
|
18
|
+
}
|
|
8
19
|
export class EventEmitter {
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
exactListeners = new Map();
|
|
21
|
+
exactOnceListeners = new Map();
|
|
22
|
+
wildcardListeners = new Map();
|
|
11
23
|
on(event, handler) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
if (isWildcard(event)) {
|
|
25
|
+
this.getOrCreateWildcard(event).handlers.add(handler);
|
|
26
|
+
} else {
|
|
27
|
+
const handlers = this.exactListeners.get(event) ?? new Set();
|
|
28
|
+
handlers.add(handler);
|
|
29
|
+
this.exactListeners.set(event, handlers);
|
|
30
|
+
}
|
|
15
31
|
return this;
|
|
16
32
|
}
|
|
17
33
|
once(event, handler) {
|
|
18
|
-
this.
|
|
19
|
-
|
|
34
|
+
this.on(event, handler);
|
|
35
|
+
if (isWildcard(event)) {
|
|
36
|
+
this.getOrCreateWildcard(event).onceHandlers.add(handler);
|
|
37
|
+
} else {
|
|
38
|
+
const onceSet = this.exactOnceListeners.get(event) ?? new Set();
|
|
39
|
+
onceSet.add(handler);
|
|
40
|
+
this.exactOnceListeners.set(event, onceSet);
|
|
41
|
+
}
|
|
42
|
+
return this;
|
|
20
43
|
}
|
|
21
44
|
off(event, handler) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
if (isWildcard(event)) {
|
|
46
|
+
const entry = this.wildcardListeners.get(event);
|
|
47
|
+
if (entry) {
|
|
48
|
+
entry.handlers.delete(handler);
|
|
49
|
+
entry.onceHandlers.delete(handler);
|
|
50
|
+
if (entry.handlers.size === 0) this.wildcardListeners.delete(event);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
const handlers = this.exactListeners.get(event);
|
|
54
|
+
if (handlers) {
|
|
55
|
+
handlers.delete(handler);
|
|
56
|
+
this.exactOnceListeners.get(event)?.delete(handler);
|
|
57
|
+
if (handlers.size === 0) this.exactListeners.delete(event);
|
|
58
|
+
}
|
|
27
59
|
}
|
|
28
|
-
this.onceListeners.delete(handler);
|
|
29
60
|
return this;
|
|
30
61
|
}
|
|
31
62
|
async emit(event, ...args) {
|
|
32
|
-
const handlers = this.getMatchingHandlers(event);
|
|
33
63
|
const toRemove = [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
// Exact match
|
|
65
|
+
const exactHandlers = this.exactListeners.get(event);
|
|
66
|
+
if (exactHandlers) {
|
|
67
|
+
const onceSet = this.exactOnceListeners.get(event);
|
|
68
|
+
await Promise.all([
|
|
69
|
+
...exactHandlers
|
|
70
|
+
].map(async (handler)=>{
|
|
71
|
+
await handler(...args);
|
|
72
|
+
if (onceSet?.has(handler)) {
|
|
73
|
+
toRemove.push(()=>this.off(event, handler));
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
// Wildcard match (regex pre-compiled at registration)
|
|
78
|
+
for (const [pattern, entry] of this.wildcardListeners){
|
|
79
|
+
if (!entry.regex.test(event)) continue;
|
|
80
|
+
await Promise.all([
|
|
81
|
+
...entry.handlers
|
|
82
|
+
].map(async (handler)=>{
|
|
83
|
+
await handler(...args);
|
|
84
|
+
if (entry.onceHandlers.has(handler)) {
|
|
85
|
+
toRemove.push(()=>this.off(pattern, handler));
|
|
86
|
+
}
|
|
87
|
+
}));
|
|
45
88
|
}
|
|
89
|
+
for (const remove of toRemove)remove();
|
|
46
90
|
}
|
|
47
91
|
removeAllListeners(event) {
|
|
48
92
|
if (event) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
93
|
+
if (isWildcard(event)) {
|
|
94
|
+
this.wildcardListeners.delete(event);
|
|
95
|
+
} else {
|
|
96
|
+
this.exactListeners.delete(event);
|
|
97
|
+
this.exactOnceListeners.delete(event);
|
|
52
98
|
}
|
|
53
|
-
this.listeners.delete(event);
|
|
54
99
|
} else {
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
100
|
+
this.exactListeners.clear();
|
|
101
|
+
this.exactOnceListeners.clear();
|
|
102
|
+
this.wildcardListeners.clear();
|
|
57
103
|
}
|
|
58
104
|
return this;
|
|
59
105
|
}
|
|
60
106
|
listenerCount(event) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const result = [];
|
|
65
|
-
for (const [pattern, handlers] of this.listeners){
|
|
66
|
-
if (this.matchPattern(pattern, event)) {
|
|
67
|
-
for (const handler of handlers){
|
|
68
|
-
result.push({
|
|
69
|
-
event: pattern,
|
|
70
|
-
handler
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
107
|
+
let count = this.exactListeners.get(event)?.size ?? 0;
|
|
108
|
+
for (const entry of this.wildcardListeners.values()){
|
|
109
|
+
if (entry.regex.test(event)) count += entry.handlers.size;
|
|
74
110
|
}
|
|
75
|
-
return
|
|
111
|
+
return count;
|
|
76
112
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return new RegExp(`^${regexStr}$`).test(event);
|
|
113
|
+
getOrCreateWildcard(pattern) {
|
|
114
|
+
const existing = this.wildcardListeners.get(pattern);
|
|
115
|
+
if (existing) return existing;
|
|
116
|
+
const entry = {
|
|
117
|
+
regex: compilePattern(pattern),
|
|
118
|
+
handlers: new Set(),
|
|
119
|
+
onceHandlers: new Set()
|
|
120
|
+
};
|
|
121
|
+
this.wildcardListeners.set(pattern, entry);
|
|
122
|
+
return entry;
|
|
88
123
|
}
|
|
89
124
|
}
|
|
90
125
|
EventEmitter = _ts_decorate([
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VelaApplication } from './application';
|
|
2
2
|
import type { Type } from './container/types';
|
|
3
|
+
import type { RouteManagerOptions } from './http/route.manager';
|
|
3
4
|
export declare const VelaFactory: {
|
|
4
|
-
create(rootModule: Type): Promise<VelaApplication>;
|
|
5
|
+
create(rootModule: Type, options?: RouteManagerOptions): Promise<VelaApplication>;
|
|
5
6
|
};
|
package/dist/factory.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ModuleLoader } from "./module/module-loader.js";
|
|
|
6
6
|
import { ComponentManager } from "./pipeline/component.manager.js";
|
|
7
7
|
import { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/tokens.js";
|
|
8
8
|
export const VelaFactory = {
|
|
9
|
-
async create (rootModule) {
|
|
9
|
+
async create (rootModule, options = {}) {
|
|
10
10
|
const container = new Container();
|
|
11
11
|
container.register({
|
|
12
12
|
provide: Container,
|
|
@@ -19,7 +19,7 @@ export const VelaFactory = {
|
|
|
19
19
|
Container
|
|
20
20
|
]
|
|
21
21
|
});
|
|
22
|
-
const routeManager = new RouteManager(container);
|
|
22
|
+
const routeManager = new RouteManager(container, options);
|
|
23
23
|
ComponentManager.init(container);
|
|
24
24
|
const loader = new ModuleLoader(container, routeManager);
|
|
25
25
|
loader.load(rootModule);
|
|
@@ -54,6 +54,15 @@ export const VelaFactory = {
|
|
|
54
54
|
routeManager.useGlobalMiddlewareTokens(APP_MIDDLEWARE);
|
|
55
55
|
}
|
|
56
56
|
routeManager.registerConsumerMiddleware(loader.getConsumerMiddlewareDefinitions());
|
|
57
|
+
if (options.globalPrefix) {
|
|
58
|
+
routeManager.setGlobalPrefix(options.globalPrefix);
|
|
59
|
+
}
|
|
60
|
+
for (const handler of options.middleware ?? []){
|
|
61
|
+
const mw = {
|
|
62
|
+
use: handler
|
|
63
|
+
};
|
|
64
|
+
routeManager.useGlobalMiddleware(mw);
|
|
65
|
+
}
|
|
57
66
|
const app = new VelaApplication(container, routeManager);
|
|
58
67
|
const instances = await loader.resolveAllInstances();
|
|
59
68
|
app.setInstances(instances);
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { RouteManager } from './route.manager';
|
|
2
|
+
export type { RouteManagerOptions } from './route.manager';
|
|
2
3
|
export { MiddlewareBuilder, RequestMethod } from './middleware-consumer';
|
|
3
4
|
export type { MiddlewareConsumer, MiddlewareConfigProxy, RouteInfo, NestModule, MiddlewareRouteDefinition } from './middleware-consumer';
|
|
4
5
|
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, isController, } from './decorators';
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
1
|
+
import { type Context, type MiddlewareHandler, Hono } from 'hono';
|
|
2
2
|
import type { Container } from '../container/container';
|
|
3
3
|
import type { Token, Type } from '../container/types';
|
|
4
4
|
import type { MiddlewareRouteDefinition } from './middleware-consumer';
|
|
5
5
|
import type { CanActivate, ExceptionFilter, NestInterceptor, NestMiddleware, PipeTransform } from '../pipeline/types';
|
|
6
6
|
import type { FilterType, GuardType, InterceptorType, MiddlewareType, PipeType } from '../registry/types';
|
|
7
7
|
import type { ControllerRegistration } from './types';
|
|
8
|
+
export interface RouteManagerOptions {
|
|
9
|
+
getClientIp?: (c: Context) => string | null;
|
|
10
|
+
middleware?: MiddlewareHandler[];
|
|
11
|
+
globalPrefix?: string;
|
|
12
|
+
}
|
|
8
13
|
export declare class RouteManager {
|
|
9
14
|
private container;
|
|
15
|
+
private static readonly METHOD_REGISTRAR;
|
|
16
|
+
private static readonly PARAM_EXTRACTORS;
|
|
10
17
|
private controllers;
|
|
11
18
|
private globalMiddleware;
|
|
12
19
|
private globalPipes;
|
|
@@ -15,7 +22,8 @@ export declare class RouteManager {
|
|
|
15
22
|
private globalFilters;
|
|
16
23
|
private globalPrefix;
|
|
17
24
|
private consumerMiddlewareDefinitions;
|
|
18
|
-
|
|
25
|
+
private readonly ipExtractor;
|
|
26
|
+
constructor(container: Container, options?: RouteManagerOptions);
|
|
19
27
|
registerConsumerMiddleware(definitions: MiddlewareRouteDefinition[]): this;
|
|
20
28
|
setGlobalPrefix(prefix: string): this;
|
|
21
29
|
useGlobalMiddleware(...middleware: MiddlewareType[]): this;
|
|
@@ -41,6 +49,7 @@ export declare class RouteManager {
|
|
|
41
49
|
private registerRoute;
|
|
42
50
|
private buildVersionedPaths;
|
|
43
51
|
private joinPaths;
|
|
44
|
-
private
|
|
52
|
+
private compilePathMatcher;
|
|
53
|
+
private compileRouteMatcher;
|
|
45
54
|
getControllers(): ControllerRegistration[];
|
|
46
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
+
import { getCookie } from "hono/cookie";
|
|
2
3
|
import { HttpMethod, ParamType } from "../constants.js";
|
|
3
4
|
import { getMetadata } from "../metadata.js";
|
|
4
5
|
import { RequestMethod } from "./middleware-consumer.js";
|
|
@@ -7,6 +8,7 @@ import { ComponentManager } from "../pipeline/component.manager.js";
|
|
|
7
8
|
import { shouldFilterCatch } from "../pipeline/decorators.js";
|
|
8
9
|
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
9
10
|
import { getHttpCode, getRedirect, getResponseHeaders } from "./decorators.js";
|
|
11
|
+
const defaultGetClientIp = (c)=>c.req.raw.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? c.req.raw.headers.get('x-real-ip') ?? null;
|
|
10
12
|
function parseRedirectResult(result) {
|
|
11
13
|
if (typeof result !== 'object' || result === null) return undefined;
|
|
12
14
|
if (!('url' in result)) return undefined;
|
|
@@ -21,6 +23,82 @@ function parseRedirectResult(result) {
|
|
|
21
23
|
}
|
|
22
24
|
export class RouteManager {
|
|
23
25
|
container;
|
|
26
|
+
static METHOD_REGISTRAR = new Map([
|
|
27
|
+
[
|
|
28
|
+
HttpMethod.GET,
|
|
29
|
+
(app, p, h)=>app.get(p, h)
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
HttpMethod.POST,
|
|
33
|
+
(app, p, h)=>app.post(p, h)
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
HttpMethod.PUT,
|
|
37
|
+
(app, p, h)=>app.put(p, h)
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
HttpMethod.PATCH,
|
|
41
|
+
(app, p, h)=>app.patch(p, h)
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
HttpMethod.DELETE,
|
|
45
|
+
(app, p, h)=>app.delete(p, h)
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
HttpMethod.OPTIONS,
|
|
49
|
+
(app, p, h)=>app.options(p, h)
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
HttpMethod.HEAD,
|
|
53
|
+
(app, p, h)=>app.get(p, h)
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
HttpMethod.ALL,
|
|
57
|
+
(app, p, h)=>app.all(p, h)
|
|
58
|
+
]
|
|
59
|
+
]);
|
|
60
|
+
static PARAM_EXTRACTORS = new Map([
|
|
61
|
+
[
|
|
62
|
+
ParamType.PARAM,
|
|
63
|
+
(c, p)=>p.name ? c.req.param(p.name) : c.req.param()
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
ParamType.QUERY,
|
|
67
|
+
(c, p)=>p.name ? c.req.query(p.name) : c.req.query()
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
ParamType.BODY,
|
|
71
|
+
async (c, p)=>{
|
|
72
|
+
let body;
|
|
73
|
+
try {
|
|
74
|
+
body = await c.req.json();
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return p.name && body !== null && typeof body === 'object' ? body[p.name] : body;
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
ParamType.HEADERS,
|
|
83
|
+
(c, p)=>p.name ? c.req.header(p.name) : c.req.header()
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
ParamType.REQUEST,
|
|
87
|
+
(c)=>c
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
ParamType.RESPONSE,
|
|
91
|
+
(c)=>c
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
ParamType.COOKIE,
|
|
95
|
+
(c, p)=>p.name ? getCookie(c, p.name) : getCookie(c)
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
ParamType.RAW_BODY,
|
|
99
|
+
async (c)=>new Uint8Array(await c.req.arrayBuffer())
|
|
100
|
+
]
|
|
101
|
+
]);
|
|
24
102
|
controllers = [];
|
|
25
103
|
globalMiddleware = [];
|
|
26
104
|
globalPipes = [];
|
|
@@ -29,8 +107,10 @@ export class RouteManager {
|
|
|
29
107
|
globalFilters = [];
|
|
30
108
|
globalPrefix = '';
|
|
31
109
|
consumerMiddlewareDefinitions = [];
|
|
32
|
-
|
|
110
|
+
ipExtractor;
|
|
111
|
+
constructor(container, options = {}){
|
|
33
112
|
this.container = container;
|
|
113
|
+
this.ipExtractor = options.getClientIp ?? defaultGetClientIp;
|
|
34
114
|
}
|
|
35
115
|
registerConsumerMiddleware(definitions) {
|
|
36
116
|
this.consumerMiddlewareDefinitions.push(...definitions);
|
|
@@ -133,25 +213,12 @@ export class RouteManager {
|
|
|
133
213
|
}
|
|
134
214
|
// Register MiddlewareConsumer-configured middleware
|
|
135
215
|
for (const def of this.consumerMiddlewareDefinitions){
|
|
216
|
+
const matchRoute = this.compileRouteMatcher(def.routes);
|
|
217
|
+
const matchExclude = this.compileRouteMatcher(def.excludes);
|
|
136
218
|
app.use('*', (c, next)=>{
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
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();
|
|
219
|
+
const path = c.req.path;
|
|
220
|
+
const method = c.req.method;
|
|
221
|
+
if (!matchRoute(path, method) || matchExclude(path, method)) return next();
|
|
155
222
|
const requestContainer = this.getRequestContainer(c);
|
|
156
223
|
const runChain = (index)=>{
|
|
157
224
|
if (index >= def.middleware.length) return next();
|
|
@@ -321,7 +388,7 @@ export class RouteManager {
|
|
|
321
388
|
c
|
|
322
389
|
];
|
|
323
390
|
}
|
|
324
|
-
const maxIndex =
|
|
391
|
+
const maxIndex = paramMetadata.at(-1).index;
|
|
325
392
|
const args = new Array(maxIndex + 1).fill(undefined);
|
|
326
393
|
for (const param of paramMetadata){
|
|
327
394
|
let value = await this.extractParam(c, param);
|
|
@@ -345,65 +412,11 @@ export class RouteManager {
|
|
|
345
412
|
}
|
|
346
413
|
return args;
|
|
347
414
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return param.name ? c.req.query(param.name) : c.req.query();
|
|
354
|
-
case ParamType.BODY:
|
|
355
|
-
{
|
|
356
|
-
let body;
|
|
357
|
-
try {
|
|
358
|
-
body = await c.req.json();
|
|
359
|
-
} catch {
|
|
360
|
-
return undefined;
|
|
361
|
-
}
|
|
362
|
-
if (param.name && body !== null && typeof body === 'object') {
|
|
363
|
-
return body[param.name];
|
|
364
|
-
}
|
|
365
|
-
return body;
|
|
366
|
-
}
|
|
367
|
-
case ParamType.HEADERS:
|
|
368
|
-
if (param.name) {
|
|
369
|
-
return c.req.header(param.name);
|
|
370
|
-
}
|
|
371
|
-
const headers = {};
|
|
372
|
-
c.req.raw.headers.forEach((value, key)=>{
|
|
373
|
-
headers[key] = value;
|
|
374
|
-
});
|
|
375
|
-
return headers;
|
|
376
|
-
case ParamType.REQUEST:
|
|
377
|
-
return c;
|
|
378
|
-
case ParamType.RESPONSE:
|
|
379
|
-
return c;
|
|
380
|
-
case ParamType.IP:
|
|
381
|
-
return c.req.raw.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? c.req.raw.headers.get('x-real-ip') ?? null;
|
|
382
|
-
case ParamType.COOKIE:
|
|
383
|
-
{
|
|
384
|
-
const cookieHeader = c.req.raw.headers.get('cookie') ?? '';
|
|
385
|
-
const cookies = {};
|
|
386
|
-
for (const pair of cookieHeader.split(';')){
|
|
387
|
-
const eqIdx = pair.indexOf('=');
|
|
388
|
-
if (eqIdx === -1) continue;
|
|
389
|
-
const name = pair.slice(0, eqIdx).trim();
|
|
390
|
-
const value = pair.slice(eqIdx + 1).trim();
|
|
391
|
-
if (name) cookies[name] = decodeURIComponent(value);
|
|
392
|
-
}
|
|
393
|
-
return param.name ? cookies[param.name] ?? undefined : cookies;
|
|
394
|
-
}
|
|
395
|
-
case ParamType.RAW_BODY:
|
|
396
|
-
{
|
|
397
|
-
const buffer = await c.req.arrayBuffer();
|
|
398
|
-
return new Uint8Array(buffer);
|
|
399
|
-
}
|
|
400
|
-
default:
|
|
401
|
-
// Custom param decorator — use factory if available
|
|
402
|
-
if (param.factory) {
|
|
403
|
-
return param.factory(param.name, c);
|
|
404
|
-
}
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
415
|
+
extractParam(c, param) {
|
|
416
|
+
if (param.type === ParamType.IP) return this.ipExtractor(c);
|
|
417
|
+
const extractor = RouteManager.PARAM_EXTRACTORS.get(param.type);
|
|
418
|
+
if (extractor) return extractor(c, param);
|
|
419
|
+
return param.factory ? param.factory(param.name, c) : undefined;
|
|
407
420
|
}
|
|
408
421
|
createResponse(c, result, statusCode) {
|
|
409
422
|
if (result instanceof Response) {
|
|
@@ -419,35 +432,8 @@ export class RouteManager {
|
|
|
419
432
|
}
|
|
420
433
|
registerRoute(app, method, path, handler) {
|
|
421
434
|
const normalizedPath = path || '/';
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
app.get(normalizedPath, handler);
|
|
425
|
-
break;
|
|
426
|
-
case HttpMethod.POST:
|
|
427
|
-
app.post(normalizedPath, handler);
|
|
428
|
-
break;
|
|
429
|
-
case HttpMethod.PUT:
|
|
430
|
-
app.put(normalizedPath, handler);
|
|
431
|
-
break;
|
|
432
|
-
case HttpMethod.PATCH:
|
|
433
|
-
app.patch(normalizedPath, handler);
|
|
434
|
-
break;
|
|
435
|
-
case HttpMethod.DELETE:
|
|
436
|
-
app.delete(normalizedPath, handler);
|
|
437
|
-
break;
|
|
438
|
-
case HttpMethod.OPTIONS:
|
|
439
|
-
app.options(normalizedPath, handler);
|
|
440
|
-
break;
|
|
441
|
-
case HttpMethod.HEAD:
|
|
442
|
-
// Hono converts HEAD to GET internally, so register as GET
|
|
443
|
-
app.get(normalizedPath, handler);
|
|
444
|
-
break;
|
|
445
|
-
case HttpMethod.ALL:
|
|
446
|
-
app.all(normalizedPath, handler);
|
|
447
|
-
break;
|
|
448
|
-
default:
|
|
449
|
-
app.on(method.toUpperCase(), normalizedPath, handler);
|
|
450
|
-
}
|
|
435
|
+
const registrar = RouteManager.METHOD_REGISTRAR.get(method);
|
|
436
|
+
registrar ? registrar(app, normalizedPath, handler) : app.on(method.toUpperCase(), normalizedPath, handler);
|
|
451
437
|
}
|
|
452
438
|
buildVersionedPaths(globalPrefix, controllerPrefix, routePath, version) {
|
|
453
439
|
if (version === undefined) {
|
|
@@ -468,15 +454,27 @@ export class RouteManager {
|
|
|
468
454
|
const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;
|
|
469
455
|
return `${cleanPrefix}${cleanPath}` || '/';
|
|
470
456
|
}
|
|
471
|
-
|
|
472
|
-
if (routePath === '*') return true;
|
|
473
|
-
const
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return reqPath.startsWith(normalized.slice(0, -1));
|
|
457
|
+
compilePathMatcher(routePath) {
|
|
458
|
+
if (routePath === '*') return ()=>true;
|
|
459
|
+
const norm = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
460
|
+
if (norm.endsWith('*')) {
|
|
461
|
+
const prefix = norm.slice(0, -1);
|
|
462
|
+
return (p)=>p.startsWith(prefix);
|
|
478
463
|
}
|
|
479
|
-
return
|
|
464
|
+
return (p)=>p === norm || p.startsWith(`${norm}/`);
|
|
465
|
+
}
|
|
466
|
+
compileRouteMatcher(routes) {
|
|
467
|
+
const matchers = routes.map((route)=>{
|
|
468
|
+
const matchPath = this.compilePathMatcher(route.path);
|
|
469
|
+
return (path, method)=>{
|
|
470
|
+
if (!matchPath(path)) return false;
|
|
471
|
+
if (route.method && route.method !== RequestMethod.ALL) return method === route.method;
|
|
472
|
+
return true;
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
if (matchers.length === 0) return ()=>false;
|
|
476
|
+
if (matchers.length === 1) return matchers[0];
|
|
477
|
+
return (path, method)=>matchers.some((m)=>m(path, method));
|
|
480
478
|
}
|
|
481
479
|
getControllers() {
|
|
482
480
|
return [
|
package/dist/index.d.ts
CHANGED
|
@@ -37,8 +37,10 @@ export { HttpException, BadRequestException, UnauthorizedException, ForbiddenExc
|
|
|
37
37
|
export type { OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, OnApplicationShutdown, BeforeApplicationShutdown, } from './lifecycle/index';
|
|
38
38
|
export { MetadataRegistry } from './registry/index';
|
|
39
39
|
export { RouteManager } from './http/index';
|
|
40
|
+
export type { RouteManagerOptions } from './http/index';
|
|
40
41
|
export { ModuleLoader } from './module/index';
|
|
41
42
|
export { ComponentManager } from './pipeline/index';
|
|
42
43
|
export { createZodDto, ValidationPipe } from './validation/index';
|
|
43
44
|
export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from './serialization/index';
|
|
44
45
|
export { Test, TestingModule, TestingModuleBuilder } from './testing/index';
|
|
46
|
+
export { getRuntimeKey, env } from 'hono/adapter';
|
package/dist/index.js
CHANGED
|
@@ -49,3 +49,5 @@ export { createZodDto, ValidationPipe } from "./validation/index.js";
|
|
|
49
49
|
export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from "./serialization/index.js";
|
|
50
50
|
// Testing
|
|
51
51
|
export { Test, TestingModule, TestingModuleBuilder } from "./testing/index.js";
|
|
52
|
+
// Hono Adapter Utilities
|
|
53
|
+
export { getRuntimeKey, env } from "hono/adapter";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Container } from
|
|
2
|
-
import type { Token, Type } from
|
|
3
|
-
import type { MiddlewareRouteDefinition } from
|
|
4
|
-
import type { RouteManager } from
|
|
1
|
+
import type { Container } from "../container/container";
|
|
2
|
+
import type { Token, Type } from "../container/types";
|
|
3
|
+
import type { MiddlewareRouteDefinition } from "../http/middleware-consumer";
|
|
4
|
+
import type { RouteManager } from "../http/route.manager";
|
|
5
5
|
export declare class ModuleLoader {
|
|
6
6
|
private container;
|
|
7
7
|
private router;
|
|
@@ -2,17 +2,24 @@ import { Scope } from "../constants.js";
|
|
|
2
2
|
import { ForwardRef, InjectionToken } from "../container/types.js";
|
|
3
3
|
import { MiddlewareBuilder } from "../http/middleware-consumer.js";
|
|
4
4
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
5
|
+
const APP_TOKENS = new Set([
|
|
6
|
+
APP_GUARD,
|
|
7
|
+
APP_PIPE,
|
|
8
|
+
APP_INTERCEPTOR,
|
|
9
|
+
APP_FILTER,
|
|
10
|
+
APP_MIDDLEWARE
|
|
11
|
+
]);
|
|
5
12
|
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
6
13
|
import { getModuleMetadata, isModule } from "./decorators.js";
|
|
7
14
|
function isDynamicModule(value) {
|
|
8
|
-
return typeof value ===
|
|
15
|
+
return typeof value === "object" && value !== null && "module" in value && typeof value.module === "function";
|
|
9
16
|
}
|
|
10
17
|
export class ModuleLoader {
|
|
11
18
|
container;
|
|
12
19
|
router;
|
|
13
20
|
processedModules = new Set();
|
|
14
21
|
processingStack = new Set();
|
|
15
|
-
collectedControllers =
|
|
22
|
+
collectedControllers = new Set();
|
|
16
23
|
registeredProviders = [];
|
|
17
24
|
moduleExportsCache = new Map();
|
|
18
25
|
globalExports = new Set();
|
|
@@ -67,9 +74,7 @@ export class ModuleLoader {
|
|
|
67
74
|
if (this.processedModules.has(moduleClass)) {
|
|
68
75
|
// Even if already processed, still collect extra controllers from dynamic module
|
|
69
76
|
for (const controller of extraControllers){
|
|
70
|
-
|
|
71
|
-
this.collectedControllers.push(controller);
|
|
72
|
-
}
|
|
77
|
+
this.collectedControllers.add(controller);
|
|
73
78
|
}
|
|
74
79
|
return this.moduleExportsCache.get(moduleClass) ?? new Set();
|
|
75
80
|
}
|
|
@@ -77,7 +82,7 @@ export class ModuleLoader {
|
|
|
77
82
|
const chain = [
|
|
78
83
|
...this.processingStack,
|
|
79
84
|
moduleClass
|
|
80
|
-
].map((m)=>m.name).join(
|
|
85
|
+
].map((m)=>m.name).join(" -> ");
|
|
81
86
|
throw new Error(`Circular module dependency detected: ${chain}`);
|
|
82
87
|
}
|
|
83
88
|
if (!isModule(moduleClass)) {
|
|
@@ -122,9 +127,7 @@ export class ModuleLoader {
|
|
|
122
127
|
...extraControllers
|
|
123
128
|
];
|
|
124
129
|
for (const controller of allControllers){
|
|
125
|
-
|
|
126
|
-
this.collectedControllers.push(controller);
|
|
127
|
-
}
|
|
130
|
+
this.collectedControllers.add(controller);
|
|
128
131
|
}
|
|
129
132
|
// Propagate module-level Use* decorators (@UseGuards, @UseInterceptors, etc.) to each controller
|
|
130
133
|
for (const controller of allControllers){
|
|
@@ -140,7 +143,7 @@ export class ModuleLoader {
|
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
// Call configure() if the module implements NestModule
|
|
143
|
-
if (typeof moduleClass.prototype?.configure ===
|
|
146
|
+
if (typeof moduleClass.prototype?.configure === "function") {
|
|
144
147
|
try {
|
|
145
148
|
const instance = new moduleClass();
|
|
146
149
|
const builder = new MiddlewareBuilder();
|
|
@@ -156,7 +159,7 @@ export class ModuleLoader {
|
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
registerProvider(provider) {
|
|
159
|
-
if (typeof provider ===
|
|
162
|
+
if (typeof provider === "function") {
|
|
160
163
|
if (!this.container.has(provider)) {
|
|
161
164
|
this.container.register(provider);
|
|
162
165
|
this.registeredProviders.push(provider);
|
|
@@ -184,17 +187,17 @@ export class ModuleLoader {
|
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
isAppToken(token) {
|
|
187
|
-
return token
|
|
190
|
+
return APP_TOKENS.has(token);
|
|
188
191
|
}
|
|
189
192
|
buildExportSet(exports, providers, importedProviders) {
|
|
190
193
|
const exportSet = new Set();
|
|
194
|
+
const providerTokens = new Set();
|
|
195
|
+
for (const p of providers){
|
|
196
|
+
const token = typeof p === "function" ? p : p.provide;
|
|
197
|
+
if (token !== undefined) providerTokens.add(token);
|
|
198
|
+
}
|
|
191
199
|
for (const exported of exports){
|
|
192
|
-
const isLocalProvider =
|
|
193
|
-
if (typeof p === 'function') {
|
|
194
|
-
return p === exported;
|
|
195
|
-
}
|
|
196
|
-
return p.provide === exported;
|
|
197
|
-
});
|
|
200
|
+
const isLocalProvider = providerTokens.has(exported);
|
|
198
201
|
const isImportedProvider = importedProviders.has(exported);
|
|
199
202
|
if (!isLocalProvider && !isImportedProvider) {
|
|
200
203
|
const name = exported.name ?? String(exported);
|
|
@@ -225,14 +228,14 @@ export class ModuleLoader {
|
|
|
225
228
|
];
|
|
226
229
|
}
|
|
227
230
|
async resolveAllInstances() {
|
|
228
|
-
const
|
|
231
|
+
const instanceSet = new Set();
|
|
229
232
|
for (const token of this.registeredProviders){
|
|
230
233
|
try {
|
|
231
234
|
if (this.container.getProviderScope(token) === Scope.REQUEST) {
|
|
232
235
|
continue;
|
|
233
236
|
}
|
|
234
237
|
const instance = await this.container.resolveAsync(token);
|
|
235
|
-
|
|
238
|
+
instanceSet.add(instance);
|
|
236
239
|
} catch {
|
|
237
240
|
// Skip unresolvable tokens
|
|
238
241
|
}
|
|
@@ -243,13 +246,13 @@ export class ModuleLoader {
|
|
|
243
246
|
continue;
|
|
244
247
|
}
|
|
245
248
|
const instance = await this.container.resolveAsync(controller);
|
|
246
|
-
|
|
247
|
-
instances.push(instance);
|
|
248
|
-
}
|
|
249
|
+
instanceSet.add(instance);
|
|
249
250
|
} catch {
|
|
250
251
|
// Skip unresolvable controllers
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
|
-
return
|
|
254
|
+
return [
|
|
255
|
+
...instanceSet
|
|
256
|
+
];
|
|
254
257
|
}
|
|
255
258
|
}
|
|
@@ -27,11 +27,10 @@ export class ComponentManager {
|
|
|
27
27
|
// 3-level resolution: global → controller → handler
|
|
28
28
|
static getComponents(type, controller, handlerName) {
|
|
29
29
|
const handlerKey = `${controller.name}:${String(handlerName)}`;
|
|
30
|
-
const globalComponents = Array.from(MetadataRegistry.getGlobal(type));
|
|
31
30
|
const controllerComponents = MetadataRegistry.getController(type, controller);
|
|
32
31
|
const handlerComponents = MetadataRegistry.getHandler(type, handlerKey);
|
|
33
32
|
return [
|
|
34
|
-
...
|
|
33
|
+
...MetadataRegistry.getGlobal(type),
|
|
35
34
|
...controllerComponents,
|
|
36
35
|
...handlerComponents
|
|
37
36
|
];
|
package/dist/pipeline/pipes.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export declare class ParseUUIDPipe implements PipeTransform<string, string> {
|
|
|
32
32
|
transform(value: string, metadata: ArgumentMetadata): string;
|
|
33
33
|
}
|
|
34
34
|
export declare class ParseEnumPipe<T extends Record<string, string | number>> implements PipeTransform<string, T[keyof T]> {
|
|
35
|
-
private readonly
|
|
35
|
+
private readonly allowedValues;
|
|
36
|
+
private readonly valuesLabel;
|
|
36
37
|
constructor(enumType: T);
|
|
37
38
|
transform(value: string, metadata: ArgumentMetadata): T[keyof T];
|
|
38
39
|
}
|
package/dist/pipeline/pipes.js
CHANGED
|
@@ -70,14 +70,16 @@ export class ParseUUIDPipe {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
export class ParseEnumPipe {
|
|
73
|
-
|
|
73
|
+
allowedValues;
|
|
74
|
+
valuesLabel;
|
|
74
75
|
constructor(enumType){
|
|
75
|
-
|
|
76
|
+
const values = Object.values(enumType);
|
|
77
|
+
this.allowedValues = new Set(values);
|
|
78
|
+
this.valuesLabel = values.join(', ');
|
|
76
79
|
}
|
|
77
80
|
transform(value, metadata) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw new BadRequestException(`Validation failed (${enumValues.join(', ')} expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
81
|
+
if (!this.allowedValues.has(value)) {
|
|
82
|
+
throw new BadRequestException(`Validation failed (${this.valuesLabel} expected)${metadata.data ? ` for parameter '${metadata.data}'` : ''}`);
|
|
81
83
|
}
|
|
82
84
|
return value;
|
|
83
85
|
}
|
package/dist/registry/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
1
2
|
import type { CanActivate, ExceptionFilter, NestInterceptor, NestMiddleware, PipeTransform } from '../pipeline/types';
|
|
2
3
|
export type Type<T = any> = new (...args: any[]) => T;
|
|
3
4
|
export type Constructor = Function;
|
|
@@ -26,7 +27,7 @@ export interface ParameterMetadata {
|
|
|
26
27
|
type: string;
|
|
27
28
|
name?: string;
|
|
28
29
|
pipes?: PipeType[];
|
|
29
|
-
factory?: (data: unknown, ctx:
|
|
30
|
+
factory?: (data: unknown, ctx: Context) => unknown;
|
|
30
31
|
}
|
|
31
32
|
export interface HttpHandlerMeta {
|
|
32
33
|
httpCode?: number;
|