Skip to content

Commit 5e8f6fb

Browse files
committed
feat(scope): create SameScope decorator
1 parent d82f644 commit 5e8f6fb

File tree

4 files changed

+124
-12
lines changed

4 files changed

+124
-12
lines changed

src/core/scope/__test__/scope.spec.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { getInstanceWithScope, TransientScope } from '../'
1+
import 'reflect-metadata'
2+
import { Injectable } from '@asuka/di'
3+
4+
import { getInstanceWithScope, TransientScope, SameScope } from '../'
25
import { createNewInstance, createOrGetInstanceInScope } from '../utils'
36

47
describe('Scope spec:', () => {
@@ -51,5 +54,75 @@ describe('Scope spec:', () => {
5154
getInstanceWithScope(Test, { scope: TransientScope }),
5255
).toBeFalsy()
5356
})
57+
58+
describe('Injection scope', () => {
59+
describe('default behavior', () => {
60+
@Injectable()
61+
class A {}
62+
63+
@Injectable()
64+
class B {
65+
constructor(public a: A) {}
66+
}
67+
68+
@Injectable()
69+
class C {
70+
constructor(public a: A) {}
71+
}
72+
73+
@Injectable()
74+
class D {
75+
constructor(public a: A) {}
76+
}
77+
78+
it('should return same instance whether scope is same or not', () => {
79+
const b = getInstanceWithScope(B, { scope: 'b' })
80+
const c1 = getInstanceWithScope(C, { scope: 'c1' })
81+
const c2 = getInstanceWithScope(C, { scope: 'c2' })
82+
const d = getInstanceWithScope(D, { scope: 'c2' })
83+
84+
expect(b.a).toBeInstanceOf(A)
85+
expect(b.a).toBe(c1.a)
86+
expect(c1.a).toBe(c2.a)
87+
expect(c2.a).toBe(d.a)
88+
})
89+
})
90+
91+
describe('with SameScope decorator', () => {
92+
@Injectable()
93+
class A {}
94+
95+
@Injectable()
96+
class B {
97+
constructor(@SameScope() public a: A) {}
98+
}
99+
100+
@Injectable()
101+
class C {
102+
constructor(@SameScope() public a: A) {}
103+
}
104+
105+
it('should return same instance if is same scope', () => {
106+
const scope = 'scope'
107+
const b = getInstanceWithScope(B, { scope })
108+
const c = getInstanceWithScope(C, { scope })
109+
110+
expect(b.a).toBeInstanceOf(A)
111+
expect(b.a).toBe(c.a)
112+
})
113+
114+
it('should return different instance if is different scope', () => {
115+
const b = getInstanceWithScope(B, { scope: 'b' })
116+
const c1 = getInstanceWithScope(C, { scope: 'c1' })
117+
const c2 = getInstanceWithScope(C, { scope: 'c2' })
118+
119+
expect(b.a).toBeInstanceOf(A)
120+
expect(c1.a).toBeInstanceOf(A)
121+
expect(c2.a).toBeInstanceOf(A)
122+
expect(b.a === c1.a).toBeFalsy()
123+
expect(c1.a === c2.a).toBeFalsy()
124+
})
125+
})
126+
})
54127
})
55128
})

src/core/scope/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { InjectableFactory } from '@asuka/di'
1+
import { InjectableFactory, ValueProvider } from '@asuka/di'
22
import { get } from 'lodash'
33

44
import { ConstructorOf } from '../types'
55
import { ScopeConfig } from './type'
66
import { createNewInstance, createOrGetInstanceInScope } from './utils'
7+
import { getSameScopeInjectionParams, SameScope } from './same-scope-decorator'
78

8-
export { ScopeConfig }
9+
export { ScopeConfig, SameScope }
910

1011
export const TransientScope = Symbol('scope:transient')
1112

