@velajs/vela 0.10.0 → 1.1.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 (91) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +36 -2
  3. package/dist/cache/cache.decorators.d.ts +2 -2
  4. package/dist/cache/cache.module.d.ts +1 -1
  5. package/dist/cache/cache.module.js +1 -1
  6. package/dist/constants.d.ts +29 -26
  7. package/dist/constants.js +26 -29
  8. package/dist/container/container.js +2 -2
  9. package/dist/container/decorators.js +8 -11
  10. package/dist/container/types.d.ts +1 -0
  11. package/dist/errors/http-exception.d.ts +21 -20
  12. package/dist/errors/http-exception.js +6 -6
  13. package/dist/event-emitter/event-emitter.decorators.js +2 -3
  14. package/dist/event-emitter/event-emitter.subscriber.js +2 -1
  15. package/dist/factory.d.ts +0 -1
  16. package/dist/factory.js +2 -32
  17. package/dist/fetch/fetch.module.d.ts +2 -2
  18. package/dist/fetch/fetch.module.js +2 -2
  19. package/dist/health/health.service.js +6 -2
  20. package/dist/http/decorators.d.ts +6 -5
  21. package/dist/http/decorators.js +28 -76
  22. package/dist/http/execution-context.d.ts +4 -0
  23. package/dist/http/execution-context.js +15 -0
  24. package/dist/http/index.d.ts +2 -2
  25. package/dist/http/index.js +1 -1
  26. package/dist/http/route.manager.d.ts +1 -2
  27. package/dist/http/route.manager.js +20 -31
  28. package/dist/http/types.d.ts +0 -1
  29. package/dist/index.d.ts +4 -10
  30. package/dist/index.js +7 -12
  31. package/dist/internal.d.ts +11 -0
  32. package/dist/internal.js +13 -0
  33. package/dist/metadata.js +6 -15
  34. package/dist/module/decorators.d.ts +1 -3
  35. package/dist/module/decorators.js +8 -20
  36. package/dist/module/graph.d.ts +10 -0
  37. package/dist/module/graph.js +35 -0
  38. package/dist/module/index.d.ts +2 -0
  39. package/dist/module/index.js +1 -0
  40. package/dist/{http/middleware-consumer.d.ts → module/middleware.d.ts} +4 -13
  41. package/dist/{http/middleware-consumer.js → module/middleware.js} +0 -13
  42. package/dist/module/module-loader.d.ts +1 -1
  43. package/dist/module/module-loader.js +17 -13
  44. package/dist/module/types.d.ts +1 -29
  45. package/dist/openapi/decorators.js +15 -10
  46. package/dist/openapi/document.js +7 -42
  47. package/dist/pipeline/app-providers.d.ts +10 -0
  48. package/dist/pipeline/app-providers.js +43 -0
  49. package/dist/pipeline/component.manager.d.ts +1 -0
  50. package/dist/pipeline/component.manager.js +10 -34
  51. package/dist/pipeline/decorators.d.ts +5 -5
  52. package/dist/pipeline/decorators.js +1 -9
  53. package/dist/pipeline/reflector.d.ts +8 -24
  54. package/dist/pipeline/reflector.js +10 -55
  55. package/dist/registry/index.d.ts +1 -1
  56. package/dist/registry/metadata.registry.d.ts +20 -15
  57. package/dist/registry/metadata.registry.js +135 -154
  58. package/dist/registry/paths.d.ts +3 -0
  59. package/dist/registry/paths.js +15 -0
  60. package/dist/registry/types.d.ts +24 -14
  61. package/dist/registry/types.js +0 -1
  62. package/dist/registry/util.d.ts +3 -0
  63. package/dist/registry/util.js +8 -0
  64. package/dist/schedule/cron-matcher.d.ts +2 -0
  65. package/dist/schedule/cron-matcher.js +62 -0
  66. package/dist/schedule/index.d.ts +4 -3
  67. package/dist/schedule/index.js +2 -2
  68. package/dist/schedule/schedule.decorators.js +3 -6
  69. package/dist/schedule/schedule.module.d.ts +2 -6
  70. package/dist/schedule/schedule.module.js +4 -42
  71. package/dist/schedule/schedule.registry.js +3 -2
  72. package/dist/schedule/schedule.tokens.d.ts +0 -3
  73. package/dist/schedule/schedule.tokens.js +0 -2
  74. package/dist/schedule/schedule.types.d.ts +0 -3
  75. package/dist/schedule-node/index.d.ts +2 -0
  76. package/dist/schedule-node/index.js +2 -0
  77. package/dist/schedule-node/schedule-node.module.d.ts +4 -0
  78. package/dist/schedule-node/schedule-node.module.js +18 -0
  79. package/dist/{schedule → schedule-node}/schedule.executor.d.ts +2 -5
  80. package/dist/schedule-node/schedule.executor.js +91 -0
  81. package/dist/services/logger.d.ts +9 -8
  82. package/dist/services/logger.js +14 -15
  83. package/dist/throttler/throttler.decorators.d.ts +2 -2
  84. package/package.json +9 -9
  85. package/dist/schedule/schedule.executor.js +0 -169
  86. package/dist/testing/index.d.ts +0 -2
  87. package/dist/testing/index.js +0 -2
  88. package/dist/testing/testing.builder.d.ts +0 -29
  89. package/dist/testing/testing.builder.js +0 -148
  90. package/dist/testing/testing.module.d.ts +0 -9
  91. package/dist/testing/testing.module.js +0 -15
@@ -0,0 +1,35 @@
1
+ import { ForwardRef } from "../container/types.js";
2
+ import { getModuleMetadata } from "./decorators.js";
3
+ function isDynamicModuleLike(value) {
4
+ return !!value && typeof value === 'object' && 'module' in value && typeof value.module === 'function';
5
+ }
6
+ /**
7
+ * Walks the module dependency graph from a root, unwrapping ForwardRef and
8
+ * dynamic-module imports along the way, and returns every controller
9
+ * reachable from it. Pure metadata read — no Container, no side effects.
10
+ *
11
+ * Used by createOpenApiDocument and any other consumer that needs a
12
+ * controller list without bootstrapping the application.
13
+ */ export function collectControllers(rootModule) {
14
+ const visited = new Set();
15
+ const controllers = new Set();
16
+ const visit = (entry)=>{
17
+ const unwrapped = entry instanceof ForwardRef ? entry.factory() : entry;
18
+ const moduleClass = isDynamicModuleLike(unwrapped) ? unwrapped.module : unwrapped;
19
+ const extraControllers = isDynamicModuleLike(unwrapped) ? unwrapped.controllers ?? [] : [];
20
+ const extraImports = isDynamicModuleLike(unwrapped) ? unwrapped.imports ?? [] : [];
21
+ if (typeof moduleClass !== 'function' || visited.has(moduleClass)) return;
22
+ visited.add(moduleClass);
23
+ const metadata = getModuleMetadata(moduleClass);
24
+ if (metadata) {
25
+ for (const controller of metadata.controllers)controllers.add(controller);
26
+ for (const imp of metadata.imports)visit(imp);
27
+ }
28
+ for (const controller of extraControllers)controllers.add(controller);
29
+ for (const imp of extraImports)visit(imp);
30
+ };
31
+ visit(rootModule);
32
+ return [
33
+ ...controllers
34
+ ];
35
+ }
@@ -1,3 +1,5 @@
1
1
  export { Global, Module, isModule, getModuleMetadata, createModuleRef } from './decorators';
2
2
  export { ModuleLoader } from './module-loader';
3
+ export { MiddlewareBuilder } from './middleware';
4
+ export type { MiddlewareConsumer, MiddlewareConfigProxy, MiddlewareRouteDefinition, NestModule, RouteInfo, } from './middleware';
3
5
  export type { ModuleOptions, ModuleMetadata, DynamicModule, AsyncModuleOptions, ModuleImport } from './types';
@@ -1,2 +1,3 @@
1
1
  export { Global, Module, isModule, getModuleMetadata, createModuleRef } from "./decorators.js";
2
2
  export { ModuleLoader } from "./module-loader.js";
3
+ export { MiddlewareBuilder } from "./middleware.js";
@@ -1,18 +1,9 @@
1
+ import { HttpMethod } from '../constants';
1
2
  import type { NestMiddleware } from '../pipeline/types';
2
- import type { Type } from '../registry/types';
3
- export declare enum RequestMethod {
4
- GET = "GET",
5
- POST = "POST",
6
- PUT = "PUT",
7
- DELETE = "DELETE",
8
- PATCH = "PATCH",
9
- OPTIONS = "OPTIONS",
10
- HEAD = "HEAD",
11
- ALL = "ALL"
12
- }
3
+ import type { Constructor, Type } from '../registry/types';
13
4
  export interface RouteInfo {
14
5
  path: string;
15
- method?: RequestMethod;
6
+ method?: HttpMethod;
16
7
  }
17
8
  export interface MiddlewareRouteDefinition {
18
9
  middleware: Array<Type<NestMiddleware> | NestMiddleware>;
@@ -24,7 +15,7 @@ export interface MiddlewareRouteDefinition {
24
15
  export interface MiddlewareConfigProxy {
25
16
  exclude(...routes: Array<string | RouteInfo>): MiddlewareConfigProxy;
26
17
  withPriority(priority: number): MiddlewareConfigProxy;
27
- forRoutes(...routes: Array<string | Function | RouteInfo>): MiddlewareConsumer;
18
+ forRoutes(...routes: Array<string | Constructor | RouteInfo>): MiddlewareConsumer;
28
19
  }
29
20
  export interface MiddlewareConsumer {
30
21
  apply(...middleware: Array<Type<NestMiddleware> | NestMiddleware>): MiddlewareConfigProxy;
@@ -1,15 +1,4 @@
1
1
  import { MetadataRegistry } from "../registry/metadata.registry.js";
2
- export var RequestMethod = /*#__PURE__*/ function(RequestMethod) {
3
- RequestMethod["GET"] = "GET";
4
- RequestMethod["POST"] = "POST";
5
- RequestMethod["PUT"] = "PUT";
6
- RequestMethod["DELETE"] = "DELETE";
7
- RequestMethod["PATCH"] = "PATCH";
8
- RequestMethod["OPTIONS"] = "OPTIONS";
9
- RequestMethod["HEAD"] = "HEAD";
10
- RequestMethod["ALL"] = "ALL";
11
- return RequestMethod;
12
- }({});
13
2
  export class MiddlewareBuilder {
14
3
  definitions = [];
15
4
  apply(...middleware) {
@@ -27,7 +16,6 @@ export class MiddlewareBuilder {
27
16
  currentPriority = priority;
28
17
  return proxy;
29
18
  },
30
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
31
19
  forRoutes: (...routes)=>{
32
20
  this.definitions.push({
33
21
  middleware: currentMiddleware,
@@ -57,7 +45,6 @@ function normalizeRouteArg(route) {
57
45
  path: route
58
46
  } : route;
59
47
  }
60
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
61
48
  function resolveRouteArg(route) {
62
49
  if (typeof route === 'string') {
63
50
  return [
@@ -1,7 +1,7 @@
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";
4
3
  import type { RouteManager } from "../http/route.manager";
4
+ import type { MiddlewareRouteDefinition } from "./middleware";
5
5
  export declare class ModuleLoader {
6
6
  private container;
7
7
  private router;
@@ -1,7 +1,10 @@
1
1
  import { Scope } from "../constants.js";
2
2
  import { ForwardRef, InjectionToken } from "../container/types.js";
3
- import { MiddlewareBuilder } from "../http/middleware-consumer.js";
4
3
  import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
4
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
5
+ import { getOrCreateArray } from "../registry/util.js";
6
+ import { getModuleMetadata, isModule } from "./decorators.js";
7
+ import { MiddlewareBuilder } from "./middleware.js";
5
8
  const APP_TOKENS = new Set([
6
9
  APP_GUARD,
7
10
  APP_PIPE,
@@ -9,11 +12,12 @@ const APP_TOKENS = new Set([
9
12
  APP_FILTER,
10
13
  APP_MIDDLEWARE
11
14
  ]);
12
- import { MetadataRegistry } from "../registry/metadata.registry.js";
13
- import { getModuleMetadata, isModule } from "./decorators.js";
14
15
  function isDynamicModule(value) {
15
16
  return typeof value === "object" && value !== null && "module" in value && typeof value.module === "function";
16
17
  }
18
+ function implementsNestModule(cls) {
19
+ return typeof cls.prototype?.configure === "function";
20
+ }
17
21
  export class ModuleLoader {
18
22
  container;
19
23
  router;
@@ -152,16 +156,16 @@ export class ModuleLoader {
152
156
  this.globalExports.add(token);
153
157
  }
154
158
  }
155
- // Call configure() if the module implements NestModule
156
- if (typeof moduleClass.prototype?.configure === "function") {
157
- try {
158
- const instance = new moduleClass();
159
- const builder = new MiddlewareBuilder();
160
- instance.configure(builder);
161
- this.consumerMiddlewareDefinitions.push(...builder.getDefinitions());
162
- } catch {
163
- // Module has constructor dependencies — configure() skipped
159
+ // Call configure() if the module implements NestModule. The module is
160
+ // resolved through the container so it can have its own DI dependencies.
161
+ if (implementsNestModule(moduleClass)) {
162
+ if (!this.container.has(moduleClass)) {
163
+ this.container.register(moduleClass);
164
164
  }
165
+ const instance = this.container.resolve(moduleClass);
166
+ const builder = new MiddlewareBuilder();
167
+ instance.configure(builder);
168
+ this.consumerMiddlewareDefinitions.push(...builder.getDefinitions());
165
169
  }
166
170
  return exports;
167
171
  } finally{
@@ -187,7 +191,7 @@ export class ModuleLoader {
187
191
  provide: syntheticToken
188
192
  });
189
193
  this.registeredProviders.push(syntheticToken);
190
- this.appProviderTokens.get(token).push(syntheticToken);
194
+ getOrCreateArray(this.appProviderTokens, token).push(syntheticToken);
191
195
  return;
192
196
  }
193
197
  if (!this.container.has(token)) {
@@ -1,29 +1 @@
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
- }
8
- export interface DynamicModule {
9
- module: Type;
10
- imports?: ModuleImport[];
11
- providers?: Array<Type | ProviderOptions>;
12
- controllers?: Type[];
13
- exports?: Array<Type | InjectionToken>;
14
- global?: boolean;
15
- }
16
- export interface ModuleOptions {
17
- providers?: Array<Type | ProviderOptions>;
18
- controllers?: Type[];
19
- imports?: ModuleImport[];
20
- exports?: Array<Type | InjectionToken>;
21
- isGlobal?: boolean;
22
- }
23
- export interface ModuleMetadata {
24
- providers: Array<Type | ProviderOptions>;
25
- controllers: Type[];
26
- imports: ModuleImport[];
27
- exports: Array<Type | InjectionToken>;
28
- isGlobal: boolean;
29
- }
1
+ export type { AsyncModuleOptions, DynamicModule, ModuleImport, ModuleMetadata, ModuleOptions, } from '../registry/types';
@@ -1,3 +1,4 @@
1
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
1
2
  export const API_DOC_METADATA = 'vela:openapi:doc';
2
3
  export const API_TAGS_METADATA = 'vela:openapi:tags';
3
4
  export const API_RESPONSES_METADATA = 'vela:openapi:responses';
@@ -12,9 +13,9 @@ export const API_RESPONSES_METADATA = 'vela:openapi:responses';
12
13
  */ export function ApiDoc(metadata) {
13
14
  return (target, propertyKey)=>{
14
15
  if (propertyKey !== undefined) {
15
- Reflect.defineMetadata(API_DOC_METADATA, metadata, target, propertyKey);
16
+ MetadataRegistry.setCustomHandlerMeta(target.constructor, propertyKey, API_DOC_METADATA, metadata);
16
17
  } else {
17
- Reflect.defineMetadata(API_DOC_METADATA, metadata, target);
18
+ MetadataRegistry.setCustomClassMeta(target, API_DOC_METADATA, metadata);
18
19
  }
19
20
  };
20
21
  }
@@ -24,17 +25,23 @@ export const API_RESPONSES_METADATA = 'vela:openapi:responses';
24
25
  */ export function ApiTags(...tags) {
25
26
  return (target, propertyKey)=>{
26
27
  if (propertyKey !== undefined) {
27
- Reflect.defineMetadata(API_TAGS_METADATA, tags, target, propertyKey);
28
+ MetadataRegistry.setCustomHandlerMeta(target.constructor, propertyKey, API_TAGS_METADATA, tags);
28
29
  } else {
29
- Reflect.defineMetadata(API_TAGS_METADATA, tags, target);
30
+ MetadataRegistry.setCustomClassMeta(target, API_TAGS_METADATA, tags);
30
31
  }
31
32
  };
32
33
  }
33
34
  export function getApiDoc(target, propertyKey) {
34
- return propertyKey !== undefined ? Reflect.getMetadata(API_DOC_METADATA, target, propertyKey) : Reflect.getMetadata(API_DOC_METADATA, target);
35
+ if (propertyKey !== undefined) {
36
+ return MetadataRegistry.getCustomHandlerMeta(target, propertyKey, API_DOC_METADATA);
37
+ }
38
+ return MetadataRegistry.getCustomClassMeta(target, API_DOC_METADATA);
35
39
  }
36
40
  export function getApiTags(target, propertyKey) {
37
- return propertyKey !== undefined ? Reflect.getMetadata(API_TAGS_METADATA, target, propertyKey) : Reflect.getMetadata(API_TAGS_METADATA, target);
41
+ if (propertyKey !== undefined) {
42
+ return MetadataRegistry.getCustomHandlerMeta(target, propertyKey, API_TAGS_METADATA);
43
+ }
44
+ return MetadataRegistry.getCustomClassMeta(target, API_TAGS_METADATA);
38
45
  }
39
46
  /**
40
47
  * Document a response for a given status code. Stackable: apply multiple
@@ -48,14 +55,12 @@ export function getApiTags(target, propertyKey) {
48
55
  * ```
49
56
  */ export function ApiResponse(status, options) {
50
57
  return (target, propertyKey)=>{
51
- const existing = Reflect.getMetadata(API_RESPONSES_METADATA, target, propertyKey) ?? [];
52
- existing.push({
58
+ MetadataRegistry.appendCustomHandlerMeta(target.constructor, propertyKey, API_RESPONSES_METADATA, {
53
59
  status,
54
60
  ...options
55
61
  });
56
- Reflect.defineMetadata(API_RESPONSES_METADATA, existing, target, propertyKey);
57
62
  };
58
63
  }
59
64
  export function getApiResponses(target, propertyKey) {
60
- return Reflect.getMetadata(API_RESPONSES_METADATA, target, propertyKey);
65
+ return MetadataRegistry.getCustomHandlerMeta(target, propertyKey, API_RESPONSES_METADATA);
61
66
  }
@@ -1,44 +1,9 @@
1
- import { getModuleMetadata } from "../module/decorators.js";
2
- import { ForwardRef } from "../container/types.js";
1
+ import { collectControllers } from "../module/graph.js";
3
2
  import { MetadataRegistry } from "../registry/metadata.registry.js";
3
+ import { joinPaths, toOpenApiPath } from "../registry/paths.js";
4
4
  import { ParamType } from "../constants.js";
5
5
  import { getApiDoc, getApiResponses, getApiTags } from "./decorators.js";
6
6
  import { isOptional, zodToJsonSchema } from "./zod-to-json-schema.js";
7
- function isDynamicModuleLike(v) {
8
- return !!v && typeof v === 'object' && 'module' in v && typeof v.module === 'function';
9
- }
10
- function collectControllers(rootModule) {
11
- const visited = new Set();
12
- const controllers = new Set();
13
- function visit(entry) {
14
- const unwrapped = entry instanceof ForwardRef ? entry.factory() : entry;
15
- const moduleClass = isDynamicModuleLike(unwrapped) ? unwrapped.module : unwrapped;
16
- const extraControllers = isDynamicModuleLike(unwrapped) ? unwrapped.controllers ?? [] : [];
17
- const extraImports = isDynamicModuleLike(unwrapped) ? unwrapped.imports ?? [] : [];
18
- if (typeof moduleClass !== 'function' || visited.has(moduleClass)) return;
19
- visited.add(moduleClass);
20
- const metadata = getModuleMetadata(moduleClass);
21
- if (metadata) {
22
- for (const controller of metadata.controllers)controllers.add(controller);
23
- for (const imp of metadata.imports)visit(imp);
24
- }
25
- for (const controller of extraControllers)controllers.add(controller);
26
- for (const imp of extraImports)visit(imp);
27
- }
28
- visit(rootModule);
29
- return [
30
- ...controllers
31
- ];
32
- }
33
- function normalizePath(path) {
34
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
35
- }
36
- function joinPath(a, b) {
37
- const left = a.endsWith('/') ? a.slice(0, -1) : a;
38
- const right = b && !b.startsWith('/') ? `/${b}` : b;
39
- const joined = `${left}${right}`;
40
- return joined || '/';
41
- }
42
7
  /**
43
8
  * Tracks DTO classes referenced during document generation and registers
44
9
  * each under `components.schemas`. Handles name collisions by suffixing.
@@ -176,9 +141,9 @@ function buildOperation(controller, route, pathString, registry) {
176
141
  });
177
142
  }
178
143
  }
179
- const docMeta = getApiDoc(controller.prototype, handlerName);
144
+ const docMeta = getApiDoc(controller, handlerName);
180
145
  const controllerTags = getApiTags(controller) ?? [];
181
- const handlerTags = getApiTags(controller.prototype, handlerName) ?? [];
146
+ const handlerTags = getApiTags(controller, handlerName) ?? [];
182
147
  const docTags = docMeta?.tags ?? [];
183
148
  const mergedTags = [
184
149
  ...new Set([
@@ -192,7 +157,7 @@ function buildOperation(controller, route, pathString, registry) {
192
157
  description: 'OK'
193
158
  }
194
159
  };
195
- const apiResponses = getApiResponses(controller.prototype, handlerName) ?? [];
160
+ const apiResponses = getApiResponses(controller, handlerName) ?? [];
196
161
  for (const entry of apiResponses){
197
162
  const key = String(entry.status);
198
163
  const resolved = {
@@ -239,8 +204,8 @@ export function createOpenApiDocument(rootModule, options = {}) {
239
204
  for (const route of routes){
240
205
  const method = route.method.toLowerCase();
241
206
  if (!VERB_WHITELIST.has(method)) continue;
242
- const rawPath = joinPath(joinPath(globalPrefix, controllerPath), route.path);
243
- const pathString = normalizePath(rawPath);
207
+ const rawPath = joinPaths(joinPaths(globalPrefix, controllerPath), route.path);
208
+ const pathString = toOpenApiPath(rawPath);
244
209
  const operation = buildOperation(controller, route, pathString, registry);
245
210
  const pathItem = paths[pathString] ?? {};
246
211
  pathItem[method] = operation;
@@ -0,0 +1,10 @@
1
+ import type { Container } from '../container/container';
2
+ import type { RouteManager } from '../http/route.manager';
3
+ import type { ModuleLoader } from '../module/module-loader';
4
+ /**
5
+ * Bind every APP_* provider registered in the container as a global
6
+ * component on the RouteManager. The single source of truth for app-level
7
+ * pipeline configuration — used by both VelaFactory.create and
8
+ * @velajs/testing's TestingModuleBuilder.compile.
9
+ */
10
+ export declare function bindAppProviders(routeManager: RouteManager, container: Container, loader: ModuleLoader): void;
@@ -0,0 +1,43 @@
1
+ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "./tokens.js";
2
+ // NestJS-style "APP_*" provider convention: each slot maps a well-known DI
3
+ // token to the RouteManager method that installs providers under it as a
4
+ // global component. Module-level @Provide(APP_*) entries expand into
5
+ // synthetic tokens (collected by ModuleLoader); a single @Inject(APP_*)
6
+ // provider falls back to the bare token.
7
+ const SLOTS = [
8
+ {
9
+ token: APP_GUARD,
10
+ bind: (rm)=>rm.useGlobalGuardTokens.bind(rm)
11
+ },
12
+ {
13
+ token: APP_PIPE,
14
+ bind: (rm)=>rm.useGlobalPipeTokens.bind(rm)
15
+ },
16
+ {
17
+ token: APP_INTERCEPTOR,
18
+ bind: (rm)=>rm.useGlobalInterceptorTokens.bind(rm)
19
+ },
20
+ {
21
+ token: APP_FILTER,
22
+ bind: (rm)=>rm.useGlobalFilterTokens.bind(rm)
23
+ },
24
+ {
25
+ token: APP_MIDDLEWARE,
26
+ bind: (rm)=>rm.useGlobalMiddlewareTokens.bind(rm)
27
+ }
28
+ ];
29
+ /**
30
+ * Bind every APP_* provider registered in the container as a global
31
+ * component on the RouteManager. The single source of truth for app-level
32
+ * pipeline configuration — used by both VelaFactory.create and
33
+ * @velajs/testing's TestingModuleBuilder.compile.
34
+ */ export function bindAppProviders(routeManager, container, loader) {
35
+ for (const { token, bind } of SLOTS){
36
+ const collected = loader.getAppProviderTokens(token);
37
+ if (collected.length > 0) {
38
+ bind(routeManager)(...collected);
39
+ } else if (container.has(token)) {
40
+ bind(routeManager)(token);
41
+ }
42
+ }
43
+ }
@@ -8,6 +8,7 @@ export declare class ComponentManager {
8
8
  static registerController<T extends ComponentType>(type: T, controller: Constructor, ...components: ComponentTypeMap[T][]): void;
9
9
  static registerHandler<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol, ...components: ComponentTypeMap[T][]): void;
10
10
  static getComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
11
+ private static resolveAll;
11
12
  static resolveMiddleware(items: MiddlewareType[]): NestMiddleware[];
12
13
  static resolveGuards(items: GuardType[]): CanActivate[];
13
14
  static resolvePipes(items: PipeType[]): PipeTransform[];
@@ -19,16 +19,14 @@ export class ComponentManager {
19
19
  }
20
20
  }
21
21
  static registerHandler(type, controller, handlerName, ...components) {
22
- const handlerKey = `${controller.name}:${String(handlerName)}`;
23
22
  for (const component of components){
24
- MetadataRegistry.registerHandler(type, handlerKey, component);
23
+ MetadataRegistry.registerHandler(type, controller, handlerName, component);
25
24
  }
26
25
  }
27
26
  // 3-level resolution: global → controller → handler
28
27
  static getComponents(type, controller, handlerName) {
29
- const handlerKey = `${controller.name}:${String(handlerName)}`;
30
28
  const controllerComponents = MetadataRegistry.getController(type, controller);
31
- const handlerComponents = MetadataRegistry.getHandler(type, handlerKey);
29
+ const handlerComponents = MetadataRegistry.getHandler(type, controller, handlerName);
32
30
  return [
33
31
  ...MetadataRegistry.getGlobal(type),
34
32
  ...controllerComponents,
@@ -36,45 +34,23 @@ export class ComponentManager {
36
34
  ];
37
35
  }
38
36
  // Type-specific resolvers
37
+ static resolveAll(items, methodKey) {
38
+ return items.map((item)=>isObject(item) && methodKey in item ? item : this.container.resolve(item));
39
+ }
39
40
  static resolveMiddleware(items) {
40
- return items.map((item)=>{
41
- if (isObject(item) && 'use' in item) {
42
- return item;
43
- }
44
- return this.container.resolve(item);
45
- });
41
+ return this.resolveAll(items, 'use');
46
42
  }
47
43
  static resolveGuards(items) {
48
- return items.map((item)=>{
49
- if (isObject(item) && 'canActivate' in item) {
50
- return item;
51
- }
52
- return this.container.resolve(item);
53
- });
44
+ return this.resolveAll(items, 'canActivate');
54
45
  }
55
46
  static resolvePipes(items) {
56
- return items.map((item)=>{
57
- if (isObject(item) && 'transform' in item) {
58
- return item;
59
- }
60
- return this.container.resolve(item);
61
- });
47
+ return this.resolveAll(items, 'transform');
62
48
  }
63
49
  static resolveInterceptors(items) {
64
- return items.map((item)=>{
65
- if (isObject(item) && 'intercept' in item) {
66
- return item;
67
- }
68
- return this.container.resolve(item);
69
- });
50
+ return this.resolveAll(items, 'intercept');
70
51
  }
71
52
  static resolveFilters(items) {
72
- return items.map((item)=>{
73
- if (isObject(item) && 'catch' in item) {
74
- return item;
75
- }
76
- return this.container.resolve(item);
77
- });
53
+ return this.resolveAll(items, 'catch');
78
54
  }
79
55
  // Pipe execution
80
56
  static async executePipes(value, metadata, pipes) {
@@ -1,10 +1,10 @@
1
1
  import type { Type } from '../container/types';
2
2
  import type { ComponentTypeMap } from '../registry/types';
3
- export declare function UseMiddleware(...middleware: ComponentTypeMap['middleware'][]): (target: Function | object, propertyKey?: string | symbol) => void;
4
- export declare function UseGuards(...guards: ComponentTypeMap['guard'][]): (target: Function | object, propertyKey?: string | symbol) => void;
5
- export declare function UsePipes(...pipes: ComponentTypeMap['pipe'][]): (target: Function | object, propertyKey?: string | symbol) => void;
6
- export declare function UseInterceptors(...interceptors: ComponentTypeMap['interceptor'][]): (target: Function | object, propertyKey?: string | symbol) => void;
7
- export declare function UseFilters(...filters: ComponentTypeMap['filter'][]): (target: Function | object, propertyKey?: string | symbol) => void;
3
+ export declare function UseMiddleware(...middleware: ComponentTypeMap['middleware'][]): (target: object, propertyKey?: string | symbol) => void;
4
+ export declare function UseGuards(...guards: ComponentTypeMap['guard'][]): (target: object, propertyKey?: string | symbol) => void;
5
+ export declare function UsePipes(...pipes: ComponentTypeMap['pipe'][]): (target: object, propertyKey?: string | symbol) => void;
6
+ export declare function UseInterceptors(...interceptors: ComponentTypeMap['interceptor'][]): (target: object, propertyKey?: string | symbol) => void;
7
+ export declare function UseFilters(...filters: ComponentTypeMap['filter'][]): (target: object, propertyKey?: string | symbol) => void;
8
8
  export declare function Catch(...exceptions: Type<Error>[]): ClassDecorator;
9
9
  export declare function getCatchTypes(filter: unknown): Type<Error>[];
10
10
  export declare function shouldFilterCatch(filter: unknown, exception: unknown): boolean;
@@ -1,15 +1,10 @@
1
- import { METADATA_KEYS } from "../constants.js";
2
- import { getMetadata } from "../metadata.js";
3
1
  import { ComponentManager } from "./component.manager.js";
4
2
  import { MetadataRegistry } from "../registry/metadata.registry.js";
5
3
  function UseComponent(type, ...components) {
6
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
7
4
  return (target, propertyKey)=>{
8
5
  if (propertyKey !== undefined) {
9
- // Method decorator — target is prototype, target.constructor is the class
10
6
  ComponentManager.registerHandler(type, target.constructor, propertyKey, ...components);
11
7
  } else {
12
- // Class decorator — target is the class itself
13
8
  ComponentManager.registerController(type, target, ...components);
14
9
  }
15
10
  };
@@ -36,10 +31,7 @@ export function Catch(...exceptions) {
36
31
  }
37
32
  export function getCatchTypes(filter) {
38
33
  const filterClass = typeof filter === 'function' ? filter : filter.constructor;
39
- if (MetadataRegistry.hasCatchTypes(filterClass)) {
40
- return MetadataRegistry.getCatchTypes(filterClass) ?? [];
41
- }
42
- return getMetadata(METADATA_KEYS.CATCH, filterClass) ?? [];
34
+ return MetadataRegistry.getCatchTypes(filterClass) ?? [];
43
35
  }
44
36
  export function shouldFilterCatch(filter, exception) {
45
37
  const catchTypes = getCatchTypes(filter);
@@ -1,3 +1,4 @@
1
+ import type { ExecutionContext } from './types';
1
2
  export interface CreateDecoratorOptions {
2
3
  key?: string;
3
4
  }
@@ -18,7 +19,7 @@ export interface ReflectableDecorator<TParam> {
18
19
  * class AdminController { ... }
19
20
  * ```
20
21
  */
21
- export declare function SetMetadata<V = unknown>(key: string, value: V): (target: Function | object, propertyKey?: string | symbol) => void;
22
+ export declare function SetMetadata<V = unknown>(key: string, value: V): (target: object, propertyKey?: string | symbol) => void;
22
23
  /**
23
24
  * Reads custom metadata set by @SetMetadata().
24
25
  * Use in guards/interceptors via ExecutionContext.
@@ -54,39 +55,25 @@ export declare class Reflector {
54
55
  * Get metadata value from handler first, then class.
55
56
  * Handler-level metadata takes priority over class-level.
56
57
  */
57
- get<T = unknown>(key: string | ReflectableDecorator<T>, context: {
58
- getClass(): Function;
59
- getHandler(): string | symbol;
60
- }): T | undefined;
58
+ get<T = unknown>(key: string | ReflectableDecorator<T>, context: ExecutionContext): T | undefined;
61
59
  /**
62
60
  * Get metadata only from the handler (method) level.
63
61
  */
64
- getHandler<T = unknown>(key: string | ReflectableDecorator<T>, context: {
65
- getClass(): Function;
66
- getHandler(): string | symbol;
67
- }): T | undefined;
62
+ getHandler<T = unknown>(key: string | ReflectableDecorator<T>, context: ExecutionContext): T | undefined;
68
63
  /**
69
64
  * Get metadata only from the class level.
70
65
  */
71
- getClass<T = unknown>(key: string | ReflectableDecorator<T>, context: {
72
- getClass(): Function;
73
- }): T | undefined;
66
+ getClass<T = unknown>(key: string | ReflectableDecorator<T>, context: Pick<ExecutionContext, "getClass">): T | undefined;
74
67
  /**
75
68
  * Get all metadata values for a key from both handler and class.
76
69
  * Returns [handlerValue, classValue] (either may be undefined).
77
70
  */
78
- getAll<T = unknown>(key: string | ReflectableDecorator<T>, context: {
79
- getClass(): Function;
80
- getHandler(): string | symbol;
81
- }): [T | undefined, T | undefined];
71
+ getAll<T = unknown>(key: string | ReflectableDecorator<T>, context: ExecutionContext): [T | undefined, T | undefined];
82
72
  /**
83
73
  * Returns handler value if defined, else class value, else undefined.
84
74
  * First defined value wins.
85
75
  */
86
- getAllAndOverride<T = unknown>(key: string | ReflectableDecorator<T>, context: {
87
- getClass(): Function;
88
- getHandler(): string | symbol;
89
- }): T | undefined;
76
+ getAllAndOverride<T = unknown>(key: string | ReflectableDecorator<T>, context: ExecutionContext): T | undefined;
90
77
  /**
91
78
  * Collects handler + class values, filters undefineds.
92
79
  * - 0 values → []
@@ -95,8 +82,5 @@ export declare class Reflector {
95
82
  * - Objects → Object.assign({}, ...values)
96
83
  * - Otherwise → wrap in array
97
84
  */
98
- getAllAndMerge<T = unknown>(key: string | ReflectableDecorator<T>, context: {
99
- getClass(): Function;
100
- getHandler(): string | symbol;
101
- }): T | T[];
85
+ getAllAndMerge<T = unknown>(key: string | ReflectableDecorator<T>, context: ExecutionContext): T | T[];
102
86
  }