typed-inject
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -0,1 +1,10 @@ | ||
## [0.2.1](https://github.com/nicojs/typed-inject/compare/v0.2.0...v0.2.1) (2019-02-11) | ||
### Features | ||
* **dispose:** Add functionality to explicit disposing of dependencies ([#1](https://github.com/nicojs/typed-inject/issues/1)) ([02b4946](https://github.com/nicojs/typed-inject/commit/02b4946)) | ||
# [0.2.0](https://github.com/nicojs/typed-inject/compare/v0.1.1...v0.2.0) (2019-02-05) | ||
@@ -2,0 +11,0 @@ |
{ | ||
"name": "typed-inject", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "Type safe dependency injection framework for TypeScript", | ||
@@ -53,2 +53,4 @@ "main": "src/index.js", | ||
"@types/node": "^10.12.18", | ||
"@types/sinon": "^7.0.5", | ||
"@types/sinon-chai": "^3.2.2", | ||
"chai": "^4.2.0", | ||
@@ -59,2 +61,4 @@ "conventional-changelog-cli": "^2.0.11", | ||
"rimraf": "^2.6.3", | ||
"sinon": "^7.2.3", | ||
"sinon-chai": "^3.3.0", | ||
"source-map-support": "^0.5.10", | ||
@@ -61,0 +65,0 @@ "stryker": "^0.34.0", |
172
README.md
@@ -101,24 +101,3 @@ [![Build Status](https://travis-ci.org/nicojs/typed-inject.svg?branch=master)](https://travis-ci.org/nicojs/typed-inject) | ||
## ✨ Magic tokens | ||
Any `Injector` instance can always inject the following tokens: | ||
| Token name | Token value | Description | | ||
| - | - | - | | ||
| `INJECTOR_TOKEN` | `'$injector'` | Injects the current injector | | ||
| `TARGET_TOKEN` | `'$target'` | The class or function in which the current values is injected, or `undefined` if resolved directly | | ||
An example: | ||
```ts | ||
import { rootInjector, Injector, tokens, TARGET_TOKEN, INJECTOR_TOKEN } from 'typed-inject'; | ||
class Foo { | ||
constructor(injector: Injector<{}>, target: Function | undefined) {} | ||
static inject = tokens(INJECTOR_TOKEN, TARGET_TOKEN); | ||
} | ||
const foo = rootInjector.inject(Foo); | ||
``` | ||
## 💭 Motivation | ||
@@ -148,2 +127,129 @@ | ||
## 👶 Creating child injectors | ||
The `Injector` interface is responsible for injecting classes of functions. However, `typed-inject` only comes with one implementation: the `rootInjector`. It does not provide any dependencies (expect for [magic tokens](#-magic-tokens)). | ||
In order to do anything useful with the `rootInjector`, you'll need to create child injectors. This what you do with the `provideXXX` methods. | ||
```ts | ||
import { rootInjector, tokens } from 'typed-inject'; | ||
function barFactory(foo: number){ return foo + 1}; | ||
barFactory.inject = tokens('foo'); | ||
class Baz { | ||
constructor(bar: number){ console.log(`bar is: ${bar}`)}; | ||
static inject = tokens('bar'); | ||
} | ||
const childInjector = rootInjector | ||
.provideValue('foo', 42) | ||
.provideFactory('bar', barFactory) | ||
.provideClass('baz', Baz); | ||
``` | ||
In the example above, a child injector is created. It can provide values for the tokens `'foo'`, `'bar'` and `'baz'`. You can create as many child injectors as you want. | ||
The `rootInjector` always remains stateless. So don't worry about reusing it in your tests or reusing it for different parts of your application. However, | ||
any ChildInjector _is stateful_. For example, it can [cache the injected value](#-control-lifecycle) or [keep track of stuff to dispose](#-disposing-provided-stuff) | ||
## ♻ Control lifecycle | ||
You can determine the lifecycle of dependencies with the third `Scope` parameter of `provideFactory` and `provideClass` methods. | ||
```ts | ||
function loggerFactory(target: Function | null){ | ||
return getLogger((target && target.name) || 'UNKNOWN'); | ||
} | ||
loggerFactory.inject('target'); | ||
class Foo { | ||
constructor(public log: Logger) { log.info('Foo created'); } | ||
static inject = tokens('log'); | ||
} | ||
const fooProvider = injector | ||
.provideFactory('log', loggerFactory, Scope.Transient) | ||
.provideClass('foo', Foo, Scope.Singleton); | ||
const foo = fooProvider.resolve('foo'); | ||
const fooCopy = fooProvider.resolve('foo'); | ||
const log = fooProvider.resolve('log'); | ||
console.log(foo === fooCopy); // => true | ||
console.log(log === foo.log); // => false | ||
``` | ||
A scope has 2 possible values. | ||
* `Scope.Singleton` (default value) | ||
Use `Scope.Singleton` to enable caching. Every time the dependency needs to be provided by the injector, the same instance is returned. Other injectors will still create their own instances, so it's only a `Singleton` for the specific injector (and child injectors created from it). In other words, | ||
the instance will be _scoped to the `Injector`_ | ||
* `Scope.Transient` | ||
Use `Scope.Transient` to completely disable cashing. You'll always get fresh instances. | ||
## 🚮 Disposing provided stuff | ||
Memory in JavaScript is garbage collected, so usually we don't care about cleaning up after ourselves. However, there might be a need to explicit clean up. For example removing a temp folder, or killing a child process. | ||
As `typed-inject` is responsible for creating (providing) your dependencies, it only makes sense it is also responsible for the disposing of them. | ||
Any `Injector` has a `dispose` method. If you call it, the injector in turn will call `dispose` on any instance that was ever created from it (if it has one). | ||
```ts | ||
import { rootInjector } from 'typed-inject'; | ||
class Foo { | ||
constructor() { console.log('Foo created'); } | ||
dispose(){ console.log('Foo disposed');} | ||
} | ||
const fooProvider = rootInjector.provideClass('foo', Foo); | ||
fooProvider.resolve('foo'); // => "Foo created" | ||
fooProvider.dispose(); // => "Foo disposed" | ||
fooProvider.resolve('foo'); // Error: Injector already disposed | ||
``` | ||
To help you implementing the `dispose` method correctly, `typed-inject` exports the `Disposable` interface for convenience: | ||
```ts | ||
import { Disposable } from 'typed-inject'; | ||
class Foo implements Disposable { | ||
dispose(){ } | ||
} | ||
``` | ||
Using `dispose` on an injector will automatically dispose it's parent injectors as well: | ||
```ts | ||
import { rootInjector } from 'typed-inject'; | ||
class Foo { } | ||
class Bar { } | ||
const fooProvider = rootInjector.provideClass('foo', Foo); | ||
const barProvider = fooProvider.provideClass('bar', Bar); | ||
barProvider.dispose(); // => fooProvider is also disposed! | ||
fooProvider.resolve('foo'); // => Error: Injector already disposed | ||
``` | ||
Disposing of provided values is done in order of parent first. So they are disposed in the order of respective `providedXXX` calls. | ||
Any instance created with `injectClass` or `injectFactory` will _not_ be disposed when `dispose` is called. You were responsible for creating it, so you are also responsible for the disposing of it. In the same vain, anything provided as a value with `providedValue` will also _not_ be disposed when `dispose` is called on it's injector. | ||
## ✨ Magic tokens | ||
Any `Injector` instance can always inject the following tokens: | ||
| Token name | Token value | Description | | ||
| - | - | - | | ||
| `INJECTOR_TOKEN` | `'$injector'` | Injects the current injector | | ||
| `TARGET_TOKEN` | `'$target'` | The class or function in which the current values is injected, or `undefined` if resolved directly | | ||
An example: | ||
```ts | ||
import { rootInjector, Injector, tokens, TARGET_TOKEN, INJECTOR_TOKEN } from 'typed-inject'; | ||
class Foo { | ||
constructor(injector: Injector<{}>, target: Function | undefined) {} | ||
static inject = tokens(INJECTOR_TOKEN, TARGET_TOKEN); | ||
} | ||
const foo = rootInjector.inject(Foo); | ||
``` | ||
## 📖 API reference | ||
@@ -159,3 +265,3 @@ | ||
Typed inject comes with only one implementation. The `rootInjector`. It implements `Injector<{}>` interface, meaning that it does not provide any tokens (except for [magic tokens](#magic-tokens)) Import it with `import { rootInjector } from 'typed-inject'`. From the `rootInjector`, you can create child injectors. | ||
Typed inject comes with only one implementation. The `rootInjector`. It implements `Injector<{}>` interface, meaning that it does not provide any tokens (except for [magic tokens](#-magic-tokens)). Import it with `import { rootInjector } from 'typed-inject'`. From the `rootInjector`, you can create child injectors. See [creating child injectors](#-creating-child-injectors) for more information. | ||
@@ -233,2 +339,10 @@ Don't worry about reusing the `rootInjector` in your application. It is stateless and read-only, so safe for concurrent use. | ||
#### `injector.dispose()` | ||
Use `dispose` to explicitly dispose the `injector`. It will in turn call `dispose` on it's parent injector as well as calling `dispose` on any dependency created by the injector (if it exists) using `provideClass` or `provideFactory` (**not** `provideValue` or `injectXXX`). | ||
After a child injector is disposed, you cannot us it any more. Any attempt to use it will result in a `Injector already disposed` error. | ||
The `rootInjector` will never be disposed. | ||
### `Scope` | ||
@@ -267,2 +381,16 @@ | ||
### `Disposable` | ||
You can implement the `Disposable` interface in your dependencies. It looks like this: | ||
```ts | ||
interface Disposable { | ||
dispose(): void; | ||
} | ||
``` | ||
With this, you can let the `Injector` call [your dispose method](#-disposing-provided-stuff). | ||
_Note:_ This is just a convenience interface. Due to TypeScripts structural typing system `typed-inject` calls your `dispose` method without you having to explicitly implement it. | ||
## 🤝 Commendation | ||
@@ -269,0 +397,0 @@ |
@@ -17,3 +17,4 @@ import { InjectableClass, InjectableFunction } from './Injectable'; | ||
} & TContext>; | ||
dispose(): void; | ||
} | ||
//# sourceMappingURL=Injector.d.ts.map |
@@ -8,2 +8,3 @@ export * from './api/Injectable'; | ||
export * from './tokens'; | ||
export * from './api/Disposable'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -14,15 +14,15 @@ "use strict"; | ||
┗━━━━━━━━━━━━━━━━━━┛ | ||
▲ | ||
┃ | ||
┏━━━━━━┻━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓ | ||
┃ ┃ ┃ | ||
┏━━━━━━━━┻━━━━━┓ ┏━━━━━━━━━━━━┻━━━━━━━━━━━┓ ┏━━━━━━━┻━━━━━━━┓ | ||
┃ RootInjector ┃ ┃ AbstractCachedInjector ┃ ┃ ValueInjector ┃ | ||
┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ | ||
▲ | ||
┃ | ||
┏━━━━━━━┻━━━━━━━━━━━━┓ | ||
┏━━━━━━━━┻━━━━━━━━┓ ┏━━━━━━━━┻━━━━━━┓ | ||
┃ FactoryInjector ┃ ┃ ClassInjector ┃ | ||
┗━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ | ||
▲ | ||
┃ | ||
┏━━━━━━━━┻━━━━━━━━┓ | ||
┃ ┃ | ||
┏━━━━━━━━┻━━━━━┓ ┏━━━━━━━┻━━━━━━━┓ | ||
┃ RootInjector ┃ ┃ ChildInjector ┃ | ||
┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ | ||
▲ | ||
┃ | ||
┏━━━━━━━━━━━━━━━━━┻━┳━━━━━━━━━━━━━━━━┓ | ||
┏━━━━━━━━┻━━━━━━━━┓ ┏━━━━━━━━┻━━━━━━┓ ┏━━━━━━━┻━━━━━━━┓ | ||
┃ FactoryInjector ┃ ┃ ClassInjector ┃ ┃ ValueInjector ┃ | ||
┗━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ | ||
*/ | ||
@@ -57,3 +57,3 @@ class AbstractInjector { | ||
default: | ||
return this.resolve(key, injectable); | ||
return this.resolveInternal(key, injectable); | ||
} | ||
@@ -63,14 +63,17 @@ }); | ||
provideValue(token, value) { | ||
return new ValueInjector(this, token, value); | ||
return new ValueProvider(this, token, value); | ||
} | ||
provideClass(token, Class, scope = DEFAULT_SCOPE) { | ||
return new ClassInjector(this, token, scope, Class); | ||
return new ClassProvider(this, token, scope, Class); | ||
} | ||
provideFactory(token, factory, scope = DEFAULT_SCOPE) { | ||
return new FactoryInjector(this, token, scope, factory); | ||
return new FactoryProvider(this, token, scope, factory); | ||
} | ||
resolve(token, providedIn) { | ||
return this.resolveInternal(token, providedIn); | ||
resolve(token, target) { | ||
return this.resolveInternal(token, target); | ||
} | ||
} | ||
function isDisposable(maybeDisposable) { | ||
return maybeDisposable && maybeDisposable.dispose && typeof maybeDisposable.dispose === 'function'; | ||
} | ||
class RootInjector extends AbstractInjector { | ||
@@ -80,25 +83,46 @@ resolveInternal(token) { | ||
} | ||
dispose() { | ||
// noop, root injector cannot be disposed | ||
} | ||
} | ||
class ValueInjector extends AbstractInjector { | ||
constructor(parent, token, value) { | ||
class ChildInjector extends AbstractInjector { | ||
constructor(parent, token, scope) { | ||
super(); | ||
this.parent = parent; | ||
this.token = token; | ||
this.value = value; | ||
this.scope = scope; | ||
this.disposables = new Set(); | ||
this.isDisposed = false; | ||
} | ||
resolveInternal(token, target) { | ||
if (token === this.token) { | ||
return this.value; | ||
injectClass(Class, providedIn) { | ||
this.throwIfDisposed(Class); | ||
return super.injectClass(Class, providedIn); | ||
} | ||
injectFunction(fn, providedIn) { | ||
this.throwIfDisposed(fn); | ||
return super.injectFunction(fn, providedIn); | ||
} | ||
resolve(token, target) { | ||
this.throwIfDisposed(token); | ||
return super.resolve(token, target); | ||
} | ||
throwIfDisposed(injectableOrToken) { | ||
if (this.isDisposed) { | ||
throw new Exception_1.Exception(`Injector is already disposed. Please don't use it anymore.${additionalErrorMessage()}`); | ||
} | ||
else { | ||
return this.parent.resolve(token, target); | ||
function additionalErrorMessage() { | ||
if (typeof injectableOrToken === 'function') { | ||
return ` Tried to inject "${injectableOrToken.name}".`; | ||
} | ||
else { | ||
return ` Tried to resolve "${injectableOrToken}".`; | ||
} | ||
} | ||
} | ||
} | ||
class AbstractCachedInjector extends AbstractInjector { | ||
constructor(parent, token, scope) { | ||
super(); | ||
this.parent = parent; | ||
this.token = token; | ||
this.scope = scope; | ||
dispose() { | ||
if (!this.isDisposed) { | ||
this.parent.dispose(); | ||
this.isDisposed = true; | ||
this.disposables.forEach(disposable => disposable.dispose()); | ||
} | ||
} | ||
@@ -112,2 +136,5 @@ resolveInternal(token, target) { | ||
const value = this.result(target); | ||
if (this.responsibleForDisposing && isDisposable(value)) { | ||
this.disposables.add(value); | ||
} | ||
if (this.scope === Scope_1.Scope.Singleton) { | ||
@@ -124,6 +151,17 @@ this.cached = { value }; | ||
} | ||
class FactoryInjector extends AbstractCachedInjector { | ||
class ValueProvider extends ChildInjector { | ||
constructor(parent, token, value) { | ||
super(parent, token, Scope_1.Scope.Transient); | ||
this.value = value; | ||
this.responsibleForDisposing = false; | ||
} | ||
result() { | ||
return this.value; | ||
} | ||
} | ||
class FactoryProvider extends ChildInjector { | ||
constructor(parent, token, scope, injectable) { | ||
super(parent, token, scope); | ||
this.injectable = injectable; | ||
this.responsibleForDisposing = true; | ||
} | ||
@@ -134,6 +172,7 @@ result(target) { | ||
} | ||
class ClassInjector extends AbstractCachedInjector { | ||
class ClassProvider extends ChildInjector { | ||
constructor(parent, token, scope, injectable) { | ||
super(parent, token, scope); | ||
this.injectable = injectable; | ||
this.responsibleForDisposing = true; | ||
} | ||
@@ -140,0 +179,0 @@ result(target) { |
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
45577
24
299
397
21