Socket
Socket
Sign inDemoInstall

typed-inject

Package Overview
Dependencies
Maintainers
2
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typed-inject - npm Package Compare versions

Comparing version 0.2.0 to 0.2.1

src/api/Disposable.d.ts

9

CHANGELOG.md

@@ -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 @@

6

package.json
{
"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",

@@ -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

111

src/InjectorImpl.js

@@ -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) {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc