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

@nodelith/injection

v0.8.1

Published

Composable, lightweight dependency injection for JavaScript/TypeScript. Build clear dependency graphs with classes, factories, functions, or static values, while keeping lifecycles and initialization explicit and easy to test.

Readme

@nodelith/injection

Composable, lightweight dependency injection for JavaScript/TypeScript. Build clear dependency graphs with classes, factories, functions, or static values, while keeping lifecycles and initialization explicit and easy to test.


📦 Installation

npm install @nodelith/injection
# or
pnpm add @nodelith/injection
# or
yarn add @nodelith/injection

Quick Start

Start by creating a container, register the values/functions/classes/factories your app needs, then resolve by token when you want to use them. The example below wires a small user service with its repository and shows the minimum flow from registration to resolution.

import { InjectionContainer } from '@nodelith/injection'

class UserRepository {
  public findById(id: string) {
    return { id }
  }
}

class UserService {
  public constructor(userRepository: UserRepository) {
    this.userRepository = userRepository
  }

  public getUser(id: string) {
    return this.userRepository.findById(id)
  }
}

const container = InjectionContainer.create()
container.mapClassRegistration('userRepository', UserRepository)
container.mapClassRegistration('userService', UserService)

const service = container.resolve('userService')
console.log(service.getUser('42'))
// > { id: 42 }

Registrations

Registrations describe how a token should be resolved. There are four types of primitive registrations that can be created through this library: value (static values), function (plain functions), factory (functions returning objects), and class (constructors) registrations. Most of the time you create registrations via map*Registration on the container/module, but you can also create one in isolation and import it later.

import { InjectionRegistration, InjectionContainer } from '@nodelith/injection'

const cachedValue = InjectionRegistration.createValueRegistration('token', { token: 'answer' })

const container = InjectionContainer.create()
container.useRegistration(cachedValue)

When adding a Registration to a Container, the Registration is automatically cloned, binding its copy to the Container's context and preventing any state from being shared across Containers.


Lazy Resolution

Class and factory registrations are resolved through a lazy proxy. The underlying class/factory only runs when you access a property or call a method on the resolved value. This avoids eager instantiation and helps with some types of circular graphs.

Factory registrations are intended for object-producing functions and get the same lazy proxy behavior as classes, while function registrations return their value directly (no proxy), which is ideal for pure helpers or computed values.

const createObject = () => {
  console.log('Object created!')
  return {
    sayHello(name: string) {
      console.log(`Hello, ${name}!`)
    },
  }
}

const container = InjectionContainer.create()

container.mapFactoryRegistration('factory', createObject)

const object = container.resolve('factory')

logger.info('Thom')
// > Object created!
// > Hello, Thom!

Parameter Matching

Registrations created with mapClassRegistration, mapFactoryRegistration, and mapFunctionRegistration resolve dependencies by matching parameter names to registered tokens. At resolution time, the container builds a dependency bundle and passes values in the same order as the resolved parameter list.

⚠️ Parameter names must be stable at runtime; minification/obfuscation can break parameter/dependency matching.

When parameters are not stable at runtime (like after minification), you should either depend on the Bundle explicitly or pass params list to lock the injection order;

const createMailer = (smtpUrl: string, timeout: number) => {
  ...
}

container.mapFunctionRegistration('mailer', createMailer, {
  params: ['smtpUrl', 'timeout'],
})

Lifetime Management

Registrations support three lifecycles:

  • transient: a new instance is created every time one is needed
  • singleton: a single instance is cached within the container
  • scoped: an instance is cached during resolution context

Visibility Management

Registrations support two visibility modes:

  • public: intended to be exposed outside the module/container boundary.
  • private: intended to stay internal to the module/container boundary.

At the Container level, visibility represents metadata on the registration. The container does not change resolution behavior based on it, but it can be used by tooling or custom bundle logic to decide what should be exposed.

At the Module level, visibility is enforced by your module boundaries. Modules will not expose registrations from child Modules and will only expose registrations that are set public. Private registrations will only be exposed to registrations within the exact same Module a the registration belongs to.

container.mapClassRegistration('dbClient', DbClient, {
  visibility: 'private',
})

container.mapClassRegistration('userService', UserService, {
  visibility: 'public',
})

Module composition

Use InjectionModule to compose reusable sets of registrations.

import { InjectionModule } from '@nodelith/injection'

const userModule = InjectionModule.create()
  .mapClassRegistration('userRepository', UserRepository)
  .mapClassRegistration('userService', UserService)

const appModule = InjectionModule.create()
  .mapValueRegistration('dbUrl', 'postgres://local')
  .useModule(userModule)

appModule.resolve('userService')

Initialization pipeline

Initializers let you run setup logic before exposing values. This is particular usefull when stating up processes or applications. Implement CoreInitializer and register it via InjectionInitializer. Initializers run in the same order they were added to the container and one initializer can depend on the other as long as they are added on the correct order the Container/Module.

import { InjectionContainer } from '@nodelith/injection'
import { CoreInitializer } from '@nodelith/core'

class CacheInitializer implements CoreInitializer<Set<string>> {
  public async initialize() {
    return ['Some', 'slow', 'operation'];
  }
}

function SomethingWithCache(cache: string[]) {
  return cache.join()
}

const container = InjectionContainer.create()
  .mapClassInitializer('cache', CacheInitializer)
  .mapFunctionRegistration('something', SomethingWithCache)

await container.initialize()
const cache = container.resolve('something')