@tsed/di
Advanced tools
Comparing version 5.15.0 to 5.16.0
@@ -1,13 +0,53 @@ | ||
import { RegistryKey, Store, Type } from "@tsed/core"; | ||
import { Store, Type } from "@tsed/core"; | ||
import { ProviderScope } from "../interfaces"; | ||
import { IProvider } from "../interfaces/IProvider"; | ||
import { ProviderType } from "../interfaces/ProviderType"; | ||
import { TokenProvider } from "../interfaces/TokenProvider"; | ||
export declare class Provider<T> implements IProvider<T> { | ||
/** | ||
* | ||
*/ | ||
type: ProviderType | any; | ||
/** | ||
* | ||
*/ | ||
injectable: boolean; | ||
/** | ||
* | ||
*/ | ||
instance: T; | ||
/** | ||
* | ||
*/ | ||
deps: any[]; | ||
/** | ||
* | ||
*/ | ||
useFactory: Function; | ||
/** | ||
* | ||
*/ | ||
useValue: any; | ||
/** | ||
* | ||
*/ | ||
protected _provide: TokenProvider; | ||
/** | ||
* | ||
*/ | ||
protected _useClass: Type<T>; | ||
/** | ||
* | ||
*/ | ||
protected _instance: T; | ||
protected _type: ProviderType | any; | ||
protected _provide: RegistryKey; | ||
/** | ||
* | ||
*/ | ||
protected _scope: ProviderScope; | ||
/** | ||
* | ||
*/ | ||
private _store; | ||
[key: string]: any; | ||
constructor(provide: RegistryKey); | ||
constructor(token: TokenProvider); | ||
/** | ||
@@ -27,3 +67,3 @@ * | ||
/** | ||
* | ||
* Create a new store if the given value is a class. Otherwise the value is ignored. | ||
* @param value | ||
@@ -34,25 +74,11 @@ */ | ||
* | ||
* @returns {T} | ||
* @returns {string} | ||
*/ | ||
readonly className: string; | ||
/** | ||
* | ||
* @param value | ||
*/ | ||
instance: T; | ||
/** | ||
* | ||
* @returns {any} | ||
*/ | ||
readonly name: string; | ||
/** | ||
* | ||
* @param value | ||
*/ | ||
type: any; | ||
/** | ||
* | ||
* @returns {string} | ||
*/ | ||
readonly className: string; | ||
/** | ||
* | ||
* @returns {Store} | ||
@@ -70,3 +96,7 @@ */ | ||
scope: ProviderScope; | ||
/** | ||
* | ||
*/ | ||
clone(): Provider<any>; | ||
toString(): string; | ||
} |
@@ -5,9 +5,16 @@ "use strict"; | ||
const core_1 = require("@tsed/core"); | ||
const interfaces_1 = require("../interfaces"); | ||
const ProviderType_1 = require("../interfaces/ProviderType"); | ||
class Provider { | ||
constructor(provide) { | ||
this._type = ProviderType_1.ProviderType.PROVIDER; | ||
this._provide = core_1.getClassOrSymbol(provide); | ||
this._useClass = core_1.getClass(this._provide); | ||
this._store = core_1.Store.from(this._provide); | ||
constructor(token) { | ||
/** | ||
* | ||
*/ | ||
this.type = ProviderType_1.ProviderType.PROVIDER; | ||
/** | ||
* | ||
*/ | ||
this.injectable = true; | ||
this.provide = token; | ||
this.useClass = token; | ||
} | ||
@@ -26,3 +33,3 @@ /** | ||
set provide(value) { | ||
this._provide = value; | ||
this._provide = core_1.isClass(value) ? core_1.getClass(value) : value; | ||
} | ||
@@ -37,42 +44,22 @@ /** | ||
/** | ||
* | ||
* Create a new store if the given value is a class. Otherwise the value is ignored. | ||
* @param value | ||
*/ | ||
set useClass(value) { | ||
this._store = core_1.Store.from(value); | ||
this._useClass = value; | ||
if (core_1.isClass(value)) { | ||
this._useClass = core_1.getClass(value); | ||
this._store = core_1.Store.from(value); | ||
} | ||
} | ||
/** | ||
* | ||
* @returns {T} | ||
* @returns {string} | ||
*/ | ||
get instance() { | ||
return this._instance; | ||
get className() { | ||
return this.name; | ||
} | ||
/** | ||
* | ||
* @param value | ||
*/ | ||
set instance(value) { | ||
this._instance = value; | ||
} | ||
/** | ||
* | ||
* @returns {any} | ||
*/ | ||
get type() { | ||
return this._type; | ||
} | ||
/** | ||
* | ||
* @param value | ||
*/ | ||
set type(value) { | ||
this._type = value; | ||
} | ||
/** | ||
* | ||
* @returns {string} | ||
*/ | ||
get className() { | ||
get name() { | ||
return core_1.nameOf(this.provide); | ||
@@ -92,3 +79,3 @@ } | ||
get scope() { | ||
return this.store.get("scope"); | ||
return this._store ? this.store.get("scope") : this._scope; | ||
} | ||
@@ -100,14 +87,48 @@ /** | ||
set scope(scope) { | ||
this.store.set("scope", scope); | ||
this._store ? this.store.set("scope", scope) : this._scope; | ||
} | ||
/** | ||
* | ||
*/ | ||
clone() { | ||
const provider = new Provider(this._provide); | ||
provider._type = this._type; | ||
provider.useClass = this._useClass; | ||
provider._instance = this._instance; | ||
const provider = new (core_1.getClass(this))(this.provide); | ||
core_1.getKeys(this).forEach(key => { | ||
provider[key] = this[key]; | ||
}); | ||
return provider; | ||
} | ||
toString() { | ||
return `Token:${this.name}`; | ||
} | ||
} | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Object) | ||
], Provider.prototype, "type", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Boolean) | ||
], Provider.prototype, "injectable", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Object) | ||
], Provider.prototype, "instance", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Array) | ||
], Provider.prototype, "deps", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Function) | ||
], Provider.prototype, "useFactory", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", Object) | ||
], Provider.prototype, "useValue", void 0); | ||
tslib_1.__decorate([ | ||
core_1.NotEnumerable(), | ||
tslib_1.__metadata("design:type", Object) | ||
], Provider.prototype, "_provide", void 0); | ||
tslib_1.__decorate([ | ||
core_1.NotEnumerable(), | ||
tslib_1.__metadata("design:type", core_1.Type) | ||
@@ -121,4 +142,4 @@ ], Provider.prototype, "_useClass", void 0); | ||
core_1.NotEnumerable(), | ||
tslib_1.__metadata("design:type", Object) | ||
], Provider.prototype, "_type", void 0); | ||
tslib_1.__metadata("design:type", String) | ||
], Provider.prototype, "_scope", void 0); | ||
tslib_1.__decorate([ | ||
@@ -128,4 +149,14 @@ core_1.NotEnumerable(), | ||
], Provider.prototype, "_store", void 0); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", core_1.Type), | ||
tslib_1.__metadata("design:paramtypes", [core_1.Type]) | ||
], Provider.prototype, "useClass", null); | ||
tslib_1.__decorate([ | ||
core_1.Enumerable(), | ||
tslib_1.__metadata("design:type", String), | ||
tslib_1.__metadata("design:paramtypes", [String]) | ||
], Provider.prototype, "scope", null); | ||
exports.Provider = Provider; | ||
//# sourceMappingURL=Provider.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const core_1 = require("@tsed/core"); | ||
const InjectablePropertyType_1 = require("../interfaces/InjectablePropertyType"); | ||
/** | ||
@@ -43,3 +44,3 @@ * Return value from ServerSettingsService. | ||
[propertyKey]: { | ||
bindingType: "constant", | ||
bindingType: InjectablePropertyType_1.InjectablePropertyType.CONSTANT, | ||
propertyKey, | ||
@@ -46,0 +47,0 @@ expression, |
import { IProvider } from "../interfaces"; | ||
/** | ||
* The decorators `@Injectable()` declare a new service can be injected in other service or controller on there `constructor`. | ||
* All services annotated with `@Injectable()` are constructed one time. | ||
* The decorators `@Injectable()` declare a new service can be injected in other service, controller, interceptor, etc.. on there `constructor`. | ||
* All classes annotated with `@Injectable()` are built one time, excepted if you change the default provider configuration. | ||
* | ||
* > `@Service()` use the `reflect-metadata` to collect and inject service on controllers or other services. | ||
* <<< @/docs/docs/snippets/providers/getting-started-injectable.ts | ||
* | ||
* ::: tip | ||
* `@Injectable()` use the `reflect-metadata` to collect and inject the built provided to other services. | ||
* ::: | ||
* | ||
* ### Options | ||
* | ||
* - type (@@ProviderType@@ or `string`): Kind of provider. (Default: `ProviderType.PROVIDER`) | ||
* - scope (@@ProviderScope@): Kind of provider. (Default: `ProviderScope.SINGLETON`) | ||
* - deps (`Type<any>`): List of class or provider which will be injected to the constructor (Note: This options override default metadata generated by Typescript). | ||
* | ||
* @returns {Function} | ||
* @decorator | ||
*/ | ||
export declare function Injectable(provider?: Partial<IProvider<any>>): Function; | ||
export declare function Injectable(options?: Partial<IProvider<any>>): Function; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const interfaces_1 = require("../interfaces"); | ||
const ProviderRegistry_1 = require("../registries/ProviderRegistry"); | ||
/** | ||
* The decorators `@Injectable()` declare a new service can be injected in other service or controller on there `constructor`. | ||
* All services annotated with `@Injectable()` are constructed one time. | ||
* The decorators `@Injectable()` declare a new service can be injected in other service, controller, interceptor, etc.. on there `constructor`. | ||
* All classes annotated with `@Injectable()` are built one time, excepted if you change the default provider configuration. | ||
* | ||
* > `@Service()` use the `reflect-metadata` to collect and inject service on controllers or other services. | ||
* <<< @/docs/docs/snippets/providers/getting-started-injectable.ts | ||
* | ||
* ::: tip | ||
* `@Injectable()` use the `reflect-metadata` to collect and inject the built provided to other services. | ||
* ::: | ||
* | ||
* ### Options | ||
* | ||
* - type (@@ProviderType@@ or `string`): Kind of provider. (Default: `ProviderType.PROVIDER`) | ||
* - scope (@@ProviderScope@): Kind of provider. (Default: `ProviderScope.SINGLETON`) | ||
* - deps (`Type<any>`): List of class or provider which will be injected to the constructor (Note: This options override default metadata generated by Typescript). | ||
* | ||
* @returns {Function} | ||
* @decorator | ||
*/ | ||
function Injectable(provider = {}) { | ||
function Injectable(options = { scope: interfaces_1.ProviderScope.SINGLETON }) { | ||
return (provide) => { | ||
ProviderRegistry_1.registerProvider(Object.assign({}, provider, { provide })); | ||
ProviderRegistry_1.registerProvider(Object.assign({}, options, { provide })); | ||
}; | ||
@@ -17,0 +28,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ProviderRegistry_1 = require("../registries/ProviderRegistry"); | ||
const GlobalProviders_1 = require("../registries/GlobalProviders"); | ||
/** | ||
@@ -12,3 +12,3 @@ * Override a provider which is already registered in ProviderRegistry. | ||
return (target) => { | ||
ProviderRegistry_1.ProviderRegistry.get(originalProvider).useClass = target; | ||
GlobalProviders_1.GlobalProviders.get(originalProvider).useClass = target; | ||
}; | ||
@@ -15,0 +15,0 @@ } |
@@ -12,5 +12,3 @@ "use strict"; | ||
function Scope(scope = ProviderScope_1.ProviderScope.REQUEST) { | ||
return core_1.Store.decorate(store => { | ||
store.set("scope", scope); | ||
}); | ||
return core_1.StoreSet("scope", scope); | ||
} | ||
@@ -17,0 +15,0 @@ exports.Scope = Scope; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const core_1 = require("@tsed/core"); | ||
const InjectablePropertyType_1 = require("../interfaces/InjectablePropertyType"); | ||
/** | ||
@@ -43,3 +44,3 @@ * Return value from ServerSettingsService. | ||
[propertyKey]: { | ||
bindingType: "value", | ||
bindingType: InjectablePropertyType_1.InjectablePropertyType.VALUE, | ||
propertyKey, | ||
@@ -46,0 +47,0 @@ expression, |
@@ -1,8 +0,7 @@ | ||
import { Type } from "@tsed/core"; | ||
/** | ||
* @private | ||
*/ | ||
import { TokenProvider } from "../interfaces"; | ||
export declare class InjectionError extends Error { | ||
name: string; | ||
constructor(target: Type<any>, serviceName: string, message?: string); | ||
tokens: TokenProvider[]; | ||
origin: any; | ||
constructor(token: TokenProvider, origin?: any); | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const core_1 = require("@tsed/core"); | ||
/** | ||
* @private | ||
*/ | ||
class InjectionError extends Error { | ||
constructor(target, serviceName, message = "not found") { | ||
super(`Service ${core_1.nameOf(target)} > ${serviceName} ${message}.`); | ||
constructor(token, origin) { | ||
super(core_1.isString(origin) ? origin : ""); | ||
this.name = "INJECTION_ERROR"; | ||
this.tokens = []; | ||
this.tokens = [token]; | ||
if (origin) { | ||
if (core_1.isString(origin)) { | ||
this.origin = { | ||
message: origin, | ||
stack: this.stack | ||
}; | ||
} | ||
else { | ||
if (origin.tokens) { | ||
this.tokens = this.tokens.concat(origin.tokens); | ||
this.origin = origin.origin; | ||
} | ||
} | ||
} | ||
this.message = "Injection failed on " + this.tokens.map(token => core_1.nameOf(token)).join(" > ") + "\nOrigin: " + this.origin.message; | ||
} | ||
@@ -12,0 +26,0 @@ } |
export * from "./class/Provider"; | ||
export * from "./class/ProviderStorable"; | ||
export * from "./class/Container"; | ||
export * from "./class/LocalsContainer"; | ||
export * from "./interfaces"; | ||
@@ -4,0 +6,0 @@ export * from "./decorators/scope"; |
@@ -6,2 +6,4 @@ "use strict"; | ||
tslib_1.__exportStar(require("./class/ProviderStorable"), exports); | ||
tslib_1.__exportStar(require("./class/Container"), exports); | ||
tslib_1.__exportStar(require("./class/LocalsContainer"), exports); | ||
tslib_1.__exportStar(require("./interfaces"), exports); | ||
@@ -8,0 +10,0 @@ tslib_1.__exportStar(require("./decorators/scope"), exports); |
@@ -1,12 +0,15 @@ | ||
export * from "./IInjectableMethod"; | ||
export * from "./IDILogger"; | ||
export * from "./IDISettings"; | ||
export * from "./IInjectableProperties"; | ||
export * from "./IInterceptor"; | ||
export * from "./IInterceptorContext"; | ||
export * from "./IInvokeOptions"; | ||
export * from "./IProvider"; | ||
export * from "./IDILogger"; | ||
export * from "./IDISettings"; | ||
export * from "./InjectablePropertyType"; | ||
export * from "./OnInit"; | ||
export * from "./OnDestroy"; | ||
export * from "./InjectablePropertyType"; | ||
export * from "./OnInit"; | ||
export * from "./ProviderScope"; | ||
export * from "./ProviderScope"; | ||
export * from "./ProviderType"; | ||
@@ -13,0 +16,0 @@ export * from "./RegistrySettings"; |
@@ -5,5 +5,7 @@ "use strict"; | ||
tslib_1.__exportStar(require("./InjectablePropertyType"), exports); | ||
tslib_1.__exportStar(require("./InjectablePropertyType"), exports); | ||
tslib_1.__exportStar(require("./ProviderScope"), exports); | ||
tslib_1.__exportStar(require("./ProviderScope"), exports); | ||
tslib_1.__exportStar(require("./ProviderType"), exports); | ||
//# sourceMappingURL=index.js.map |
import { Type } from "@tsed/core"; | ||
import { ProviderScope } from "./ProviderScope"; | ||
import { ProviderType } from "./ProviderType"; | ||
@@ -13,2 +14,14 @@ import { TokenProvider } from "./TokenProvider"; | ||
/** | ||
* Provider type | ||
*/ | ||
type?: ProviderType | string; | ||
/** | ||
* Instance build by the injector | ||
*/ | ||
instance?: T; | ||
/** | ||
* Define dependencies to build the provider | ||
*/ | ||
deps?: TokenProvider[]; | ||
/** | ||
* Class to instantiate for the `token`. | ||
@@ -18,10 +31,14 @@ */ | ||
/** | ||
* | ||
* Provide a function to build the provider | ||
*/ | ||
instance?: T; | ||
useFactory?: Function; | ||
/** | ||
* Provider type | ||
* Provide predefined value | ||
*/ | ||
type: ProviderType | any; | ||
useValue?: any; | ||
/** | ||
* Scope used by the injector to build the provider. | ||
*/ | ||
scope?: ProviderScope; | ||
/** | ||
* | ||
@@ -28,0 +45,0 @@ */ |
export declare enum ProviderScope { | ||
SINGLETON = "singleton", | ||
REQUEST = "request" | ||
REQUEST = "request", | ||
INSTANCE = "instance" | ||
} |
@@ -7,4 +7,5 @@ "use strict"; | ||
ProviderScope["REQUEST"] = "request"; | ||
ProviderScope["INSTANCE"] = "instance"; | ||
})(ProviderScope = exports.ProviderScope || (exports.ProviderScope = {})); | ||
//# sourceMappingURL=ProviderScope.js.map |
export declare enum ProviderType { | ||
VALUE = "value", | ||
FACTORY = "factory", | ||
@@ -6,6 +7,6 @@ SERVICE = "service", | ||
CONTROLLER = "controller", | ||
INTERCEPTOR = "interceptor", | ||
CONVERTER = "converter", | ||
INTERCEPTOR = "interceptor", | ||
FILTER = "filter", | ||
MIDDLEWARE = "middleware" | ||
} |
@@ -5,2 +5,3 @@ "use strict"; | ||
(function (ProviderType) { | ||
ProviderType["VALUE"] = "value"; | ||
ProviderType["FACTORY"] = "factory"; | ||
@@ -10,4 +11,5 @@ ProviderType["SERVICE"] = "service"; | ||
ProviderType["CONTROLLER"] = "controller"; | ||
ProviderType["INTERCEPTOR"] = "interceptor"; | ||
// NOT STANDARD | ||
ProviderType["CONVERTER"] = "converter"; | ||
ProviderType["INTERCEPTOR"] = "interceptor"; | ||
ProviderType["FILTER"] = "filter"; | ||
@@ -14,0 +16,0 @@ ProviderType["MIDDLEWARE"] = "middleware"; |
@@ -7,10 +7,9 @@ import { Registry } from "@tsed/core"; | ||
injectable?: boolean; | ||
buildable: boolean; | ||
/** | ||
* | ||
* @param target | ||
* @param provider | ||
* @param {Map<string | Function, any>} locals | ||
* @param {any[]} designParamTypes | ||
* @param deps | ||
*/ | ||
onInvoke?(target: Provider<any>, locals: Map<string | Function, any>, designParamTypes: any[]): void; | ||
onInvoke?(provider: Provider<any>, locals: Map<string | Function, any>, deps: any[]): void; | ||
} |
@@ -18,3 +18,3 @@ import { Registry, Type } from "@tsed/core"; | ||
*/ | ||
createRegistry(type: string, model: Type<Provider<any>>, options: Partial<RegistrySettings>): TypedProvidersRegistry; | ||
createRegistry(type: string, model: Type<Provider<any>>, options?: Partial<RegistrySettings>): TypedProvidersRegistry; | ||
/** | ||
@@ -21,0 +21,0 @@ * |
@@ -21,3 +21,3 @@ "use strict"; | ||
*/ | ||
createRegistry(type, model, options) { | ||
createRegistry(type, model, options = { injectable: true }) { | ||
const registry = new core_1.Registry(model, { | ||
@@ -28,4 +28,3 @@ onCreate: this.set.bind(this) | ||
registry, | ||
injectable: true, | ||
buildable: true | ||
injectable: true | ||
}, options)); | ||
@@ -55,4 +54,3 @@ return registry; | ||
registry: this, | ||
injectable: true, | ||
buildable: true | ||
injectable: true | ||
}; | ||
@@ -59,0 +57,0 @@ } |
@@ -1,8 +0,3 @@ | ||
import { IProvider, TypedProvidersRegistry } from "../interfaces"; | ||
import { IProvider } from "../interfaces"; | ||
/** | ||
* | ||
* @type {GlobalProviderRegistry} | ||
*/ | ||
export declare const ProviderRegistry: TypedProvidersRegistry; | ||
/** | ||
* Register a provider configuration. | ||
@@ -77,2 +72,24 @@ * @param {IProvider<any>} provider | ||
/** | ||
* Add a new value in the `ProviderRegistry`. | ||
* | ||
* #### Example with symbol definition | ||
* | ||
* | ||
* ```typescript | ||
* import {registerValue, InjectorService} from "@tsed/common"; | ||
* | ||
* const MyValue = Symbol.from("MyValue") | ||
* | ||
* registerValue(MyValue, "myValue"); | ||
* | ||
* @Service() | ||
* export class OtherService { | ||
* constructor(@Inject(MyValue) myValue: string){ | ||
* console.log(myValue); /// "myValue" | ||
* } | ||
* } | ||
* ``` | ||
*/ | ||
export declare const registerValue: (provider: any, value?: any) => void; | ||
/** | ||
* Add a new service in the `ProviderRegistry`. This service will be built when `InjectorService` will be loaded. | ||
@@ -79,0 +96,0 @@ * |
@@ -11,3 +11,3 @@ "use strict"; | ||
// tslint:disable-next-line: variable-name | ||
exports.ProviderRegistry = GlobalProviders_1.GlobalProviders.getRegistry(interfaces_1.ProviderType.PROVIDER); | ||
GlobalProviders_1.GlobalProviders.getRegistry(interfaces_1.ProviderType.PROVIDER); | ||
/** | ||
@@ -17,6 +17,3 @@ * | ||
*/ | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.SERVICE, Provider_1.Provider, { | ||
injectable: true, | ||
buildable: true | ||
}); | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.SERVICE, Provider_1.Provider); | ||
/** | ||
@@ -26,6 +23,3 @@ *` | ||
*/ | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.FACTORY, Provider_1.Provider, { | ||
injectable: true, | ||
buildable: false | ||
}); | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.FACTORY, Provider_1.Provider); | ||
/** | ||
@@ -35,6 +29,3 @@ * | ||
*/ | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.INTERCEPTOR, Provider_1.Provider, { | ||
injectable: true, | ||
buildable: true | ||
}); | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.INTERCEPTOR, Provider_1.Provider); | ||
/** | ||
@@ -44,4 +35,3 @@ * | ||
GlobalProviders_1.GlobalProviders.createRegistry(interfaces_1.ProviderType.CONTROLLER, Provider_1.Provider, { | ||
injectable: false, | ||
buildable: true | ||
injectable: false | ||
}); | ||
@@ -122,4 +112,50 @@ /** | ||
*/ | ||
exports.registerFactory = GlobalProviders_1.GlobalProviders.createRegisterFn(interfaces_1.ProviderType.FACTORY); | ||
exports.registerFactory = (provider, instance) => { | ||
if (!provider.provide) { | ||
provider = { | ||
provide: provider | ||
}; | ||
} | ||
provider = Object.assign({ | ||
scope: interfaces_1.ProviderScope.SINGLETON, | ||
useFactory() { | ||
return instance; | ||
} | ||
}, provider, { type: interfaces_1.ProviderType.FACTORY }); | ||
GlobalProviders_1.GlobalProviders.getRegistry(interfaces_1.ProviderType.FACTORY).merge(provider.provide, provider); | ||
}; | ||
/** | ||
* Add a new value in the `ProviderRegistry`. | ||
* | ||
* #### Example with symbol definition | ||
* | ||
* | ||
* ```typescript | ||
* import {registerValue, InjectorService} from "@tsed/common"; | ||
* | ||
* const MyValue = Symbol.from("MyValue") | ||
* | ||
* registerValue(MyValue, "myValue"); | ||
* | ||
* @Service() | ||
* export class OtherService { | ||
* constructor(@Inject(MyValue) myValue: string){ | ||
* console.log(myValue); /// "myValue" | ||
* } | ||
* } | ||
* ``` | ||
*/ | ||
exports.registerValue = (provider, value) => { | ||
if (!provider.provide) { | ||
provider = { | ||
provide: provider | ||
}; | ||
} | ||
provider = Object.assign({ | ||
scope: interfaces_1.ProviderScope.SINGLETON, | ||
useValue: value | ||
}, provider, { type: interfaces_1.ProviderType.VALUE }); | ||
GlobalProviders_1.GlobalProviders.getRegistry(interfaces_1.ProviderType.VALUE).merge(provider.provide, provider); | ||
}; | ||
/** | ||
* Add a new service in the `ProviderRegistry`. This service will be built when `InjectorService` will be loaded. | ||
@@ -126,0 +162,0 @@ * |
@@ -0,3 +1,5 @@ | ||
import { Container } from "../class/Container"; | ||
import { LocalsContainer } from "../class/LocalsContainer"; | ||
import { Provider } from "../class/Provider"; | ||
import { IDILogger, IDISettings, IInjectableMethod, IInjectablePropertyService, IInjectablePropertyValue, ProviderScope, ProviderType, TokenProvider } from "../interfaces"; | ||
import { IDILogger, IDISettings, IInjectablePropertyService, IInjectablePropertyValue, IInvokeOptions, ProviderScope, TokenProvider } from "../interfaces"; | ||
/** | ||
@@ -26,3 +28,3 @@ * This service contain all services collected by `@Service` or services declared manually with `InjectorService.factory()` or `InjectorService.service()`. | ||
*/ | ||
export declare class InjectorService extends Map<TokenProvider, Provider<any>> { | ||
export declare class InjectorService extends Container { | ||
settings: IDISettings; | ||
@@ -34,9 +36,7 @@ logger: IDILogger; | ||
constructor(); | ||
scopeOf(providerType: ProviderType): ProviderScope; | ||
/** | ||
* The getProvider() method returns a specified element from a Map object. | ||
* @returns {T} Returns the element associated with the specified key or undefined if the key can't be found in the Map object. | ||
* @param token | ||
* Retrieve default scope for a given provider. | ||
* @param provider | ||
*/ | ||
getProvider(token: TokenProvider): Provider<any> | undefined; | ||
scopeOf(provider: Provider<any>): ProviderScope; | ||
/** | ||
@@ -49,8 +49,2 @@ * Clone a provider from GlobalProviders and the given token. forkProvider method build automatically the provider if the instance parameter ins't given. | ||
/** | ||
* | ||
* @param {ProviderType} type | ||
* @returns {[RegistryKey , Provider<any>][]} | ||
*/ | ||
getProviders(type?: ProviderType | string): Provider<any>[]; | ||
/** | ||
* Return a list of instance build by the injector. | ||
@@ -81,3 +75,2 @@ */ | ||
* The has() method returns a boolean indicating whether an element with the specified key exists or not. | ||
* @param key | ||
* @returns {boolean} | ||
@@ -87,3 +80,2 @@ * @param token | ||
has(token: TokenProvider): boolean; | ||
destroy(): Promise<void>; | ||
/** | ||
@@ -107,7 +99,6 @@ * Invoke the class and inject all services that required by the class constructor. | ||
* @param locals Optional object. If preset then any argument Class are read from this object first, before the `InjectorService` is consulted. | ||
* @param designParamTypes Optional object. List of injectable types. | ||
* @param requiredScope | ||
* @param options | ||
* @returns {T} The class constructed. | ||
*/ | ||
invoke<T>(token: TokenProvider, locals?: Map<string | Function, any>, designParamTypes?: any[], requiredScope?: boolean): T; | ||
invoke<T>(token: TokenProvider, locals?: Map<TokenProvider, any>, options?: Partial<IInvokeOptions<T>>): T; | ||
/** | ||
@@ -118,3 +109,3 @@ * Build all providers from GlobalProviders or from given providers parameters and emit `$onInit` event. | ||
*/ | ||
load(): Promise<any>; | ||
load(container?: Map<TokenProvider, Provider<any>>): Promise<LocalsContainer<any>>; | ||
/** | ||
@@ -176,41 +167,14 @@ * | ||
* | ||
* ```typescript | ||
* import {InjectorService} from "@tsed/common"; | ||
* | ||
* class MyService { | ||
* constructor(injectorService: InjectorService) { | ||
* injectorService.invokeMethod(this.method, { | ||
* this, | ||
* methodName: 'method' | ||
* }); | ||
* } | ||
* | ||
* method(otherService: OtherService) {} | ||
* } | ||
* ``` | ||
* | ||
* @returns {any} | ||
* @param handler The injectable method to invoke. Method parameters are injected according method signature. | ||
* @param options Object to configure the invocation. | ||
* @deprecated | ||
* @param target | ||
* @param locals | ||
* @param options | ||
* @private | ||
*/ | ||
invokeMethod(handler: any, options: IInjectableMethod<any>): any; | ||
private _invoke; | ||
/** | ||
* Emit an event to all service. See service [lifecycle hooks](/docs/services.md#lifecycle-hooks). | ||
* @param eventName The event name to emit at all services. | ||
* @param args List of the parameters to give to each services. | ||
* @returns {Promise<any[]>} A list of promises. | ||
*/ | ||
emit(eventName: string, ...args: any[]): Promise<void>; | ||
/** | ||
* | ||
* @returns {any} | ||
* Create options to invoke a provider or class. | ||
* @param token | ||
* @param options | ||
*/ | ||
private mapServices; | ||
/** | ||
* | ||
* @returns {Map<Type<any>, any>} | ||
*/ | ||
private build; | ||
private mapInvokeOptions; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const tslib_1 = require("tslib"); | ||
var InjectorService_1; | ||
const core_1 = require("@tsed/core"); | ||
const Container_1 = require("../class/Container"); | ||
const LocalsContainer_1 = require("../class/LocalsContainer"); | ||
const injectable_1 = require("../decorators/injectable"); | ||
const InjectionError_1 = require("../errors/InjectionError"); | ||
const InjectionScopeError_1 = require("../errors/InjectionScopeError"); | ||
const interfaces_1 = require("../interfaces"); | ||
const GlobalProviders_1 = require("../registries/GlobalProviders"); | ||
const ProviderRegistry_1 = require("../registries/ProviderRegistry"); | ||
/** | ||
@@ -33,3 +35,3 @@ * This service contain all services collected by `@Service` or services declared manually with `InjectorService.factory()` or `InjectorService.service()`. | ||
*/ | ||
class InjectorService extends Map { | ||
let InjectorService = InjectorService_1 = class InjectorService extends Container_1.Container { | ||
constructor() { | ||
@@ -40,14 +42,11 @@ super(); | ||
this.scopes = {}; | ||
this.forkProvider(InjectorService, this); | ||
const provider = this.addProvider(InjectorService_1).getProvider(InjectorService_1); | ||
provider.instance = this; | ||
} | ||
scopeOf(providerType) { | ||
return this.scopes[providerType] || interfaces_1.ProviderScope.SINGLETON; | ||
} | ||
/** | ||
* The getProvider() method returns a specified element from a Map object. | ||
* @returns {T} Returns the element associated with the specified key or undefined if the key can't be found in the Map object. | ||
* @param token | ||
* Retrieve default scope for a given provider. | ||
* @param provider | ||
*/ | ||
getProvider(token) { | ||
return super.get(core_1.getClassOrSymbol(token)); | ||
scopeOf(provider) { | ||
return provider.scope || this.scopes[provider.type] || interfaces_1.ProviderScope.SINGLETON; | ||
} | ||
@@ -60,4 +59,6 @@ /** | ||
forkProvider(token, instance) { | ||
const provider = GlobalProviders_1.GlobalProviders.get(token).clone(); | ||
this.set(token, provider); | ||
const provider = this.addProvider(token).getProvider(token); | ||
if (!instance) { | ||
instance = /* await */ this.invoke(token); | ||
} | ||
provider.instance = instance; | ||
@@ -67,16 +68,6 @@ return provider; | ||
/** | ||
* | ||
* @param {ProviderType} type | ||
* @returns {[RegistryKey , Provider<any>][]} | ||
*/ | ||
getProviders(type) { | ||
return Array.from(this) | ||
.filter(([key, provider]) => (type ? provider.type === type : true)) | ||
.map(([key, provider]) => provider); | ||
} | ||
/** | ||
* Return a list of instance build by the injector. | ||
*/ | ||
toArray() { | ||
return Array.from(this.values()).map(provider => provider.instance); | ||
return super.toArray().map(provider => provider.instance); | ||
} | ||
@@ -107,3 +98,2 @@ /** | ||
* The has() method returns a boolean indicating whether an element with the specified key exists or not. | ||
* @param key | ||
* @returns {boolean} | ||
@@ -115,8 +105,2 @@ * @param token | ||
} | ||
destroy() { | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
yield this.emit("$onDestroy"); | ||
this.clear(); | ||
}); | ||
} | ||
/** | ||
@@ -140,25 +124,31 @@ * Invoke the class and inject all services that required by the class constructor. | ||
* @param locals Optional object. If preset then any argument Class are read from this object first, before the `InjectorService` is consulted. | ||
* @param designParamTypes Optional object. List of injectable types. | ||
* @param requiredScope | ||
* @param options | ||
* @returns {T} The class constructed. | ||
*/ | ||
invoke(token, locals = new Map(), designParamTypes, requiredScope = false) { | ||
const { onInvoke } = GlobalProviders_1.GlobalProviders.getRegistrySettings(token); | ||
invoke(token, locals = new LocalsContainer_1.LocalsContainer(), options = {}) { | ||
const provider = this.getProvider(token); | ||
const parentScope = core_1.Store.from(token).get("scope"); | ||
if (!designParamTypes) { | ||
designParamTypes = core_1.Metadata.getParamTypes(token); | ||
let instance; | ||
if (locals.has(token)) { | ||
instance = locals.get(token); | ||
} | ||
if (provider && onInvoke) { | ||
onInvoke(provider, locals, designParamTypes); | ||
else if (!provider || options.rebuild) { | ||
instance = /* await */ this._invoke(token, locals, options); | ||
} | ||
const services = designParamTypes.map(serviceType => this.mapServices({ | ||
serviceType, | ||
target: token, | ||
locals, | ||
requiredScope, | ||
parentScope | ||
})); | ||
const instance = new token(...services); | ||
this.bindInjectableProperties(instance); | ||
else { | ||
switch (this.scopeOf(provider)) { | ||
case interfaces_1.ProviderScope.SINGLETON: | ||
if (!this.has(token)) { | ||
provider.instance = /* await */ this._invoke(token, locals, options); | ||
} | ||
instance = this.get(token); | ||
break; | ||
case interfaces_1.ProviderScope.REQUEST: | ||
instance = /* await */ this._invoke(token, locals, options); | ||
locals.set(token, instance); | ||
break; | ||
case interfaces_1.ProviderScope.INSTANCE: | ||
instance = /* await */ this._invoke(provider.provide, locals, options); | ||
break; | ||
} | ||
} | ||
return instance; | ||
@@ -171,12 +161,26 @@ } | ||
*/ | ||
load() { | ||
load(container = GlobalProviders_1.GlobalProviders) { | ||
const _super = Object.create(null, { | ||
toArray: { get: () => super.toArray } | ||
}); | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
// TODO copy all provider from GlobalProvider registry. In future this action will be performed from Bootstrap class | ||
GlobalProviders_1.GlobalProviders.forEach((p, k) => { | ||
if (!this.has(k)) { | ||
this.set(k, p.clone()); | ||
const locals = new LocalsContainer_1.LocalsContainer(); | ||
// Clone all providers in the container | ||
container.forEach((provider, token) => { | ||
if (!this.hasProvider(token)) { | ||
this.setProvider(token, provider.clone()); | ||
} | ||
}); | ||
this.build(); | ||
return Promise.all([this.emit("$onInit")]); | ||
const providers = _super.toArray.call(this); | ||
for (const provider of providers) { | ||
if (!locals.has(provider.provide) && this.scopeOf(provider) === interfaces_1.ProviderScope.SINGLETON) { | ||
/* await */ | ||
this.invoke(provider.provide, locals); | ||
} | ||
if (provider.instance) { | ||
locals.set(provider.provide, provider.instance); | ||
} | ||
} | ||
yield locals.emit("$onInit"); | ||
return locals; | ||
}); | ||
@@ -316,139 +320,88 @@ } | ||
* | ||
* ```typescript | ||
* import {InjectorService} from "@tsed/common"; | ||
* | ||
* class MyService { | ||
* constructor(injectorService: InjectorService) { | ||
* injectorService.invokeMethod(this.method, { | ||
* this, | ||
* methodName: 'method' | ||
* }); | ||
* } | ||
* | ||
* method(otherService: OtherService) {} | ||
* } | ||
* ``` | ||
* | ||
* @returns {any} | ||
* @param handler The injectable method to invoke. Method parameters are injected according method signature. | ||
* @param options Object to configure the invocation. | ||
* @deprecated | ||
* @param target | ||
* @param locals | ||
* @param options | ||
* @private | ||
*/ | ||
invokeMethod(handler, options) { | ||
let { designParamTypes } = options; | ||
const { locals = new Map(), target, methodName } = options; | ||
if (handler.$injected) { | ||
return handler.call(target, locals); | ||
_invoke(target, locals, options = {}) { | ||
const { token, deps, construct, isBindable } = this.mapInvokeOptions(target, options); | ||
const provider = this.getProvider(target); | ||
if (provider) { | ||
if (!provider.injectable && options.parent) { | ||
throw new InjectionError_1.InjectionError(token, `${core_1.nameOf(token)} ${provider.type} is not injectable to another provider`); | ||
} | ||
const { onInvoke } = GlobalProviders_1.GlobalProviders.getRegistrySettings(target); | ||
if (onInvoke) { | ||
onInvoke(provider, locals, deps); | ||
} | ||
} | ||
if (!designParamTypes) { | ||
designParamTypes = core_1.Metadata.getParamTypes(core_1.prototypeOf(target), methodName); | ||
} | ||
const services = designParamTypes.map((serviceType) => this.mapServices({ | ||
serviceType, | ||
target, | ||
locals, | ||
requiredScope: false, | ||
parentScope: false | ||
})); | ||
return handler(...services); | ||
} | ||
/** | ||
* Emit an event to all service. See service [lifecycle hooks](/docs/services.md#lifecycle-hooks). | ||
* @param eventName The event name to emit at all services. | ||
* @param args List of the parameters to give to each services. | ||
* @returns {Promise<any[]>} A list of promises. | ||
*/ | ||
emit(eventName, ...args) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
this.logger.debug("\x1B[1mCall hook", eventName, "\x1B[22m"); | ||
const providers = this.getProviders(); | ||
for (const provider of providers) { | ||
const service = provider.instance; | ||
if (service && eventName in service) { | ||
const startTime = new Date().getTime(); | ||
this.logger.debug(`Call ${core_1.nameOf(provider.provide)}.${eventName}()`); | ||
yield service[eventName](...args); | ||
this.logger.debug(`Run ${core_1.nameOf(provider.provide)}.${eventName}() in ${new Date().getTime() - startTime} ms`); | ||
} | ||
let instance; | ||
try { | ||
const services = []; | ||
for (const dependency of deps) { | ||
const service = /* await */ this.invoke(dependency, locals, { parent: token }); | ||
services.push(service); | ||
} | ||
}); | ||
} | ||
/** | ||
* | ||
* @returns {any} | ||
* @param options | ||
*/ | ||
mapServices(options) { | ||
const { serviceType, target, locals, parentScope, requiredScope } = options; | ||
const serviceName = typeof serviceType === "function" ? core_1.nameOf(serviceType) : serviceType; | ||
const localService = locals.get(serviceName) || locals.get(serviceType); | ||
if (localService) { | ||
return localService; | ||
instance = construct(services); | ||
} | ||
const provider = this.getProvider(serviceType); | ||
if (!provider) { | ||
throw new InjectionError_1.InjectionError(target, serviceName.toString()); | ||
catch (error) { | ||
throw new InjectionError_1.InjectionError(token, error); | ||
} | ||
const { buildable, injectable } = GlobalProviders_1.GlobalProviders.getRegistrySettings(provider.type); | ||
const scopeReq = provider.scope === interfaces_1.ProviderScope.REQUEST; | ||
if (!injectable) { | ||
throw new InjectionError_1.InjectionError(target, serviceName.toString(), "not injectable"); | ||
if (instance === undefined) { | ||
throw new InjectionError_1.InjectionError(token, `Unable to create new instance from undefined value. Check your provider declaration for ${core_1.nameOf(token)}`); | ||
} | ||
if (!buildable || (provider.instance && !scopeReq)) { | ||
return provider.instance; | ||
if (instance && isBindable) { | ||
this.bindInjectableProperties(instance); | ||
} | ||
if (scopeReq && requiredScope && !parentScope) { | ||
throw new InjectionScopeError_1.InjectionScopeError(provider.useClass, target); | ||
} | ||
try { | ||
const instance = this.invoke(provider.useClass, locals, undefined, requiredScope); | ||
locals.set(provider.provide, instance); | ||
return instance; | ||
} | ||
catch (er) { | ||
const error = new InjectionError_1.InjectionError(target, serviceName.toString(), "injection failed"); | ||
error.origin = er; | ||
throw error; | ||
} | ||
return instance; | ||
} | ||
/** | ||
* | ||
* @returns {Map<Type<any>, any>} | ||
* Create options to invoke a provider or class. | ||
* @param token | ||
* @param options | ||
*/ | ||
build() { | ||
const locals = new Map(); | ||
this.forEach(provider => { | ||
const token = core_1.nameOf(provider.provide); | ||
const settings = GlobalProviders_1.GlobalProviders.getRegistrySettings(provider.type); | ||
const useClass = core_1.nameOf(provider.useClass); | ||
if (settings.buildable) { | ||
const defaultScope = this.scopeOf(provider.type); | ||
if (defaultScope && !provider.scope) { | ||
provider.scope = defaultScope; | ||
} | ||
if (!locals.has(provider.provide)) { | ||
provider.instance = this.invoke(provider.useClass, locals); | ||
} | ||
else if (provider.scope === interfaces_1.ProviderScope.SINGLETON) { | ||
provider.instance = locals.get(provider.provide); | ||
} | ||
this.logger.debug(core_1.nameOf(provider.provide), "built", token === useClass ? "" : `from class ${useClass}`); | ||
mapInvokeOptions(token, options) { | ||
const { useScope = false } = options; | ||
let deps = options.deps; | ||
let scope = options.scope; | ||
let construct = (deps) => new token(...deps); | ||
let isBindable = false; | ||
if (this.hasProvider(token)) { | ||
const provider = this.getProvider(token); | ||
scope = scope || this.scopeOf(provider); | ||
deps = deps || provider.deps; | ||
if (provider.useValue) { | ||
construct = () => (core_1.isFunction(provider.useValue) ? provider.useValue() : provider.useValue); | ||
} | ||
else { | ||
provider.scope = interfaces_1.ProviderScope.SINGLETON; | ||
this.logger.debug(core_1.nameOf(provider.provide), "loaded"); | ||
else if (provider.useFactory) { | ||
construct = (deps) => provider.useFactory(...deps); | ||
} | ||
if (provider.instance) { | ||
locals.set(provider.provide, provider.instance); | ||
else if (provider.useClass) { | ||
isBindable = true; | ||
deps = deps || core_1.Metadata.getParamTypes(provider.useClass); | ||
construct = (deps) => new provider.useClass(...deps); | ||
} | ||
}); | ||
return locals; | ||
} | ||
else { | ||
deps = deps || core_1.Metadata.getParamTypes(token); | ||
} | ||
return { | ||
token, | ||
scope: scope || core_1.Store.from(token).get("scope") || interfaces_1.ProviderScope.SINGLETON, | ||
deps: deps || [], | ||
useScope, | ||
isBindable, | ||
construct | ||
}; | ||
} | ||
} | ||
}; | ||
InjectorService = InjectorService_1 = tslib_1.__decorate([ | ||
injectable_1.Injectable({ | ||
scope: interfaces_1.ProviderScope.SINGLETON, | ||
global: true | ||
}), | ||
tslib_1.__metadata("design:paramtypes", []) | ||
], InjectorService); | ||
exports.InjectorService = InjectorService; | ||
/** | ||
* Create the first service InjectorService | ||
*/ | ||
ProviderRegistry_1.registerFactory(InjectorService); | ||
//# sourceMappingURL=InjectorService.js.map |
{ | ||
"name": "@tsed/di", | ||
"version": "5.15.0", | ||
"version": "5.16.0", | ||
"description": "DI module for Ts.ED Framework", | ||
@@ -11,3 +11,3 @@ "main": "lib/index.js", | ||
"peerDependencies": { | ||
"@tsed/core": "5.15.0" | ||
"@tsed/core": "5.16.0" | ||
}, | ||
@@ -28,3 +28,3 @@ "devDependencies": {}, | ||
"license": "MIT", | ||
"gitHead": "69c4068db560b6ca7e042e847617d45c1af1920a" | ||
"gitHead": "6aebb315554a5499718a10f10efe43faa689c705" | ||
} |
111
readme.md
@@ -16,2 +16,113 @@ # @tsed/di | ||
```json | ||
{ | ||
"compilerOptions": { | ||
"target": "es2016", | ||
"lib": ["es2016"], | ||
"typeRoots": [ | ||
"./node_modules/@types" | ||
], | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"experimentalDecorators":true, | ||
"emitDecoratorMetadata": true, | ||
"allowSyntheticDefaultImports": true | ||
}, | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
} | ||
``` | ||
## Introduction | ||
Basically, almost everything may be considered as a provider – service, factory, intereceptors, and so on. | ||
All of them can inject dependencies, meaning, they can create various relationships with each other. | ||
But in fact, a provider is nothing else than just a simple class annotated with an `@Injectable()` decorator. | ||
## Usage | ||
Here is a basic usage to declare an injectable service to another one: | ||
```typescript | ||
import {Injectable} from "@tsed/di"; | ||
import {Calendar} from "./models/calendar"; | ||
@Injectable() | ||
export class CalendarsService { | ||
private readonly calendars: Calendar[] = []; | ||
create(calendar: Calendar) { | ||
this.calendars.push(calendar); | ||
} | ||
findAll(): Calendar[] { | ||
return this.calendars; | ||
} | ||
} | ||
``` | ||
Here's a CalendarsService, a basic class with one property and two methods. The only new trait is that it uses the `@Injectable()` decorator. | ||
The `@Injectable()` attaches the metadata, thereby Ts.ED knows that this class is a provider. | ||
Now we have the service class already done, let's use it inside a controller: | ||
```typescript | ||
import { Controller, Post, Body, Get } from '@tsed/common'; | ||
import { CalendarsService } from './CalendarsService'; | ||
import { Calendar } from './models/Calendar'; | ||
@Controller('/calendars') | ||
export class CalendarService { | ||
constructor(private readonly calendarsService: CalendarsService) {} | ||
@Post() | ||
async create(@Body() calendar: Calendar) { | ||
this.calendarsService.create(calendar); | ||
} | ||
@Get() | ||
async findAll(): Promise<Calendar[]> { | ||
return this.calendarsService.findAll(); | ||
} | ||
} | ||
``` | ||
> Note: Controller isn't a part of `@tsed/di`. `@Controller` decorator are defined by `@tsed/common` package because it's a specific provider | ||
used by the Ts.ED framework. Ts.ED DI allow you to define your own Provider and decorator. | ||
Finally, we can load the injector and use: | ||
```typescript | ||
import {InjectorService} from "@tsed/di"; | ||
import {CalendarCtrl} from "./CalendarCtrl"; | ||
async function bootstrap() { | ||
const injector = new InjectorService() | ||
// Load all providers registered via @Injectable decorator | ||
await injector.load() | ||
const calendarController = injector.get<CalendarCtrl>() | ||
await calendarController.create(new Calendar()) | ||
// And finally destroy injector and his instances (see injector hooks) | ||
await injector.destroy() | ||
} | ||
bootstrap() | ||
``` | ||
## Custom providers | ||
To organize your code Ts.ED DI provide different kind of providers: | ||
- Provider can be declared with `@Injectable`, | ||
- Service can be declared with `@Service`, | ||
- Interceptor can be declared with `@Interceptor`, | ||
- Factory and Value can be declared with `registerFactory` and `registerValue`. | ||
See more details on our documentation https://tsed.io/providers.html | ||
## Contributors | ||
@@ -18,0 +129,0 @@ Please read [contributing guidelines here](https://tsed.io/CONTRIBUTING.html) |
@@ -1,16 +0,61 @@ | ||
import {getClass, getClassOrSymbol, nameOf, NotEnumerable, RegistryKey, Store, Type} from "@tsed/core"; | ||
import {Enumerable, getClass, getKeys, isClass, nameOf, NotEnumerable, Store, Type} from "@tsed/core"; | ||
import {ProviderScope} from "../interfaces"; | ||
import {IProvider} from "../interfaces/IProvider"; | ||
import {ProviderType} from "../interfaces/ProviderType"; | ||
import {TokenProvider} from "../interfaces/TokenProvider"; | ||
export class Provider<T> implements IProvider<T> { | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public type: ProviderType | any = ProviderType.PROVIDER; | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public injectable: boolean = true; | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public instance: T; | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public deps: any[]; | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public useFactory: Function; | ||
/** | ||
* | ||
*/ | ||
@Enumerable() | ||
public useValue: any; | ||
/** | ||
* | ||
*/ | ||
@NotEnumerable() | ||
protected _provide: TokenProvider; | ||
/** | ||
* | ||
*/ | ||
@NotEnumerable() | ||
protected _useClass: Type<T>; | ||
/** | ||
* | ||
*/ | ||
@NotEnumerable() | ||
protected _instance: T; | ||
/** | ||
* | ||
*/ | ||
@NotEnumerable() | ||
protected _type: ProviderType | any = ProviderType.PROVIDER; | ||
protected _provide: RegistryKey; | ||
protected _scope: ProviderScope; | ||
/** | ||
* | ||
*/ | ||
@NotEnumerable() | ||
@@ -21,6 +66,5 @@ private _store: Store; | ||
constructor(provide: RegistryKey) { | ||
this._provide = getClassOrSymbol(provide); | ||
this._useClass = getClass(this._provide); | ||
this._store = Store.from(this._provide); | ||
constructor(token: TokenProvider) { | ||
this.provide = token; | ||
this.useClass = token; | ||
} | ||
@@ -41,3 +85,3 @@ | ||
set provide(value: any) { | ||
this._provide = value; | ||
this._provide = isClass(value) ? getClass(value) : value; | ||
} | ||
@@ -54,8 +98,11 @@ | ||
/** | ||
* | ||
* Create a new store if the given value is a class. Otherwise the value is ignored. | ||
* @param value | ||
*/ | ||
@Enumerable() | ||
set useClass(value: Type<T>) { | ||
this._store = Store.from(value); | ||
this._useClass = value; | ||
if (isClass(value)) { | ||
this._useClass = getClass(value); | ||
this._store = Store.from(value); | ||
} | ||
} | ||
@@ -65,6 +112,6 @@ | ||
* | ||
* @returns {T} | ||
* @returns {string} | ||
*/ | ||
get instance(): T { | ||
return this._instance; | ||
get className() { | ||
return this.name; | ||
} | ||
@@ -74,29 +121,4 @@ | ||
* | ||
* @param value | ||
*/ | ||
set instance(value: T) { | ||
this._instance = value; | ||
} | ||
/** | ||
* | ||
* @returns {any} | ||
*/ | ||
get type(): any { | ||
return this._type; | ||
} | ||
/** | ||
* | ||
* @param value | ||
*/ | ||
set type(value: any) { | ||
this._type = value; | ||
} | ||
/** | ||
* | ||
* @returns {string} | ||
*/ | ||
get className() { | ||
get name() { | ||
return nameOf(this.provide); | ||
@@ -118,3 +140,3 @@ } | ||
get scope(): ProviderScope { | ||
return this.store.get("scope"); | ||
return this._store ? this.store.get("scope") : this._scope; | ||
} | ||
@@ -126,14 +148,23 @@ | ||
*/ | ||
@Enumerable() | ||
set scope(scope: ProviderScope) { | ||
this.store.set("scope", scope); | ||
this._store ? this.store.set("scope", scope) : this._scope; | ||
} | ||
/** | ||
* | ||
*/ | ||
clone(): Provider<any> { | ||
const provider = new Provider(this._provide); | ||
provider._type = this._type; | ||
provider.useClass = this._useClass; | ||
provider._instance = this._instance; | ||
const provider = new (getClass(this))(this.provide); | ||
getKeys(this).forEach(key => { | ||
provider[key] = this[key]; | ||
}); | ||
return provider; | ||
} | ||
toString() { | ||
return `Token:${this.name}`; | ||
} | ||
} |
import {Store} from "@tsed/core"; | ||
import {IInjectableProperties} from "../interfaces/IInjectableProperties"; | ||
import {InjectablePropertyType} from "../interfaces/InjectablePropertyType"; | ||
@@ -43,3 +44,3 @@ /** | ||
[propertyKey]: { | ||
bindingType: "constant", | ||
bindingType: InjectablePropertyType.CONSTANT, | ||
propertyKey, | ||
@@ -46,0 +47,0 @@ expression, |
@@ -1,18 +0,28 @@ | ||
import {IProvider} from "../interfaces"; | ||
import {Type} from "@tsed/core"; | ||
import {IProvider, ProviderScope} from "../interfaces"; | ||
import {registerProvider} from "../registries/ProviderRegistry"; | ||
import {Type} from "@tsed/core"; | ||
/** | ||
* The decorators `@Injectable()` declare a new service can be injected in other service or controller on there `constructor`. | ||
* All services annotated with `@Injectable()` are constructed one time. | ||
* The decorators `@Injectable()` declare a new service can be injected in other service, controller, interceptor, etc.. on there `constructor`. | ||
* All classes annotated with `@Injectable()` are built one time, excepted if you change the default provider configuration. | ||
* | ||
* > `@Service()` use the `reflect-metadata` to collect and inject service on controllers or other services. | ||
* <<< @/docs/docs/snippets/providers/getting-started-injectable.ts | ||
* | ||
* ::: tip | ||
* `@Injectable()` use the `reflect-metadata` to collect and inject the built provided to other services. | ||
* ::: | ||
* | ||
* ### Options | ||
* | ||
* - type (@@ProviderType@@ or `string`): Kind of provider. (Default: `ProviderType.PROVIDER`) | ||
* - scope (@@ProviderScope@): Kind of provider. (Default: `ProviderScope.SINGLETON`) | ||
* - deps (`Type<any>`): List of class or provider which will be injected to the constructor (Note: This options override default metadata generated by Typescript). | ||
* | ||
* @returns {Function} | ||
* @decorator | ||
*/ | ||
export function Injectable(provider: Partial<IProvider<any>> = {}): Function { | ||
export function Injectable(options: Partial<IProvider<any>> = {scope: ProviderScope.SINGLETON}): Function { | ||
return (provide: Type<any>) => { | ||
registerProvider({ | ||
...provider, | ||
...options, | ||
provide | ||
@@ -19,0 +29,0 @@ }); |
import {Type} from "@tsed/core"; | ||
import {ProviderRegistry} from "../registries/ProviderRegistry"; | ||
import {GlobalProviders} from "../registries/GlobalProviders"; | ||
@@ -12,4 +12,4 @@ /** | ||
return (target: Type<any>): void => { | ||
ProviderRegistry.get(originalProvider)!.useClass = target; | ||
GlobalProviders.get(originalProvider)!.useClass = target; | ||
}; | ||
} |
@@ -1,2 +0,2 @@ | ||
import {Store} from "@tsed/core"; | ||
import {StoreSet} from "@tsed/core"; | ||
import {ProviderScope} from "../interfaces/ProviderScope"; | ||
@@ -11,5 +11,3 @@ | ||
export function Scope(scope: "request" | "singleton" | ProviderScope = ProviderScope.REQUEST) { | ||
return Store.decorate(store => { | ||
store.set("scope", scope); | ||
}); | ||
return StoreSet("scope", scope); | ||
} |
import {Store} from "@tsed/core"; | ||
import {IInjectableProperties} from "../interfaces/IInjectableProperties"; | ||
import {InjectablePropertyType} from "../interfaces/InjectablePropertyType"; | ||
@@ -43,3 +44,3 @@ /** | ||
[propertyKey]: { | ||
bindingType: "value", | ||
bindingType: InjectablePropertyType.VALUE, | ||
propertyKey, | ||
@@ -46,0 +47,0 @@ expression, |
@@ -1,12 +0,31 @@ | ||
import {nameOf, Type} from "@tsed/core"; | ||
import {isString, nameOf} from "@tsed/core"; | ||
import {TokenProvider} from "../interfaces"; | ||
/** | ||
* @private | ||
*/ | ||
export class InjectionError extends Error { | ||
name = "INJECTION_ERROR"; | ||
constructor(target: Type<any>, serviceName: string, message = "not found") { | ||
super(`Service ${nameOf(target)} > ${serviceName} ${message}.`); | ||
public tokens: TokenProvider[] = []; | ||
public origin: any; | ||
constructor(token: TokenProvider, origin?: any) { | ||
super(isString(origin) ? origin : ""); | ||
this.tokens = [token]; | ||
if (origin) { | ||
if (isString(origin)) { | ||
this.origin = { | ||
message: origin, | ||
stack: this.stack | ||
}; | ||
} else { | ||
if (origin.tokens) { | ||
this.tokens = this.tokens.concat(origin.tokens); | ||
this.origin = origin.origin; | ||
} | ||
} | ||
} | ||
this.message = "Injection failed on " + this.tokens.map(token => nameOf(token)).join(" > ") + "\nOrigin: " + this.origin.message; | ||
} | ||
} |
export * from "./class/Provider"; | ||
export * from "./class/ProviderStorable"; | ||
export * from "./class/Container"; | ||
export * from "./class/LocalsContainer"; | ||
export * from "./interfaces"; | ||
@@ -4,0 +6,0 @@ export * from "./decorators/scope"; |
@@ -1,12 +0,15 @@ | ||
export * from "./IInjectableMethod"; | ||
export * from "./IDILogger"; | ||
export * from "./IDISettings"; | ||
export * from "./IInjectableProperties"; | ||
export * from "./IInterceptor"; | ||
export * from "./IInterceptorContext"; | ||
export * from "./IInvokeOptions"; | ||
export * from "./IProvider"; | ||
export * from "./IDILogger"; | ||
export * from "./IDISettings"; | ||
export * from "./InjectablePropertyType"; | ||
export * from "./OnInit"; | ||
export * from "./OnDestroy"; | ||
export * from "./InjectablePropertyType"; | ||
export * from "./OnInit"; | ||
export * from "./ProviderScope"; | ||
export * from "./ProviderScope"; | ||
export * from "./ProviderType"; | ||
@@ -13,0 +16,0 @@ export * from "./RegistrySettings"; |
import {Type} from "@tsed/core"; | ||
import {ProviderScope} from "./ProviderScope"; | ||
import {ProviderType} from "./ProviderType"; | ||
@@ -13,17 +14,30 @@ import {TokenProvider} from "./TokenProvider"; | ||
provide: TokenProvider; | ||
/** | ||
* Provider type | ||
*/ | ||
type?: ProviderType | string; | ||
/** | ||
* Instance build by the injector | ||
*/ | ||
instance?: T; | ||
/** | ||
* Define dependencies to build the provider | ||
*/ | ||
deps?: TokenProvider[]; | ||
/** | ||
* Class to instantiate for the `token`. | ||
*/ | ||
useClass?: Type<T>; | ||
/** | ||
* | ||
* Provide a function to build the provider | ||
*/ | ||
instance?: T; | ||
useFactory?: Function; | ||
/** | ||
* Provider type | ||
* Provide predefined value | ||
*/ | ||
type: ProviderType | any; | ||
useValue?: any; | ||
/** | ||
* Scope used by the injector to build the provider. | ||
*/ | ||
scope?: ProviderScope; | ||
@@ -30,0 +44,0 @@ /** |
export enum ProviderScope { | ||
SINGLETON = "singleton", | ||
REQUEST = "request" | ||
REQUEST = "request", | ||
INSTANCE = "instance" | ||
} |
export enum ProviderType { | ||
VALUE = "value", | ||
FACTORY = "factory", | ||
@@ -6,6 +7,8 @@ SERVICE = "service", | ||
CONTROLLER = "controller", | ||
INTERCEPTOR = "interceptor", | ||
// NOT STANDARD | ||
CONVERTER = "converter", | ||
INTERCEPTOR = "interceptor", | ||
FILTER = "filter", | ||
MIDDLEWARE = "middleware" | ||
} |
@@ -8,11 +8,10 @@ import {Registry} from "@tsed/core"; | ||
injectable?: boolean; | ||
buildable: boolean; | ||
/** | ||
* | ||
* @param target | ||
* @param provider | ||
* @param {Map<string | Function, any>} locals | ||
* @param {any[]} designParamTypes | ||
* @param deps | ||
*/ | ||
onInvoke?(target: Provider<any>, locals: Map<string | Function, any>, designParamTypes: any[]): void; | ||
onInvoke?(provider: Provider<any>, locals: Map<string | Function, any>, deps: any[]): void; | ||
} |
@@ -23,3 +23,7 @@ import {Registry, Type} from "@tsed/core"; | ||
*/ | ||
createRegistry(type: string, model: Type<Provider<any>>, options: Partial<RegistrySettings>): TypedProvidersRegistry { | ||
createRegistry( | ||
type: string, | ||
model: Type<Provider<any>>, | ||
options: Partial<RegistrySettings> = {injectable: true} | ||
): TypedProvidersRegistry { | ||
const registry = new Registry<Provider<any>, IProvider<any>>(model, { | ||
@@ -34,4 +38,3 @@ onCreate: this.set.bind(this) | ||
registry, | ||
injectable: true, | ||
buildable: true | ||
injectable: true | ||
}, | ||
@@ -68,4 +71,3 @@ options | ||
registry: this, | ||
injectable: true, | ||
buildable: true | ||
injectable: true | ||
}; | ||
@@ -72,0 +74,0 @@ } |
import {Provider} from "../class/Provider"; | ||
import {IProvider, ProviderType, TypedProvidersRegistry} from "../interfaces"; | ||
import {IProvider, ProviderScope, ProviderType} from "../interfaces"; | ||
import {GlobalProviders} from "./GlobalProviders"; | ||
@@ -10,4 +10,3 @@ | ||
// tslint:disable-next-line: variable-name | ||
export const ProviderRegistry: TypedProvidersRegistry = GlobalProviders.getRegistry(ProviderType.PROVIDER); | ||
GlobalProviders.getRegistry(ProviderType.PROVIDER); | ||
/** | ||
@@ -17,6 +16,3 @@ * | ||
*/ | ||
GlobalProviders.createRegistry(ProviderType.SERVICE, Provider, { | ||
injectable: true, | ||
buildable: true | ||
}); | ||
GlobalProviders.createRegistry(ProviderType.SERVICE, Provider); | ||
/** | ||
@@ -26,6 +22,4 @@ *` | ||
*/ | ||
GlobalProviders.createRegistry(ProviderType.FACTORY, Provider, { | ||
injectable: true, | ||
buildable: false | ||
}); | ||
GlobalProviders.createRegistry(ProviderType.FACTORY, Provider); | ||
/** | ||
@@ -35,6 +29,3 @@ * | ||
*/ | ||
GlobalProviders.createRegistry(ProviderType.INTERCEPTOR, Provider, { | ||
injectable: true, | ||
buildable: true | ||
}); | ||
GlobalProviders.createRegistry(ProviderType.INTERCEPTOR, Provider); | ||
/** | ||
@@ -44,4 +35,3 @@ * | ||
GlobalProviders.createRegistry(ProviderType.CONTROLLER, Provider, { | ||
injectable: false, | ||
buildable: true | ||
injectable: false | ||
}); | ||
@@ -124,4 +114,62 @@ | ||
*/ | ||
export const registerFactory = GlobalProviders.createRegisterFn(ProviderType.FACTORY); | ||
export const registerFactory = (provider: any | IProvider<any>, instance?: any): void => { | ||
if (!provider.provide) { | ||
provider = { | ||
provide: provider | ||
}; | ||
} | ||
provider = Object.assign( | ||
{ | ||
scope: ProviderScope.SINGLETON, | ||
useFactory() { | ||
return instance; | ||
} | ||
}, | ||
provider, | ||
{type: ProviderType.FACTORY} | ||
); | ||
GlobalProviders.getRegistry(ProviderType.FACTORY).merge(provider.provide, provider); | ||
}; | ||
/** | ||
* Add a new value in the `ProviderRegistry`. | ||
* | ||
* #### Example with symbol definition | ||
* | ||
* | ||
* ```typescript | ||
* import {registerValue, InjectorService} from "@tsed/common"; | ||
* | ||
* const MyValue = Symbol.from("MyValue") | ||
* | ||
* registerValue(MyValue, "myValue"); | ||
* | ||
* @Service() | ||
* export class OtherService { | ||
* constructor(@Inject(MyValue) myValue: string){ | ||
* console.log(myValue); /// "myValue" | ||
* } | ||
* } | ||
* ``` | ||
*/ | ||
export const registerValue = (provider: any | IProvider<any>, value?: any): void => { | ||
if (!provider.provide) { | ||
provider = { | ||
provide: provider | ||
}; | ||
} | ||
provider = Object.assign( | ||
{ | ||
scope: ProviderScope.SINGLETON, | ||
useValue: value | ||
}, | ||
provider, | ||
{type: ProviderType.VALUE} | ||
); | ||
GlobalProviders.getRegistry(ProviderType.VALUE).merge(provider.provide, provider); | ||
}; | ||
/** | ||
* Add a new service in the `ProviderRegistry`. This service will be built when `InjectorService` will be loaded. | ||
@@ -128,0 +176,0 @@ * |
@@ -1,9 +0,10 @@ | ||
import {deepClone, getClass, getClassOrSymbol, Metadata, nameOf, prototypeOf, RegistryKey, Store, Type} from "@tsed/core"; | ||
import {deepClone, getClass, getClassOrSymbol, isFunction, Metadata, nameOf, prototypeOf, Store} from "@tsed/core"; | ||
import {Container} from "../class/Container"; | ||
import {LocalsContainer} from "../class/LocalsContainer"; | ||
import {Provider} from "../class/Provider"; | ||
import {Injectable} from "../decorators/injectable"; | ||
import {InjectionError} from "../errors/InjectionError"; | ||
import {InjectionScopeError} from "../errors/InjectionScopeError"; | ||
import { | ||
IDILogger, | ||
IDISettings, | ||
IInjectableMethod, | ||
IInjectableProperties, | ||
@@ -14,10 +15,20 @@ IInjectablePropertyService, | ||
IInterceptorContext, | ||
IInvokeOptions, | ||
InjectablePropertyType, | ||
ProviderScope, | ||
ProviderType, | ||
TokenProvider | ||
} from "../interfaces"; | ||
import {GlobalProviders} from "../registries/GlobalProviders"; | ||
import {registerFactory} from "../registries/ProviderRegistry"; | ||
interface IInvokeSettings { | ||
token: TokenProvider; | ||
parent?: TokenProvider; | ||
scope: ProviderScope; | ||
useScope: boolean; | ||
isBindable: boolean; | ||
deps: any[]; | ||
construct(deps: TokenProvider[]): any; | ||
} | ||
/** | ||
@@ -46,3 +57,7 @@ * This service contain all services collected by `@Service` or services declared manually with `InjectorService.factory()` or `InjectorService.service()`. | ||
*/ | ||
export class InjectorService extends Map<TokenProvider, Provider<any>> { | ||
@Injectable({ | ||
scope: ProviderScope.SINGLETON, | ||
global: true | ||
}) | ||
export class InjectorService extends Container { | ||
public settings: IDISettings = new Map(); | ||
@@ -54,16 +69,12 @@ public logger: IDILogger = console; | ||
super(); | ||
this.forkProvider(InjectorService, this); | ||
const provider = this.addProvider(InjectorService).getProvider(InjectorService)!; | ||
provider.instance = this; | ||
} | ||
scopeOf(providerType: ProviderType) { | ||
return this.scopes[providerType] || ProviderScope.SINGLETON; | ||
} | ||
/** | ||
* The getProvider() method returns a specified element from a Map object. | ||
* @returns {T} Returns the element associated with the specified key or undefined if the key can't be found in the Map object. | ||
* @param token | ||
* Retrieve default scope for a given provider. | ||
* @param provider | ||
*/ | ||
public getProvider(token: TokenProvider): Provider<any> | undefined { | ||
return super.get(getClassOrSymbol(token)); | ||
public scopeOf(provider: Provider<any>) { | ||
return provider.scope || this.scopes[provider.type] || ProviderScope.SINGLETON; | ||
} | ||
@@ -76,6 +87,9 @@ | ||
*/ | ||
public forkProvider(token: TokenProvider, instance?: any): Provider<any> { | ||
const provider = GlobalProviders.get(token)!.clone(); | ||
this.set(token, provider); | ||
public /* async */ forkProvider(token: TokenProvider, instance?: any): Provider<any> { | ||
const provider = this.addProvider(token).getProvider(token)!; | ||
if (!instance) { | ||
instance = /* await */ this.invoke(token); | ||
} | ||
provider.instance = instance; | ||
@@ -87,17 +101,6 @@ | ||
/** | ||
* | ||
* @param {ProviderType} type | ||
* @returns {[RegistryKey , Provider<any>][]} | ||
*/ | ||
getProviders(type?: ProviderType | string): Provider<any>[] { | ||
return Array.from(this) | ||
.filter(([key, provider]) => (type ? provider.type === type : true)) | ||
.map(([key, provider]) => provider); | ||
} | ||
/** | ||
* Return a list of instance build by the injector. | ||
*/ | ||
public toArray(): any[] { | ||
return Array.from(this.values()).map(provider => provider.instance); | ||
return super.toArray().map(provider => provider.instance); | ||
} | ||
@@ -130,3 +133,2 @@ | ||
* The has() method returns a boolean indicating whether an element with the specified key exists or not. | ||
* @param key | ||
* @returns {boolean} | ||
@@ -139,7 +141,2 @@ * @param token | ||
async destroy() { | ||
await this.emit("$onDestroy"); | ||
this.clear(); | ||
} | ||
/** | ||
@@ -163,38 +160,38 @@ * Invoke the class and inject all services that required by the class constructor. | ||
* @param locals Optional object. If preset then any argument Class are read from this object first, before the `InjectorService` is consulted. | ||
* @param designParamTypes Optional object. List of injectable types. | ||
* @param requiredScope | ||
* @param options | ||
* @returns {T} The class constructed. | ||
*/ | ||
invoke<T>( | ||
public /* async */ invoke<T>( | ||
token: TokenProvider, | ||
locals: Map<string | Function, any> = new Map(), | ||
designParamTypes?: any[], | ||
requiredScope: boolean = false | ||
locals: Map<TokenProvider, any> = new LocalsContainer(), | ||
options: Partial<IInvokeOptions<T>> = {} | ||
): T { | ||
const {onInvoke} = GlobalProviders.getRegistrySettings(token); | ||
const provider = this.getProvider(token); | ||
const parentScope = Store.from(token).get("scope"); | ||
let instance: any; | ||
if (!designParamTypes) { | ||
designParamTypes = Metadata.getParamTypes(token); | ||
} | ||
if (locals.has(token)) { | ||
instance = locals.get(token); | ||
} else if (!provider || options.rebuild) { | ||
instance = /* await */ this._invoke(token, locals, options); | ||
} else { | ||
switch (this.scopeOf(provider)) { | ||
case ProviderScope.SINGLETON: | ||
if (!this.has(token)) { | ||
provider.instance = /* await */ this._invoke(token, locals, options); | ||
} | ||
if (provider && onInvoke) { | ||
onInvoke(provider, locals, designParamTypes); | ||
} | ||
instance = this.get<T>(token)!; | ||
break; | ||
const services = designParamTypes.map(serviceType => | ||
this.mapServices({ | ||
serviceType, | ||
target: token, | ||
locals, | ||
requiredScope, | ||
parentScope | ||
}) | ||
); | ||
case ProviderScope.REQUEST: | ||
instance = /* await */ this._invoke(token, locals, options); | ||
locals.set(token, instance); | ||
break; | ||
const instance = new token(...services); | ||
case ProviderScope.INSTANCE: | ||
instance = /* await */ this._invoke(provider.provide, locals, options); | ||
break; | ||
} | ||
} | ||
this.bindInjectableProperties(instance); | ||
return instance; | ||
@@ -208,13 +205,28 @@ } | ||
*/ | ||
async load(): Promise<any> { | ||
// TODO copy all provider from GlobalProvider registry. In future this action will be performed from Bootstrap class | ||
GlobalProviders.forEach((p, k) => { | ||
if (!this.has(k)) { | ||
this.set(k, p.clone()); | ||
async load(container: Map<TokenProvider, Provider<any>> = GlobalProviders): Promise<LocalsContainer<any>> { | ||
const locals = new LocalsContainer(); | ||
// Clone all providers in the container | ||
container.forEach((provider, token) => { | ||
if (!this.hasProvider(token)) { | ||
this.setProvider(token, provider.clone()); | ||
} | ||
}); | ||
this.build(); | ||
const providers = super.toArray(); | ||
return Promise.all([this.emit("$onInit")]); | ||
for (const provider of providers) { | ||
if (!locals.has(provider.provide) && this.scopeOf(provider) === ProviderScope.SINGLETON) { | ||
/* await */ | ||
this.invoke(provider.provide, locals); | ||
} | ||
if (provider.instance) { | ||
locals.set(provider.provide, provider.instance); | ||
} | ||
} | ||
await locals.emit("$onInit"); | ||
return locals; | ||
} | ||
@@ -371,162 +383,94 @@ | ||
* | ||
* ```typescript | ||
* import {InjectorService} from "@tsed/common"; | ||
* | ||
* class MyService { | ||
* constructor(injectorService: InjectorService) { | ||
* injectorService.invokeMethod(this.method, { | ||
* this, | ||
* methodName: 'method' | ||
* }); | ||
* } | ||
* | ||
* method(otherService: OtherService) {} | ||
* } | ||
* ``` | ||
* | ||
* @returns {any} | ||
* @param handler The injectable method to invoke. Method parameters are injected according method signature. | ||
* @param options Object to configure the invocation. | ||
* @deprecated | ||
* @param target | ||
* @param locals | ||
* @param options | ||
* @private | ||
*/ | ||
public invokeMethod(handler: any, options: IInjectableMethod<any>): any { | ||
let {designParamTypes} = options; | ||
const {locals = new Map<any, any>(), target, methodName} = options; | ||
private /* async */ _invoke<T>( | ||
target: TokenProvider, | ||
locals: Map<TokenProvider, any>, | ||
options: Partial<IInvokeOptions<T>> = {} | ||
): Promise<T> { | ||
const {token, deps, construct, isBindable} = this.mapInvokeOptions(target, options); | ||
const provider = this.getProvider(target); | ||
if (handler.$injected) { | ||
return handler.call(target, locals); | ||
} | ||
if (provider) { | ||
if (!provider.injectable && options.parent) { | ||
throw new InjectionError(token, `${nameOf(token)} ${provider.type} is not injectable to another provider`); | ||
} | ||
if (!designParamTypes) { | ||
designParamTypes = Metadata.getParamTypes(prototypeOf(target), methodName); | ||
const {onInvoke} = GlobalProviders.getRegistrySettings(target); | ||
if (onInvoke) { | ||
onInvoke(provider, locals, deps); | ||
} | ||
} | ||
const services = designParamTypes.map((serviceType: any) => | ||
this.mapServices({ | ||
serviceType, | ||
target, | ||
locals, | ||
requiredScope: false, | ||
parentScope: false | ||
}) | ||
); | ||
let instance: any; | ||
return handler(...services); | ||
} | ||
/** | ||
* Emit an event to all service. See service [lifecycle hooks](/docs/services.md#lifecycle-hooks). | ||
* @param eventName The event name to emit at all services. | ||
* @param args List of the parameters to give to each services. | ||
* @returns {Promise<any[]>} A list of promises. | ||
*/ | ||
public async emit(eventName: string, ...args: any[]) { | ||
this.logger.debug("\x1B[1mCall hook", eventName, "\x1B[22m"); | ||
const providers = this.getProviders(); | ||
for (const provider of providers) { | ||
const service = provider.instance; | ||
if (service && eventName in service) { | ||
const startTime = new Date().getTime(); | ||
this.logger.debug(`Call ${nameOf(provider.provide)}.${eventName}()`); | ||
await service[eventName](...args); | ||
this.logger.debug(`Run ${nameOf(provider.provide)}.${eventName}() in ${new Date().getTime() - startTime} ms`); | ||
try { | ||
const services = []; | ||
for (const dependency of deps) { | ||
const service = /* await */ this.invoke(dependency, locals, {parent: token}); | ||
services.push(service); | ||
} | ||
} | ||
} | ||
/** | ||
* | ||
* @returns {any} | ||
* @param options | ||
*/ | ||
private mapServices(options: any) { | ||
const {serviceType, target, locals, parentScope, requiredScope} = options; | ||
const serviceName = typeof serviceType === "function" ? nameOf(serviceType) : serviceType; | ||
const localService = locals.get(serviceName) || locals.get(serviceType); | ||
if (localService) { | ||
return localService; | ||
instance = construct(services); | ||
} catch (error) { | ||
throw new InjectionError(token, error); | ||
} | ||
const provider = this.getProvider(serviceType); | ||
if (!provider) { | ||
throw new InjectionError(target, serviceName.toString()); | ||
if (instance === undefined) { | ||
throw new InjectionError( | ||
token, | ||
`Unable to create new instance from undefined value. Check your provider declaration for ${nameOf(token)}` | ||
); | ||
} | ||
const {buildable, injectable} = GlobalProviders.getRegistrySettings(provider.type); | ||
const scopeReq = provider.scope === ProviderScope.REQUEST; | ||
if (!injectable) { | ||
throw new InjectionError(target, serviceName.toString(), "not injectable"); | ||
if (instance && isBindable) { | ||
this.bindInjectableProperties(instance); | ||
} | ||
if (!buildable || (provider.instance && !scopeReq)) { | ||
return provider.instance; | ||
} | ||
if (scopeReq && requiredScope && !parentScope) { | ||
throw new InjectionScopeError(provider.useClass, target); | ||
} | ||
try { | ||
const instance = this.invoke<any>(provider.useClass, locals, undefined, requiredScope); | ||
locals.set(provider.provide, instance); | ||
return instance; | ||
} catch (er) { | ||
const error = new InjectionError(target, serviceName.toString(), "injection failed"); | ||
(error as any).origin = er; | ||
throw error; | ||
} | ||
return instance; | ||
} | ||
/** | ||
* | ||
* @returns {Map<Type<any>, any>} | ||
* Create options to invoke a provider or class. | ||
* @param token | ||
* @param options | ||
*/ | ||
private build(): Map<Type<any>, any> { | ||
const locals: Map<Type<any>, any> = new Map(); | ||
private mapInvokeOptions(token: TokenProvider, options: Partial<IInvokeOptions<any>>): IInvokeSettings { | ||
const {useScope = false} = options; | ||
let deps: TokenProvider[] | undefined = options.deps; | ||
let scope = options.scope; | ||
let construct = (deps: TokenProvider[]) => new token(...deps); | ||
let isBindable = false; | ||
this.forEach(provider => { | ||
const token = nameOf(provider.provide); | ||
const settings = GlobalProviders.getRegistrySettings(provider.type); | ||
const useClass = nameOf(provider.useClass); | ||
if (this.hasProvider(token)) { | ||
const provider = this.getProvider(token)!; | ||
if (settings.buildable) { | ||
const defaultScope: ProviderScope = this.scopeOf(provider.type); | ||
scope = scope || this.scopeOf(provider); | ||
deps = deps || provider.deps; | ||
if (defaultScope && !provider.scope) { | ||
provider.scope = defaultScope; | ||
} | ||
if (!locals.has(provider.provide)) { | ||
provider.instance = this.invoke(provider.useClass, locals); | ||
} else if (provider.scope === ProviderScope.SINGLETON) { | ||
provider.instance = locals.get(provider.provide); | ||
} | ||
this.logger.debug(nameOf(provider.provide), "built", token === useClass ? "" : `from class ${useClass}`); | ||
} else { | ||
provider.scope = ProviderScope.SINGLETON; | ||
this.logger.debug(nameOf(provider.provide), "loaded"); | ||
if (provider.useValue) { | ||
construct = () => (isFunction(provider.useValue) ? provider.useValue() : provider.useValue); | ||
} else if (provider.useFactory) { | ||
construct = (deps: TokenProvider[]) => provider.useFactory(...deps); | ||
} else if (provider.useClass) { | ||
isBindable = true; | ||
deps = deps || Metadata.getParamTypes(provider.useClass); | ||
construct = (deps: TokenProvider[]) => new provider.useClass(...deps); | ||
} | ||
} else { | ||
deps = deps || Metadata.getParamTypes(token); | ||
} | ||
if (provider.instance) { | ||
locals.set(provider.provide, provider.instance); | ||
} | ||
}); | ||
return locals; | ||
return { | ||
token, | ||
scope: scope || Store.from(token).get("scope") || ProviderScope.SINGLETON, | ||
deps: deps! || [], | ||
useScope, | ||
isBindable, | ||
construct | ||
}; | ||
} | ||
} | ||
/** | ||
* Create the first service InjectorService | ||
*/ | ||
registerFactory(InjectorService); |
import {expect} from "chai"; | ||
import {getKeys} from "../../../core/src/utils"; | ||
import {Provider} from "../../src/class/Provider"; | ||
import {ProviderScope} from "../../src/interfaces"; | ||
class T1 {} | ||
class T1 { | ||
} | ||
class T2 {} | ||
const S1 = Symbol.for("S1"); | ||
const S2 = Symbol.for("S2"); | ||
describe("Provider", () => { | ||
describe("className", () => { | ||
it("should return the class name", () => { | ||
expect(new Provider(T1).className).to.eq("T1"); | ||
}); | ||
describe("when is a class", () => { | ||
it("should wrap the token provided", () => { | ||
const provider = new Provider(T1); | ||
provider.scope = ProviderScope.REQUEST; | ||
provider.customProp = "test"; | ||
it("should return the symbol name", () => { | ||
expect(new Provider(S1).className).to.eq("S1"); | ||
expect(provider.provide).to.eq(T1); | ||
expect(provider.useClass).to.eq(T1); | ||
expect(!!provider.store).to.eq(true); | ||
expect(getKeys(provider)).to.deep.eq([ | ||
"type", | ||
"injectable", | ||
"customProp", | ||
"useClass", | ||
"scope", | ||
"instance", | ||
"deps", | ||
"useFactory", | ||
"useValue" | ||
]); | ||
expect(provider.clone()).to.deep.eq(provider); | ||
}); | ||
}); | ||
describe("provide", () => { | ||
it("should equal to the class provided", () => { | ||
expect(new Provider(T2).provide).to.equal(T2); | ||
describe("when is a symbol", () => { | ||
it("should wrap the token provided", () => { | ||
const provider = new Provider(S1); | ||
expect(provider.provide).to.eq(S1); | ||
expect(!!provider.useClass).to.eq(false); | ||
expect(!!provider.store).to.eq(false); | ||
}); | ||
it("should equal to the symbol provided", () => { | ||
expect(new Provider(S2).provide).to.equal(S2); | ||
it("should should return scope", () => { | ||
const provider = new Provider(T1); | ||
}); | ||
}); | ||
describe("useClass", () => { | ||
it("should not equal to the class provided", () => { | ||
expect(new Provider(class {}).provide).not.to.equal(T2); | ||
describe("when is a string", () => { | ||
it("should wrap the token provided", () => { | ||
const provider = new Provider("test"); | ||
expect(provider.provide).to.eq("test"); | ||
expect(!!provider.useClass).to.eq(false); | ||
expect(!!provider.store).to.eq(false); | ||
}); | ||
}); | ||
describe("instance", () => { | ||
it("should have an instance", () => { | ||
it("should should return scope", () => { | ||
const provider = new Provider(T1); | ||
provider.useClass = T2; | ||
provider.instance = new provider.useClass(); | ||
expect(provider.instance).instanceOf(T2); | ||
}); | ||
}); | ||
describe("type", () => { | ||
it("should set a type of provider", () => { | ||
describe("clone()", () => { | ||
it("should clone a provider", () => { | ||
const provider = new Provider(T1); | ||
provider.type = "typeTest"; | ||
expect(provider.type).to.equal("typeTest"); | ||
provider.type = "provider"; | ||
provider.scope = ProviderScope.REQUEST; | ||
}); | ||
}); | ||
describe("className", () => { | ||
it("should return the class name", () => { | ||
expect(new Provider(T1).className).to.eq("T1"); | ||
}); | ||
it("should return the symbol name", () => { | ||
expect(new Provider(S1).className).to.eq("S1"); | ||
}); | ||
}); | ||
describe("name", () => { | ||
it("should return the class name", () => { | ||
expect(new Provider(T1).name).to.eq("T1"); | ||
}); | ||
}); | ||
describe("toString()", () => { | ||
it("should return the class name", () => { | ||
expect(new Provider(T1).toString()).to.eq("Token:T1"); | ||
}); | ||
}); | ||
}); |
import {Store} from "@tsed/core"; | ||
import {expect} from "chai"; | ||
import {Constant} from "../../src"; | ||
class Test {} | ||
class Test { | ||
} | ||
describe("@Constant()", () => { | ||
before(() => { | ||
it("should store metadata", () => { | ||
// WHEN | ||
Constant("expression")(Test, "test"); | ||
this.store = Store.from(Test).get("injectableProperties"); | ||
}); | ||
it("should store metadata", () => { | ||
expect(this.store).to.deep.eq({ | ||
// THEN | ||
const store = Store.from(Test).get("injectableProperties"); | ||
store.should.deep.eq({ | ||
test: { | ||
@@ -16,0 +17,0 @@ bindingType: "constant", |
@@ -6,18 +6,22 @@ import {descriptorOf, Metadata, Store} from "@tsed/core"; | ||
class Test { | ||
test() {} | ||
} | ||
describe("@Inject()", () => { | ||
describe("used on unsupported decorator type", () => { | ||
before(() => { | ||
it("should store metadata", () => { | ||
// GIVEN | ||
class Test { | ||
test() { | ||
} | ||
} | ||
// WHEN | ||
let actualError; | ||
try { | ||
Inject()(Test, "test", descriptorOf(Test, "test")); | ||
} catch (er) { | ||
this.error = er; | ||
actualError = er; | ||
} | ||
}); | ||
it("should store metadata", () => { | ||
expect(this.error.message).to.deep.eq("Inject cannot used as method.static at Test.test"); | ||
// THEN | ||
expect(actualError.message).to.deep.eq("Inject cannot used as method.static at Test.test"); | ||
}); | ||
@@ -28,14 +32,23 @@ }); | ||
before(() => { | ||
this.getTypeStub = Sinon.stub(Metadata, "getType").returns(String); | ||
Inject()(Test.prototype, "test", descriptorOf(Test, "test")); | ||
this.store = Store.from(Test).get("injectableProperties"); | ||
Sinon.stub(Metadata, "getType").returns(String); | ||
}); | ||
after(() => { | ||
this.getTypeStub.restore(); | ||
// @ts-ignore | ||
Metadata.getType.restore(); | ||
}); | ||
it("should store metadata", () => { | ||
expect(this.store).to.deep.eq({ | ||
// GIVEN | ||
class Test { | ||
test() { | ||
} | ||
} | ||
// WHEN | ||
Inject()(Test.prototype, "test", descriptorOf(Test, "test")); | ||
// THEN | ||
const store = Store.from(Test).get("injectableProperties"); | ||
store.should.deep.eq({ | ||
test: { | ||
@@ -51,8 +64,18 @@ bindingType: "method", | ||
before(() => { | ||
Inject(String)(Test.prototype, "test"); | ||
this.store = Store.from(Test).get("injectableProperties"); | ||
}); | ||
it("should store metadata", () => { | ||
expect(this.store).to.deep.eq({ | ||
// GIVEN | ||
class Test { | ||
test() { | ||
} | ||
} | ||
// WHEN | ||
Inject(String)(Test.prototype, "test"); | ||
// THEN | ||
const store = Store.from(Test).get("injectableProperties"); | ||
store.should.deep.eq({ | ||
test: { | ||
@@ -68,19 +91,27 @@ bindingType: "property", | ||
describe("used on constructor/params", () => { | ||
const sandbox = Sinon.createSandbox(); | ||
before(() => { | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes").returns([]); | ||
this.setParamTypesStub = Sinon.stub(Metadata, "setParamTypes"); | ||
Inject(String)(Test.prototype, undefined, 0); | ||
sandbox.stub(Metadata, "getParamTypes"); | ||
sandbox.stub(Metadata, "setParamTypes"); | ||
}); | ||
after(() => { | ||
this.getParamTypesStub.restore(); | ||
this.setParamTypesStub.restore(); | ||
sandbox.restore(); | ||
}); | ||
it("should call Metadata.getParamTypes()", () => { | ||
this.getParamTypesStub.should.have.been.calledWithExactly(Test.prototype, undefined); | ||
}); | ||
// GIVEN | ||
class Test { | ||
test() { | ||
} | ||
} | ||
it("should call Metadata.setParamTypes()", () => { | ||
this.setParamTypesStub.should.have.been.calledWithExactly(Test.prototype, undefined, [String]); | ||
// @ts-ignore | ||
Metadata.getParamTypes.returns([]); | ||
// WHEN | ||
Inject(String)(Test.prototype, undefined, 0); | ||
// THEN | ||
Metadata.getParamTypes.should.have.been.calledWithExactly(Test.prototype, undefined); | ||
Metadata.setParamTypes.should.have.been.calledWithExactly(Test.prototype, undefined, [String]); | ||
}); | ||
@@ -90,21 +121,29 @@ }); | ||
describe("used on method/params", () => { | ||
const sandbox = Sinon.createSandbox(); | ||
before(() => { | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes").returns([]); | ||
this.setParamTypesStub = Sinon.stub(Metadata, "setParamTypes"); | ||
Inject(String)(Test.prototype, "propertyKey", 0); | ||
sandbox.stub(Metadata, "getParamTypes"); | ||
sandbox.stub(Metadata, "setParamTypes"); | ||
}); | ||
after(() => { | ||
this.getParamTypesStub.restore(); | ||
this.setParamTypesStub.restore(); | ||
sandbox.restore(); | ||
}); | ||
it("should call Metadata.getParamTypes()", () => { | ||
this.getParamTypesStub.should.have.been.calledWithExactly(Test.prototype, "propertyKey"); | ||
}); | ||
// GIVEN | ||
class Test { | ||
test() { | ||
} | ||
} | ||
it("should call Metadata.setParamTypes()", () => { | ||
this.setParamTypesStub.should.have.been.calledWithExactly(Test.prototype, "propertyKey", [String]); | ||
// @ts-ignore | ||
Metadata.getParamTypes.returns([]); | ||
// WHEN | ||
Inject(String)(Test.prototype, "propertyKey", 0); | ||
// THEN | ||
Metadata.getParamTypes.should.have.been.calledWithExactly(Test.prototype, "propertyKey"); | ||
Metadata.setParamTypes.should.have.been.calledWithExactly(Test.prototype, "propertyKey", [String]); | ||
}); | ||
}); | ||
}); |
import * as Sinon from "sinon"; | ||
import {Injectable} from "../../src/decorators/injectable"; | ||
import {Injectable, ProviderScope} from "../../src"; | ||
import * as ProviderRegistry from "../../src/registries/ProviderRegistry"; | ||
@@ -8,8 +8,6 @@ | ||
class Test {} | ||
describe("with options", () => { | ||
before(() => { | ||
sandbox.stub(ProviderRegistry, "registerProvider"); | ||
Injectable({options: "options"})(Test); | ||
}); | ||
@@ -22,2 +20,10 @@ | ||
it("should called registerProvider", () => { | ||
// GIVEN | ||
class Test { | ||
} | ||
// WHEN | ||
Injectable({options: "options"})(Test); | ||
// THEN | ||
ProviderRegistry.registerProvider.should.have.been.calledWithExactly({ | ||
@@ -33,4 +39,2 @@ options: "options", | ||
sandbox.stub(ProviderRegistry, "registerProvider"); | ||
Injectable()(Test); | ||
}); | ||
@@ -43,4 +47,13 @@ | ||
it("should called registerProvider", () => { | ||
// GIVEN | ||
class Test { | ||
} | ||
// WHEN | ||
Injectable()(Test); | ||
// THEN | ||
ProviderRegistry.registerProvider.should.have.been.calledWithExactly({ | ||
provide: Test | ||
provide: Test, | ||
scope: ProviderScope.SINGLETON | ||
}); | ||
@@ -47,0 +60,0 @@ }); |
import {Store} from "@tsed/core"; | ||
import {expect} from "chai"; | ||
import {IInterceptor, IInterceptorContext, InjectablePropertyType, Intercept} from "../../src"; | ||
describe("@Intercept", () => { | ||
class TestInterceptor implements IInterceptor { | ||
aroundInvoke(ctx: IInterceptorContext<any>, options?: any) { | ||
it("should store metadata", () => { | ||
// GIVEN | ||
class TestInterceptor implements IInterceptor { | ||
aroundInvoke(ctx: IInterceptorContext<any>, options?: any) { | ||
return ""; | ||
return ""; | ||
} | ||
} | ||
} | ||
it("should store metadata", () => { | ||
// WHEN | ||
@@ -15,0 +15,0 @@ class TestService { |
@@ -5,18 +5,24 @@ import {GlobalProviders, ProviderType} from "@tsed/di"; | ||
class Test { | ||
} | ||
describe("@Interceptor", () => { | ||
const interceptorRegistry = GlobalProviders.getRegistry(ProviderType.INTERCEPTOR); | ||
before(() => { | ||
this.serviceStub = Sinon.stub(GlobalProviders.getRegistry(ProviderType.INTERCEPTOR), "merge"); | ||
Interceptor()(Test); | ||
Sinon.stub(interceptorRegistry, "merge"); | ||
}); | ||
after(() => { | ||
this.serviceStub.restore(); | ||
// @ts-ignore | ||
interceptorRegistry.merge.restore(); | ||
}); | ||
it("should set metadata", () => { | ||
this.serviceStub.should.have.been.calledWithExactly(Test, { | ||
// GIVEN | ||
class Test { | ||
} | ||
// WHEN | ||
Interceptor()(Test); | ||
// THEN | ||
interceptorRegistry.merge.should.have.been.calledWithExactly(Test, { | ||
instance: undefined, | ||
@@ -23,0 +29,0 @@ provide: Test, |
import {Store} from "@tsed/core"; | ||
import {expect} from "chai"; | ||
import {Scope} from "../../src/decorators/scope"; | ||
import {Scope} from "../../src"; | ||
@@ -10,9 +9,6 @@ class Test { | ||
describe("when parameters is given", () => { | ||
before(() => { | ||
it("should set metadata", () => { | ||
Scope("request")(Test); | ||
this.store = Store.from(Test); | ||
}); | ||
it("should set metadata", () => { | ||
expect(this.store.get("scope")).to.eq("request"); | ||
Store.from(Test).get("scope").should.eq("request"); | ||
}); | ||
@@ -23,10 +19,11 @@ }); | ||
before(() => { | ||
Scope()(Test); | ||
this.store = Store.from(Test); | ||
}); | ||
it("should set metadata", () => { | ||
expect(this.store.get("scope")).to.eq("request"); | ||
Scope()(Test); | ||
Store.from(Test).get("scope").should.eq("request"); | ||
}); | ||
}); | ||
}); |
@@ -1,19 +0,25 @@ | ||
import {GlobalProviders, ProviderType, Service} from "@tsed/common"; | ||
import {GlobalProviders, ProviderType, Service} from "@tsed/di"; | ||
import * as Sinon from "sinon"; | ||
class Test {} | ||
class Test { | ||
} | ||
describe("Service", () => { | ||
const serviceRegistry = GlobalProviders.getRegistry(ProviderType.SERVICE); | ||
before(() => { | ||
this.serviceStub = Sinon.stub(GlobalProviders.getRegistry(ProviderType.SERVICE), "merge"); | ||
Service()(Test); | ||
Sinon.stub(serviceRegistry, "merge"); | ||
}); | ||
after(() => { | ||
this.serviceStub.restore(); | ||
// @ts-ignore | ||
serviceRegistry.merge.restore(); | ||
}); | ||
it("should set metadata", () => { | ||
this.serviceStub.should.have.been.calledWithExactly(Test, { | ||
// WHEN | ||
Service()(Test); | ||
// THEN | ||
serviceRegistry.merge.should.have.been.calledWithExactly(Test, { | ||
instance: undefined, | ||
@@ -20,0 +26,0 @@ provide: Test, |
import {Store} from "@tsed/core"; | ||
import {expect} from "chai"; | ||
import {Value} from "../../src"; | ||
class Test {} | ||
describe("@Value()", () => { | ||
before(() => { | ||
it("should store metadata", () => { | ||
// GIVEN | ||
class Test { | ||
} | ||
// WHEN | ||
Value("expression")(Test, "test"); | ||
this.store = Store.from(Test).get("injectableProperties"); | ||
}); | ||
it("should store metadata", () => { | ||
expect(this.store).to.deep.eq({ | ||
// THEN | ||
Store.from(Test).get("injectableProperties").should.deep.eq({ | ||
test: { | ||
@@ -16,0 +17,0 @@ bindingType: "value", |
@@ -1,20 +0,11 @@ | ||
import {expect} from "chai"; | ||
import {InjectionError} from "../../src/errors/InjectionError"; | ||
describe("InjectionError", () => { | ||
before(() => { | ||
this.errorInstance = new InjectionError(class Target {}, "SERVICE"); | ||
}); | ||
it("should create new instance of InjectionError", () => { | ||
const error = new InjectionError(class Target { | ||
}, "SERVICE"); | ||
after(() => { | ||
delete this.errorInstance; | ||
error.message.should.equal("Injection failed on Target\nOrigin: SERVICE"); | ||
error.name.should.equal("INJECTION_ERROR"); | ||
}); | ||
it("should have a message", () => { | ||
expect(this.errorInstance.message).to.equal("Service Target > SERVICE not found."); | ||
}); | ||
it("should have a name", () => { | ||
expect(this.errorInstance.name).to.equal("INJECTION_ERROR"); | ||
}); | ||
}); |
import {Registry} from "@tsed/core"; | ||
import {expect} from "chai"; | ||
import * as Sinon from "sinon"; | ||
import {GlobalProviderRegistry} from "../../src"; | ||
import {Provider} from "../../src/class/Provider"; | ||
import {GlobalProviderRegistry, Provider} from "../../src"; | ||
const sandbox = Sinon.createSandbox(); | ||
describe("GlobalProviderRegistry", () => { | ||
describe("createRegistry()", () => { | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.setStub = Sinon.stub(this.providers._registries, "set"); | ||
this.result = this.providers.createRegistry("test", Provider, { | ||
options: "options", | ||
buildable: false, | ||
it("should create registry", () => { | ||
// GIVEN | ||
const providers = new GlobalProviderRegistry(); | ||
// @ts-ignore | ||
const setStub = sandbox.stub(providers._registries, "set"); | ||
// WHEN | ||
const result = providers.createRegistry("test", Provider, { | ||
injectable: false | ||
}); | ||
}); | ||
it("should create registry", () => { | ||
expect(this.result).to.be.instanceOf(Registry); | ||
}); | ||
// THEN | ||
result.should.be.instanceOf(Registry); | ||
it("should call registries.set", () => { | ||
this.setStub.should.have.been.calledWithExactly("test", { | ||
registry: this.result, | ||
options: "options", | ||
injectable: false, | ||
buildable: false | ||
setStub.should.have.been.calledWithExactly("test", { | ||
registry: result, | ||
injectable: false | ||
}); | ||
@@ -35,24 +34,21 @@ }); | ||
describe("when type is a string", () => { | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.providersGetStub = Sinon.stub(this.providers, "get"); | ||
this.hasStub = Sinon.stub(this.providers._registries, "has").returns(true); | ||
this.getStub = Sinon.stub(this.providers._registries, "get").returns("instance"); | ||
this.result = this.providers.getRegistrySettings("type"); | ||
}); | ||
it("should return registry settings", () => { | ||
// GIVEN | ||
const providers = new GlobalProviderRegistry(); | ||
Sinon.stub(providers, "get"); | ||
it("should not call providers.get", () => { | ||
return this.providersGetStub.should.not.have.been.called; | ||
}); | ||
// @ts-ignore | ||
const hasStub = Sinon.stub(providers._registries, "has").returns(true); | ||
// @ts-ignore | ||
const getStub = Sinon.stub(providers._registries, "get").returns("instance"); | ||
it("should call registries.has", () => { | ||
this.hasStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
// WHEN | ||
const result = providers.getRegistrySettings("type"); | ||
it("should call registries.get", () => { | ||
this.getStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
// THEN | ||
expect(result).to.eq("instance"); | ||
getStub.should.have.been.calledWithExactly("type"); | ||
hasStub.should.have.been.calledWithExactly("type"); | ||
it("should return settings", () => { | ||
expect(this.result).to.eq("instance"); | ||
return providers.get.should.not.have.been.called; | ||
}); | ||
@@ -62,26 +58,28 @@ }); | ||
describe("when type is a Type", () => { | ||
class Test {} | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.providersGetStub = Sinon.stub(this.providers, "get").returns({type: "type"}); | ||
this.hasStub = Sinon.stub(this.providers._registries, "has").returns(true); | ||
this.getStub = Sinon.stub(this.providers._registries, "get").returns("instance"); | ||
this.result = this.providers.getRegistrySettings(Test); | ||
}); | ||
it("should call providers.get", () => { | ||
return this.providersGetStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call registries.has", () => { | ||
this.hasStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
it("should return registry settings", () => { | ||
// GIVEN | ||
class Test { | ||
} | ||
it("should call registries.get", () => { | ||
this.getStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
const providers = new GlobalProviderRegistry(); | ||
Sinon.stub(providers, "get").returns({type: "type"} as Provider<any>); | ||
it("should return settings", () => { | ||
expect(this.result).to.eq("instance"); | ||
// @ts-ignore | ||
const hasStub = Sinon.stub(providers._registries, "has").returns(true); | ||
// @ts-ignore | ||
const getStub = Sinon.stub(providers._registries, "get").returns("instance"); | ||
// WHEN | ||
const result = providers.getRegistrySettings(Test); | ||
// THEN | ||
result.should.eq("instance"); | ||
hasStub.should.have.been.calledWithExactly("type"); | ||
getStub.should.have.been.calledWithExactly("type"); | ||
providers.get.should.have.been.calledWithExactly(Test); | ||
}); | ||
@@ -91,28 +89,22 @@ }); | ||
describe("when type is a string but is unknow", () => { | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.providersGetStub = Sinon.stub(this.providers, "get"); | ||
this.hasStub = Sinon.stub(this.providers._registries, "has").returns(false); | ||
this.getStub = Sinon.stub(this.providers._registries, "get").returns("instance"); | ||
this.result = this.providers.getRegistrySettings("type"); | ||
}); | ||
it("should not call providers.get", () => { | ||
return this.providersGetStub.should.not.have.been.called; | ||
}); | ||
// GIVEN | ||
const providers = new GlobalProviderRegistry(); | ||
Sinon.stub(providers, "get"); | ||
// @ts-ignore | ||
const hasStub = Sinon.stub(providers._registries, "has").returns(false); | ||
// @ts-ignore | ||
const getStub = Sinon.stub(providers._registries, "get").returns("instance"); | ||
it("should call registries.has", () => { | ||
this.hasStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
// WHEN | ||
const result = providers.getRegistrySettings("type"); | ||
it("should not call registries.get", () => { | ||
return this.getStub.should.not.have.been.called; | ||
}); | ||
// THEN | ||
result.should.deep.eq({ | ||
registry: providers, | ||
injectable: true | ||
}); | ||
hasStub.should.have.been.calledWithExactly("type"); | ||
it("should return settings", () => { | ||
expect(this.result).to.deep.eq({ | ||
registry: this.providers, | ||
injectable: true, | ||
buildable: true | ||
}); | ||
return providers.get.should.not.have.been.called && getStub.should.not.have.been.called; | ||
}); | ||
@@ -123,16 +115,19 @@ }); | ||
describe("createRegisterFn()", () => { | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.registryStub = { | ||
it("should create a register function", () => { | ||
// GIVEN | ||
const providers = new GlobalProviderRegistry(); | ||
const registryStub = { | ||
merge: Sinon.stub() | ||
}; | ||
this.getRegistryStub = Sinon.stub(this.providers, "getRegistry").returns(this.registryStub); | ||
this.providers.createRegisterFn("type")("provide"); | ||
}); | ||
it("should call getRegistryStub()", () => { | ||
this.getRegistryStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
it("should merge options", () => { | ||
this.registryStub.merge.should.have.been.calledWithExactly("provide", { | ||
// @ts-ignore | ||
Sinon.stub(providers, "getRegistry").returns(registryStub); | ||
// WHEN | ||
const fn = providers.createRegisterFn("type"); | ||
fn("provide"); | ||
// THEN | ||
providers.getRegistry.should.have.been.calledWithExactly("type"); | ||
registryStub.merge.should.have.been.calledWithExactly("provide", { | ||
provide: "provide", | ||
@@ -146,16 +141,17 @@ instance: undefined, | ||
describe("getRegistry()", () => { | ||
before(() => { | ||
this.providers = new GlobalProviderRegistry(); | ||
this.getRegistrySettingsStub = Sinon.stub(this.providers, "getRegistrySettings").returns({registry: "registry"}); | ||
this.result = this.providers.getRegistry("type"); | ||
}); | ||
it("should call getRegistrySettings and return the registry", () => { | ||
// GIVEN | ||
const providers = new GlobalProviderRegistry(); | ||
it("should call getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly("type"); | ||
}); | ||
// @ts-ignore | ||
Sinon.stub(providers, "getRegistrySettings").returns({registry: "registry"}); | ||
it("should return the registry", () => { | ||
expect(this.result).to.eq("registry"); | ||
// WHEN | ||
const result = providers.getRegistry("type"); | ||
// THEN | ||
providers.getRegistrySettings.should.have.been.calledWithExactly("type"); | ||
result.should.eq("registry"); | ||
}); | ||
}); | ||
}); |
import * as Sinon from "sinon"; | ||
import {ProviderRegistry, registerProvider} from "../../src"; | ||
import { | ||
GlobalProviders, | ||
ProviderScope, | ||
ProviderType, | ||
registerFactory, | ||
registerProvider, | ||
registerValue | ||
} from "../../src"; | ||
const sandbox = Sinon.createSandbox(); | ||
describe("ProviderRegistry", () => { | ||
describe("registerProvider()", () => { | ||
describe("when provide field is not given", () => { | ||
before(() => { | ||
this.mergeStub = Sinon.stub(ProviderRegistry, "merge"); | ||
this.hasStub = Sinon.stub(ProviderRegistry, "has").returns(false); | ||
try { | ||
registerProvider({provide: undefined}); | ||
} catch (er) { | ||
this.error = er; | ||
} | ||
before(() => { | ||
sandbox.stub(GlobalProviders, "merge"); | ||
sandbox.stub(GlobalProviders, "has").returns(false); | ||
}); | ||
after(() => { | ||
sandbox.restore(); | ||
}); | ||
afterEach(() => { | ||
sandbox.resetHistory(); | ||
}); | ||
it("should throw an error when provide field is not given ", () => { | ||
// GIVEN | ||
// @ts-ignore | ||
GlobalProviders.has.returns(false); | ||
let actualError; | ||
try { | ||
registerProvider({provide: undefined}); | ||
} catch (er) { | ||
actualError = er; | ||
} | ||
actualError.message.should.deep.eq("Provider.provide is required"); | ||
}); | ||
it("should add provider", () => { | ||
class Test { | ||
} | ||
registerProvider({provide: Test}); | ||
GlobalProviders.merge.should.have.been.calledWithExactly(Test, { | ||
provide: Test | ||
}); | ||
}); | ||
}); | ||
describe("registerValue()", () => { | ||
before(() => { | ||
sandbox.stub(GlobalProviders, "merge"); | ||
sandbox.stub(GlobalProviders, "has").returns(false); | ||
}); | ||
after(() => { | ||
this.hasStub.restore(); | ||
this.mergeStub.restore(); | ||
after(() => { | ||
sandbox.restore(); | ||
}); | ||
afterEach(() => { | ||
sandbox.resetHistory(); | ||
}); | ||
it("should add provider (1)", () => { | ||
const token = Symbol.for("CustomTokenValue"); | ||
registerValue(token, "myValue"); | ||
GlobalProviders.merge.should.have.been.calledWithExactly(token, { | ||
provide: token, | ||
useValue: "myValue", | ||
scope: ProviderScope.SINGLETON, | ||
type: ProviderType.VALUE | ||
}); | ||
}); | ||
it("should throw an error", () => { | ||
this.error.message.should.deep.eq("Provider.provide is required"); | ||
it("should add provider", () => { | ||
const token = Symbol.for("CustomTokenValue"); | ||
registerValue({provide: token, useValue: "myValue", scope: ProviderScope.REQUEST}); | ||
GlobalProviders.merge.should.have.been.calledWithExactly(token, { | ||
provide: token, | ||
useValue: "myValue", | ||
scope: ProviderScope.REQUEST, | ||
type: ProviderType.VALUE | ||
}); | ||
}); | ||
}); | ||
describe("registerFactory()", () => { | ||
before(() => { | ||
sandbox.stub(GlobalProviders.getRegistry(ProviderType.FACTORY), "merge"); | ||
}); | ||
describe("when a configuration is given", () => { | ||
class Test {} | ||
after(() => { | ||
sandbox.restore(); | ||
}); | ||
afterEach(() => { | ||
sandbox.resetHistory(); | ||
}); | ||
before(() => { | ||
this.mergeStub = Sinon.stub(ProviderRegistry, "merge"); | ||
registerProvider({provide: Test}); | ||
it("should add provider (1)", () => { | ||
const token = Symbol.for("CustomTokenFactory"); | ||
registerFactory(token, {factory: "factory"}); | ||
const factoryRegistry = GlobalProviders.getRegistry(ProviderType.FACTORY); | ||
factoryRegistry.merge.should.have.been.calledWithExactly(token, { | ||
provide: token, | ||
useFactory: Sinon.match.func, | ||
scope: ProviderScope.SINGLETON, | ||
type: ProviderType.FACTORY | ||
}); | ||
after(() => { | ||
this.mergeStub.restore(); | ||
// @ts-ignore | ||
factoryRegistry.merge.args[0][1].useFactory().should.deep.eq({factory: "factory"}); | ||
}); | ||
it("should add provider (2)", () => { | ||
const token = Symbol.for("CustomTokenFactory"); | ||
registerFactory({ | ||
provide: token, | ||
scope: ProviderScope.REQUEST, | ||
useFactory() { | ||
return {factory: "factory"}; | ||
} | ||
}); | ||
it("should call ProviderRegistry.merge()", () => { | ||
this.mergeStub.should.have.been.calledWithExactly(Test, { | ||
provide: Test | ||
}); | ||
GlobalProviders.getRegistry(ProviderType.FACTORY).merge.should.have.been.calledWithExactly(token, { | ||
provide: token, | ||
useFactory: Sinon.match.func, | ||
scope: ProviderScope.REQUEST, | ||
type: ProviderType.FACTORY | ||
}); | ||
@@ -44,0 +140,0 @@ }); |
@@ -1,9 +0,7 @@ | ||
import {GlobalProviders, Inject, Provider, ProviderScope, ProviderType} from "@tsed/common"; | ||
import {Metadata, Store} from "@tsed/core"; | ||
import {inject} from "@tsed/testing"; | ||
import {Store} from "@tsed/core"; | ||
import {Inject, InjectorService, Provider, ProviderScope} from "@tsed/di"; | ||
import {expect} from "chai"; | ||
import * as Sinon from "sinon"; | ||
import {$log} from "ts-log-debug"; | ||
import {TestContext} from "../../../testing/src"; | ||
import {InjectorService} from "../../src"; | ||
import {GlobalProviders, LocalsContainer} from "../../src"; | ||
import {ProviderType} from "../../src/interfaces"; | ||
@@ -35,20 +33,2 @@ class Test { | ||
describe("InjectorService", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
}); | ||
describe("invoke test with Inject decorator", () => { | ||
before(() => { | ||
this.instance = this.injector.invoke(Test); | ||
}); | ||
it("should bind the method", () => { | ||
expect(this.instance.test("test")).to.be.instanceOf(InjectorService); | ||
}); | ||
it("should bind the property", () => { | ||
expect(this.instance.prop).to.be.instanceOf(InjectorService); | ||
}); | ||
}); | ||
describe("has()", () => { | ||
@@ -80,1074 +60,351 @@ it("should return true", () => { | ||
describe("forEach()", () => { | ||
before(() => { | ||
this.list = []; | ||
this.injector.forEach((item: any) => { | ||
this.list.push(item); | ||
}); | ||
}); | ||
it("should return the list", () => { | ||
expect(this.list.length).to.eq(this.injector.size); | ||
}); | ||
}); | ||
describe("forkProvider()", () => { | ||
class Test { | ||
} | ||
describe("keys()", () => { | ||
before(() => { | ||
this.list = Array.from(this.injector.keys()); | ||
}); | ||
it("should return the list", () => { | ||
expect(this.list).to.be.an("array"); | ||
}); | ||
}); | ||
it("should return a provider", async () => { | ||
// GIVEN | ||
const injector = new InjectorService(); | ||
describe("entries()", () => { | ||
before(() => { | ||
this.list = Array.from(this.injector.entries()); | ||
}); | ||
it("should return the list", () => { | ||
expect(this.list[0]).to.be.an("array"); | ||
}); | ||
}); | ||
// WHEN | ||
const provider = await injector.forkProvider(InjectorService); | ||
describe("values()", () => { | ||
before(() => { | ||
this.list = Array.from(this.injector.values()); | ||
// THEN | ||
provider.should.be.instanceof(Provider); | ||
provider.provide.should.eq(InjectorService); | ||
}); | ||
it("should return the list", () => { | ||
expect(this.list).to.be.an("array"); | ||
expect(this.list[0].instance).to.be.instanceof(InjectorService); | ||
}); | ||
}); | ||
describe("Array.from()", () => { | ||
before(() => { | ||
this.list = Array.from(this.injector); | ||
}); | ||
describe("invoke()", () => { | ||
describe("when we call invoke with rebuild options (SINGLETON)", () => { | ||
it("should invoke the provider from container", async () => { | ||
// GIVEN | ||
const token = class Test { | ||
}; | ||
it("should return a list", () => { | ||
expect(this.list).to.be.an("array"); | ||
}); | ||
}); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
provider.deps = [InjectorService]; | ||
describe("getProvider()", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.provider = injector.getProvider(InjectorService); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
it("should return a provider", () => { | ||
expect(this.provider).to.be.instanceOf(Provider); | ||
}); | ||
}); | ||
await injector.load(); | ||
describe("getProviders()", () => { | ||
describe("with type ProviderType.MIDDLEWARE", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.providers = injector.getProviders(ProviderType.MIDDLEWARE); | ||
this.hasOther = this.providers.find((item: any) => item.type !== ProviderType.MIDDLEWARE); | ||
}) | ||
); | ||
Sinon.spy(injector as any, "_invoke"); | ||
Sinon.spy(injector as any, "invoke"); | ||
Sinon.spy(injector, "get"); | ||
Sinon.spy(injector, "getProvider"); | ||
it("should return a list", () => { | ||
expect(this.providers.length > 0).to.be.true; | ||
}); | ||
const locals = new Map(); | ||
it("sohuld return a list", () => { | ||
expect(this.providers[0]).to.be.instanceOf(Provider); | ||
}); | ||
// WHEN | ||
it("should have only provider typed as CONVERTER", () => { | ||
expect(this.hasOther).to.be.undefined; | ||
}); | ||
}); | ||
const result1 = await injector.invoke(token, locals); | ||
const result2 = await injector.invoke(token, locals, {rebuild: true}); | ||
describe("without type", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.providers = injector.getProviders(); | ||
this.hasOther = this.providers.find((item: any) => item.type === ProviderType.MIDDLEWARE); | ||
}) | ||
); | ||
it("sohuld return a list", () => { | ||
expect(this.providers.length > 0).to.be.true; | ||
}); | ||
it("should return a list", () => { | ||
expect(this.providers[0]).to.be.instanceOf(Provider); | ||
}); | ||
it("should have only provider typed as CONVERTER", () => { | ||
expect(!!this.hasOther).to.be.true; | ||
}); | ||
}); | ||
}); | ||
describe("mapServices()", () => { | ||
describe("when serviceType is a string", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = "ServiceName"; | ||
const locals = new Map(); | ||
locals.set(this.symbol, "ServiceInstanceName"); | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals | ||
// THEN | ||
result1.should.not.eq(result2); | ||
injector.getProvider.should.have.been.calledWithExactly(token); | ||
injector.get.should.have.been.calledWithExactly(token); | ||
(injector as any)._invoke.should.have.been.calledWithExactly(token, locals, {rebuild: true}); | ||
(injector as any).invoke.should.have.been.calledWithExactly(InjectorService, locals, { | ||
parent: token | ||
}); | ||
}); | ||
it("should return the service instance from the locals map", () => { | ||
expect(this.result).to.eq("ServiceInstanceName"); | ||
}); | ||
}); | ||
describe("when serviceType is a class from locals", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
describe("when provider is a SINGLETON", () => { | ||
it("should invoke the provider from container", async () => { | ||
// GIVEN | ||
const token = class Test { | ||
}; | ||
const locals = new Map(); | ||
locals.set(this.symbol, new this.symbol()); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals | ||
}); | ||
}); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
it("should return the service instance from the locals map", () => { | ||
expect(this.result).to.be.instanceOf(this.symbol); | ||
}); | ||
}); | ||
await injector.load(); | ||
describe("when serviceType is a class from registry (unknow)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
Sinon.spy(injector as any, "_invoke"); | ||
Sinon.spy(injector, "get"); | ||
Sinon.spy(injector, "getProvider"); | ||
this.symbol = class Test { | ||
}; | ||
const locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider"); | ||
try { | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
} catch (er) { | ||
this.error = er; | ||
} | ||
}); | ||
// WHEN | ||
after(() => { | ||
this.getStub.restore(); | ||
}); | ||
const result1 = await injector.invoke(token, locals); | ||
const result2 = await injector.invoke(token, locals); | ||
it("should call GlobalProviders.has", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
// THEN | ||
result1.should.eq(result2); | ||
injector.getProvider.should.have.been.calledWithExactly(token); | ||
injector.get.should.have.been.calledWithExactly(token); | ||
it("should throw an error", () => { | ||
expect(this.error.message).to.eq("Service ServiceTest > Test not found."); | ||
return (injector as any)._invoke.should.not.have.been.called; | ||
}); | ||
}); | ||
describe("when serviceType is a class from registry (know, buildable, instance undefined)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
describe("when provider is a REQUEST", () => { | ||
it("should invoke a request from local container", async () => { | ||
// GIVEN | ||
const token = class Test { | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: undefined, | ||
useClass: "useClass", | ||
type: "provider" | ||
}); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.REQUEST; | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: true | ||
}); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns("instance"); | ||
await injector.load(); | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
}); | ||
Sinon.spy(injector as any, "_invoke"); | ||
Sinon.spy(injector, "get"); | ||
Sinon.spy(injector, "getProvider"); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
}); | ||
const locals = new Map(); // LocalContainer for the first request | ||
const locals2 = new Map(); // LocalContainer for the second request | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
// WHEN REQ1 | ||
const result1 = await injector.invoke(token, locals); | ||
const result2 = await injector.invoke(token, locals); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should build instance and return the service", () => { | ||
this.invokeStub.should.have.been.calledWithExactly("useClass", this.locals, undefined, true); | ||
}); | ||
it("should return the service instance", () => { | ||
expect(this.result).to.deep.eq("instance"); | ||
}); | ||
}); | ||
// WHEN REQ2 | ||
const result3 = await injector.invoke(token, locals2); | ||
describe("when serviceType is a class from registry (know, instance defined, not buildable)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
// THEN | ||
result1.should.eq(result2); | ||
result2.should.not.eq(result3); | ||
this.symbol = class Test { | ||
}; | ||
injector.getProvider.should.have.been.calledWithExactly(token); | ||
(injector as any)._invoke.should.have.been.calledWithExactly(token, locals, {}); | ||
locals.get(token).should.eq(result1); | ||
locals2.get(token).should.eq(result3); | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider" | ||
}); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: false, | ||
injectable: true | ||
}); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns("instance"); | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
return injector.get.should.not.have.been.called; | ||
}); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
}); | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should build instance and return the service", () => { | ||
return this.invokeStub.should.not.have.been.called; | ||
}); | ||
it("should return the service instance", () => { | ||
expect(this.result).to.deep.eq({instance: "instance"}); | ||
}); | ||
}); | ||
describe("when serviceType is a class from registry (know, instance defined, buildable, SINGLETON)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
describe("when provider is a INSTANCE", () => { | ||
it("should invoke a new instance", async () => { | ||
// GIVEN | ||
const token = class Test { | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider", | ||
scope: ProviderScope.SINGLETON | ||
}); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.INSTANCE; | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: true | ||
}); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns("instance"); | ||
await injector.load(); | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
}); | ||
Sinon.spy(injector as any, "_invoke"); | ||
Sinon.spy(injector, "get"); | ||
Sinon.spy(injector, "getProvider"); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
}); | ||
const locals = new Map(); // LocalContainer for the first request | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
// WHEN REQ1 | ||
const result1 = await injector.invoke(token, locals); | ||
const result2 = await injector.invoke(token, locals); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should not build instance", () => { | ||
return this.invokeStub.should.not.have.been.called; | ||
}); | ||
it("should return the service instance", () => { | ||
expect(this.result).to.deep.eq({instance: "instance"}); | ||
}); | ||
}); | ||
// THEN | ||
result1.should.not.eq(result2); | ||
describe("when serviceType is a class from registry (know, instance defined, buildable, REQUEST)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
injector.getProvider.should.have.been.calledWithExactly(token); | ||
(injector as any)._invoke.should.have.been.calledWithExactly(token, locals, {}); | ||
locals.has(token).should.eq(false); | ||
this.symbol = class Test { | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider", | ||
scope: ProviderScope.REQUEST | ||
}); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: true | ||
}); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns("instance"); | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
parentScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
return injector.get.should.not.have.been.called; | ||
}); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
}); | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should build instance and return the service", () => { | ||
return this.invokeStub.should.have.been.calledWithExactly("useClass", this.locals, undefined, true); | ||
}); | ||
it("should return the service instance", () => { | ||
expect(this.result).to.deep.eq("instance"); | ||
}); | ||
}); | ||
describe("when serviceType is a class from registry (know, instance defined, buildable, SCOPE ERROR)", () => { | ||
describe("when provider is a SINGLETON", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider", | ||
scope: ProviderScope.REQUEST | ||
}); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: true | ||
}); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns("instance"); | ||
try { | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
parentScope: false, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
} catch (er) { | ||
this.error = er; | ||
} | ||
Sinon.stub(GlobalProviders, "getRegistrySettings"); | ||
}); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
// @ts-ignore | ||
GlobalProviders.getRegistrySettings.restore(); | ||
}); | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should not build instance", () => { | ||
return this.invokeStub.should.not.have.been.called; | ||
}); | ||
it("should throw an error", () => { | ||
expect(this.error.message).to.eq( | ||
"Service of type useClass can not be injected as it is request scoped, while ServiceTest is singleton scoped" | ||
); | ||
}); | ||
}); | ||
describe("when serviceType is a class from registry (INJECTION ERROR)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
it("should invoke the provider from container", async () => { | ||
// GIVEN | ||
const token = class Test { | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider", | ||
scope: ProviderScope.REQUEST | ||
}); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: true | ||
}); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").throws(new Error("Origin Error")); | ||
try { | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
parentScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
} catch (er) { | ||
this.error = er; | ||
} | ||
}); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
}); | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should build instance and return the service", () => { | ||
return this.invokeStub.should.have.been.calledWithExactly("useClass", this.locals, undefined, true); | ||
}); | ||
it("should throw an error", () => { | ||
expect(this.error.message).to.deep.eq("Service ServiceTest > Test injection failed."); | ||
}); | ||
it("should throw an error with origin error", () => { | ||
expect(this.error.origin.message).to.deep.eq("Origin Error"); | ||
}); | ||
}); | ||
describe("when serviceType is a class from registry (NOT INJECTABLE)", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.symbol = class Test { | ||
const registry = { | ||
onInvoke: Sinon.stub() | ||
}; | ||
this.locals = new Map(); | ||
this.getStub = Sinon.stub(this.injector, "getProvider").returns({ | ||
instance: {instance: "instance"}, | ||
useClass: "useClass", | ||
type: "provider", | ||
scope: ProviderScope.REQUEST | ||
}); | ||
// @ts-ignore | ||
GlobalProviders.getRegistrySettings.returns(registry); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true, | ||
injectable: false | ||
}); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
this.invokeStub = Sinon.stub(this.injector, "invoke"); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
try { | ||
this.result = this.injector.mapServices({ | ||
serviceType: this.symbol, | ||
locals: this.locals, | ||
requiredScope: true, | ||
parentScope: true, | ||
target: class ServiceTest { | ||
} | ||
}); | ||
} catch (er) { | ||
this.error = er; | ||
} | ||
}); | ||
// WHEN | ||
const result = await injector.invoke(token); | ||
after(() => { | ||
this.getStub.restore(); | ||
this.invokeStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
// THEN | ||
result.should.instanceof(token); | ||
registry.onInvoke.should.have.been.calledWithExactly(provider, Sinon.match.instanceOf(LocalsContainer), []); | ||
}); | ||
it("should call GlobalProviders.get", () => { | ||
this.getStub.should.have.been.calledWithExactly(this.symbol); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings", () => { | ||
this.getRegistrySettingsStub.should.be.calledWithExactly("provider"); | ||
}); | ||
it("should not build service", () => { | ||
return this.invokeStub.should.not.have.been.called; | ||
}); | ||
it("should throw an error", () => { | ||
expect(this.error.message).to.deep.eq("Service ServiceTest > Test not injectable."); | ||
}); | ||
}); | ||
}); | ||
describe("build()", () => { | ||
class Test { | ||
} | ||
describe("when provider is a Value (useValue)", () => { | ||
it("should invoke the provider from container (1)", async () => { | ||
// GIVEN | ||
const token = Symbol.for("TokenValue"); | ||
describe("when the provider is buildable", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.injector.logger = $log; | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
provider.useValue = "TEST"; | ||
this.injector.scopes = { | ||
[ProviderType.CONTROLLER]: ProviderScope.REQUEST | ||
}; | ||
this.provider = new Provider(Test); | ||
this.provider.type = "controller"; | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
this.injector.set(Test, this.provider); | ||
await injector.load(); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: true | ||
}); | ||
// WHEN | ||
const result = await injector.invoke(token); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke").returns(new Test()); | ||
this.locals = this.injector.build(); | ||
// THEN | ||
result.should.eq("TEST"); | ||
}); | ||
after(() => { | ||
this.getRegistrySettingsStub.restore(); | ||
this.invokeStub.restore(); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings()", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly("controller"); | ||
}); | ||
it("should invoke the provider from container (2)", async () => { | ||
// GIVEN | ||
const token = Symbol.for("TokenValue"); | ||
it("should call InjectorService.invoke()", () => { | ||
this.invokeStub.should.have.been.calledWithExactly(Test, Sinon.match.instanceOf(Map)); | ||
}); | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
provider.useValue = () => "TEST"; | ||
it("should create an instance", () => { | ||
expect(this.provider.instance).to.be.instanceOf(Test); | ||
}); | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
it("should set the default scope", () => { | ||
expect(this.provider.scope).to.eq(ProviderScope.REQUEST); | ||
}); | ||
await injector.load(); | ||
it("should store the instance in locals map", () => { | ||
expect(this.locals.get(Test)).to.be.instanceOf(Test); | ||
}); | ||
}); | ||
describe("when the provider is not buildable", () => { | ||
before(() => { | ||
this.injector = new InjectorService(); | ||
this.injector.logger = $log; | ||
this.provider = new Provider(Test); | ||
this.provider.type = "factory"; | ||
this.provider.instance = new Test(); | ||
// WHEN | ||
const result = await injector.invoke(token); | ||
this.injector.set(Test, this.provider); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns({ | ||
registry: GlobalProviders, | ||
buildable: false | ||
}); | ||
this.invokeStub = Sinon.stub(this.injector, "invoke"); | ||
this.locals = this.injector.build(); | ||
// THEN | ||
result.should.eq("TEST"); | ||
}); | ||
after(() => { | ||
this.getRegistrySettingsStub.restore(); | ||
this.invokeStub.restore(); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings()", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly("factory"); | ||
}); | ||
it("should call InjectorService.invoke()", () => { | ||
return this.invokeStub.should.not.have.been.called; | ||
}); | ||
it("should create an instance", () => { | ||
expect(this.provider.instance).to.be.instanceOf(Test); | ||
}); | ||
it("should set the default scope", () => { | ||
expect(this.provider.scope).to.eq(ProviderScope.SINGLETON); | ||
}); | ||
it("should store the instance in locals map", () => { | ||
expect(this.locals.get(Test)).to.be.instanceOf(Test); | ||
}); | ||
}); | ||
}); | ||
describe("when provider is a Factory (useFactory)", () => { | ||
it("should invoke the provider from container", async () => { | ||
// GIVEN | ||
const token = Symbol.for("TokenFactory"); | ||
describe("invoke()", () => { | ||
class Test { | ||
args: any[]; | ||
const provider = new Provider<any>(token); | ||
provider.scope = ProviderScope.SINGLETON; | ||
provider.useFactory = () => ({factory: "factory"}); | ||
constructor(...args: any[]) { | ||
this.args = args; | ||
} | ||
} | ||
const injector = new InjectorService(); | ||
injector.set(token, provider); | ||
class TestDep { | ||
} | ||
await injector.load(); | ||
describe("when designParamsTypes is not given", () => { | ||
before( | ||
inject([InjectorService], (injectorService: InjectorService) => { | ||
this.provider = new Provider(Test); | ||
this.registrySettings = { | ||
onInvoke: Sinon.stub() | ||
}; | ||
// WHEN | ||
const result = await injector.invoke(token); | ||
this.designParamTypes = [TestDep]; | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns(this.registrySettings); | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes").returns(this.designParamTypes); | ||
this.getStub = Sinon.stub(injectorService, "getProvider").returns(this.provider); | ||
this.mapServicesStub = Sinon.stub(injectorService as any, "mapServices").returns((this.dep = new TestDep())); | ||
Store.from(Test).set("scope", "request"); | ||
this.locals = new Map(); | ||
this.result = injectorService.invoke(Test, this.locals, undefined, false); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
this.getParamTypesStub.restore(); | ||
// THEN | ||
result.should.deep.eq({factory: "factory"}); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings method", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call GlobalProviders.get method", () => { | ||
this.getStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call Metadata.getParamTypes method", () => { | ||
this.getParamTypesStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call settings.onInvoke method", () => { | ||
this.registrySettings.onInvoke.should.have.been.calledWithExactly(this.provider, this.locals, this.designParamTypes); | ||
}); | ||
it("should call injectorService.mapServices method", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
target: Test, | ||
serviceType: TestDep, | ||
locals: this.locals, | ||
requiredScope: false, | ||
parentScope: "request" | ||
}); | ||
}); | ||
it("should return a new instance of the given service", () => { | ||
expect(this.result).to.instanceOf(Test); | ||
}); | ||
it("should injected services into the given service constructor", () => { | ||
expect(this.result.args).to.deep.eq([this.dep]); | ||
}); | ||
}); | ||
describe("when designParamsTypes is given", () => { | ||
before( | ||
inject([InjectorService], (injectorService: InjectorService) => { | ||
this.provider = new Provider(Test); | ||
describe("when provider is an unknow provider", () => { | ||
it("should invoke the class from given parameter", async () => { | ||
// GIVEN | ||
const token = class { | ||
}; | ||
this.registrySettings = { | ||
onInvoke: Sinon.stub() | ||
}; | ||
const injector = new InjectorService(); | ||
this.designParamTypes = [TestDep]; | ||
// WHEN | ||
const result = await injector.invoke(token); | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns(this.registrySettings); | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes"); | ||
this.getStub = Sinon.stub(injectorService, "getProvider").returns(this.provider); | ||
this.mapServicesStub = Sinon.stub(injectorService as any, "mapServices").returns((this.dep = new TestDep())); | ||
Store.from(Test).set("scope", "request"); | ||
this.locals = new Map(); | ||
this.result = injectorService.invoke(Test, this.locals, this.designParamTypes, false); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
this.getParamTypesStub.restore(); | ||
// THEN | ||
result.should.instanceof(token); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings method", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call GlobalProviders.get method", () => { | ||
this.getStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("shouldn't call Metadata.getParamTypes method", () => { | ||
return this.getParamTypesStub.should.not.have.been.called; | ||
}); | ||
it("should call settings.onInvoke method", () => { | ||
this.registrySettings.onInvoke.should.have.been.calledWithExactly(this.provider, this.locals, this.designParamTypes); | ||
}); | ||
it("should call injectorService.mapServices method", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
target: Test, | ||
serviceType: TestDep, | ||
locals: this.locals, | ||
requiredScope: false, | ||
parentScope: "request" | ||
}); | ||
}); | ||
it("should return a new instance of the given service", () => { | ||
expect(this.result).to.instanceOf(Test); | ||
}); | ||
it("should injected services into the given service constructor", () => { | ||
expect(this.result.args).to.deep.eq([this.dep]); | ||
}); | ||
}); | ||
describe("when onInvoke is empty", () => { | ||
before( | ||
inject([InjectorService], (injectorService: InjectorService) => { | ||
this.provider = new Provider(Test); | ||
this.registrySettings = {}; | ||
this.designParamTypes = [TestDep]; | ||
describe("when one of dependencies isn't injectable", () => { | ||
it("should throw InjectionError", async () => { | ||
// GIVEN | ||
const token2 = class Ctrl { | ||
}; | ||
const token3 = class Test { | ||
}; | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns(this.registrySettings); | ||
const provider2 = new Provider<any>(token2); | ||
provider2.scope = ProviderScope.SINGLETON; | ||
provider2.type = ProviderType.CONTROLLER; | ||
provider2.useClass = token2; | ||
provider2.injectable = false; | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes").returns(this.designParamTypes); | ||
const provider3 = new Provider<any>(token3); | ||
provider3.scope = ProviderScope.SINGLETON; | ||
provider3.deps = [token2]; | ||
this.getStub = Sinon.stub(injectorService, "getProvider").returns(this.provider); | ||
const injector = new InjectorService(); | ||
injector.set(token2, provider2); | ||
injector.set(token3, provider3); | ||
this.mapServicesStub = Sinon.stub(injectorService as any, "mapServices").returns((this.dep = new TestDep())); | ||
// WHEN | ||
let actualError; | ||
try { | ||
await injector.invoke(token3); | ||
} catch (er) { | ||
actualError = er; | ||
} | ||
Store.from(Test).set("scope", "request"); | ||
this.locals = new Map(); | ||
this.result = injectorService.invoke(Test, this.locals, undefined, false); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
this.getParamTypesStub.restore(); | ||
// THEN | ||
actualError.message.should.eq("Injection failed on Test > Ctrl\nOrigin: Ctrl controller is not injectable to another provider"); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings method", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call GlobalProviders.get method", () => { | ||
this.getStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call Metadata.getParamTypes method", () => { | ||
this.getParamTypesStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call injectorService.mapServices method", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
target: Test, | ||
serviceType: TestDep, | ||
locals: this.locals, | ||
requiredScope: false, | ||
parentScope: "request" | ||
}); | ||
}); | ||
it("should return a new instance of the given service", () => { | ||
expect(this.result).to.instanceOf(Test); | ||
}); | ||
it("should injected services into the given service constructor", () => { | ||
expect(this.result.args).to.deep.eq([this.dep]); | ||
}); | ||
}); | ||
describe("when provider didn't exists", () => { | ||
before( | ||
inject([InjectorService], (injectorService: InjectorService) => { | ||
this.registrySettings = { | ||
onInvoke: Sinon.stub() | ||
}; | ||
this.designParamTypes = [TestDep]; | ||
describe("when error occur", () => { | ||
it("should throw InjectionError", async () => { | ||
// GIVEN | ||
const token1 = Symbol.for("TokenValue"); | ||
const token2 = Symbol.for("TokenFactory"); | ||
const token3 = class Test { | ||
}; | ||
this.getRegistrySettingsStub = Sinon.stub(GlobalProviders, "getRegistrySettings").returns(this.registrySettings); | ||
const provider1 = new Provider<any>(token1); | ||
provider1.scope = ProviderScope.SINGLETON; | ||
provider1.useValue = () => undefined; // should throw error because instance is undefined | ||
this.getParamTypesStub = Sinon.stub(Metadata, "getParamTypes").returns(this.designParamTypes); | ||
const provider2 = new Provider<any>(token2); | ||
provider2.scope = ProviderScope.SINGLETON; | ||
provider2.deps = [token1]; | ||
provider2.useFactory = () => ({}); | ||
this.getStub = Sinon.stub(injectorService, "getProvider").returns(undefined); | ||
const provider3 = new Provider<any>(token3); | ||
provider3.scope = ProviderScope.SINGLETON; | ||
provider3.deps = [token2]; | ||
this.mapServicesStub = Sinon.stub(injectorService as any, "mapServices").returns((this.dep = new TestDep())); | ||
const injector = new InjectorService(); | ||
injector.set(token1, provider1); | ||
injector.set(token2, provider2); | ||
injector.set(token3, provider3); | ||
Store.from(Test).set("scope", "request"); | ||
// WHEN | ||
let actualError; | ||
try { | ||
await injector.invoke(token3); | ||
} catch (er) { | ||
actualError = er; | ||
} | ||
this.locals = new Map(); | ||
this.result = injectorService.invoke(Test, this.locals, undefined, false); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getStub.restore(); | ||
this.getRegistrySettingsStub.restore(); | ||
this.getParamTypesStub.restore(); | ||
// THEN | ||
actualError.message.should.eq("Injection failed on Test > TokenFactory > TokenValue\nOrigin: Unable to create new instance from undefined value. Check your provider declaration for TokenValue"); | ||
}); | ||
it("should call GlobalProviders.getRegistrySettings method", () => { | ||
this.getRegistrySettingsStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call GlobalProviders.get method", () => { | ||
this.getStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("should call Metadata.getParamTypes method", () => { | ||
this.getParamTypesStub.should.have.been.calledWithExactly(Test); | ||
}); | ||
it("shouldn't call settings.onInvoke method", () => { | ||
return this.registrySettings.onInvoke.should.not.have.been.called; | ||
}); | ||
it("should call injectorService.mapServices method", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
target: Test, | ||
serviceType: TestDep, | ||
locals: this.locals, | ||
requiredScope: false, | ||
parentScope: "request" | ||
}); | ||
}); | ||
it("should return a new instance of the given service", () => { | ||
expect(this.result).to.instanceOf(Test); | ||
}); | ||
it("should injected services into the given service constructor", () => { | ||
expect(this.result.args).to.deep.eq([this.dep]); | ||
}); | ||
}); | ||
}); | ||
describe("invokeMethod()", () => { | ||
class InjectTest { | ||
} | ||
describe("when designParamTypes is given", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.injector = injector; | ||
this.mapServicesStub = Sinon.stub(this.injector, "mapServices"); | ||
this.mapServicesStub.returns(new InjectTest()); | ||
this.getParamsTypesStub = Sinon.stub(Metadata, "getParamTypes"); | ||
this.handler = Sinon.stub(); | ||
this.injector.invokeMethod(this.handler, { | ||
target: Test, | ||
methodName: "test", | ||
locals: "locals", | ||
designParamTypes: [InjectTest] | ||
}); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getParamsTypesStub.restore(); | ||
}); | ||
it("should call injectorService.mapServices()", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
serviceType: InjectTest, | ||
target: Test, | ||
locals: "locals", | ||
requiredScope: false, | ||
parentScope: false | ||
}); | ||
}); | ||
it("shouldn't call Metadata.getParamTypes()", () => { | ||
this.getParamsTypesStub.should.not.have.been.called; | ||
}); | ||
it("should call the handler", () => { | ||
this.handler.should.have.been.calledWithExactly(new InjectTest()); | ||
}); | ||
}); | ||
describe("when designParamTypes is not given", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.injector = injector; | ||
this.mapServicesStub = Sinon.stub(this.injector, "mapServices"); | ||
this.mapServicesStub.returns(new InjectTest()); | ||
this.getParamsTypesStub = Sinon.stub(Metadata, "getParamTypes").returns([InjectTest]); | ||
this.handler = Sinon.stub(); | ||
this.injector.invokeMethod(this.handler, { | ||
target: Test, | ||
methodName: "test", | ||
locals: "locals" | ||
}); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getParamsTypesStub.restore(); | ||
}); | ||
it("shouldn't call Metadata.getParamTypes()", () => { | ||
this.getParamsTypesStub.should.have.been.calledWithExactly(Test.prototype, "test"); | ||
}); | ||
it("should call injectorService.mapServices()", () => { | ||
this.mapServicesStub.should.have.been.calledWithExactly({ | ||
serviceType: InjectTest, | ||
target: Test, | ||
locals: "locals", | ||
requiredScope: false, | ||
parentScope: false | ||
}); | ||
}); | ||
it("should call the handler", () => { | ||
this.handler.should.have.been.calledWithExactly(new InjectTest()); | ||
}); | ||
}); | ||
describe("when handler is already injected", () => { | ||
before( | ||
inject([InjectorService], (injector: InjectorService) => { | ||
this.injector = injector; | ||
this.mapServicesStub = Sinon.stub(this.injector, "mapServices"); | ||
this.mapServicesStub.returns(new InjectTest()); | ||
this.getParamsTypesStub = Sinon.stub(Metadata, "getParamTypes"); | ||
this.handler = Sinon.stub(); | ||
this.handler.$injected = true; | ||
this.injector.invokeMethod(this.handler, { | ||
target: Test, | ||
methodName: "test", | ||
locals: "locals" | ||
}); | ||
}) | ||
); | ||
after(TestContext.reset); | ||
after(() => { | ||
this.mapServicesStub.restore(); | ||
this.getParamsTypesStub.restore(); | ||
}); | ||
it("shouldn't call Metadata.getParamTypes()", () => { | ||
this.getParamsTypesStub.should.not.have.been.called; | ||
}); | ||
it("shouldn't call injectorService.mapServices()", () => { | ||
this.mapServicesStub.should.not.have.been.called; | ||
}); | ||
it("should call the handler", () => { | ||
this.handler.should.have.been.calledWithExactly("locals"); | ||
}); | ||
}); | ||
}); | ||
describe("bindInjectableProperties()", () => { | ||
@@ -1249,3 +506,3 @@ const sandbox = Sinon.createSandbox(); | ||
describe("bindValue()", () => { | ||
it("should bind a property with a value", () => { | ||
it("should bind a property with a value (1)", () => { | ||
// GIVEN | ||
@@ -1262,6 +519,18 @@ const injector = new InjectorService(); | ||
}); | ||
it("should bind a property with a value (2)", () => { | ||
// GIVEN | ||
const injector = new InjectorService(); | ||
const instance = new Test(); | ||
// WHEN | ||
injector.bindValue(instance, {propertyKey: "value", expression: "UNKNOW", defaultValue: "test2"} as any); | ||
// THEN | ||
expect(instance.value).to.eq("test2"); | ||
}); | ||
}); | ||
describe("bindConstant()", () => { | ||
it("should bind a property with a value", () => { | ||
it("should bind a property with a value (1)", () => { | ||
// GIVEN | ||
@@ -1287,2 +556,14 @@ const injector = new InjectorService(); | ||
}); | ||
it("should bind a property with a value (2)", () => { | ||
// GIVEN | ||
const injector = new InjectorService(); | ||
const instance = new Test(); | ||
// WHEN | ||
injector.bindConstant(instance, {propertyKey: "constant", expression: "UNKNOW", defaultValue: "test"} as any); | ||
// THEN | ||
expect(instance.constant).to.eq("test"); | ||
}); | ||
}); | ||
@@ -1294,5 +575,4 @@ | ||
after(() => sandbox.restore()); | ||
afterEach(() => sandbox.resetHistory()); | ||
it("should bind the method and return result", async () => { | ||
it("should bind the method", async () => { | ||
// GIVEN | ||
@@ -1306,6 +586,10 @@ class InterceptorTest { | ||
const injector = new InjectorService(); | ||
injector.addProvider(InterceptorTest); | ||
await injector.load(); | ||
const instance = new Test(); | ||
const originalMethod = instance["test3"]; | ||
const originalMethod = instance["test"]; | ||
sandbox.stub(injector, "get").returns(new InterceptorTest()); | ||
sandbox.spy(injector, "get"); | ||
@@ -1327,37 +611,3 @@ // WHEN | ||
}); | ||
it("should bind the method and throw error", async () => { | ||
// GIVEN | ||
class InterceptorTest { | ||
aroundInvoke(ctx: any) { | ||
return ctx.proceed(new Error()); | ||
} | ||
} | ||
const injector = new InjectorService(); | ||
const instance = new Test(); | ||
const originalMethod = instance["test3"]; | ||
sandbox.stub(injector, "get").returns(new InterceptorTest()); | ||
// WHEN | ||
injector.bindInterceptor(instance, { | ||
bindingType: "interceptor", | ||
propertyKey: "test3", | ||
useType: InterceptorTest | ||
} as any); | ||
let actualError; | ||
try { | ||
(instance as any).test3("test"); | ||
} catch (er) { | ||
actualError = er; | ||
} | ||
// THEN | ||
expect(originalMethod).should.not.eq(instance.test3); | ||
injector.get.should.have.been.calledWithExactly(InterceptorTest); | ||
actualError.should.instanceOf(Error); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
298313
198
5184
155