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 🙏

© 2026 – Pkg Stats / Ryan Hefner

genioc

v0.0.11

Published

Bloat-free inversion of control container for Typescript based on code-generation

Readme

genioc

Bloat-free and magical IoC-container for Typescript based on code-generation

Introduction

In Java or PHP, because of powerful built-in reflection features, you can inject dependencies into constructors just by specifying correct type:

class UserService {
    private final IUserRepository userRepo;

    public UserService(IUserRepository userRepo) {
        this.userRepo = userRepo;
    }
}

in current TS/JS solutions, you will have either to:

  1. use tokens with decorators (via old reflect-metadata):
const UserRepositoryIdentifier = Symbol.for('UserRepository')

class UserService {
    constructor(
        @inject(UserRepositoryIdentifier) private readonly userRepo: IUserRepository
    ) {

    }
}
  1. in such solutions as awilix with InjectMode.CLASSIC or for example with AngularJS injector, which just stringify function definition and parse argument names, you must preserve the original names of the arguments in the code, and therefore cannot minify the code.

  2. destruct object in the constructor which contains all dependencies, which basically is a violation of IoC principle and resolves to Service locator pattern

class UserService {
  constructor(opts) {
    this.emailService = opts.emailService // this is not true DI!
    this.logger = opts.logger
  }
}

And as you might see, all these solutions are kinda hacky and not as magical and easy-to-use as Java one for example.

genioc on other hand, allows you to write Java-way injection:

class UserService {
    constructor(
        private readonly userRepo: IUserRepository
    ) {

    }
}

Yes, that is really that simple. Just do not forget to bind the actual IUserRepository to an implementation.

However, there is one catch - you must run genioc script everytime you add a new dependency in the code. And to find out why, now we come to the mechanism how genioc works.

How it works?

For genioc to work, there is additional pre-processing step before running your app.

genioc compiles your Typescript project, parses dependencies of each class constructor, and generates a specific-for-you-project file which exports a ready-to-use IoC container for you (example here).

Please note, that it's not the same as constructor signature parsing as for example awilix does - with genioc you may use whatever names or order of constructor arguments you want - only the type of them matters.

Example usage:

//userService.ts
export class UserService {
  constructor(
    private readonly userRepository: IUserRepository
  ) {
    
  }
}
//index.ts
import container from './genioc.autogenerated.ts'

container.bind("IUserRepository", SqlUserRepository);
container.bindSelf(UserService);

const userService: UserService = container.get(UserService);

userService.doStuff(); //at this point, userService would be a valid instance with SqlUserRepository implementation injected

Because of current Typescript limitations, you must pass interface names as strings to bind them. However, genioc creates a union special type for that to guarantee that you will enter a valid interface name.

Usage in project

Due genioc being not quite a default library, you must alter your development workflow a little:

  1. First you define interfaces of your dependencies such as IUserRepository, IMailer or ILogger, etc..
  2. Then you create actual implementations for them: SqlUserRepository, MailchimpMailer, ConsoleLogger
  3. Then you pass interfaces for these dependencies to classes where you need them, for example:
class MyService {
    constructor(
        private readonly mailer: IMailer,
        private readonly userRepo: IUserRepository
    ) {

    }

    public resetPassword(email: string) {
        const user = await this.useRepo.findByEmail(email);

        await mailer.sendResetPasswordEmail(user.email, user.resetToken);
    }
}
  1. You run genioc to generate IoC container code for you
  2. You bind interfaces to their dependencies:
import container from './genioc.autogenerated.ts'

container.bind('ILogger', ConsoleLogger);
container.bind('IMailer', MailchimpMailer);
container.bind('IUserRepository', SqlUserRepository);
  1. Done. You can now run your app

Container API

genioc container is default-exported from generated file, so you should use the following import statement as a starting point:

import container from './genioc.autogenerated.ts'

All methods support either classes, strings or Symbols as tokens.

Following methods are supported:

container.bind(token, classConstructor);

Binds token to class classConstructor.

interface IDog {
    bark(): string;
}

class Doggy implements IDog {
    public bark() {
        return "Woof!";
    }
}

//...

container.bind("IDog", Doggy);

container.bindSelf(classConstructor);

Binds given class to itself, for injecting in the following manner:

container.bindSelf(MyClass);

//...
class MyClassUser {
    constructor(
        private readonly myClass: MyClass
    ) {

    }
}

container.bindValue<T>(token, value: T)

Binds token to given constant value. You can use optional generic type T to make sure the correct value type is passed. Useful for binding static objects such as configs or messages collections.

interface IAppConfig {
    niceDog: boolean;
} 

container.bindValue<IAppConfig>("IAppConfig", {
    niceDog: true
});

container.bindFactory<T>(token, factory: () => T)

Binds token to a factory function, which will be called every time you try to obtain an instance by this token.

interface ICurentTimeProvider {
    time: number;
} 

container.bindFactory<ICurentTimeProvider>("ICurentTimeProvider", () => {
    return {
        time: Date.now()
    }
});

container.get<T>(token)

Use to obtain instance of binding by token.

By default, it uses lazy resolving, which means that if instance does not exist, it will be resolved in-place and then cached for future use.

Will throw an error if you are trying to query token which has no assigned binding.

You should use generic argument T to explicitly define the return type of the function.

const app: Application = container.get<Application>("IApplication");

app.run();

Command line usage

genioc [--dev] [--watch | -w] [--output | -o <output path>] [project directory]
  • --dev - only used when testing genioc example itself
  • --watch | -w - starts in watch mode, which will automatically rebuild your IoC container when source code is changed
  • --output | -o <path> - specify name/path for output container file, relative to project directory
  • project directory - path to your project with tsconfig.json. If not used, it will use current process working directory.

Run this tool to re-build dependency tree for your project. You can run this before each build or just use it in watch mode.

Circular dependencies

genioc container is capable of resolving circular dependencies automatically, you can check the example. No additional decorators or wrappers needed.

Ways of injection

The main way of injection a dependency is using the constructor injection, and it should be the preferred one.

You can resolve them also in runtime by using container.get method, however, again, this is not dependency injection in classical form then.

Author

Nikita Kogut

License

MIT (see LICENSE file)