npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

phantomdi

v0.0.5

Published

A dependency injection framework

Downloads

10

Readme

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())