@nestjs/core 12.0.0-alpha.3 → 12.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +1 -0
- package/application-config.d.ts +8 -2
- package/application-config.js +14 -0
- package/errors/exceptions/index.d.ts +1 -0
- package/errors/exceptions/index.js +1 -0
- package/errors/exceptions/invalid-class-module.exception.d.ts +1 -1
- package/errors/exceptions/invalid-class-module.exception.js +2 -2
- package/errors/exceptions/invalid-module.exception.d.ts +1 -1
- package/errors/exceptions/invalid-module.exception.js +2 -2
- package/errors/exceptions/route-conflict.exception.d.ts +4 -0
- package/errors/exceptions/route-conflict.exception.js +7 -0
- package/errors/messages.d.ts +5 -2
- package/errors/messages.js +37 -4
- package/injector/injector.d.ts +4 -3
- package/injector/injector.js +28 -3
- package/injector/instance-wrapper.d.ts +2 -0
- package/injector/instance-wrapper.js +25 -0
- package/nest-application.d.ts +1 -0
- package/nest-application.js +77 -1
- package/package.json +3 -3
- package/router/interfaces/index.d.ts +3 -0
- package/router/interfaces/index.js +3 -0
- package/router/interfaces/resolved-route.interface.d.ts +32 -0
- package/router/interfaces/resolved-route.interface.js +1 -0
- package/router/interfaces/resolver.interface.d.ts +5 -1
- package/router/interfaces/route-conflict.interface.d.ts +14 -0
- package/router/interfaces/route-conflict.interface.js +1 -0
- package/router/interfaces/route-resolution-options.interface.d.ts +24 -0
- package/router/interfaces/route-resolution-options.interface.js +1 -0
- package/router/route-conflict-detector.d.ts +71 -0
- package/router/route-conflict-detector.js +276 -0
- package/router/route-specificity-sorter.d.ts +23 -0
- package/router/route-specificity-sorter.js +59 -0
- package/router/router-explorer.d.ts +11 -2
- package/router/router-explorer.js +61 -19
- package/router/routes-resolver.d.ts +7 -4
- package/router/routes-resolver.js +9 -7
- package/scanner.js +9 -5
package/Readme.md
CHANGED
|
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
|
|
129
129
|
<tr>
|
|
130
130
|
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
|
|
131
131
|
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
|
|
132
|
+
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
|
|
132
133
|
</tr>
|
|
133
134
|
</table>
|
|
134
135
|
|
package/application-config.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { CanActivate, ExceptionFilter, NestInterceptor, PipeTransform, PreRequestHook, VersioningOptions, WebSocketAdapter } from '@nestjs/common';
|
|
1
|
+
import type { CanActivate, ExceptionFilter, NestInterceptor, PipeTransform, PreRequestHook, RouteConflictPolicy, RouteResolutionStrategy, VersioningOptions, WebSocketAdapter } from '@nestjs/common';
|
|
2
|
+
import type { GlobalPrefixOptions } from '@nestjs/common/internal';
|
|
2
3
|
import { InstanceWrapper } from './injector/instance-wrapper.js';
|
|
3
4
|
import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface.js';
|
|
4
|
-
import type { GlobalPrefixOptions } from '@nestjs/common/internal';
|
|
5
5
|
export declare class ApplicationConfig {
|
|
6
6
|
private ioAdapter;
|
|
7
7
|
private globalPrefix;
|
|
@@ -12,6 +12,8 @@ export declare class ApplicationConfig {
|
|
|
12
12
|
private globalGuards;
|
|
13
13
|
private globalPreRequestHooks;
|
|
14
14
|
private versioningOptions;
|
|
15
|
+
private routeConflictPolicy;
|
|
16
|
+
private routeResolutionStrategy;
|
|
15
17
|
private readonly globalRequestPipes;
|
|
16
18
|
private readonly globalRequestFilters;
|
|
17
19
|
private readonly globalRequestInterceptors;
|
|
@@ -47,4 +49,8 @@ export declare class ApplicationConfig {
|
|
|
47
49
|
getGlobalPreRequestHooks(): PreRequestHook[];
|
|
48
50
|
enableVersioning(options: VersioningOptions): void;
|
|
49
51
|
getVersioning(): VersioningOptions | undefined;
|
|
52
|
+
setRouteConflictPolicy(policy: RouteConflictPolicy | undefined): void;
|
|
53
|
+
getRouteConflictPolicy(): RouteConflictPolicy | undefined;
|
|
54
|
+
setRouteResolutionStrategy(strategy: RouteResolutionStrategy | undefined): void;
|
|
55
|
+
getRouteResolutionStrategy(): RouteResolutionStrategy | undefined;
|
|
50
56
|
}
|
package/application-config.js
CHANGED
|
@@ -8,6 +8,8 @@ export class ApplicationConfig {
|
|
|
8
8
|
globalGuards = [];
|
|
9
9
|
globalPreRequestHooks = [];
|
|
10
10
|
versioningOptions;
|
|
11
|
+
routeConflictPolicy;
|
|
12
|
+
routeResolutionStrategy;
|
|
11
13
|
globalRequestPipes = [];
|
|
12
14
|
globalRequestFilters = [];
|
|
13
15
|
globalRequestInterceptors = [];
|
|
@@ -109,4 +111,16 @@ export class ApplicationConfig {
|
|
|
109
111
|
getVersioning() {
|
|
110
112
|
return this.versioningOptions;
|
|
111
113
|
}
|
|
114
|
+
setRouteConflictPolicy(policy) {
|
|
115
|
+
this.routeConflictPolicy = policy;
|
|
116
|
+
}
|
|
117
|
+
getRouteConflictPolicy() {
|
|
118
|
+
return this.routeConflictPolicy;
|
|
119
|
+
}
|
|
120
|
+
setRouteResolutionStrategy(strategy) {
|
|
121
|
+
this.routeResolutionStrategy = strategy;
|
|
122
|
+
}
|
|
123
|
+
getRouteResolutionStrategy() {
|
|
124
|
+
return this.routeResolutionStrategy;
|
|
125
|
+
}
|
|
112
126
|
}
|
|
@@ -3,6 +3,7 @@ export * from './runtime.exception.js';
|
|
|
3
3
|
export * from './unknown-element.exception.js';
|
|
4
4
|
export * from './invalid-class-scope.exception.js';
|
|
5
5
|
export * from './invalid-class.exception.js';
|
|
6
|
+
export * from './route-conflict.exception.js';
|
|
6
7
|
export * from './unknown-export.exception.js';
|
|
7
8
|
export * from './unknown-module.exception.js';
|
|
8
9
|
export * from './undefined-forwardref.exception.js';
|
|
@@ -3,6 +3,7 @@ export * from './runtime.exception.js';
|
|
|
3
3
|
export * from './unknown-element.exception.js';
|
|
4
4
|
export * from './invalid-class-scope.exception.js';
|
|
5
5
|
export * from './invalid-class.exception.js';
|
|
6
|
+
export * from './route-conflict.exception.js';
|
|
6
7
|
export * from './unknown-export.exception.js';
|
|
7
8
|
export * from './unknown-module.exception.js';
|
|
8
9
|
export * from './undefined-forwardref.exception.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { RuntimeException } from './runtime.exception.js';
|
|
2
2
|
export declare class InvalidClassModuleException extends RuntimeException {
|
|
3
|
-
constructor(metatypeUsedAsAModule: any, scope: any[]);
|
|
3
|
+
constructor(metatypeUsedAsAModule: any, scope: any[], classKind: 'provider' | 'controller' | 'filter');
|
|
4
4
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { USING_INVALID_CLASS_AS_A_MODULE_MESSAGE } from '../messages.js';
|
|
2
2
|
import { RuntimeException } from './runtime.exception.js';
|
|
3
3
|
export class InvalidClassModuleException extends RuntimeException {
|
|
4
|
-
constructor(metatypeUsedAsAModule, scope) {
|
|
5
|
-
super(USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(metatypeUsedAsAModule, scope));
|
|
4
|
+
constructor(metatypeUsedAsAModule, scope, classKind) {
|
|
5
|
+
super(USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(metatypeUsedAsAModule, scope, classKind));
|
|
6
6
|
}
|
|
7
7
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { RuntimeException } from './runtime.exception.js';
|
|
2
2
|
export declare class InvalidModuleException extends RuntimeException {
|
|
3
|
-
constructor(parentModule: any, index: number, scope: any[]);
|
|
3
|
+
constructor(parentModule: any, index: number, scope: any[], receivedValue: unknown);
|
|
4
4
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { INVALID_MODULE_MESSAGE } from '../messages.js';
|
|
2
2
|
import { RuntimeException } from './runtime.exception.js';
|
|
3
3
|
export class InvalidModuleException extends RuntimeException {
|
|
4
|
-
constructor(parentModule, index, scope) {
|
|
5
|
-
super(INVALID_MODULE_MESSAGE(parentModule, index, scope));
|
|
4
|
+
constructor(parentModule, index, scope, receivedValue) {
|
|
5
|
+
super(INVALID_MODULE_MESSAGE(parentModule, index, scope, receivedValue));
|
|
6
6
|
}
|
|
7
7
|
}
|
package/errors/messages.d.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { Module } from '../injector/module.js';
|
|
|
4
4
|
export declare const UNKNOWN_DEPENDENCIES_MESSAGE: (type: string | symbol, unknownDependencyContext: InjectorDependencyContext, moduleRef: Module | undefined) => string;
|
|
5
5
|
export declare const INVALID_MIDDLEWARE_MESSAGE: (text: TemplateStringsArray, name: string) => string;
|
|
6
6
|
export declare const UNDEFINED_FORWARDREF_MESSAGE: (scope: Type<any>[]) => string;
|
|
7
|
-
export declare const INVALID_MODULE_MESSAGE: (parentModule: any, index: number, scope: any[]) => string;
|
|
8
|
-
export declare const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE: (metatypeUsedAsAModule: Type | ForwardReference, scope: any[]) => string;
|
|
7
|
+
export declare const INVALID_MODULE_MESSAGE: (parentModule: any, index: number, scope: any[], receivedValue: unknown) => string;
|
|
8
|
+
export declare const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE: (metatypeUsedAsAModule: Type | ForwardReference, scope: any[], classKind: "provider" | "controller" | "filter") => string;
|
|
9
9
|
export declare const UNDEFINED_MODULE_MESSAGE: (parentModule: any, index: number, scope: any[]) => string;
|
|
10
10
|
export declare const UNKNOWN_EXPORT_MESSAGE: (token: string | symbol | undefined, module: string) => string;
|
|
11
11
|
export declare const INVALID_CLASS_MESSAGE: (text: TemplateStringsArray, value: any) => string;
|
|
12
12
|
export declare const INVALID_CLASS_SCOPE_MESSAGE: (text: TemplateStringsArray, name: string | undefined) => string;
|
|
13
13
|
export declare const UNKNOWN_REQUEST_MAPPING: (metatype: Type) => string;
|
|
14
|
+
export declare const ROUTE_CONFLICT_MESSAGE: (messages: string[]) => string;
|
|
15
|
+
export declare const DUPLICATE_ROUTE_MESSAGE: (method: string, path: string, firstHandlerLabel: string, secondHandlerLabel: string) => string;
|
|
16
|
+
export declare const SHADOWED_ROUTE_MESSAGE: (method: string, shadowedPath: string, shadowedHandlerLabel: string, winnerPath: string, winnerHandlerLabel: string) => string;
|
|
14
17
|
export declare const INVALID_MIDDLEWARE_CONFIGURATION = "An invalid middleware configuration has been passed inside the module 'configure()' method.";
|
|
15
18
|
export declare const UNHANDLED_RUNTIME_EXCEPTION = "Unhandled Runtime Exception.";
|
|
16
19
|
export declare const INVALID_EXCEPTION_FILTER = "Invalid exception filters (@UseFilters()).";
|
package/errors/messages.js
CHANGED
|
@@ -111,17 +111,43 @@ export const UNDEFINED_FORWARDREF_MESSAGE = (scope) => `Nest cannot create the m
|
|
|
111
111
|
(Read more: https://docs.nestjs.com/fundamentals/circular-dependency)
|
|
112
112
|
Scope [${stringifyScope(scope)}]
|
|
113
113
|
`;
|
|
114
|
-
export const INVALID_MODULE_MESSAGE = (parentModule, index, scope) => {
|
|
114
|
+
export const INVALID_MODULE_MESSAGE = (parentModule, index, scope, receivedValue) => {
|
|
115
115
|
const parentModuleName = parentModule?.name || 'module';
|
|
116
|
+
let formattedValue;
|
|
117
|
+
let receivedType;
|
|
118
|
+
if (receivedValue === null) {
|
|
119
|
+
formattedValue = 'null';
|
|
120
|
+
receivedType = 'null';
|
|
121
|
+
}
|
|
122
|
+
else if (typeof receivedValue === 'string') {
|
|
123
|
+
formattedValue = `"${receivedValue}"`;
|
|
124
|
+
receivedType = 'string';
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
formattedValue = String(receivedValue);
|
|
128
|
+
receivedType = typeof receivedValue;
|
|
129
|
+
}
|
|
116
130
|
return `Nest cannot create the ${parentModuleName} instance.
|
|
117
131
|
Received an unexpected value at index [${index}] of the ${parentModuleName} "imports" array.
|
|
132
|
+
The received value \`${formattedValue}\` is of type "${receivedType}".
|
|
118
133
|
|
|
119
134
|
Scope [${stringifyScope(scope)}]`;
|
|
120
135
|
};
|
|
121
|
-
export const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE = (metatypeUsedAsAModule, scope) => {
|
|
136
|
+
export const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE = (metatypeUsedAsAModule, scope, classKind) => {
|
|
122
137
|
const metatypeNameQuote = `"${getInstanceName(metatypeUsedAsAModule)}"`;
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
let hint;
|
|
139
|
+
switch (classKind) {
|
|
140
|
+
case 'controller':
|
|
141
|
+
hint = `${metatypeNameQuote} is decorated with @Controller() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "controllers" array of the importing module instead.`;
|
|
142
|
+
break;
|
|
143
|
+
case 'provider':
|
|
144
|
+
hint = `${metatypeNameQuote} is decorated with @Injectable() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "providers" array of the importing module instead.`;
|
|
145
|
+
break;
|
|
146
|
+
case 'filter':
|
|
147
|
+
hint = `${metatypeNameQuote} is decorated with @Catch() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "providers" array (using the APP_FILTER token to apply it globally) or apply it via @UseFilters() instead.`;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
return `${hint}
|
|
125
151
|
|
|
126
152
|
Scope [${stringifyScope(scope)}]
|
|
127
153
|
`;
|
|
@@ -155,6 +181,13 @@ export const UNKNOWN_REQUEST_MAPPING = (metatype) => {
|
|
|
155
181
|
? `An invalid controller has been detected. "${className}" does not have the @Controller() decorator but it is being listed in the "controllers" array of some module.`
|
|
156
182
|
: `An invalid controller has been detected. Perhaps, one of your controllers is missing the @Controller() decorator.`;
|
|
157
183
|
};
|
|
184
|
+
export const ROUTE_CONFLICT_MESSAGE = (messages) => [
|
|
185
|
+
'Conflicting HTTP routes detected:',
|
|
186
|
+
...messages.map(message => ` - ${message}`),
|
|
187
|
+
`Adjust route declarations or relax the 'routeConflictPolicy' option passed to NestFactory.create() to allow the application to start.`,
|
|
188
|
+
].join('\n');
|
|
189
|
+
export const DUPLICATE_ROUTE_MESSAGE = (method, path, firstHandlerLabel, secondHandlerLabel) => `Duplicate route: ${method} ${path} is registered by both ${firstHandlerLabel} and ${secondHandlerLabel}.`;
|
|
190
|
+
export const SHADOWED_ROUTE_MESSAGE = (method, shadowedPath, shadowedHandlerLabel, winnerPath, winnerHandlerLabel) => `Route ${method} ${shadowedPath} (${shadowedHandlerLabel}) is shadowed by ${method} ${winnerPath} (${winnerHandlerLabel}). The first-registered route will match all matching requests on order-sensitive adapters.`;
|
|
158
191
|
export const INVALID_MIDDLEWARE_CONFIGURATION = `An invalid middleware configuration has been passed inside the module 'configure()' method.`;
|
|
159
192
|
export const UNHANDLED_RUNTIME_EXCEPTION = `Unhandled Runtime Exception.`;
|
|
160
193
|
export const INVALID_EXCEPTION_FILTER = `Invalid exception filters (@UseFilters()).`;
|
package/injector/injector.d.ts
CHANGED
|
@@ -73,9 +73,9 @@ export declare class Injector {
|
|
|
73
73
|
resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject: InjectorDependency[] | undefined, callback: (args: unknown[], depth?: number) => void | Promise<void>, resolutionContext?: ResolutionContext, parentInquirer?: InstanceWrapper): Promise<void>;
|
|
74
74
|
getClassDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
|
|
75
75
|
getFactoryProviderDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
|
|
76
|
-
reflectConstructorParams
|
|
77
|
-
reflectOptionalParams
|
|
78
|
-
reflectSelfParams
|
|
76
|
+
reflectConstructorParams(type: Type<unknown> | Function): any[];
|
|
77
|
+
reflectOptionalParams(type: Type<unknown> | Function): any[];
|
|
78
|
+
reflectSelfParams(type: Type<unknown> | Function): any[];
|
|
79
79
|
resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol, dependencyContext: InjectorDependencyContext, moduleRef: Module, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<any>>;
|
|
80
80
|
resolveParamToken<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol | ForwardReference): any;
|
|
81
81
|
resolveComponentWrapper<T>(moduleRef: Module, token: InjectionToken, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper>;
|
|
@@ -107,6 +107,7 @@ export declare class Injector {
|
|
|
107
107
|
private getEffectiveInquirerId;
|
|
108
108
|
private getStaticTransientResolutionContext;
|
|
109
109
|
private getEffectiveResolutionContext;
|
|
110
|
+
private hasDenseCtorMetadata;
|
|
110
111
|
private resolveScopedComponentHost;
|
|
111
112
|
private isInquirerRequest;
|
|
112
113
|
private isInquirer;
|
package/injector/injector.js
CHANGED
|
@@ -72,6 +72,9 @@ export class Injector {
|
|
|
72
72
|
settlementSignal.complete();
|
|
73
73
|
};
|
|
74
74
|
await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, localResolutionContext, resolutionContext.inquirer);
|
|
75
|
+
if (!instanceHost.isResolved) {
|
|
76
|
+
settlementSignal.complete();
|
|
77
|
+
}
|
|
75
78
|
}
|
|
76
79
|
catch (err) {
|
|
77
80
|
wrapper.removeInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
|
|
@@ -114,7 +117,8 @@ export class Injector {
|
|
|
114
117
|
}
|
|
115
118
|
async resolveConstructorParams(wrapper, moduleRef, inject, callback, resolutionContext = { contextId: STATIC_CONTEXT }, parentInquirer) {
|
|
116
119
|
const metadata = wrapper.getCtorMetadata();
|
|
117
|
-
if (
|
|
120
|
+
if (resolutionContext.contextId !== STATIC_CONTEXT &&
|
|
121
|
+
this.hasDenseCtorMetadata(wrapper, inject, metadata)) {
|
|
118
122
|
const deps = await this.loadCtorMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer, parentInquirer);
|
|
119
123
|
return callback(deps);
|
|
120
124
|
}
|
|
@@ -501,7 +505,6 @@ export class Injector {
|
|
|
501
505
|
wrapper.isExplicitlyRequested(resolutionContext.contextId, resolutionContext.inquirer));
|
|
502
506
|
}
|
|
503
507
|
shouldSkipProviderLoading(wrapper, resolutionContext) {
|
|
504
|
-
const isSnapshotGraphCompilation = !!this.options?.snapshot;
|
|
505
508
|
const isStaticContext = resolutionContext.contextId === STATIC_CONTEXT;
|
|
506
509
|
const hasNoInquirer = !resolutionContext.inquirer;
|
|
507
510
|
const isTopLevelStaticTransientOrRequestProvider = hasNoInquirer && (wrapper.isTransient || wrapper.scope === Scope.REQUEST);
|
|
@@ -510,7 +513,7 @@ export class Injector {
|
|
|
510
513
|
const shouldSkipForStaticBootstrap = isStaticContext &&
|
|
511
514
|
(isTopLevelStaticTransientOrRequestProvider ||
|
|
512
515
|
isStaticInquirerOutsideResolutionContext);
|
|
513
|
-
return
|
|
516
|
+
return shouldSkipForStaticBootstrap;
|
|
514
517
|
}
|
|
515
518
|
/**
|
|
516
519
|
* For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
|
|
@@ -554,6 +557,28 @@ export class Injector {
|
|
|
554
557
|
getEffectiveResolutionContext(dependency, resolutionContext, parentInquirer) {
|
|
555
558
|
return this.createResolutionContext(resolutionContext.contextId, this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer), this.getEffectiveInquirerId(dependency, resolutionContext, parentInquirer));
|
|
556
559
|
}
|
|
560
|
+
hasDenseCtorMetadata(wrapper, inject, metadata) {
|
|
561
|
+
if (!metadata) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
// The fast path requires a fully populated metadata array.
|
|
565
|
+
// While another request is still registering dependency metadata,
|
|
566
|
+
// sparse entries here would feed request-scoped factories `undefined`.
|
|
567
|
+
const expectedDepsLength = !isNil(inject)
|
|
568
|
+
? inject.length
|
|
569
|
+
: wrapper.metatype
|
|
570
|
+
? this.reflectConstructorParams(wrapper.metatype).length
|
|
571
|
+
: 0;
|
|
572
|
+
if (metadata.length !== expectedDepsLength) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
for (let index = 0; index < expectedDepsLength; index++) {
|
|
576
|
+
if (metadata[index] === undefined) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
557
582
|
resolveScopedComponentHost(item, contextId, inquirer, parentInquirer) {
|
|
558
583
|
return this.isInquirerRequest(item, parentInquirer)
|
|
559
584
|
? parentInquirer
|
|
@@ -94,6 +94,8 @@ export declare class InstanceWrapper<T = any> {
|
|
|
94
94
|
getStaticTransientInstances(): (InstancePerContext<T> | undefined)[];
|
|
95
95
|
mergeWith(provider: Provider): void;
|
|
96
96
|
private isNewable;
|
|
97
|
+
private registerDependencyTreeParent;
|
|
98
|
+
private resetDependencyTreeState;
|
|
97
99
|
private initialize;
|
|
98
100
|
private printIntrospectedAsRequestScoped;
|
|
99
101
|
private printIntrospectedAsDurable;
|
|
@@ -6,6 +6,7 @@ import { STATIC_CONTEXT } from './constants.js';
|
|
|
6
6
|
import { isClassProvider, isFactoryProvider, isValueProvider, } from './helpers/provider-classifier.js';
|
|
7
7
|
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
|
|
8
8
|
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
|
|
9
|
+
const dependencyTreeParents = new WeakMap();
|
|
9
10
|
export class InstanceWrapper {
|
|
10
11
|
name;
|
|
11
12
|
token;
|
|
@@ -121,6 +122,8 @@ export class InstanceWrapper {
|
|
|
121
122
|
this[INSTANCE_METADATA_SYMBOL].dependencies = [];
|
|
122
123
|
}
|
|
123
124
|
this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper;
|
|
125
|
+
this.registerDependencyTreeParent(wrapper);
|
|
126
|
+
this.resetDependencyTreeState();
|
|
124
127
|
}
|
|
125
128
|
getCtorMetadata() {
|
|
126
129
|
return this[INSTANCE_METADATA_SYMBOL].dependencies;
|
|
@@ -133,6 +136,8 @@ export class InstanceWrapper {
|
|
|
133
136
|
key,
|
|
134
137
|
wrapper,
|
|
135
138
|
});
|
|
139
|
+
this.registerDependencyTreeParent(wrapper);
|
|
140
|
+
this.resetDependencyTreeState();
|
|
136
141
|
}
|
|
137
142
|
getPropertiesMetadata() {
|
|
138
143
|
return this[INSTANCE_METADATA_SYMBOL].properties;
|
|
@@ -142,6 +147,8 @@ export class InstanceWrapper {
|
|
|
142
147
|
this[INSTANCE_METADATA_SYMBOL].enhancers = [];
|
|
143
148
|
}
|
|
144
149
|
this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper);
|
|
150
|
+
this.registerDependencyTreeParent(wrapper);
|
|
151
|
+
this.resetDependencyTreeState();
|
|
145
152
|
}
|
|
146
153
|
getEnhancersMetadata() {
|
|
147
154
|
return this[INSTANCE_METADATA_SYMBOL].enhancers;
|
|
@@ -333,6 +340,24 @@ export class InstanceWrapper {
|
|
|
333
340
|
isNewable() {
|
|
334
341
|
return isNil(this.inject) && this.metatype && this.metatype.prototype;
|
|
335
342
|
}
|
|
343
|
+
registerDependencyTreeParent(wrapper) {
|
|
344
|
+
if (wrapper instanceof InstanceWrapper) {
|
|
345
|
+
const parents = dependencyTreeParents.get(wrapper) ?? new Set();
|
|
346
|
+
parents.add(this);
|
|
347
|
+
dependencyTreeParents.set(wrapper, parents);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
resetDependencyTreeState(lookupRegistry = new Set()) {
|
|
351
|
+
if (lookupRegistry.has(this[INSTANCE_ID_SYMBOL])) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
lookupRegistry.add(this[INSTANCE_ID_SYMBOL]);
|
|
355
|
+
this.isTreeStatic = undefined;
|
|
356
|
+
this.isTreeDurable = undefined;
|
|
357
|
+
dependencyTreeParents
|
|
358
|
+
.get(this)
|
|
359
|
+
?.forEach(parent => parent.resetDependencyTreeState(lookupRegistry));
|
|
360
|
+
}
|
|
336
361
|
initialize(metadata) {
|
|
337
362
|
const { instance, isResolved, ...wrapperPartial } = metadata;
|
|
338
363
|
Object.assign(this, wrapperPartial);
|
package/nest-application.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class NestApplication extends NestApplicationContext<NestApplicat
|
|
|
22
22
|
private readonly microservices;
|
|
23
23
|
private httpServer;
|
|
24
24
|
private isListening;
|
|
25
|
+
private isWsModuleRegistered;
|
|
25
26
|
constructor(container: NestContainer, httpAdapter: HttpServer, config: ApplicationConfig, graphInspector: GraphInspector, appOptions?: NestApplicationOptions);
|
|
26
27
|
protected prepareClose(): Promise<void>;
|
|
27
28
|
protected dispose(): Promise<void>;
|
package/nest-application.js
CHANGED
|
@@ -12,6 +12,8 @@ import { NestApplicationContext } from './nest-application-context.js';
|
|
|
12
12
|
import { RoutesResolver } from './router/routes-resolver.js';
|
|
13
13
|
import { Logger } from '@nestjs/common';
|
|
14
14
|
import { loadPackage, loadPackageCached, tryLoadPackage, addLeadingSlash, isFunction, isObject, isString, } from '@nestjs/common/internal';
|
|
15
|
+
import { RouteConflictDetector } from './router/route-conflict-detector.js';
|
|
16
|
+
import { RouteSpecificitySorter } from './router/route-specificity-sorter.js';
|
|
15
17
|
/**
|
|
16
18
|
* @publicApi
|
|
17
19
|
*/
|
|
@@ -30,11 +32,14 @@ export class NestApplication extends NestApplicationContext {
|
|
|
30
32
|
microservices = [];
|
|
31
33
|
httpServer;
|
|
32
34
|
isListening = false;
|
|
35
|
+
isWsModuleRegistered = false;
|
|
33
36
|
constructor(container, httpAdapter, config, graphInspector, appOptions = {}) {
|
|
34
37
|
super(container, appOptions);
|
|
35
38
|
this.httpAdapter = httpAdapter;
|
|
36
39
|
this.config = config;
|
|
37
40
|
this.graphInspector = graphInspector;
|
|
41
|
+
this.config.setRouteConflictPolicy(appOptions.routeConflictPolicy);
|
|
42
|
+
this.config.setRouteResolutionStrategy(appOptions.routeResolutionStrategy);
|
|
38
43
|
this.selectContextModule();
|
|
39
44
|
this.registerHttpServer();
|
|
40
45
|
this.injector = new Injector({
|
|
@@ -92,6 +97,7 @@ export class NestApplication extends NestApplicationContext {
|
|
|
92
97
|
return;
|
|
93
98
|
}
|
|
94
99
|
this.socketModule.register(this.container, this.config, this.graphInspector, this.appOptions, this.httpServer);
|
|
100
|
+
this.isWsModuleRegistered = true;
|
|
95
101
|
}
|
|
96
102
|
async init() {
|
|
97
103
|
if (this.isInitialized) {
|
|
@@ -124,7 +130,74 @@ export class NestApplication extends NestApplicationContext {
|
|
|
124
130
|
await this.registerMiddleware(this.httpAdapter);
|
|
125
131
|
const prefix = this.config.getGlobalPrefix();
|
|
126
132
|
const basePath = addLeadingSlash(prefix);
|
|
127
|
-
this.
|
|
133
|
+
const conflictPolicy = this.config.getRouteConflictPolicy();
|
|
134
|
+
const resolutionStrategy = this.config.getRouteResolutionStrategy();
|
|
135
|
+
const adapterIsOrderSensitive = this.httpAdapter.isRouteOrderSensitive?.() ?? true;
|
|
136
|
+
const shouldSortBySpecificity = resolutionStrategy === 'specificity' && adapterIsOrderSensitive;
|
|
137
|
+
// Adapters that are not order-sensitive (e.g. Fastify) currently
|
|
138
|
+
// also reject duplicate (method, URL) registrations synchronously
|
|
139
|
+
// from the underlying router. Treat the two properties as one
|
|
140
|
+
// signal until a separate capability flag is introduced.
|
|
141
|
+
const adapterRejectsDuplicates = !adapterIsOrderSensitive;
|
|
142
|
+
if (!conflictPolicy && !shouldSortBySpecificity) {
|
|
143
|
+
this.routesResolver.resolve(this.httpAdapter, basePath);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Defer registration whenever we collect routes for diagnostics or
|
|
147
|
+
// re-ordering. In particular, when a conflict policy is set we
|
|
148
|
+
// must run detection *before* the adapter sees any route, because
|
|
149
|
+
// duplicate-rejecting adapters like Fastify throw synchronously
|
|
150
|
+
// from `instance.route()` and would short-circuit both the resolve
|
|
151
|
+
// loop and the aggregated `RouteConflictException`.
|
|
152
|
+
const resolvedRoutes = [];
|
|
153
|
+
this.routesResolver.resolve(this.httpAdapter, basePath, {
|
|
154
|
+
onRouteResolved: route => resolvedRoutes.push(route),
|
|
155
|
+
deferRegistration: true,
|
|
156
|
+
});
|
|
157
|
+
// Sort before conflict detection so that winner/shadowed pairs in every
|
|
158
|
+
// conflict record reflect actual adapter registration order. Without this,
|
|
159
|
+
// the reported winner could be the declaration-first (less-specific) route
|
|
160
|
+
// even though the sorted-first (more-specific) route is what really wins.
|
|
161
|
+
const orderedRoutes = shouldSortBySpecificity
|
|
162
|
+
? RouteSpecificitySorter.sort(resolvedRoutes)
|
|
163
|
+
: resolvedRoutes;
|
|
164
|
+
const routesToSkip = new Set();
|
|
165
|
+
if (conflictPolicy) {
|
|
166
|
+
const filteredPolicy = adapterIsOrderSensitive
|
|
167
|
+
? conflictPolicy
|
|
168
|
+
: { duplicate: conflictPolicy.duplicate };
|
|
169
|
+
const conflicts = RouteConflictDetector.detect(orderedRoutes, this.config.getVersioning());
|
|
170
|
+
// On adapters that reject duplicate registrations the policy
|
|
171
|
+
// cannot be honoured by simply logging — the adapter would
|
|
172
|
+
// throw on the second `instance.route()` call. Drop the
|
|
173
|
+
// shadowed route of every duplicate conflict so the detector
|
|
174
|
+
// (not the adapter) decides which one wins. The detector
|
|
175
|
+
// always picks the earlier-registered route as the winner.
|
|
176
|
+
if (adapterRejectsDuplicates) {
|
|
177
|
+
conflicts.forEach(conflict => {
|
|
178
|
+
if (conflict.kind === 'duplicate') {
|
|
179
|
+
routesToSkip.add(conflict.shadowed);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// When specificity sorting is active, shadow conflicts where the sort
|
|
184
|
+
// promoted the winner (declared later but more specific) are resolved
|
|
185
|
+
// at runtime: the more-specific route is first-registered and handles
|
|
186
|
+
// its requests, the less-specific route handles the rest. Filtering
|
|
187
|
+
// these out prevents shadow: 'error' from aborting an app whose routes
|
|
188
|
+
// work correctly after specificity ordering. Genuine shadows — where the
|
|
189
|
+
// winner was already first in declaration order and the sort did not help
|
|
190
|
+
// — are kept and still apply the configured policy.
|
|
191
|
+
const effectiveConflicts = shouldSortBySpecificity
|
|
192
|
+
? RouteConflictDetector.filterSortResolvedShadows(conflicts, resolvedRoutes)
|
|
193
|
+
: conflicts;
|
|
194
|
+
RouteConflictDetector.handle(effectiveConflicts, filteredPolicy, this.logger);
|
|
195
|
+
}
|
|
196
|
+
orderedRoutes.forEach(route => {
|
|
197
|
+
if (routesToSkip.has(route))
|
|
198
|
+
return;
|
|
199
|
+
this.routesResolver.registerResolvedRoute(this.httpAdapter, route);
|
|
200
|
+
});
|
|
128
201
|
}
|
|
129
202
|
async registerRouterHooks() {
|
|
130
203
|
this.routesResolver.registerNotFoundHandler();
|
|
@@ -260,6 +333,9 @@ export class NestApplication extends NestApplicationContext {
|
|
|
260
333
|
return this;
|
|
261
334
|
}
|
|
262
335
|
useWebSocketAdapter(adapter) {
|
|
336
|
+
if (this.isWsModuleRegistered) {
|
|
337
|
+
this.logger.warn('useWebSocketAdapter() was called after WebSocket gateways were already initialized. The provided adapter will be stored but will NOT be applied to existing gateways — they remain bound to the previously installed adapter. To install a custom adapter, call app.useWebSocketAdapter(...) BEFORE app.init() (or app.listen()).');
|
|
338
|
+
}
|
|
263
339
|
this.config.setIoAdapter(adapter);
|
|
264
340
|
return this;
|
|
265
341
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/core",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.5",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"uid": "2.0.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@nestjs/common": "^12.0.0-alpha.
|
|
50
|
+
"@nestjs/common": "^12.0.0-alpha.5"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@nestjs/common": "^11.0.0",
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"optional": true
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "d91b72cc9567e6a09e3ad6075b2fb801e71adfc8"
|
|
72
72
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RequestMethod } from '@nestjs/common';
|
|
2
|
+
import { type VersionValue } from '@nestjs/common/internal';
|
|
3
|
+
import { InstanceWrapper } from '../../injector/instance-wrapper.js';
|
|
4
|
+
import { RouterProxyCallback } from '../router-proxy.js';
|
|
5
|
+
/**
|
|
6
|
+
* Loose callable signature shared by the various handler wrappers that
|
|
7
|
+
* are composed before adapter registration (host filter, version
|
|
8
|
+
* filter, request-scoped handler, etc.). They all accept the (req, res,
|
|
9
|
+
* next) trio but may be invoked variadically by adapter shims.
|
|
10
|
+
*/
|
|
11
|
+
export type ResolvedRouteHandler = (...args: unknown[]) => unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Final route description produced during the "collect" phase of the
|
|
14
|
+
* router pipeline and consumed during the "register" phase. Holds the
|
|
15
|
+
* fully composed path, the pre-built handler chain (proxy + host filter
|
|
16
|
+
* + optional version filter), and the metadata needed to register the
|
|
17
|
+
* route on the HTTP adapter and to insert an entrypoint into the graph
|
|
18
|
+
* inspector.
|
|
19
|
+
*/
|
|
20
|
+
export interface ResolvedRoute {
|
|
21
|
+
method: RequestMethod;
|
|
22
|
+
path: string;
|
|
23
|
+
rawPath?: string;
|
|
24
|
+
host: string | RegExp | Array<string | RegExp> | undefined;
|
|
25
|
+
version: VersionValue | undefined;
|
|
26
|
+
methodVersion: VersionValue | undefined;
|
|
27
|
+
controllerVersion: VersionValue | undefined;
|
|
28
|
+
handler: ResolvedRouteHandler;
|
|
29
|
+
targetCallback: RouterProxyCallback;
|
|
30
|
+
methodName: string;
|
|
31
|
+
instanceWrapper: InstanceWrapper;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { HttpServer } from '@nestjs/common';
|
|
2
|
+
import { ResolvedRoute } from './resolved-route.interface.js';
|
|
3
|
+
import { RouteResolutionOptions } from './route-resolution-options.interface.js';
|
|
1
4
|
export interface Resolver {
|
|
2
|
-
resolve(
|
|
5
|
+
resolve(applicationRef: HttpServer, basePath: string, options?: RouteResolutionOptions): void;
|
|
6
|
+
registerResolvedRoute(applicationRef: HttpServer, route: ResolvedRoute): void;
|
|
3
7
|
registerNotFoundHandler(): void;
|
|
4
8
|
registerExceptionHandler(): void;
|
|
5
9
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ResolvedRoute } from './resolved-route.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Distinguishes the two flavors of route overlap.
|
|
4
|
+
* - `duplicate` — identical method + path + version + host registered twice.
|
|
5
|
+
* - `shadow` — patterns can match the same request but are not identical.
|
|
6
|
+
*/
|
|
7
|
+
export type ConflictKind = 'duplicate' | 'shadow';
|
|
8
|
+
export interface RouteConflict {
|
|
9
|
+
/** Route registered first; on order-sensitive adapters this wins. */
|
|
10
|
+
winner: ResolvedRoute;
|
|
11
|
+
/** Route registered later; on order-sensitive adapters this never matches. */
|
|
12
|
+
shadowed: ResolvedRoute;
|
|
13
|
+
kind: ConflictKind;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ResolvedRoute } from './resolved-route.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options that control how `Resolver.resolve` walks the controller
|
|
4
|
+
* graph and registers routes on the HTTP adapter. Used internally to
|
|
5
|
+
* thread route-collection and deferred-registration concerns through
|
|
6
|
+
* the resolver chain without bloating individual method signatures.
|
|
7
|
+
*/
|
|
8
|
+
export interface RouteResolutionOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Invoked once for each route after its final path, host and version
|
|
11
|
+
* have been composed. Lets the caller observe resolved routes (for
|
|
12
|
+
* conflict detection, specificity sorting, etc.) without coupling
|
|
13
|
+
* those concerns to the resolver itself.
|
|
14
|
+
*/
|
|
15
|
+
onRouteResolved?: (route: ResolvedRoute) => void;
|
|
16
|
+
/**
|
|
17
|
+
* When `true`, the resolver still walks every controller and emits
|
|
18
|
+
* `onRouteResolved` callbacks but skips the actual adapter
|
|
19
|
+
* registration step. The caller is then responsible for ordering and
|
|
20
|
+
* installing the collected routes via `registerResolvedRoute`.
|
|
21
|
+
* Defaults to `false`.
|
|
22
|
+
*/
|
|
23
|
+
deferRegistration?: boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|