@velajs/vela 1.10.0 → 1.13.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 (85) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/application.d.ts +25 -0
  3. package/dist/application.js +103 -2
  4. package/dist/container/container.d.ts +46 -1
  5. package/dist/container/container.js +156 -13
  6. package/dist/container/disposable.js +1 -1
  7. package/dist/container/types.d.ts +27 -0
  8. package/dist/cors/cors.module.d.ts +5 -5
  9. package/dist/cors/cors.module.js +26 -25
  10. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  11. package/dist/discovery/discoverable.decorator.js +42 -0
  12. package/dist/discovery/discovery.service.d.ts +102 -0
  13. package/dist/discovery/discovery.service.js +177 -0
  14. package/dist/discovery/index.d.ts +2 -0
  15. package/dist/discovery/index.js +2 -0
  16. package/dist/entrypoint/entrypoint.registry.d.ts +42 -0
  17. package/dist/entrypoint/entrypoint.registry.js +112 -0
  18. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  19. package/dist/entrypoint/entrypoint.types.js +3 -0
  20. package/dist/entrypoint/execution-context.d.ts +15 -0
  21. package/dist/entrypoint/execution-context.js +21 -0
  22. package/dist/entrypoint/execution-scope.d.ts +21 -0
  23. package/dist/entrypoint/execution-scope.js +28 -0
  24. package/dist/entrypoint/index.d.ts +4 -0
  25. package/dist/entrypoint/index.js +4 -0
  26. package/dist/event-emitter/event-emitter.module.js +1 -0
  27. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  28. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  29. package/dist/factory/adapter.d.ts +36 -0
  30. package/dist/factory/adapter.js +18 -0
  31. package/dist/factory/bootstrap.js +14 -2
  32. package/dist/factory.d.ts +11 -1
  33. package/dist/factory.js +23 -2
  34. package/dist/http/handler-executor.js +22 -19
  35. package/dist/http/route-contributor.d.ts +68 -0
  36. package/dist/http/route-contributor.js +22 -0
  37. package/dist/http/route.manager.js +33 -19
  38. package/dist/i18n/i18n.module.js +9 -1
  39. package/dist/index.d.ts +12 -3
  40. package/dist/index.js +9 -2
  41. package/dist/internal.d.ts +0 -2
  42. package/dist/internal.js +0 -7
  43. package/dist/module/configurable-module.builder.d.ts +12 -0
  44. package/dist/module/configurable-module.builder.js +21 -114
  45. package/dist/module/decorators.js +4 -2
  46. package/dist/module/define-module.d.ts +112 -0
  47. package/dist/module/define-module.js +232 -0
  48. package/dist/module/index.d.ts +3 -1
  49. package/dist/module/index.js +3 -1
  50. package/dist/module/lazy-modules.d.ts +82 -0
  51. package/dist/module/lazy-modules.js +231 -0
  52. package/dist/module/lazy-provider.d.ts +65 -0
  53. package/dist/module/lazy-provider.js +111 -0
  54. package/dist/module/module-loader.d.ts +21 -0
  55. package/dist/module/module-loader.js +99 -24
  56. package/dist/openapi/document.js +16 -15
  57. package/dist/pipeline/component.manager.d.ts +15 -9
  58. package/dist/pipeline/component.manager.js +32 -46
  59. package/dist/pipeline/index.d.ts +2 -0
  60. package/dist/pipeline/index.js +1 -0
  61. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  62. package/dist/pipeline/pipeline-runner.js +40 -0
  63. package/dist/registry/metadata.registry.d.ts +9 -3
  64. package/dist/registry/metadata.registry.js +55 -25
  65. package/dist/registry/types.d.ts +10 -0
  66. package/dist/schedule/schedule.module.d.ts +5 -0
  67. package/dist/schedule/schedule.module.js +21 -8
  68. package/dist/schedule/schedule.registry.d.ts +3 -3
  69. package/dist/schedule/schedule.registry.js +23 -44
  70. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  71. package/dist/schedule-node/schedule-node.module.js +20 -10
  72. package/dist/seeder/seeder.module.d.ts +8 -10
  73. package/dist/seeder/seeder.module.js +19 -15
  74. package/dist/websocket/index.d.ts +1 -1
  75. package/dist/websocket/index.js +1 -1
  76. package/dist/websocket/websocket.module.d.ts +20 -3
  77. package/dist/websocket/websocket.module.js +50 -39
  78. package/dist/websocket/websocket.tokens.d.ts +2 -1
  79. package/dist/websocket/websocket.tokens.js +3 -2
  80. package/dist/websocket/ws-dispatcher.d.ts +21 -9
  81. package/dist/websocket/ws-dispatcher.js +44 -38
  82. package/dist/websocket-node/register-gateways.js +3 -3
  83. package/package.json +1 -1
  84. package/dist/http/crud-bridge.d.ts +0 -90
  85. package/dist/http/crud-bridge.js +0 -25