@@ -14,12 +15,19 @@ export const SingletonScope = Symbol('scope:singleton')
1415
export function getInstanceWithScope<T>(constructor: ConstructorOf<T>, config?: ScopeConfig): T {
1516
const scope = get(config, 'scope', SingletonScope)
1617

18+
const providers = getSameScopeInjectionParams(constructor).map(
19+
(sameScopeInjectionParam): ValueProvider => ({
20+
provide: sameScopeInjectionParam,
21+
useValue: getInstanceWithScope(sameScopeInjectionParam, { scope }),
22+
}),
23+
)
24+
1725
switch (scope) {
1826
case SingletonScope:
1927
return InjectableFactory.getInstance(constructor)
2028
case TransientScope:
21-
return createNewInstance(constructor)
29+
return createNewInstance(constructor, providers)
2230
default:
23-
return createOrGetInstanceInScope(constructor, scope)
31+
return createOrGetInstanceInScope(constructor, scope, providers)
2432
}
2533
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const SameScopeMetadataKey = Symbol('SameScopeInjectionParams')
2+
3+
export function getSameScopeInjectionParams(target: any): any[] {
4+
if (Reflect.hasMetadata(SameScopeMetadataKey, target)) {
5+
return Reflect.getMetadata(SameScopeMetadataKey, target)
6+
} else {
7+
const sameScopeInjectionParams: any[] = []
8+
Reflect.defineMetadata(SameScopeMetadataKey, sameScopeInjectionParams, target)
9+
return sameScopeInjectionParams
10+
}
11+
}
12+
13+
function addSameScopeInjectionParam(target: any, param: object) {
14+
const sameScopeInjectionParams = getSameScopeInjectionParams(target)
15+
sameScopeInjectionParams.push(param)
16+
}
17+
18+
export const SameScope = () => (target: any, _propertyKey: string, parameterIndex: number) => {
19+
const param = Reflect.getMetadata('design:paramtypes', target)[parameterIndex]
20+
addSameScopeInjectionParam(target, param)
21+
}

src/core/scope/utils.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InjectableFactory } from '@asuka/di'
1+
import { InjectableFactory, Provider } from '@asuka/di'
22

33
import { ConstructorOf } from '../types'
44
import { Scope } from './type'
@@ -11,18 +11,28 @@ type Key = ConstructorOf<Instance>
1111

1212
const map: Map<Key, ScopeMap<Scope, Instance>> = new Map()
1313

14-
export function createNewInstance<T>(constructor: ConstructorOf<T>): T {
15-
return InjectableFactory.injector.resolveAndCreateChild([constructor]).get(constructor)
14+
export function createNewInstance<T>(constructor: ConstructorOf<T>, providers: Provider[] = []): T {
15+
return InjectableFactory.injector
16+
.resolveAndCreateChild([...providers, constructor])
17+
.get(constructor)
1618
}
1719

18-
export function createOrGetInstanceInScope<T>(constructor: ConstructorOf<T>, scope: Scope): T {
20+
export function createOrGetInstanceInScope<T>(
21+
constructor: ConstructorOf<T>,
22+
scope: Scope,
23+
providers: Provider[] = [],
24+
): T {
1925
const instanceAtScope = getInstanceFrom(constructor, scope)
2026

21-
return instanceAtScope ? instanceAtScope : createInstanceInScope(constructor, scope)
27+
return instanceAtScope ? instanceAtScope : createInstanceInScope(constructor, scope, providers)
2228
}
2329

24-
function createInstanceInScope<T>(constructor: ConstructorOf<T>, scope: Scope): T {
25-
const newInstance = createNewInstance(constructor)
30+
function createInstanceInScope<T>(
31+
constructor: ConstructorOf<T>,
32+
scope: Scope,
33+
providers: Provider[],
34+
): T {
35+
const newInstance = createNewInstance(constructor, providers)
2636

2737
setInstanceInScope(constructor, scope, newInstance)
2838

0 commit comments

Comments
 (0)