Comparing version 4.1.0 to 4.2.0
@@ -10,2 +10,3 @@ "use strict"; | ||
const error_helpers_1 = require("./error-helpers"); | ||
const lazy_helpers_1 = require("./lazy-helpers"); | ||
exports.typeInfo = new Map(); | ||
@@ -80,3 +81,6 @@ class InternalDependencyContainer { | ||
} | ||
return this.construct(token, context); | ||
if (injection_token_1.isConstructorToken(token)) { | ||
return this.construct(token, context); | ||
} | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function."); | ||
} | ||
@@ -137,2 +141,12 @@ resolveRegistration(registration, context) { | ||
} | ||
clearInstances() { | ||
for (const [token, registrations] of this._registry.entries()) { | ||
this._registry.setAll(token, registrations | ||
.filter(registration => !providers_1.isValueProvider(registration.provider)) | ||
.map(registration => { | ||
registration.instance = undefined; | ||
return registration; | ||
})); | ||
} | ||
} | ||
createChildContainer() { | ||
@@ -174,4 +188,4 @@ const childContainer = new InternalDependencyContainer(this); | ||
construct(ctor, context) { | ||
if (typeof ctor === "undefined") { | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem."); | ||
if (ctor instanceof lazy_helpers_1.DelayedConstructor) { | ||
return ctor.createProxy((target) => this.resolve(target, context)); | ||
} | ||
@@ -178,0 +192,0 @@ if (ctor.length === 0) { |
@@ -12,3 +12,5 @@ "use strict"; | ||
tslib_1.__exportStar(require("./providers"), exports); | ||
var lazy_helpers_1 = require("./lazy-helpers"); | ||
exports.delay = lazy_helpers_1.delay; | ||
var dependency_container_1 = require("./dependency-container"); | ||
exports.container = dependency_container_1.instance; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lazy_helpers_1 = require("../lazy-helpers"); | ||
function isNormalToken(token) { | ||
@@ -13,1 +14,5 @@ return typeof token === "string" || typeof token === "symbol"; | ||
exports.isTokenDescriptor = isTokenDescriptor; | ||
function isConstructorToken(token) { | ||
return typeof token === "function" || token instanceof lazy_helpers_1.DelayedConstructor; | ||
} | ||
exports.isConstructorToken = isConstructorToken; |
import { isClassProvider, isFactoryProvider, isNormalToken, isTokenProvider, isValueProvider } from "./providers"; | ||
import { isProvider } from "./providers/provider"; | ||
import { isTokenDescriptor } from "./providers/injection-token"; | ||
import { isConstructorToken, isTokenDescriptor } from "./providers/injection-token"; | ||
import Registry from "./registry"; | ||
@@ -8,2 +8,3 @@ import Lifecycle from "./types/lifecycle"; | ||
import { formatErrorCtor } from "./error-helpers"; | ||
import { DelayedConstructor } from "./lazy-helpers"; | ||
export const typeInfo = new Map(); | ||
@@ -78,3 +79,6 @@ class InternalDependencyContainer { | ||
} | ||
return this.construct(token, context); | ||
if (isConstructorToken(token)) { | ||
return this.construct(token, context); | ||
} | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function."); | ||
} | ||
@@ -135,2 +139,12 @@ resolveRegistration(registration, context) { | ||
} | ||
clearInstances() { | ||
for (const [token, registrations] of this._registry.entries()) { | ||
this._registry.setAll(token, registrations | ||
.filter(registration => !isValueProvider(registration.provider)) | ||
.map(registration => { | ||
registration.instance = undefined; | ||
return registration; | ||
})); | ||
} | ||
} | ||
createChildContainer() { | ||
@@ -172,4 +186,4 @@ const childContainer = new InternalDependencyContainer(this); | ||
construct(ctor, context) { | ||
if (typeof ctor === "undefined") { | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem."); | ||
if (ctor instanceof DelayedConstructor) { | ||
return ctor.createProxy((target) => this.resolve(target, context)); | ||
} | ||
@@ -176,0 +190,0 @@ if (ctor.length === 0) { |
@@ -8,2 +8,3 @@ if (typeof Reflect === "undefined" || !Reflect.getMetadata) { | ||
export * from "./providers"; | ||
export { delay } from "./lazy-helpers"; | ||
export { instance as container } from "./dependency-container"; |
@@ -0,1 +1,2 @@ | ||
import { DelayedConstructor } from "../lazy-helpers"; | ||
export function isNormalToken(token) { | ||
@@ -9,1 +10,4 @@ return typeof token === "string" || typeof token === "symbol"; | ||
} | ||
export function isConstructorToken(token) { | ||
return typeof token === "function" || token instanceof DelayedConstructor; | ||
} |
import { __read, __spread, __values } from "tslib"; | ||
import { isClassProvider, isFactoryProvider, isNormalToken, isTokenProvider, isValueProvider } from "./providers"; | ||
import { isProvider } from "./providers/provider"; | ||
import { isTokenDescriptor } from "./providers/injection-token"; | ||
import { isConstructorToken, isTokenDescriptor } from "./providers/injection-token"; | ||
import Registry from "./registry"; | ||
@@ -9,2 +9,3 @@ import Lifecycle from "./types/lifecycle"; | ||
import { formatErrorCtor } from "./error-helpers"; | ||
import { DelayedConstructor } from "./lazy-helpers"; | ||
export var typeInfo = new Map(); | ||
@@ -81,3 +82,6 @@ var InternalDependencyContainer = (function () { | ||
} | ||
return this.construct(token, context); | ||
if (isConstructorToken(token)) { | ||
return this.construct(token, context); | ||
} | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function."); | ||
}; | ||
@@ -143,4 +147,25 @@ InternalDependencyContainer.prototype.resolveRegistration = function (registration, context) { | ||
}; | ||
InternalDependencyContainer.prototype.clearInstances = function () { | ||
var e_1, _a; | ||
try { | ||
for (var _b = __values(this._registry.entries()), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var _d = __read(_c.value, 2), token = _d[0], registrations = _d[1]; | ||
this._registry.setAll(token, registrations | ||
.filter(function (registration) { return !isValueProvider(registration.provider); }) | ||
.map(function (registration) { | ||
registration.instance = undefined; | ||
return registration; | ||
})); | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
}; | ||
InternalDependencyContainer.prototype.createChildContainer = function () { | ||
var e_1, _a; | ||
var e_2, _a; | ||
var childContainer = new InternalDependencyContainer(this); | ||
@@ -166,3 +191,3 @@ try { | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
@@ -172,3 +197,3 @@ try { | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
@@ -196,4 +221,7 @@ return childContainer; | ||
InternalDependencyContainer.prototype.construct = function (ctor, context) { | ||
if (typeof ctor === "undefined") { | ||
throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem."); | ||
var _this = this; | ||
if (ctor instanceof DelayedConstructor) { | ||
return ctor.createProxy(function (target) { | ||
return _this.resolve(target, context); | ||
}); | ||
} | ||
@@ -200,0 +228,0 @@ if (ctor.length === 0) { |
@@ -8,2 +8,3 @@ if (typeof Reflect === "undefined" || !Reflect.getMetadata) { | ||
export * from "./providers"; | ||
export { delay } from "./lazy-helpers"; | ||
export { instance as container } from "./dependency-container"; |
@@ -0,1 +1,2 @@ | ||
import { DelayedConstructor } from "../lazy-helpers"; | ||
export function isNormalToken(token) { | ||
@@ -9,1 +10,4 @@ return typeof token === "string" || typeof token === "symbol"; | ||
} | ||
export function isConstructorToken(token) { | ||
return typeof token === "function" || token instanceof DelayedConstructor; | ||
} |
@@ -5,2 +5,3 @@ export { DependencyContainer, Lifecycle, RegistrationOptions } from "./types"; | ||
export * from "./providers"; | ||
export { delay } from "./lazy-helpers"; | ||
export { instance as container } from "./dependency-container"; |
import constructor from "../types/constructor"; | ||
import Provider from "./provider"; | ||
import { DelayedConstructor } from "../lazy-helpers"; | ||
export default interface ClassProvider<T> { | ||
useClass: constructor<T>; | ||
useClass: constructor<T> | DelayedConstructor<T>; | ||
} | ||
export declare function isClassProvider<T>(provider: Provider<T>): provider is ClassProvider<any>; |
import constructor from "../types/constructor"; | ||
declare type InjectionToken<T = any> = constructor<T> | string | symbol; | ||
import { DelayedConstructor } from "../lazy-helpers"; | ||
declare type InjectionToken<T = any> = constructor<T> | string | symbol | DelayedConstructor<T>; | ||
export declare function isNormalToken(token?: InjectionToken<any>): token is string | symbol; | ||
export declare function isTokenDescriptor(descriptor: any): descriptor is TokenDescriptor; | ||
export declare function isConstructorToken(token?: InjectionToken<any>): token is constructor<any> | DelayedConstructor<any>; | ||
export interface TokenDescriptor { | ||
@@ -6,0 +8,0 @@ token: InjectionToken<any>; |
@@ -38,3 +38,4 @@ import FactoryProvider from "../providers/factory-provider"; | ||
reset(): void; | ||
clearInstances(): void; | ||
createChildContainer(): DependencyContainer; | ||
} |
{ | ||
"name": "tsyringe", | ||
"version": "4.1.0", | ||
"version": "4.2.0", | ||
"description": "Lightweight dependency injection container for JavaScript/TypeScript", | ||
@@ -5,0 +5,0 @@ "main": "dist/cjs/index.js", |
157
README.md
@@ -29,2 +29,6 @@ [![Travis](https://img.shields.io/travis/Microsoft/tsyringe.svg)](https://travis-ci.org/Microsoft/tsyringe/) | ||
- [Child Containers](#child-containers) | ||
- [Clearing Instances](#clearing-instances) | ||
- [Circular dependencies](#circular-dependencies) | ||
- [The `delay` helper function](#the-delay-helper-function) | ||
- [Interfaces and circular dependencies](#interfaces-and-circular-dependencies) | ||
- [Full examples](#full-examples) | ||
@@ -229,6 +233,6 @@ - [Example without interfaces](#example-without-interfaces) | ||
A token may be either a string, a symbol, or a class constructor. | ||
A token may be either a string, a symbol, a class constructor, or a instance of [`DelayedConstructor`](#circular-dependencies). | ||
```typescript | ||
type InjectionToken<T = any> = constructor<T> | string | symbol; | ||
type InjectionToken<T = any> = constructor<T> | DelayedConstructor<T> | string | symbol; | ||
``` | ||
@@ -346,9 +350,7 @@ | ||
```TypeScript | ||
@injectable() | ||
@registry([ | ||
Foo, | ||
Bar, | ||
{ | ||
token: "IFoobar", | ||
useClass: MockFoobar | ||
{ token: Foobar, useClass: Foobar }, | ||
{ token: "theirClass", useFactory: (c) => { | ||
return new TheirClass( "arg" ) | ||
}, | ||
} | ||
@@ -359,5 +361,8 @@ ]) | ||
This is useful when you don't control the entry point for your code (e.g. being instantiated by a framework), and need | ||
an opportunity to do registration. Otherwise, it's preferable to use `.register()`. **Note** the `@injectable()` decorator | ||
must precede the `@registry()` decorator, since TypeScript executes decorators inside out. | ||
This is useful when you want to [register multiple classes for the same token](#register). | ||
You can also use it to register and declare objects that wouldn't be imported by anything else, | ||
such as more classes annotated with `@registry` or that are otherwise responsible for registering objects. | ||
Lastly you might choose to use this to register 3rd party instances instead of the `container.register(...)` method. | ||
note: if you want this class to be `@injectable` you must put the decorator before `@registry`, this annotation is not | ||
required though. | ||
@@ -394,5 +399,7 @@ ### Resolution | ||
``` | ||
### Child Containers | ||
If you need to have multiple containers that have disparate sets of registrations, you can create child containers | ||
If you need to have multiple containers that have disparate sets of registrations, you can create child containers: | ||
```typescript | ||
@@ -403,4 +410,130 @@ const childContainer1 = container.createChildContainer(); | ||
``` | ||
Each of the child containers will have independent registrations, but if a registration is absent in the child container at resolution, the token will be resolved from the parent. This allows for a set of common services to be registered at the root, with specialized services registered on the child. This can be useful, for example, if you wish to create per-request containers that use common stateless services from the root container. | ||
### Clearing Instances | ||
The `container.clearInstances()` method allows you to clear all previously created and registered instances: | ||
```typescript | ||
class Foo {} | ||
@singleton() | ||
class Bar {} | ||
const myFoo = new Foo(); | ||
container.registerInstance("Test", myFoo); | ||
const myBar = container.resolve(Bar); | ||
container.clearInstances(); | ||
container.resolve("Test"); // throws error | ||
const myBar2 = container.resolve(Bar); // myBar !== myBar2 | ||
const myBar3 = container.resolve(Bar); // myBar2 === myBar3 | ||
``` | ||
Unlike with `container.reset()`, the registrations themselves are not cleared. | ||
This is especially useful for testing: | ||
```typescript | ||
@singleton() | ||
class Foo {} | ||
beforeEach(() => { | ||
container.clearInstances(); | ||
}); | ||
test("something", () => { | ||
container.resolve(Foo); // will be a new singleton instance in every test | ||
}); | ||
``` | ||
# Circular dependencies | ||
Sometimes you need to inject services that have cyclic dependencies between them. As an example: | ||
```typescript | ||
@injectable() | ||
export class Foo { | ||
constructor(public bar: Bar) {} | ||
} | ||
@injectable() | ||
export class Bar { | ||
constructor(public foo: Foo) {} | ||
} | ||
``` | ||
Trying to resolve one of the services will end in an error because always one of the constructor will not be fully defined to construct the other one. | ||
```typescript | ||
container.resolve(Foo) | ||
``` | ||
``` | ||
Error: Cannot inject the dependency at position #0 of "Foo" constructor. Reason: | ||
Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function. | ||
``` | ||
### The `delay` helper function | ||
The best way to deal with this situation is to do some kind of refactor to avoid the cyclic dependencies. Usually this implies introducing additional services to cut the cycles. | ||
But when refactor is not an option you can use the `delay` function helper. The `delay` function wraps the constructor in an instance of `DelayedConstructor`. | ||
The *delayed constructor* is a kind of special `InjectionToken` that will eventually be evaluated to construct an intermediate proxy object wrapping a factory for the real object. | ||
When the proxy object is used for the first time it will construct a real object using this factory and any usage will be forwarded to the real object. | ||
```typescript | ||
@injectable() | ||
export class Foo { | ||
constructor(@inject(delay(Bar)) public bar: Bar) {} | ||
} | ||
@injectable() | ||
export class Bar { | ||
constructor(@inject(delay(Foo)) public foo: Foo) {} | ||
} | ||
// construction of foo is possible | ||
const foo = container.resolve(Foo); | ||
// property bar will hold a proxy that looks and acts as a real Bar instance. | ||
foo.bar instanceof Bar; // true | ||
``` | ||
### Interfaces and circular dependencies | ||
We can rest in the fact that a `DelayedConstructor` could be used in the same contexts that a constructor and will be handled transparently by tsyringe. Such idea is used in the next example involving interfaces: | ||
```typescript | ||
export interface IFoo {} | ||
@injectable() | ||
@registry([ | ||
{ | ||
token: "IBar", | ||
// `DelayedConstructor` of Bar will be the token | ||
useToken: delay(Bar) | ||
} | ||
]) | ||
export class Foo implements IFoo { | ||
constructor(@inject("IBar") public bar: IBar) {} | ||
} | ||
export interface IBar {} | ||
@injectable() | ||
@registry([ | ||
{ | ||
token: "IFoo", | ||
useToken: delay(Foo) | ||
} | ||
]) | ||
export class Bar implements IBar { | ||
constructor(@inject("IFoo") public foo: IFoo) {} | ||
} | ||
``` | ||
# Full examples | ||
@@ -407,0 +540,0 @@ |
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
97419
131
1918
628