Skip to content

Commit 322e95b

Browse files
update(@nestjs/core & @nestjs/common) injector improvements, forwardRef
1 parent 1feb631 commit 322e95b

10 files changed

Lines changed: 87 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 4.1.1
2+
- **common**: add `forwardRef()` util
3+
- **core**: improve injector & dependencies scanner
4+
15
## 4.1.0
26
- **common**: add `@Bind()` and `@Dependencies()` decorators to fix route parameters decorators (pure JavaScript compatibility issue)
37
- **core**: improve performance
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'reflect-metadata';
2+
import { expect } from 'chai';
3+
import { Bind } from '../../utils/decorators/bind.decorator';
4+
import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants';
5+
import { Req } from '../../utils/decorators/route-params.decorator';
6+
7+
describe('@Bind', () => {
8+
class TestWithMethod {
9+
@Bind(Req())
10+
public test() {}
11+
}
12+
13+
it('should enhance method - bind each decorator to method', () => {
14+
const test = new TestWithMethod();
15+
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, test, 'test');
16+
17+
expect(metadata).to.be.deep.equal({
18+
'0:0': {
19+
data: undefined,
20+
index: 0,
21+
pipes: [],
22+
},
23+
});
24+
});
25+
26+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { expect } from 'chai';
2+
import { forwardRef } from './../../utils/forward-ref.util';
3+
4+
describe('forwardRef', () => {
5+
it('should return object with forwardRef property', () => {
6+
const fn = () => ({});
7+
const referenceFn = forwardRef(() => fn);
8+
expect(referenceFn.forwardRef()).to.be.eql(fn);
9+
});
10+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { isFunction } from './shared.utils';
2+
3+
export function forwardRef(fn: () => any) {
4+
return { forwardRef: fn };
5+
}

src/common/utils/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export * from './decorators/use-guards.decorator';
1414
export * from './decorators/reflect-metadata.decorator';
1515
export * from './decorators/use-interceptors.decorator';
1616
export * from './decorators/http-code.decorator';
17-
export * from './decorators/bind.decorator';
17+
export * from './decorators/bind.decorator';
18+
export * from './forward-ref.util';

src/core/errors/messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const InvalidMiddlewareMessage = (name: string) =>
22
`The middleware doesn't provide the 'resolve' method (${name})`;
33

44
export const InvalidModuleMessage = (scope: string) =>
5-
`Nest can't create the module instance. The frequent reason of this exception is the circular dependency between modules. Scope [${scope}]`;
5+
`Nest can't create the module instance. The frequent reason of this exception is the circular dependency between modules. Use forwardRef() to avoid it (read more https://docs.nestjs.com/advanced/circular-dependency). Scope [${scope}]`;
66

77
export const UnknownDependenciesMessage = (type: string) =>
88
`Nest can't resolve dependencies of the ${type}. Please verify whether all of them are available in the current context.`;

src/core/injector/container.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ export class NestContainer {
5050
module.addComponent(component);
5151
}
5252

53-
public addInjectable(Injectable: Metatype<Injectable>, token: string) {
53+
public addInjectable(injectable: Metatype<Injectable>, token: string) {
5454
if (!this.modules.has(token)) {
5555
throw new UnknownModuleException();
5656
}
5757
const module = this.modules.get(token);
58-
module.addInjectable(Injectable);
58+
module.addInjectable(injectable);
5959
}
6060

6161

@@ -95,5 +95,6 @@ export interface InstanceWrapper<T> {
9595
done$?: Promise<void>;
9696
inject?: Metatype<any>[];
9797
isNotMetatype?: boolean;
98+
forwardRef?: boolean;
9899
async?: boolean;
99100
}

src/core/injector/injector.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class Injector {
102102

103103
const instances = await Promise.all(args.map(async (param) => {
104104
const paramWrapper = await this.resolveSingleParam<T>(wrapper, param, module, context);
105-
if (!paramWrapper.isResolved) {
105+
if (!paramWrapper.isResolved && !paramWrapper.forwardRef) {
106106
isResolved = false;
107107
}
108108
return paramWrapper.instance;
@@ -124,16 +124,18 @@ export class Injector {
124124

125125
public async resolveSingleParam<T>(
126126
wrapper: InstanceWrapper<T>,
127-
param: Metatype<any> | string | symbol,
127+
param: Metatype<any> | string | symbol | any,
128128
module: Module,
129129
context: Module[]) {
130130

131131
if (isUndefined(param)) {
132132
throw new UndefinedDependencyException(wrapper.name);
133133
}
134+
const token = param.forwardRef ? param.forwardRef() : param;
135+
wrapper.forwardRef = true;
134136
return await this.resolveComponentInstance<T>(
135137
module,
136-
isFunction(param) ? (param as Metatype<any>).name : param,
138+
isFunction(token) ? (token as Metatype<any>).name : token,
137139
wrapper,
138140
context,
139141
);
@@ -143,7 +145,7 @@ export class Injector {
143145
const components = module.components;
144146
const instanceWrapper = await this.scanForComponent(components, name, module, wrapper, context);
145147

146-
if (!instanceWrapper.isResolved) {
148+
if (!instanceWrapper.isResolved && !instanceWrapper.forwardRef) {
147149
await this.loadInstanceOfComponent(instanceWrapper, module);
148150
}
149151
if (instanceWrapper.async) {
@@ -183,7 +185,7 @@ export class Injector {
183185
const component = await this.scanForComponent(
184186
context.components, name, context, { metatype }, null,
185187
);
186-
if (!component.isResolved) {
188+
if (!component.isResolved && !component.forwardRef) {
187189
await this.loadInstanceOfComponent(component, context);
188190
}
189191
return component;
@@ -207,7 +209,7 @@ export class Injector {
207209
continue;
208210
}
209211
component = components.get(name);
210-
if (!component.isResolved) {
212+
if (!component.isResolved && !component.forwardRef) {
211213
const ctx = isInScope ? [module] : [].concat(context, module);
212214
await this.loadInstanceOfComponent(component, relatedModule, ctx);
213215
break;

src/core/scanner.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export class DependenciesScanner {
2727
});
2828
}
2929

30-
public storeModule(module: NestModuleMetatype, scope: NestModuleMetatype[]) {
30+
public storeModule(module: any, scope: NestModuleMetatype[]) {
31+
if (module.forwardRef) {
32+
return this.container.addModule(module.forwardRef(), scope);
33+
}
3134
this.container.addModule(module, scope);
3235
}
3336

@@ -108,7 +111,10 @@ export class DependenciesScanner {
108111
return descriptor ? Reflect.getMetadata(key, descriptor.value) : undefined;
109112
}
110113

111-
public storeRelatedModule(related: NestModuleMetatype, token: string) {
114+
public storeRelatedModule(related: any, token: string) {
115+
if (related.forwardRef) {
116+
return this.container.addRelatedModule(related.forwardRef(), token);
117+
}
112118
this.container.addRelatedModule(related, token);
113119
}
114120

@@ -131,5 +137,4 @@ export class DependenciesScanner {
131137
public reflectMetadata(metatype, metadata: string) {
132138
return Reflect.getMetadata(metadata, metatype) || [];
133139
}
134-
135140
}

src/core/test/scanner.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,24 @@ describe('DependenciesScanner', () => {
121121
expect(result).to.be.eql(['test']);
122122
});
123123
});
124+
125+
describe('storeModule', () => {
126+
it('should call forwardRef() when forwardRef property exists', () => {
127+
const module = { forwardRef: sinon.spy() };
128+
129+
sinon.stub(container, 'addModule').returns({});
130+
scanner.storeModule(module as any, [] as any);
131+
expect(module.forwardRef.called).to.be.true;
132+
});
133+
});
134+
135+
describe('storeRelatedModule', () => {
136+
it('should call forwardRef() when forwardRef property exists', () => {
137+
const module = { forwardRef: sinon.stub().returns({}) };
138+
139+
sinon.stub(container, 'addRelatedModule').returns({});
140+
scanner.storeRelatedModule(module as any, [] as any);
141+
expect(module.forwardRef.called).to.be.true;
142+
});
143+
});
124144
});

0 commit comments

Comments
 (0)