@@ -1,5 +1,5 @@
1
- import { METADATA_KEYS, ParamType } from "../constants.js";
2
- import { getCrudBridge } from "../http/crud-bridge.js";
1
+ import { ParamType } from "../constants.js";
2
+ import { getRouteContributors } from "../http/route-contributor.js";
3
3
  import { getMetadata } from "../metadata.js";
4
4
  import { collectControllers } from "../module/graph.js";
5
5
  import { MetadataRegistry } from "../registry/metadata.registry.js";
@@ -215,25 +215,26 @@ export function createOpenApiDocument(rootModule, options = {}) {
215
215
  paths[pathString] = pathItem;
216
216
  }
217
217
  }
218
- // Second pass: include `@Crud()`-generated routes via the registered
219
- // CrudBridge. The bridge knows how to turn its own metadata config into
220
- // OpenAPI path items; this loop is silent when no bridge is registered or
221
- // no controller carries `vela:crud` metadata, so consumers without
222
- // `@velajs/crud` see no behavioral change.
223
- const bridge = getCrudBridge();
224
- if (bridge) {
218
+ // Second pass: include contributor-generated routes (e.g. `@Crud()` via
219
+ // `@velajs/crud`'s registered RouteContributor). Silent when no contributor
220
+ // is registered or no controller carries claiming metadata, so consumers
221
+ // without contributor packages see no behavioral change.
222
+ for (const contributor of getRouteContributors()){
223
+ if (!contributor.buildOpenApiPaths) continue;
225
224
  for (const controller of controllers){
226
- const crudConfig = getMetadata(METADATA_KEYS.CRUD, controller);
227
- if (!crudConfig) continue;
225
+ const meta = getMetadata(contributor.claimsMetaKey, controller);
226
+ if (meta === undefined) continue;
228
227
  const controllerPrefix = MetadataRegistry.getControllerPath(controller);
229
- const crudPaths = bridge.buildOpenApiPaths(controller, crudConfig, {
228
+ const contributedPaths = contributor.buildOpenApiPaths({
229
+ controller: controller,
230
+ meta,
230
231
  globalPrefix,
231
232
  controllerPrefix
232
233
  });
233
- for (const [pathKey, pathItem] of Object.entries(crudPaths)){
234
+ for (const [pathKey, pathItem] of Object.entries(contributedPaths)){
234
235
  // Verb-level merge: a hand-written `@Get('/')` on the same controller
235
- // is preserved when the bridge contributes `post`/`patch`/etc. on
236
- // the same path key.
236
+ // is preserved when the contributor adds `post`/`patch`/etc. on the
237
+ // same path key.
237
238
  paths[pathKey] = {
238
239
  ...paths[pathKey] ?? {},
239
240
  ...pathItem
@@ -1,19 +1,25 @@
1
1
  import type { Container } from '../container/container';
2
2
  import type { ComponentType, ComponentTypeMap, Constructor, FilterType, GuardType, InterceptorType, MiddlewareType, PipeType } from '../registry/types';
3
3
  import type { ArgumentMetadata, CanActivate, ExceptionFilter, ExecutionContext, NestInterceptor, NestMiddleware, PipeTransform } from './types';
4
+ /**
5
+ * Component registration + resolution for the controller/handler tiers.
6
+ * App-wide (global) components have exactly ONE source: the per-app
7
+ * `RouteManager` (`APP_*` provider tokens + `useGlobalX()`) — callers merge
8
+ * `routeManager.getGlobalComponents()` with {@link getScopedComponents}.
9
+ *
10
+ * Stateless by design: no process-global container (two apps in one process
11
+ * never cross-talk) — every `resolve*` takes the resolving container.
12
+ */
4
13
  export declare class ComponentManager {
5
- private static container;
6
- static init(container: Container): void;
7
- static registerGlobal<T extends ComponentType>(type: T, ...components: ComponentTypeMap[T][]): void;
8
14
  static registerController<T extends ComponentType>(type: T, controller: Constructor, ...components: ComponentTypeMap[T][]): void;
9
15
  static registerHandler<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol, ...components: ComponentTypeMap[T][]): void;
10
- static getComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
16
+ static getScopedComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
11
17
  private static resolveAll;
12
- static resolveMiddleware(items: MiddlewareType[]): NestMiddleware[];
13
- static resolveGuards(items: GuardType[]): CanActivate[];
14
- static resolvePipes(items: PipeType[]): PipeTransform[];
15
- static resolveInterceptors(items: InterceptorType[]): NestInterceptor[];
16
- static resolveFilters(items: FilterType[]): ExceptionFilter[];
18
+ static resolveMiddleware(items: MiddlewareType[], container: Container): NestMiddleware[];
19
+ static resolveGuards(items: GuardType[], container: Container): CanActivate[];
20
+ static resolvePipes(items: PipeType[], container: Container): PipeTransform[];
21
+ static resolveInterceptors(items: InterceptorType[], container: Container): NestInterceptor[];
22
+ static resolveFilters(items: FilterType[], container: Container): ExceptionFilter[];
17
23
  static executePipes(value: unknown, metadata: ArgumentMetadata, pipes: PipeTransform[]): Promise<unknown>;
18
24
  static runInterceptorChain(interceptors: NestInterceptor[], context: ExecutionContext, coreHandler: () => Promise<unknown>): Promise<unknown>;
19
25
  }
@@ -1,18 +1,18 @@
1
1
  import { MetadataRegistry } from "../registry/metadata.registry.js";
2
+ import { PipelineRunner } from "./pipeline-runner.js";
2
3
  function isObject(value) {
3
4
  return typeof value === 'object' && value !== null;
4
5
  }
5
- export class ComponentManager {
6
- static container;
7
- static init(container) {
8
- this.container = container;
9
- }
10
- // 3-level registration
11
- static registerGlobal(type, ...components) {
12
- for (const component of components){
13
- MetadataRegistry.registerGlobal(type, component);
14
- }
15
- }
6
+ /**
7
+ * Component registration + resolution for the controller/handler tiers.
8
+ * App-wide (global) components have exactly ONE source: the per-app
9
+ * `RouteManager` (`APP_*` provider tokens + `useGlobalX()`) — callers merge
10
+ * `routeManager.getGlobalComponents()` with {@link getScopedComponents}.
11
+ *
12
+ * Stateless by design: no process-global container (two apps in one process
13
+ * never cross-talk) — every `resolve*` takes the resolving container.
14
+ */ export class ComponentManager {
15
+ // Scoped registration: controller → handler
16
16
  static registerController(type, controller, ...components) {
17
17
  for (const component of components){
18
18
  MetadataRegistry.registerController(type, controller, component);
@@ -23,34 +23,32 @@ export class ComponentManager {
23
23
  MetadataRegistry.registerHandler(type, controller, handlerName, component);
24
24
  }
25
25
  }
26
- // 3-level resolution: globalcontroller handler
27
- static getComponents(type, controller, handlerName) {
28
- const controllerComponents = MetadataRegistry.getController(type, controller);
29
- const handlerComponents = MetadataRegistry.getHandler(type, controller, handlerName);
26
+ // Scoped resolution: controllerhandler. App-wide components come from
27
+ // RouteManager (APP_* tokens + useGlobalX()) — merged by the caller.
28
+ static getScopedComponents(type, controller, handlerName) {
30
29
  return [
31
- ...MetadataRegistry.getGlobal(type),
32
- ...controllerComponents,
33
- ...handlerComponents
30
+ ...MetadataRegistry.getController(type, controller),
31
+ ...MetadataRegistry.getHandler(type, controller, handlerName)
34
32
  ];
35
33
  }
36
- // Type-specific resolvers
37
- static resolveAll(items, methodKey) {
38
- return items.map((item)=>isObject(item) && methodKey in item ? item : this.container.resolve(item));
34
+ // Type-specific resolvers — explicit container, always.
35
+ static resolveAll(items, methodKey, container) {
36
+ return items.map((item)=>isObject(item) && methodKey in item ? item : container.resolve(item));
39
37
  }
40
- static resolveMiddleware(items) {
41
- return this.resolveAll(items, 'use');
38
+ static resolveMiddleware(items, container) {
39
+ return this.resolveAll(items, 'use', container);
42
40
  }
43
- static resolveGuards(items) {
44
- return this.resolveAll(items, 'canActivate');
41
+ static resolveGuards(items, container) {
42
+ return this.resolveAll(items, 'canActivate', container);
45
43
  }
46
- static resolvePipes(items) {
47
- return this.resolveAll(items, 'transform');
44
+ static resolvePipes(items, container) {
45
+ return this.resolveAll(items, 'transform', container);
48
46
  }
49
- static resolveInterceptors(items) {
50
- return this.resolveAll(items, 'intercept');
47
+ static resolveInterceptors(items, container) {
48
+ return this.resolveAll(items, 'intercept', container);
51
49
  }
52
- static resolveFilters(items) {
53
- return this.resolveAll(items, 'catch');
50
+ static resolveFilters(items, container) {
51
+ return this.resolveAll(items, 'catch', container);
54
52
  }
55
53
  // Pipe execution
56
54
  static async executePipes(value, metadata, pipes) {
@@ -60,21 +58,9 @@ export class ComponentManager {
60
58
  }
61
59
  return transformed;
62
60
  }
63
- // Interceptor chain (onion pattern)
61
+ // Interceptor chain (onion pattern) — canonical implementation lives in
62
+ // PipelineRunner; kept here as a stable alias.
64
63
  static async runInterceptorChain(interceptors, context, coreHandler) {
65
- if (interceptors.length === 0) {
66
- return coreHandler();
67
- }
68
- let next = {
69
- handle: coreHandler
70
- };
71
- for(let i = interceptors.length - 1; i >= 0; i--){
72
- const interceptor = interceptors[i];
73
- const currentNext = next;
74
- next = {
75
- handle: ()=>interceptor.intercept(context, currentNext)
76
- };
77
- }
78
- return next.handle();
64
+ return PipelineRunner.chainInterceptors(interceptors, context, coreHandler);
79
65
  }
80
66
  }
@@ -1,4 +1,6 @@
1
1
  export { ComponentManager } from './component.manager';
2
+ export { PipelineRunner } from './pipeline-runner';
3
+ export type { PipelineRunOptions } from './pipeline-runner';
2
4
  export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, getCatchTypes, shouldFilterCatch, } from './decorators';
3
5
  export { SetMetadata, Reflector } from './reflector';
4
6
  export type { ReflectableDecorator, CreateDecoratorOptions } from './reflector';
@@ -1,4 +1,5 @@
1
1
  export { ComponentManager } from "./component.manager.js";
2
+ export { PipelineRunner } from "./pipeline-runner.js";
2
3
  export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, getCatchTypes, shouldFilterCatch } from "./decorators.js";
3
4
  export { SetMetadata, Reflector } from "./reflector.js";
4
5
  export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./tokens.js";
@@ -0,0 +1,31 @@
1
+ import type { CanActivate, ExecutionContext, NestInterceptor } from './types';
2
+ export interface PipelineRunOptions {
3
+ context: ExecutionContext;
4
+ /** Pre-merged (global ++ scoped), pre-instantiated. */
5
+ guards: CanActivate[];
6
+ interceptors: NestInterceptor[];
7
+ /** Extract handler arguments (pipes already bound inside the closure). */
8
+ resolveArgs: () => Promise<unknown[]>;
9
+ /** Call the actual handler. */
10
+ invoke: (args: unknown[]) => Promise<unknown>;
11
+ /**
12
+ * HTTP runs argument extraction + pipes BEFORE guards (a deliberate vela
13
+ * deviation — see handler-executor); WebSocket runs guards first. Default
14
+ * false (guards first).
15
+ */
16
+ argsBeforeGuards?: boolean;
17
+ /** Error thrown when a guard rejects (default `ForbiddenException`). */
18
+ onGuardReject?: () => Error;
19
+ }
20
+ /**
21
+ * The transport-agnostic guard → pipe → interceptor execution core shared by
22
+ * the HTTP `HandlerExecutor` and the WebSocket `WsDispatcher` (and any future
23
+ * entrypoint dispatcher: queue consumers, scheduled jobs, …). Exception
24
+ * FILTERS stay with the caller — their terminal behavior is transport-specific
25
+ * (HTTP maps to a Response, WS sends an error frame).
26
+ */
27
+ export declare class PipelineRunner {
28
+ static run(options: PipelineRunOptions): Promise<unknown>;
29
+ /** Interceptor onion — innermost is the core handler. */
30
+ static chainInterceptors(interceptors: NestInterceptor[], context: ExecutionContext, coreHandler: () => Promise<unknown>): Promise<unknown>;
31
+ }
@@ -0,0 +1,40 @@
1
+ import { ForbiddenException } from "../errors/http-exception.js";
2
+ /**
3
+ * The transport-agnostic guard → pipe → interceptor execution core shared by
4
+ * the HTTP `HandlerExecutor` and the WebSocket `WsDispatcher` (and any future
5
+ * entrypoint dispatcher: queue consumers, scheduled jobs, …). Exception
6
+ * FILTERS stay with the caller — their terminal behavior is transport-specific
7
+ * (HTTP maps to a Response, WS sends an error frame).
8
+ */ export class PipelineRunner {
9
+ static async run(options) {
10
+ let args;
11
+ if (options.argsBeforeGuards) {
12
+ args = await options.resolveArgs();
13
+ }
14
+ for (const guard of options.guards){
15
+ const canActivate = await guard.canActivate(options.context);
16
+ if (!canActivate) {
17
+ throw options.onGuardReject?.() ?? new ForbiddenException();
18
+ }
19
+ }
20
+ args ??= await options.resolveArgs();
21
+ const resolvedArgs = args;
22
+ return PipelineRunner.chainInterceptors(options.interceptors, options.context, ()=>options.invoke(resolvedArgs));
23
+ }
24
+ /** Interceptor onion — innermost is the core handler. */ static async chainInterceptors(interceptors, context, coreHandler) {
25
+ if (interceptors.length === 0) {
26
+ return coreHandler();
27
+ }
28
+ let next = {
29
+ handle: coreHandler
30
+ };
31
+ for(let i = interceptors.length - 1; i >= 0; i--){
32
+ const interceptor = interceptors[i];
33
+ const currentNext = next;
34
+ next = {
35
+ handle: ()=>interceptor.intercept(context, currentNext)
36
+ };
37
+ }
38
+ return next.handle();
39
+ }
40
+ }
@@ -18,9 +18,10 @@ export declare class MetadataRegistry {
18
18
  private static get routeVersions();
19
19
  private static get classMeta();
20
20
  private static get handlerMeta();
21
+ private static get classMetaIndex();
22
+ private static get handlerMetaIndex();
21
23
  private static get controllerComponents();
22
24
  private static get handlerComponents();
23
- private static get globalComponents();
24
25
  static getRoutes(controller: Constructor): RouteDefinition[];
25
26
  static addRoute(controller: Constructor, route: RouteDefinition): void;
26
27
  static getControllerPath(controller: Constructor): string;
@@ -31,8 +32,6 @@ export declare class MetadataRegistry {
31
32
  static setModuleOptions(module: Constructor, options: ModuleOptions): void;
32
33
  static getParameters(controller: Constructor): Map<string | symbol, ParameterMetadata[]>;
33
34
  static addParameter(controller: Constructor, methodName: string | symbol, param: ParameterMetadata): void;
34
- static registerGlobal<T extends ComponentType>(type: T, component: ComponentTypeMap[T]): void;
35
- static getGlobal<T extends ComponentType>(type: T): Set<ComponentTypeMap[T]>;
36
35
  static registerController<T extends ComponentType>(type: T, controller: Constructor, component: ComponentTypeMap[T]): void;
37
36
  static getController<T extends ComponentType>(type: T, controller: Constructor): ComponentTypeMap[T][];
38
37
  static registerHandler<T extends ComponentType>(type: T, controller: Constructor, methodName: string | symbol, component: ComponentTypeMap[T]): void;
@@ -58,6 +57,13 @@ export declare class MetadataRegistry {
58
57
  static getCustomHandlerMetaAll(target: object, handler: string | symbol): Map<string, unknown> | undefined;
59
58
  static appendCustomClassMeta<T>(target: object, key: string, item: T): void;
60
59
  static appendCustomHandlerMeta<T>(target: object, handler: string | symbol, key: string, item: T): void;
60
+ static getClassesWithClassMeta(key: string): ReadonlySet<object>;
61
+ static getClassesWithHandlerMeta(key: string): ReadonlySet<object>;
62
+ /** Every (handler, value) pair on `target` carrying metadata under `key`. */
63
+ static getHandlersWithMeta(target: object, key: string): Array<{
64
+ handler: string | symbol;
65
+ value: unknown;
66
+ }>;
61
67
  static setReflectMetadata(target: object, key: string, value: unknown, propertyKey?: string | symbol): void;
62
68
  static getReflectMetadata<T = unknown>(target: object, key: string, propertyKey?: string | symbol): T | undefined;
63
69
  static getParamTypes(target: object, propertyKey?: string | symbol): unknown[] | undefined;
@@ -1,13 +1,4 @@
1
1
  import { getOrCreate, getOrCreateArray, getOrCreateMap } from "./util.js";
2
- function emptyComponentStore() {
3
- return {
4
- middleware: new Set(),
5
- guard: new Set(),
6
- pipe: new Set(),
7
- interceptor: new Set(),
8
- filter: new Set()
9
- };
10
- }
11
2
  function emptyComponentByOwner() {
12
3
  return {
13
4
  middleware: new Map(),
@@ -32,6 +23,8 @@ function createRegistryState() {
32
23
  routeVersions: new Map(),
33
24
  classMeta: new Map(),
34
25
  handlerMeta: new Map(),
26
+ classMetaIndex: new Map(),
27
+ handlerMetaIndex: new Map(),
35
28
  controllerComponents: emptyComponentByOwner(),
36
29
  handlerComponents: {
37
30
  middleware: new Map(),
@@ -39,8 +32,7 @@ function createRegistryState() {
39
32
  pipe: new Map(),
40
33
  interceptor: new Map(),
41
34
  filter: new Map()
42
- },
43
- globalComponents: emptyComponentStore()
35
+ }
44
36
  };
45
37
  }
46
38
  // HMR-safe: anchor ALL backing state on `globalThis` so a Vite dev re-eval of
@@ -50,9 +42,17 @@ function createRegistryState() {
50
42
  // versioned symbol avoids collisions across framework major versions in one
51
43
  // process. `globalThis` + `Symbol.for` exist on every target runtime; no node:*.
52
44
  const REGISTRY_STATE_KEY = Symbol.for('vela:registry:v1');
45
+ // Shared empty result for index misses — avoids allocating per lookup.
46
+ const EMPTY_TARGET_SET = new Set();
53
47
  function registryState() {
54
48
  const g = globalThis;
55
- return g[REGISTRY_STATE_KEY] ??= createRegistryState();
49
+ const state = g[REGISTRY_STATE_KEY] ??= createRegistryState();
50
+ // Backfill fields added after the v1 state shape was first anchored: a state
51
+ // created by an older copy of this module (dist/src coexistence in tests,
52
+ // mixed package versions in one process) must not crash newer readers.
53
+ state.classMetaIndex ??= new Map();
54
+ state.handlerMetaIndex ??= new Map();
55
+ return state;
56
56
  }
57
57
  export class MetadataRegistry {
58
58
  // Every field is a getter over the globalThis-anchored state (registryState).
@@ -97,15 +97,18 @@ export class MetadataRegistry {
97
97
  static get handlerMeta() {
98
98
  return registryState().handlerMeta;
99
99
  }
100
+ static get classMetaIndex() {
101
+ return registryState().classMetaIndex;
102
+ }
103
+ static get handlerMetaIndex() {
104
+ return registryState().handlerMetaIndex;
105
+ }
100
106
  static get controllerComponents() {
101
107
  return registryState().controllerComponents;
102
108
  }
103
109
  static get handlerComponents() {
104
110
  return registryState().handlerComponents;
105
111
  }
106
- static get globalComponents() {
107
- return registryState().globalComponents;
108
- }
109
112
  // Routes
110
113
  static getRoutes(controller) {
111
114
  return this.routes.get(controller) ?? [];
@@ -141,14 +144,9 @@ export class MetadataRegistry {
141
144
  const methodMap = getOrCreateMap(this.parameters, controller);
142
145
  getOrCreateArray(methodMap, methodName).push(param);
143
146
  }
144
- // Component registration — global
145
- static registerGlobal(type, component) {
146
- this.globalComponents[type].add(component);
147
- }
148
- static getGlobal(type) {
149
- return this.globalComponents[type];
150
- }
151
- // Component registration — controller-level
147
+ // Component registration — controller-level. (There is deliberately NO
148
+ // global tier here: app-wide components live on the per-app RouteManager,
149
+ // one source, never process-global state.)
152
150
  static registerController(type, controller, component) {
153
151
  const map = this.controllerComponents[type];
154
152
  getOrCreateArray(map, controller).push(component);
@@ -228,6 +226,9 @@ export class MetadataRegistry {
228
226
  // external Reflect.defineMetadata calls — one model, one source of truth.
229
227
  static setCustomClassMeta(target, key, value) {
230
228
  getOrCreate(this.classMeta, target, ()=>new Map()).set(key, value);
229
+ if (!key.startsWith('design:')) {
230
+ getOrCreate(this.classMetaIndex, key, ()=>new Set()).add(target);
231
+ }
231
232
  }
232
233
  static getCustomClassMeta(target, key) {
233
234
  return this.classMeta.get(target)?.get(key);
@@ -238,6 +239,9 @@ export class MetadataRegistry {
238
239
  static setCustomHandlerMeta(target, handler, key, value) {
239
240
  const byHandler = getOrCreate(this.handlerMeta, target, ()=>new Map());
240
241
  getOrCreate(byHandler, handler, ()=>new Map()).set(key, value);
242
+ if (!key.startsWith('design:')) {
243
+ getOrCreate(this.handlerMetaIndex, key, ()=>new Set()).add(target);
244
+ }
241
245
  }
242
246
  static getCustomHandlerMeta(target, handler, key) {
243
247
  return this.handlerMeta.get(target)?.get(handler)?.get(key);
@@ -256,6 +260,28 @@ export class MetadataRegistry {
256
260
  list.push(item);
257
261
  this.setCustomHandlerMeta(target, handler, key, list);
258
262
  }
263
+ // Reverse-index readers — the DiscoveryService seam. Return the constructors
264
+ // known to carry class-level (resp. handler-level) metadata under `key`.
265
+ // Callers must still intersect with container-registered tokens: decoration
266
+ // alone does not make a class a provider.
267
+ static getClassesWithClassMeta(key) {
268
+ return this.classMetaIndex.get(key) ?? EMPTY_TARGET_SET;
269
+ }
270
+ static getClassesWithHandlerMeta(key) {
271
+ return this.handlerMetaIndex.get(key) ?? EMPTY_TARGET_SET;
272
+ }
273
+ /** Every (handler, value) pair on `target` carrying metadata under `key`. */ static getHandlersWithMeta(target, key) {
274
+ const byHandler = this.handlerMeta.get(target);
275
+ if (!byHandler) return [];
276
+ const out = [];
277
+ for (const [handler, metaMap] of byHandler){
278
+ if (metaMap.has(key)) out.push({
279
+ handler,
280
+ value: metaMap.get(key)
281
+ });
282
+ }
283
+ return out;
284
+ }
259
285
  // Reflect-style API — same storage as the typed setters above. Lets external
260
286
  // code (and the SWC shim's Reflect.metadata polyfill) write/read uniformly.
261
287
  static setReflectMetadata(target, key, value, propertyKey) {
@@ -294,8 +320,11 @@ export class MetadataRegistry {
294
320
  }
295
321
  // Clear app-time state. Decoration metadata persists — once a class is
296
322
  // decorated, that fact is permanent for the lifetime of the process.
323
+ // Currently a no-op: the last piece of app-time registry state (the global
324
+ // component tier) moved to the per-app RouteManager. Kept because test
325
+ // suites call it between cases and future app-time state belongs here.
297
326
  static clear() {
298
- registryState().globalComponents = emptyComponentStore();
327
+ // no app-time state to clear
299
328
  }
300
329
  // Full reset, including decoration metadata. Used in framework-internal scenarios.
301
330
  static reset() {
@@ -312,6 +341,8 @@ export class MetadataRegistry {
312
341
  this.routeVersions.clear();
313
342
  this.classMeta.clear();
314
343
  this.handlerMeta.clear();
344
+ this.classMetaIndex.clear();
345
+ this.handlerMetaIndex.clear();
315
346
  for (const type of [
316
347
  'middleware',
317
348
  'guard',
@@ -322,6 +353,5 @@ export class MetadataRegistry {
322
353
  this.controllerComponents[type].clear();
323
354
  this.handlerComponents[type].clear();
324
355
  }
325
- registryState().globalComponents = emptyComponentStore();
326
356
  }
327
357
  }
@@ -81,6 +81,13 @@ export interface DynamicModule {
81
81
  controllers?: Type[];
82
82
  exports?: Array<Type | InjectionToken>;
83
83
  global?: boolean;
84
+ /**
85
+ * Defer this module instance's providers/controllers to first use: nothing
86
+ * constructs at bootstrap; the first resolution of any of its tokens
87
+ * materializes the whole group and replays its lifecycle hooks (memoized).
88
+ * See MODULE_AUTHORING.md "Lazy modules" for the contract.
89
+ */
90
+ lazy?: boolean;
84
91
  }
85
92
  export interface ModuleOptions {
86
93
  providers?: Array<Type | ProviderOptions>;
@@ -88,6 +95,8 @@ export interface ModuleOptions {
88
95
  imports?: ModuleImport[];
89
96
  exports?: Array<Type | InjectionToken>;
90
97
  isGlobal?: boolean;
98
+ /** Defer to first use (see {@link DynamicModule.lazy}). */
99
+ lazy?: boolean;
91
100
  }
92
101
  export interface ModuleMetadata {
93
102
  providers: Array<Type | ProviderOptions>;
@@ -95,4 +104,5 @@ export interface ModuleMetadata {
95
104
  imports: ModuleImport[];
96
105
  exports: Array<Type | InjectionToken>;
97
106
  isGlobal: boolean;
107
+ lazy: boolean;
98
108
  }
@@ -1,4 +1,9 @@
1
1
  import type { DynamicModule } from '../module/types';
2
+ /**
3
+ * Zero-config module (Tier C): providers live on the `@Module` bag; the
4
+ * `forRoot()` static is NestJS-parity sugar returning the bare dynamic module
5
+ * (default key — repeated calls dedup).
6
+ */
2
7
  export declare class ScheduleModule {
3
8
  static forRoot(): DynamicModule;
4
9
  }
@@ -1,15 +1,28 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { Module } from "../module/decorators.js";
1
8
  import { ScheduleRegistry } from "./schedule.registry.js";
2
9
  export class ScheduleModule {
3
10
  static forRoot() {
4
- const providers = [
5
- ScheduleRegistry
6
- ];
7
11
  return {
8
- module: ScheduleModule,
9
- providers,
10
- exports: [
11
- ScheduleRegistry
12
- ]
12
+ module: ScheduleModule
13
13
  };
14
14
  }
15
15
  }
16
+ ScheduleModule = _ts_decorate([
17
+ Module({
18
+ // Lazy: the @Cron/@Interval discovery pass runs when ScheduleRegistry is
19
+ // first resolved (executor bootstrap, introspection, first dispatch).
20
+ lazy: true,
21
+ providers: [
22
+ ScheduleRegistry
23
+ ],
24
+ exports: [
25
+ ScheduleRegistry
26
+ ]
27
+ })
28
+ ], ScheduleModule);
@@ -1,4 +1,4 @@
1
- import { Container } from '../container/container';
1
+ import { DiscoveryService } from '../discovery/discovery.service';
2
2
  import type { OnApplicationBootstrap } from '../lifecycle/index';
3
3
  export interface RegisteredCronJob {
4
4
  expression: string;
@@ -13,10 +13,10 @@ export interface RegisteredIntervalJob {
13
13
  target: Function;
14
14
  }
15
15
  export declare class ScheduleRegistry implements OnApplicationBootstrap {
16
- private container;
16
+ private readonly discovery;
17
17
  private cronJobs;
18
18
  private intervalJobs;
19
- constructor(container: Container);
19
+ constructor(discovery: DiscoveryService);
20
20
  onApplicationBootstrap(): void;
21
21
  getCronJobs(): RegisteredCronJob[];
22
22
  getIntervalJobs(): RegisteredIntervalJob[];