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

jection

v0.1.1

Published

An opinionated and thin DI framework

Downloads

4

Readme

jection

A light-wight dependency-injection framework with support for lazy loading.

Inspiration

I couldn't find a simple and unintrusive DI framework that I could slap on top of my existing class definitions. Inversify has very powerful but very verbose coupling. I want auto-wiring. Additionally, no DI framework I tried had great support for scoped containers. I feel that a "module" system encompasses this use-case quite well, and provides a great point for lazy loading.

I drew a lot of inspiration from the many great DI frameworks out there like those bundled with Angular, and NestJS. I mainly envision this DI framework on a backend or desktop application.

Requirements

This library relies on the reflect-metadata package to be instantiated at your application's entrypoint.

Features / Goals

  • [x] Value, factory, class, and "use-existing" provider types.
  • [x] Root providers
  • [x] Singleton injectable classes with @Injectable(...)
  • [x] Singleton scoped provision with @Injectable() and declaring in a @Module({ providers: [SomeInjectableClass] })
  • [x] Tokens and Inject helpers for representing non-reflactable items
  • [x] Modules for scoping dependencies
  • [x] Modules importing modules
  • [x] Lazy loading modules with elegant 🤷‍♀️ API
  • [x] Form a "composition root" with bootstrapping a module (good read here)

Usage

  • You can define any class as a module with the @Module() decorator.
  • Modules can declare providers scoped to them and import other modules to gain exported providers from said other modules.
  • You can declare injectable classes as providers with @Injectable
  • You can declare tokens to wire up non-reflectable items in the injection system

export const MY_DB_ADDRESS = new InjectionToken<string>("Database Address");

export function provideMyDatabaseAddress() {
  return {
    provide: MY_DB_ADDRESS,
    useValue: "localhost:1234",
  } satisfies ValueProvider;
}

@Injectable()
export class MyDatabaseService {

  constructor(@Inject(MY_DB_ADDRESS) private readonly dbAddress) {}

  async query(...args: unknown[]): Promise<unknown> {
    // Imagine database query code here.
  }

}

@Injectable()
export class AppController {

  constructor() {

  }

  initializeWindows() {
    // ....
  }

}

@Module({
  providers: [
    // Your module-scoped providers go here.
    MyDatabaseService,
    AppController,
    provideMyDatabaseAddress()
  ]
})
export class MyApplicationEntryModule {

  constructor(
    private readonly database: MyDatabaseService,
    private readonly controller: AppController
  ) {}

  // Put whatever functions you want on your module. 

  async initialize() {
    const operationAllowed = await this.database.query("op-allowed");

    if(operationAllowed) {
      this.controller.initializeWindows();
    }
  }

}

Then you can bootstrap that module


// You can supply root providers here 

const moduleRef = bootstrapModule(MyApplicationEntryModule);
moduleRef.instance.initialize().catch((e) => {
  // Uh oh
})

Injection philosophy

You should not be injecting concrete implementations like in the simple example above. To correctly rely on abstractions, and simultaneously avoid the verbosity of wiring that Inversify presents, you'll need to rely on abstract class definitions. Abstract classes in TypeScript are reflectable.

In your individual units of code (injectable classes and provider factories) you should use these abstract definitions. Elsewhere you should define which concrete implementation (that extends the abstract class) is provided to fill in for the abstract class.

This of course means that you can not have a single class that fulfills multiple abstract providers. That would probably violate the Single Responsibility Principle anyways.

@Injectable()
export abstract class DatabaseService {

  abstract query(...args: unknown[]): Promise<unknown> | unknown;

}

@Injectable()
export abstract class MetricsService {

  abstract collectMetrics(): Promise<void>;

}

export class GoogleMetricsService extends MetricsService {

  constructor(private readonly dbService: DatabaseService) { }

  async collectMetrics() {
    const metrics = {}// Do your metric collection

    await this.dbService.query("save", metrics);
  }

}

export class AwsDatabaseService extends DatabaseService {

  constructor(
    @Inject(MY_DB_ADDRESS) private readonly dbAddress: string
  ) { }

  async query(...args: unknown[]) {
    // Some specific query code
  }

}

@Module({
  providers: [
    {
      provide: MetricsService,
      useClass: GoogleMetricsService
    },
    {
      provide: DatabaseService,
      useClass: AwsDatabaseService,
    }
    provideMyDatabaseAddress(),
  ]
})
export class DataModule {

}