@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
@@ -1,28 +1,14 @@
1
1
  import { Hono } from "hono";
2
- import { getCookie } from "hono/cookie";
3
- import { HttpMethod, ParamType } from "../constants.js";
2
+ import { HttpMethod } from "../constants.js";
4
3
  import { getMetadata } from "../metadata.js";
5
4
  import { joinPaths } from "../registry/paths.js";
6
- import { buildExecutionContext } from "./execution-context.js";
5
+ import { ArgumentResolver } from "./argument-resolver.js";
6
+ import { HandlerExecutor } from "./handler-executor.js";
7
+ import { instantiate, instantiateMany } from "./instantiate.js";
7
8
  import { REQUEST_CONTEXT, createRequestContext } from "./request-context.js";
8
- import { ForbiddenException, HttpException } from "../errors/http-exception.js";
9
9
  import { ComponentManager } from "../pipeline/component.manager.js";
10
- import { shouldFilterCatch } from "../pipeline/decorators.js";
11
10
  import { MetadataRegistry } from "../registry/metadata.registry.js";
12
- import { getHttpCode, getRedirect, getResponseHeaders } from "./decorators.js";
13
11
  const defaultGetClientIp = (c)=>c.req.raw.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? c.req.raw.headers.get('x-real-ip') ?? null;
14
- function parseRedirectResult(result) {
15
- if (typeof result !== 'object' || result === null) return undefined;
16
- if (!('url' in result)) return undefined;
17
- const obj = result;
18
- if (typeof obj.url !== 'string') return undefined;
19
- return {
20
- url: obj.url,
21
- ...typeof obj.statusCode === 'number' ? {
22
- statusCode: obj.statusCode
23
- } : {}
24
- };
25
- }
26
12
  export class RouteManager {
27
13
  container;
28
14
  static METHOD_REGISTRAR = new Map([
@@ -59,48 +45,6 @@ export class RouteManager {
59
45
  (app, p, h)=>app.all(p, h)
60
46
  ]
61
47
  ]);
62
- static PARAM_EXTRACTORS = new Map([
63
- [
64
- ParamType.PARAM,
65
- (c, p)=>p.name ? c.req.param(p.name) : c.req.param()
66
- ],
67
- [
68
- ParamType.QUERY,
69
- (c, p)=>p.name ? c.req.query(p.name) : c.req.query()
70
- ],
71
- [
72
- ParamType.BODY,
73
- async (c, p)=>{
74
- let body;
75
- try {
76
- body = await c.req.json();
77
- } catch {
78
- return undefined;
79
- }
80
- return p.name && body !== null && typeof body === 'object' ? body[p.name] : body;
81
- }
82
- ],
83
- [
84
- ParamType.HEADERS,
85
- (c, p)=>p.name ? c.req.header(p.name) : c.req.header()
86
- ],
87
- [
88
- ParamType.REQUEST,
89
- (c)=>c
90
- ],
91
- [
92
- ParamType.RESPONSE,
93
- (c)=>c
94
- ],
95
- [
96
- ParamType.COOKIE,
97
- (c, p)=>p.name ? getCookie(c, p.name) : getCookie(c)
98
- ],
99
- [
100
- ParamType.RAW_BODY,
101
- async (c)=>new Uint8Array(await c.req.arrayBuffer())
102
- ]
103
- ]);
104
48
  controllers = [];
105
49
  globalMiddleware = [];
106
50
  globalPipes = [];
@@ -109,10 +53,16 @@ export class RouteManager {
109
53
  globalFilters = [];
110
54
  globalPrefix = '';
111
55
  consumerMiddlewareDefinitions = [];
112
- ipExtractor;
56
+ handlerExecutor;
113
57
  constructor(container, options = {}){
114
58
  this.container = container;
115
- this.ipExtractor = options.getClientIp ?? defaultGetClientIp;
59
+ const argumentResolver = new ArgumentResolver(options.getClientIp ?? defaultGetClientIp);
60
+ this.handlerExecutor = new HandlerExecutor(argumentResolver, ()=>({
61
+ guards: this.globalGuards,
62
+ pipes: this.globalPipes,
63
+ interceptors: this.globalInterceptors,
64
+ filters: this.globalFilters
65
+ }), (c)=>this.getRequestContainer(c));
116
66
  }
117
67
  registerConsumerMiddleware(definitions) {
118
68
  this.consumerMiddlewareDefinitions.push(...definitions);
@@ -162,21 +112,6 @@ export class RouteManager {
162
112
  this.globalFilters.push(...filterTokens);
163
113
  return this;
164
114
  }
165
- instantiate(classOrInstance, container) {
166
- if (typeof classOrInstance === 'function') {
167
- if (container.has(classOrInstance)) {
168
- return container.resolve(classOrInstance);
169
- }
170
- return new classOrInstance();
171
- }
172
- if (container.has(classOrInstance)) {
173
- return container.resolve(classOrInstance);
174
- }
175
- return classOrInstance;
176
- }
177
- instantiateMany(items, container) {
178
- return items.map((item)=>this.instantiate(item, container));
179
- }
180
115
  getMiddlewarePriority(entry) {
181
116
  if (entry == null) return 0;
182
117
  if (typeof entry === 'function') {
@@ -189,7 +124,7 @@ export class RouteManager {
189
124
  if (typeof inst.constructor?.priority === 'number') return inst.constructor.priority;
190
125
  }
191
126
  try {
192
- const resolved = this.instantiate(entry, this.container);
127
+ const resolved = instantiate(entry, this.container);
193
128
  if (resolved && typeof resolved === 'object') {
194
129
  const p = resolved.priority;
195
130
  if (typeof p === 'number') return p;
@@ -215,6 +150,9 @@ export class RouteManager {
215
150
  const prefix = MetadataRegistry.getControllerPath(controller);
216
151
  const options = MetadataRegistry.getControllerOptions(controller);
217
152
  const routes = MetadataRegistry.getRoutes(controller);
153
+ // The ModuleLoader registers controllers in their owning module's bucket;
154
+ // fall back to a `__root__` registration only for controllers that arrive
155
+ // here outside the module-loading flow (test harnesses, custom adapters).
218
156
  if (!this.container.has(controller)) {
219
157
  this.container.register(controller);
220
158
  }
@@ -237,21 +175,18 @@ export class RouteManager {
237
175
  index,
238
176
  priority: this.getMiddlewarePriority(entry)
239
177
  })).sort((a, b)=>a.priority - b.priority || a.index - b.index);
240
- // Register global middleware on the Hono app
241
178
  for (const { entry } of sortedGlobal){
242
179
  app.use('*', (c, next)=>{
243
180
  const requestContainer = this.getRequestContainer(c);
244
- const resolved = this.instantiate(entry, requestContainer);
181
+ const resolved = instantiate(entry, requestContainer);
245
182
  return resolved.use(c, next);
246
183
  });
247
184
  }
248
- // Sort consumer middleware with the same stable-by-index rule.
249
185
  const sortedConsumer = this.consumerMiddlewareDefinitions.map((def, index)=>({
250
186
  def,
251
187
  index,
252
188
  priority: def.priority ?? 0
253
189
  })).sort((a, b)=>a.priority - b.priority || a.index - b.index);
254
- // Register MiddlewareConsumer-configured middleware
255
190
  for (const { def } of sortedConsumer){
256
191
  const matchRoute = this.compileRouteMatcher(def.routes);
257
192
  const matchExclude = this.compileRouteMatcher(def.excludes);
@@ -262,30 +197,27 @@ export class RouteManager {
262
197
  const requestContainer = this.getRequestContainer(c);
263
198
  const runChain = (index)=>{
264
199
  if (index >= def.middleware.length) return next();
265
- const instance = this.instantiate(def.middleware[index], requestContainer);
200
+ const instance = instantiate(def.middleware[index], requestContainer);
266
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
267
202
  return instance.use(c, ()=>runChain(index + 1)).then(()=>{});
268
203
  };
269
204
  return runChain(0);
270
205
  });
271
206
  }
272
- // First pass: register all custom routes (must come before CRUD /:id routes)
207
+ // First pass: register all custom routes (must come before CRUD /:id routes).
273
208
  for (const { controller, metadata, routes } of this.controllers){
274
209
  if (routes.length > 0) {
275
210
  const allParamMetadata = MetadataRegistry.getParameters(controller);
276
211
  for (const route of routes){
277
- // Determine effective version: route-level overrides controller-level
278
212
  const effectiveVersion = route.version ?? metadata.version;
279
- // Build versioned paths
280
213
  const versionedPaths = this.buildVersionedPaths(this.globalPrefix, metadata.prefix, route.path, effectiveVersion);
281
- // Register per-route middleware as Hono middleware before the handler
282
214
  const middlewareItems = ComponentManager.getComponents('middleware', controller, route.handlerName);
283
- const handler = this.createHandler(route, controller, allParamMetadata);
215
+ const handler = this.handlerExecutor.create(route, controller, allParamMetadata);
284
216
  for (const fullPath of versionedPaths){
285
217
  for (const middlewareItem of middlewareItems){
286
218
  app.use(fullPath, (c, next)=>{
287
219
  const requestContainer = this.getRequestContainer(c);
288
- const resolved = this.instantiate(middlewareItem, requestContainer);
220
+ const resolved = instantiate(middlewareItem, requestContainer);
289
221
  return resolved.use(c, next);
290
222
  });
291
223
  }
@@ -294,7 +226,7 @@ export class RouteManager {
294
226
  }
295
227
  }
296
228
  }
297
- // Second pass: mount CRUD sub-apps (/:id routes registered last)
229
+ // Second pass: mount CRUD sub-apps (/:id routes registered last).
298
230
  for (const { controller, metadata } of this.controllers){
299
231
  const crudConfig = getMetadata('vela:crud', controller);
300
232
  if (crudConfig) {
@@ -303,7 +235,7 @@ export class RouteManager {
303
235
  const { buildCrudRoutes } = await import(pkg);
304
236
  await buildCrudRoutes(app, controller, metadata.prefix, crudConfig, {
305
237
  globalPrefix: this.globalPrefix,
306
- globalGuards: this.instantiateMany(this.globalGuards, this.container),
238
+ globalGuards: instantiateMany(this.globalGuards, this.container),
307
239
  joinPaths
308
240
  });
309
241
  } catch (err) {
@@ -315,155 +247,6 @@ export class RouteManager {
315
247
  }
316
248
  return app;
317
249
  }
318
- createExecutionContext(c, controller, route) {
319
- return buildExecutionContext(c, controller, route.handlerName);
320
- }
321
- createHandler(route, controller, allParamMetadata) {
322
- const paramMetadata = (allParamMetadata.get(route.handlerName) || []).sort((a, b)=>a.index - b.index);
323
- // Read param types once at build time for metatype population.
324
- // Routed via Reflect so the polyfill funnels both src-level and dist-level
325
- // consumers to the same registry (matters in tests that import from dist).
326
- const paramTypes = Reflect.getMetadata('design:paramtypes', controller.prototype, route.handlerName);
327
- // Collect controller + method level components at build time
328
- const methodGuards = ComponentManager.getComponents('guard', controller, route.handlerName);
329
- const methodPipes = ComponentManager.getComponents('pipe', controller, route.handlerName);
330
- const methodInterceptors = ComponentManager.getComponents('interceptor', controller, route.handlerName);
331
- // Filters: reverse order (handler → controller → global) — closest to handler runs first
332
- const methodFilters = [
333
- ...ComponentManager.getComponents('filter', controller, route.handlerName)
334
- ].reverse();
335
- // Read response decorators at build time
336
- const httpCode = getHttpCode(controller, route.handlerName);
337
- const responseHeaders = getResponseHeaders(controller, route.handlerName);
338
- const redirect = getRedirect(controller, route.handlerName);
339
- return async (c)=>{
340
- // Combine global + method at request time (allows post-create registration)
341
- const requestContainer = this.getRequestContainer(c);
342
- const guards = [
343
- ...this.instantiateMany(this.globalGuards, requestContainer),
344
- ...this.instantiateMany(methodGuards, requestContainer)
345
- ];
346
- const pipes = [
347
- ...this.instantiateMany(this.globalPipes, requestContainer),
348
- ...this.instantiateMany(methodPipes, requestContainer)
349
- ];
350
- const interceptors = [
351
- ...this.instantiateMany(this.globalInterceptors, requestContainer),
352
- ...this.instantiateMany(methodInterceptors, requestContainer)
353
- ];
354
- const filters = [
355
- ...this.instantiateMany(methodFilters, requestContainer),
356
- ...this.instantiateMany(this.globalFilters, requestContainer)
357
- ];
358
- const executionContext = this.createExecutionContext(c, controller, route);
359
- try {
360
- const instance = requestContainer.resolve(controller);
361
- // 1. Extract args + run pipes
362
- const args = await this.extractArguments(c, paramMetadata, pipes, requestContainer, paramTypes);
363
- // 2. Guards (fail-fast)
364
- for (const guard of guards){
365
- const canActivate = await guard.canActivate(executionContext);
366
- if (!canActivate) {
367
- throw new ForbiddenException();
368
- }
369
- }
370
- // 3. Get handler method
371
- const method = Reflect.get(instance, route.handlerName);
372
- if (typeof method !== 'function') {
373
- throw new Error(`Method ${String(route.handlerName)} not found on controller`);
374
- }
375
- // 4. Build core handler
376
- const coreHandler = async ()=>Reflect.apply(method, instance, args);
377
- // 5. Run interceptor chain
378
- const result = await ComponentManager.runInterceptorChain(interceptors, executionContext, coreHandler);
379
- // Handle @Redirect
380
- if (redirect) {
381
- const overrides = parseRedirectResult(result);
382
- const finalUrl = overrides?.url ?? redirect.url;
383
- const finalStatus = overrides?.statusCode ?? redirect.statusCode;
384
- return c.redirect(finalUrl, finalStatus);
385
- }
386
- // Build response with @HttpCode and @Header support
387
- const response = this.createResponse(c, result, httpCode);
388
- // Apply @Header decorators
389
- for (const [headerName, headerValue] of responseHeaders){
390
- response.headers.set(headerName, headerValue);
391
- }
392
- return response;
393
- } catch (error) {
394
- // Run exception filters
395
- for (const filter of filters){
396
- if (shouldFilterCatch(filter, error)) {
397
- try {
398
- const result = await filter.catch(error, executionContext);
399
- return this.createResponse(c, result);
400
- } catch {
401
- break;
402
- }
403
- }
404
- }
405
- // Default HttpException handling
406
- if (error instanceof HttpException) {
407
- const response = error.getResponse();
408
- const status = error.getStatus();
409
- return c.json(response, status);
410
- }
411
- // Default 500
412
- return c.json({
413
- statusCode: 500,
414
- message: 'Internal Server Error'
415
- }, 500);
416
- }
417
- };
418
- }
419
- async extractArguments(c, paramMetadata, pipes, requestContainer, paramTypes) {
420
- if (paramMetadata.length === 0) {
421
- return [
422
- c
423
- ];
424
- }
425
- const maxIndex = paramMetadata.at(-1).index;
426
- const args = new Array(maxIndex + 1).fill(undefined);
427
- for (const param of paramMetadata){
428
- let value = await this.extractParam(c, param);
429
- const metadata = {
430
- type: param.type,
431
- data: param.name,
432
- metatype: paramTypes?.[param.index]
433
- };
434
- // Run shared pipes (global + controller + method)
435
- for (const pipe of pipes){
436
- value = await pipe.transform(value, metadata);
437
- }
438
- // Run param-level pipes
439
- if (param.pipes && param.pipes.length > 0) {
440
- for (const paramPipe of param.pipes){
441
- const pipeInstance = this.instantiate(paramPipe, requestContainer);
442
- value = await pipeInstance.transform(value, metadata);
443
- }
444
- }
445
- args[param.index] = value;
446
- }
447
- return args;
448
- }
449
- extractParam(c, param) {
450
- if (param.type === ParamType.IP) return this.ipExtractor(c);
451
- const extractor = RouteManager.PARAM_EXTRACTORS.get(param.type);
452
- if (extractor) return extractor(c, param);
453
- return param.factory ? param.factory(param.name, c) : undefined;
454
- }
455
- createResponse(c, result, statusCode) {
456
- if (result instanceof Response) {
457
- return result;
458
- }
459
- if (result === null || result === undefined) {
460
- return c.body(null, statusCode ?? 204);
461
- }
462
- if (typeof result === 'string') {
463
- return c.text(result, statusCode ?? 200);
464
- }
465
- return c.json(result, statusCode ?? 200);
466
- }
467
250
  registerRoute(app, method, path, handler) {
468
251
  const normalizedPath = path || '/';
469
252
  const registrar = RouteManager.METHOD_REGISTRAR.get(method);
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { bootstrap } from './factory/bootstrap';
6
6
  export type { BootstrapOptions, BootstrapResult } from './factory/bootstrap';
7
7
  export { createOpenApiDocument, ApiDoc, ApiTags, ApiResponse, zodToJsonSchema, } from './openapi/index';
8
8
  export type { OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiRequestBody, OpenApiResponse, ApiDocMetadata, ApiResponseEntry, ApiResponseOptions, CreateOpenApiDocumentOptions, HttpVerb, JsonSchema, } from './openapi/index';
9
- export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, mixin, } from './container/index';
9
+ export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, MultipleProvidersFoundError, ROOT_MODULE_ID, mixin, } from './container/index';
10
10
  export type { Type, Token, InjectableOptions, ProviderOptions, ModuleScope, ContainerOptions, Diagnostics, } from './container/index';
11
11
  export { METADATA_KEYS, HttpMethod, ParamType, Scope } from './constants';
12
12
  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, } from './http/index';
@@ -30,7 +30,7 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
30
30
  export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
31
31
  export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
32
32
  export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerStorageRecord, RateLimitInfo, } from './throttler/index';
33
- export { Global, Module, createModuleRef } from './module/index';
33
+ export { Global, Module, defineDynamicModule, stableHash, } from './module/index';
34
34
  export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport } from './module/index';
35
35
  export type { MiddlewareConsumer, NestModule, RouteInfo } from './http/index';
36
36
  export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN, } from './plugin/plugin';
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { bootstrap } from "./factory/bootstrap.js";
7
7
  // OpenAPI
8
8
  export { createOpenApiDocument, ApiDoc, ApiTags, ApiResponse, zodToJsonSchema } from "./openapi/index.js";
9
9
  // DI Container
10
- export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, mixin } from "./container/index.js";
10
+ export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, MultipleProvidersFoundError, ROOT_MODULE_ID, mixin } from "./container/index.js";
11
11
  // Constants
12
12
  export { METADATA_KEYS, HttpMethod, ParamType, Scope } from "./constants.js";
13
13
  // HTTP Decorators
@@ -33,7 +33,7 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
33
33
  // Throttler
34
34
  export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
35
35
  // Module
36
- export { Global, Module, createModuleRef } from "./module/index.js";
36
+ export { Global, Module, defineDynamicModule, stableHash } from "./module/index.js";
37
37
  // Plugin manifest + composer
38
38
  export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN } from "./plugin/plugin.js";
39
39
  // Pipeline Decorators
@@ -1,6 +1,10 @@
1
- import type { Constructor, ModuleMetadata, ModuleOptions, Type } from '../registry/types';
1
+ import type { Constructor, DynamicModule, ModuleMetadata, ModuleOptions } from '../registry/types';
2
2
  export declare function Global(): ClassDecorator;
3
3
  export declare function Module(options?: ModuleOptions): ClassDecorator;
4
4
  export declare function isModule(target: Constructor): boolean;
5
- export declare function createModuleRef(name: string): Type;
5
+ /**
6
+ * Normalize a DynamicModule, defaulting `key` to `"default"`. Module authors
7
+ * call this from `forRoot()` so the loader always sees an explicit key.
8
+ */
9
+ export declare function defineDynamicModule(input: DynamicModule): DynamicModule;
6
10
  export declare function getModuleMetadata(target: Constructor): ModuleMetadata | undefined;
@@ -23,13 +23,14 @@ export function Module(options = {}) {
23
23
  export function isModule(target) {
24
24
  return MetadataRegistry.getModuleOptions(target) !== undefined;
25
25
  }
26
- export function createModuleRef(name) {
27
- const moduleClass = class {
26
+ /**
27
+ * Normalize a DynamicModule, defaulting `key` to `"default"`. Module authors
28
+ * call this from `forRoot()` so the loader always sees an explicit key.
29
+ */ export function defineDynamicModule(input) {
30
+ return {
31
+ ...input,
32
+ key: input.key ?? 'default'
28
33
  };
29
- Object.defineProperty(moduleClass, 'name', {
30
- value: name
31
- });
32
- return moduleClass;
33
34
  }
34
35
  export function getModuleMetadata(target) {
35
36
  const options = MetadataRegistry.getModuleOptions(target);
@@ -1,4 +1,5 @@
1
- export { Global, Module, isModule, getModuleMetadata, createModuleRef } from './decorators';
1
+ export { Global, Module, isModule, getModuleMetadata, defineDynamicModule, } from './decorators';
2
+ export { stableHash } from './stable-hash';
2
3
  export { ModuleLoader } from './module-loader';
3
4
  export { MiddlewareBuilder } from './middleware';
4
5
  export type { MiddlewareConsumer, MiddlewareConfigProxy, MiddlewareRouteDefinition, NestModule, RouteInfo, } from './middleware';
@@ -1,3 +1,4 @@
1
- export { Global, Module, isModule, getModuleMetadata, createModuleRef } from "./decorators.js";
1
+ export { Global, Module, isModule, getModuleMetadata, defineDynamicModule } from "./decorators.js";
2
+ export { stableHash } from "./stable-hash.js";
2
3
  export { ModuleLoader } from "./module-loader.js";
3
4
  export { MiddlewareBuilder } from "./middleware.js";
@@ -14,12 +14,17 @@ export declare class ModuleLoader {
14
14
  private consumerMiddlewareDefinitions;
15
15
  private appProviderCounter;
16
16
  private appProviderTokens;
17
- private moduleIdByClass;
17
+ private moduleIdByClassKey;
18
18
  private seenModuleIds;
19
19
  constructor(container: Container, router: RouteManager);
20
20
  load(rootModule: Type): void;
21
21
  private getModuleId;
22
+ private isProcessed;
23
+ private markProcessed;
24
+ private getCachedExports;
25
+ private cacheExports;
22
26
  private processModule;
27
+ private warnOnMixedDefaultAndKeyed;
23
28
  private registerProvider;
24
29
  private isAppToken;
25
30
  private buildExportSet;