New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

phantomdi

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

phantomdi

A dependency injection framework

latest
Source
npmnpm
Version
0.0.5
Version published
Maintainers
1
Created
Source

phantomdi

Version CircleCI

phantomdi is a no-boilerplate DI framework for classes and functions which can optionally leverage typescript-rtti.

  • No decorators needed (when paired with typescript-rtti)
  • Supports classes, interfaces and arbitrary values as tokens
  • Supports injection on union types (ie OptionA | OptionB)
  • Inject on both constructor parameters and properties
  • Alter injectables using aspect-oriented programming
  • Also supports the standard emitDecoratorMetadata-style injection (a la Angular, injection-js, @alterior/di, etc)
  • Well tested: 87% coverage
import { injector, provide } from 'phantomdi';
import { reify } from 'typescript-rtti';

interface Foobar { 
    version : number;
}

class A {
    constructor(readonly foobar : Foobar) {

    }

    get version() { return this.foobar.version; }
}

let a = injector([ provide(reify<Foobar>, { version: 123 }), provide(A) ]).provide(A)
expect(a.version).to.equal(123);

Functions:

import { injector, provide } from 'phantomdi';

class A { foo = 123 }
class B { bar = 321 }

function foobar(a : A, b : B) {
    return a.foo + b.bar;
}

expect(injector([ provide(A), provide(B) ]).invoke(globalThis, foobar))
    .to.equal(123 + 321);

Optional:

import { injector, provide } from 'phantomdi';

class A { foo: 123 }
class B { bar: 321 }

function foobar(a : A, b? : B) {
    return a.foo + (b?.bar ?? 555);
}

expect(injector([ provide(A) ]).invoke(globalThis, foobar))
    .to.equal(123 + 555);

Initializers:

import { injector, provide } from 'phantomdi';

class A { foo: 123 }
class B { bar: 321 }

function foobar(a : A, b = new B(555)) {
    return a.foo + (b?.bar);
}

expect(injector([ provide(A) ]).invoke(globalThis, foobar))
    .to.equal(123 + 555);

Heirarchical injection:

import { injector, provide } from 'phantomdi';

class A { 
    constructor(readonly foo = 123) {
    }
}

class B {
    bar = 321;
}

let parent = injector([ provide(A), provide(B) ]);
let injector = injector([ provide(A, () => new A(555))], parent)

expect(injector.provide(A).foo).to.equal(555);
expect(injector.provide(B).bar).to.equal(321);

Alterations:

import { provide, alter, injector } from 'phantomdi';

class A {
    bar = 123;
    foo() {
        return 'original';
    }
}

let i = injector([ provide(A), alter(A, {
    beforeFoo() {
        console.log(`foo() is about to run`);
    }

    afterFoo() {
        console.log(`foo() is finished running`);
    }

    aroundFoo(foo : () => string) {
        return function () {
            return `around(${foo.call(this)})`
        }
    }

})]);

let a = i.provide(A);

expect(a.bar).to.equal(123);
expect(a.foo()).to.equal('around(original)');

API

The injector() function (and the Injector constructor) accept an array of providers. Each provider is a tuple of two values: a token and a function which provides the value for that token.

let i = injector([
    ['foo', () => 123],
    ['bar', () => 321]
]);

expect(i.provide('foo')).to.equal(123);
expect(i.provide('bar')).to.equal(321);

The provide() function provides syntactic sugar for defining these:

let i = injector([
    provide('foo', () => 123),
    provide('bar', () => 321)
]);

expect(i.provide('foo')).to.equal(123);
expect(i.provide('bar')).to.equal(321);

Provider functions are also subject to dependency injection:

let i = injector([
    provide(Number, () => 123),
    provide('bar', (num : number) => num + 1)
])

expect(i.provide('bar').to.equal(124));

Calling provide() with a class constructor will provide that class using its constructor as the token:

class Foo { }

injector([ provide(Foo) ]);

This is done using the construct(constructor) function. It returns a provider function which constructs the given class using the dependency injector.

You can provide a class token using another class:

class Foo { }
class Bar extends Foo { }

injector([ provide(Foo, Bar) ]);

You can also invoke a function by injecting its parameters based on the available metadata:

class Foo { bar = 123; }

let result = injector([ provide(Foo) ]).invoke((foo : Foo) => foo.bar);

expect(result).to.equal(123);

In addition to parameter injection, you can do property injection:

class Foo {
    baz = 123;
}

class Bar {
    @Inject() foo : Foo;
}

let result = inject([ provide(Foo), provide(Bar) ]).provide(Bar).foo.baz;

expect(result).to.equal(123);

You can define an onInjectionCompleted() method which will get called after all injection is resolved:

class Foo {
    baz = 123;
}

class Bar {
    @Inject() foo : Foo;

    onInjectionCompleted() {
        expect(this.foo.baz).to.equal(123);
    }
}

let bar = inject([ provide(Foo), provide(Bar) ]).provide(Bar);

If you specify that a parameter or property is optional, it will be treated as optional. If you specify an initializer for a property or parameter it will automatically be considered "optional", with its value set automatically to the initializer.

Using without typescript-rtti

When using typescript-rtti, no decorators are required, the library will automatically determine all relevant Typescript types and do the right thing. However you can still use the library without it- the library provides @Injectable() along with @Inject() and @Optional(), and it supports emitDecoratorMetadata:

@Injectable()
class Foo {
    baz = 123;
}

@Injectable()
class Bar {
    constructor(readonly foo : Foo) {
    }
}

let result = inject([ provide(Foo), provide(Bar) ]).provide(Bar).foo.baz;

expect(result).to.equal(123);

As with other dependency injection libraries, technically any decorator on the class being injected is fine, the specific use of @Injectable() is not enforced.

Alterations

Alterations are special providers. Use alter(token, provider) to define an alteration provider. The provider function is invoked in a special child injector where token is already provided, and the provider is expected to return a new value for token. An alteration provider usually uses Proxy to modify the value injected for token in some way, but it could also completely replace the object.

You can also pass a special alteration definition object (Alteration<T>) to alter() which will create the Proxy for you. That definition supports adding functions before (ie beforeMethod()), after (ie afterMethod()), and around (ie aroundMethod()) the original function value. You can completely replace the method by providing a function property with the same name as the method (ie method())

Keywords

dependency injection

FAQs

Package last updated on 09 Mar 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts