@velajs/vela 1.4.0 → 1.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +52 -0
  3. package/dist/cache/cache.module.d.ts +1 -0
  4. package/dist/cache/cache.module.js +6 -0
  5. package/dist/config/config.module.d.ts +4 -1
  6. package/dist/config/config.module.js +6 -0
  7. package/dist/container/container.d.ts +32 -4
  8. package/dist/container/container.js +252 -103
  9. package/dist/container/decorators.d.ts +8 -1
  10. package/dist/container/decorators.js +7 -4
  11. package/dist/container/index.d.ts +1 -1
  12. package/dist/container/index.js +1 -1
  13. package/dist/container/types.d.ts +13 -0
  14. package/dist/container/types.js +10 -0
  15. package/dist/cors/cors.module.d.ts +3 -1
  16. package/dist/cors/cors.module.js +2 -0
  17. package/dist/fetch/fetch.module.d.ts +3 -1
  18. package/dist/fetch/fetch.module.js +8 -3
  19. package/dist/http/argument-resolver.d.ts +10 -0
  20. package/dist/http/argument-resolver.js +89 -0
  21. package/dist/http/handler-executor.d.ts +20 -0
  22. package/dist/http/handler-executor.js +108 -0
  23. package/dist/http/instantiate.d.ts +4 -0
  24. package/dist/http/instantiate.js +18 -0
  25. package/dist/http/response-mapper.d.ts +8 -0
  26. package/dist/http/response-mapper.js +42 -0
  27. package/dist/http/route.manager.d.ts +1 -9
  28. package/dist/http/route.manager.js +23 -240
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -2
  31. package/dist/module/decorators.d.ts +6 -2
  32. package/dist/module/decorators.js +7 -6
  33. package/dist/module/index.d.ts +2 -1
  34. package/dist/module/index.js +2 -1
  35. package/dist/module/module-loader.d.ts +6 -1
  36. package/dist/module/module-loader.js +127 -47
  37. package/dist/module/stable-hash.d.ts +15 -0
  38. package/dist/module/stable-hash.js +52 -0
  39. package/dist/registry/types.d.ts +11 -0
  40. package/dist/throttler/throttler.module.d.ts +6 -2
  41. package/dist/throttler/throttler.module.js +6 -0
  42. package/package.json +1 -1
@@ -4,13 +4,14 @@ 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 { createModuleRef } from "../module/decorators.js";
8
7
  import { Module } from "../module/decorators.js";
8
+ import { stableHash } from "../module/stable-hash.js";
9
9
  import { HttpService, HTTP_MODULE_OPTIONS } from "./fetch.service.js";
10
10
  export class HttpModule {
11
11
  static forRoot(options = {}) {
12
12
  return {
13
- module: createModuleRef('HttpModule'),
13
+ module: HttpModule,
14
+ key: stableHash(options),
14
15
  providers: [
15
16
  {
16
17
  provide: HTTP_MODULE_OPTIONS,
@@ -26,7 +27,11 @@ export class HttpModule {
26
27
  }
27
28
  static forRootAsync(options) {
28
29
  return {
29
- module: createModuleRef('HttpModule'),
30
+ module: HttpModule,
31
+ key: options.key ?? stableHash({
32
+ inject: options.inject,
33
+ useFactory: options.useFactory
34
+ }),
30
35
  imports: options.imports ?? [],
31
36
  providers: [
32
37
  {
@@ -0,0 +1,10 @@
1
+ import type { Context } from 'hono';
2
+ import type { Container } from '../container/container';
3
+ import type { PipeTransform } from '../pipeline/types';
4
+ import type { ParamMetadata } from './types';
5
+ export declare class ArgumentResolver {
6
+ private readonly ipExtractor;
7
+ constructor(ipExtractor: (c: Context) => string | null);
8
+ extract(c: Context, paramMetadata: ParamMetadata[], pipes: PipeTransform[], requestContainer: Container, paramTypes?: unknown[]): Promise<unknown[]>;
9
+ private extractParam;
10
+ }
@@ -0,0 +1,89 @@
1
+ import { getCookie } from "hono/cookie";
2
+ import { ParamType } from "../constants.js";
3
+ import { instantiate } from "./instantiate.js";
4
+ const PARAM_EXTRACTORS = new Map([
5
+ [
6
+ ParamType.PARAM,
7
+ (c, p)=>p.name ? c.req.param(p.name) : c.req.param()
8
+ ],
9
+ [
10
+ ParamType.QUERY,
11
+ (c, p)=>p.name ? c.req.query(p.name) : c.req.query()
12
+ ],
13
+ [
14
+ ParamType.BODY,
15
+ async (c, p)=>{
16
+ let body;
17
+ try {
18
+ body = await c.req.json();
19
+ } catch {
20
+ return undefined;
21
+ }
22
+ return p.name && body !== null && typeof body === 'object' ? body[p.name] : body;
23
+ }
24
+ ],
25
+ [
26
+ ParamType.HEADERS,
27
+ (c, p)=>p.name ? c.req.header(p.name) : c.req.header()
28
+ ],
29
+ [
30
+ ParamType.REQUEST,
31
+ (c)=>c
32
+ ],
33
+ [
34
+ ParamType.RESPONSE,
35
+ (c)=>c
36
+ ],
37
+ [
38
+ ParamType.COOKIE,
39
+ (c, p)=>p.name ? getCookie(c, p.name) : getCookie(c)
40
+ ],
41
+ [
42
+ ParamType.RAW_BODY,
43
+ async (c)=>new Uint8Array(await c.req.arrayBuffer())
44
+ ]
45
+ ]);
46
+ // Pulls handler arguments from the request, applies shared pipes (global +
47
+ // controller + method) and then per-param pipes. Empty paramMetadata returns
48
+ // `[c]` to match the old direct-Hono-handler shape — a back-compat behavior
49
+ // the framework's tests rely on.
50
+ export class ArgumentResolver {
51
+ ipExtractor;
52
+ constructor(ipExtractor){
53
+ this.ipExtractor = ipExtractor;
54
+ }
55
+ async extract(c, paramMetadata, pipes, requestContainer, paramTypes) {
56
+ if (paramMetadata.length === 0) {
57
+ return [
58
+ c
59
+ ];
60
+ }
61
+ const maxIndex = paramMetadata.at(-1).index;
62
+ const args = new Array(maxIndex + 1).fill(undefined);
63
+ for (const param of paramMetadata){
64
+ let value = await this.extractParam(c, param);
65
+ const metadata = {
66
+ type: param.type,
67
+ data: param.name,
68
+ metatype: paramTypes?.[param.index]
69
+ };
70
+ for (const pipe of pipes){
71
+ value = await pipe.transform(value, metadata);
72
+ }
73
+ if (param.pipes && param.pipes.length > 0) {
74
+ for (const paramPipe of param.pipes){
75
+ const pipeInstance = instantiate(paramPipe, requestContainer);
76
+ value = await pipeInstance.transform(value, metadata);
77
+ }
78
+ }
79
+ args[param.index] = value;
80
+ }
81
+ return args;
82
+ }
83
+ extractParam(c, param) {
84
+ if (param.type === ParamType.IP) return this.ipExtractor(c);
85
+ const extractor = PARAM_EXTRACTORS.get(param.type);
86
+ if (extractor) return extractor(c, param);
87
+ return param.factory ? param.factory(param.name, c) : undefined;
88
+ }
89
+ }
@@ -0,0 +1,20 @@
1
+ import type { Context } from 'hono';
2
+ import type { Container } from '../container/container';
3
+ import type { Token, Type } from '../container/types';
4
+ import type { CanActivate, ExceptionFilter, NestInterceptor, PipeTransform } from '../pipeline/types';
5
+ import type { FilterType, GuardType, InterceptorType, ParameterMetadata, PipeType } from '../registry/types';
6
+ import type { ArgumentResolver } from './argument-resolver';
7
+ import type { RouteMetadata } from './types';
8
+ export interface HandlerGlobals {
9
+ guards: Array<GuardType | Token<CanActivate>>;
10
+ pipes: Array<PipeType | Token<PipeTransform>>;
11
+ interceptors: Array<InterceptorType | Token<NestInterceptor>>;
12
+ filters: Array<FilterType | Token<ExceptionFilter>>;
13
+ }
14
+ export declare class HandlerExecutor {
15
+ private readonly argumentResolver;
16
+ private readonly getGlobals;
17
+ private readonly getRequestContainer;
18
+ constructor(argumentResolver: ArgumentResolver, getGlobals: () => HandlerGlobals, getRequestContainer: (c: Context) => Container);
19
+ create(route: RouteMetadata, controller: Type, allParamMetadata: Map<string | symbol, ParameterMetadata[]>): (c: Context) => Promise<Response>;
20
+ }
@@ -0,0 +1,108 @@
1
+ import { ForbiddenException, HttpException } from "../errors/http-exception.js";
2
+ import { ComponentManager } from "../pipeline/component.manager.js";
3
+ import { shouldFilterCatch } from "../pipeline/decorators.js";
4
+ import { getHttpCode, getRedirect, getResponseHeaders } from "./decorators.js";
5
+ import { buildExecutionContext } from "./execution-context.js";
6
+ import { instantiateMany } from "./instantiate.js";
7
+ import { applyResponseHeaders, mapRedirect, mapResponse } from "./response-mapper.js";
8
+ // Builds the per-request closure that runs guards, pipes, interceptors, the
9
+ // handler itself, and exception filters. Pure orchestration — Hono route
10
+ // registration stays in RouteManager. Globals are read via a callback so
11
+ // post-create additions (`useGlobalGuards`, etc.) propagate to subsequent
12
+ // requests without rebuilding routes.
13
+ export class HandlerExecutor {
14
+ argumentResolver;
15
+ getGlobals;
16
+ getRequestContainer;
17
+ constructor(argumentResolver, getGlobals, getRequestContainer){
18
+ this.argumentResolver = argumentResolver;
19
+ this.getGlobals = getGlobals;
20
+ this.getRequestContainer = getRequestContainer;
21
+ }
22
+ create(route, controller, allParamMetadata) {
23
+ const paramMetadata = (allParamMetadata.get(route.handlerName) || []).sort((a, b)=>a.index - b.index);
24
+ // Read param types once at build time for metatype population.
25
+ // Routed via Reflect so the polyfill funnels both src-level and dist-level
26
+ // consumers to the same registry (matters in tests that import from dist).
27
+ const paramTypes = Reflect.getMetadata('design:paramtypes', controller.prototype, route.handlerName);
28
+ const methodGuards = ComponentManager.getComponents('guard', controller, route.handlerName);
29
+ const methodPipes = ComponentManager.getComponents('pipe', controller, route.handlerName);
30
+ const methodInterceptors = ComponentManager.getComponents('interceptor', controller, route.handlerName);
31
+ // Filters: reverse order (handler → controller → global) — closest to handler runs first
32
+ const methodFilters = [
33
+ ...ComponentManager.getComponents('filter', controller, route.handlerName)
34
+ ].reverse();
35
+ const httpCode = getHttpCode(controller, route.handlerName);
36
+ const responseHeaders = getResponseHeaders(controller, route.handlerName);
37
+ const redirect = getRedirect(controller, route.handlerName);
38
+ return async (c)=>{
39
+ // Combine global + method at request time so post-create registrations propagate.
40
+ const requestContainer = this.getRequestContainer(c);
41
+ const globals = this.getGlobals();
42
+ const guards = [
43
+ ...instantiateMany(globals.guards, requestContainer),
44
+ ...instantiateMany(methodGuards, requestContainer)
45
+ ];
46
+ const pipes = [
47
+ ...instantiateMany(globals.pipes, requestContainer),
48
+ ...instantiateMany(methodPipes, requestContainer)
49
+ ];
50
+ const interceptors = [
51
+ ...instantiateMany(globals.interceptors, requestContainer),
52
+ ...instantiateMany(methodInterceptors, requestContainer)
53
+ ];
54
+ const filters = [
55
+ ...instantiateMany(methodFilters, requestContainer),
56
+ ...instantiateMany(globals.filters, requestContainer)
57
+ ];
58
+ const executionContext = buildExecutionContext(c, controller, route.handlerName);
59
+ try {
60
+ const instance = requestContainer.resolve(controller);
61
+ // 1. Extract args + run pipes (vela order: args before guards).
62
+ const args = await this.argumentResolver.extract(c, paramMetadata, pipes, requestContainer, paramTypes);
63
+ // 2. Guards (fail-fast).
64
+ for (const guard of guards){
65
+ const canActivate = await guard.canActivate(executionContext);
66
+ if (!canActivate) {
67
+ throw new ForbiddenException();
68
+ }
69
+ }
70
+ // 3. Get handler method.
71
+ const method = Reflect.get(instance, route.handlerName);
72
+ if (typeof method !== 'function') {
73
+ throw new Error(`Method ${String(route.handlerName)} not found on controller`);
74
+ }
75
+ // 4. Build core handler.
76
+ const coreHandler = async ()=>Reflect.apply(method, instance, args);
77
+ // 5. Run interceptor chain.
78
+ const result = await ComponentManager.runInterceptorChain(interceptors, executionContext, coreHandler);
79
+ if (redirect) {
80
+ return mapRedirect(c, result, redirect);
81
+ }
82
+ const response = mapResponse(c, result, httpCode);
83
+ applyResponseHeaders(response, responseHeaders);
84
+ return response;
85
+ } catch (error) {
86
+ for (const filter of filters){
87
+ if (shouldFilterCatch(filter, error)) {
88
+ try {
89
+ const filtered = await filter.catch(error, executionContext);
90
+ return mapResponse(c, filtered);
91
+ } catch {
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ if (error instanceof HttpException) {
97
+ const response = error.getResponse();
98
+ const status = error.getStatus();
99
+ return c.json(response, status);
100
+ }
101
+ return c.json({
102
+ statusCode: 500,
103
+ message: 'Internal Server Error'
104
+ }, 500);
105
+ }
106
+ };
107
+ }
108
+ }
@@ -0,0 +1,4 @@
1
+ import type { Container } from '../container/container';
2
+ import type { Token, Type } from '../container/types';
3
+ export declare function instantiate<T>(classOrInstance: Type<T> | Token<T> | T, container: Container): T;
4
+ export declare function instantiateMany<T>(items: Array<Type<T> | Token<T> | T>, container: Container): T[];
@@ -0,0 +1,18 @@
1
+ // Resolve a class/token through the container if registered, otherwise treat
2
+ // the input as a plain instance. Used by RouteManager and HandlerExecutor to
3
+ // materialize middleware, guards, pipes, interceptors, and filters per request.
4
+ export function instantiate(classOrInstance, container) {
5
+ if (typeof classOrInstance === 'function') {
6
+ if (container.has(classOrInstance)) {
7
+ return container.resolve(classOrInstance);
8
+ }
9
+ return new classOrInstance();
10
+ }
11
+ if (container.has(classOrInstance)) {
12
+ return container.resolve(classOrInstance);
13
+ }
14
+ return classOrInstance;
15
+ }
16
+ export function instantiateMany(items, container) {
17
+ return items.map((item)=>instantiate(item, container));
18
+ }
@@ -0,0 +1,8 @@
1
+ import type { Context } from 'hono';
2
+ export interface ResponseRedirect {
3
+ url: string;
4
+ statusCode: number;
5
+ }
6
+ export declare function mapResponse(c: Context, result: unknown, statusCode?: number): Response;
7
+ export declare function mapRedirect(c: Context, result: unknown, redirect: ResponseRedirect): Response;
8
+ export declare function applyResponseHeaders(response: Response, headers: Iterable<readonly [string, string]>): void;
@@ -0,0 +1,42 @@
1
+ // A handler returning `{ url, statusCode? }` may override the @Redirect-decorator
2
+ // destination — kept identical to the previous inline behavior.
3
+ function parseRedirectResult(result) {
4
+ if (typeof result !== 'object' || result === null) return undefined;
5
+ if (!('url' in result)) return undefined;
6
+ const obj = result;
7
+ if (typeof obj.url !== 'string') return undefined;
8
+ return {
9
+ url: obj.url,
10
+ ...typeof obj.statusCode === 'number' ? {
11
+ statusCode: obj.statusCode
12
+ } : {}
13
+ };
14
+ }
15
+ // Maps a controller return value to a Hono Response. `null`/`undefined` → 204
16
+ // (empty body); strings → text; anything else → JSON. A pre-built Response
17
+ // passes through unchanged.
18
+ export function mapResponse(c, result, statusCode) {
19
+ if (result instanceof Response) {
20
+ return result;
21
+ }
22
+ if (result === null || result === undefined) {
23
+ return c.body(null, statusCode ?? 204);
24
+ }
25
+ if (typeof result === 'string') {
26
+ return c.text(result, statusCode ?? 200);
27
+ }
28
+ return c.json(result, statusCode ?? 200);
29
+ }
30
+ // Honors @Redirect on the handler. If the handler returned `{ url, statusCode? }`,
31
+ // those override the decorator's defaults; otherwise the decorator wins.
32
+ export function mapRedirect(c, result, redirect) {
33
+ const overrides = parseRedirectResult(result);
34
+ const finalUrl = overrides?.url ?? redirect.url;
35
+ const finalStatus = overrides?.statusCode ?? redirect.statusCode;
36
+ return c.redirect(finalUrl, finalStatus);
37
+ }
38
+ export function applyResponseHeaders(response, headers) {
39
+ for (const [name, value] of headers){
40
+ response.headers.set(name, value);
41
+ }
42
+ }
@@ -13,7 +13,6 @@ export interface RouteManagerOptions {
13
13
  export declare class RouteManager {
14
14
  private container;
15
15
  private static readonly METHOD_REGISTRAR;
16
- private static readonly PARAM_EXTRACTORS;
17
16
  private controllers;
18
17
  private globalMiddleware;
19
18
  private globalPipes;
@@ -22,7 +21,7 @@ export declare class RouteManager {
22
21
  private globalFilters;
23
22
  private globalPrefix;
24
23
  private consumerMiddlewareDefinitions;
25
- private readonly ipExtractor;
24
+ private readonly handlerExecutor;
26
25
  constructor(container: Container, options?: RouteManagerOptions);
27
26
  registerConsumerMiddleware(definitions: MiddlewareRouteDefinition[]): this;
28
27
  setGlobalPrefix(prefix: string): this;
@@ -36,17 +35,10 @@ export declare class RouteManager {
36
35
  useGlobalInterceptorTokens(...interceptorTokens: Array<Token<NestInterceptor>>): this;
37
36
  useGlobalFilters(...filters: FilterType[]): this;
38
37
  useGlobalFilterTokens(...filterTokens: Array<Token<ExceptionFilter>>): this;
39
- private instantiate;
40
- private instantiateMany;
41
38
  private getMiddlewarePriority;
42
39
  private getRequestContainer;
43
40
  registerController(controller: Type): this;
44
41
  build(): Promise<Hono>;
45
- private createExecutionContext;
46
- private createHandler;
47
- private extractArguments;
48
- private extractParam;
49
- private createResponse;
50
42
  private registerRoute;
51
43
  private buildVersionedPaths;
52
44
  private compilePathMatcher;