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

lepton-di

v1.1.3

Published

A lightweight dependency injection framework for TypeScript

Readme

lepton-di

A lightweight dependency injection framework for TypeScript

Install

npm install --save lepton-di

Quick Example

import { inject, using, Container } from 'lepton-di';

// Declare an interface
interface ILogger
{
    write(message: string): void;
}

// Declare a "physical" symbol for us to bind to.  (See below)
const ILogger: unique symbol = symbol("myproj:common:logger"); // Or whatever clever schema you wish.

// Build a concrete class
class ConsoleLogger implements ILogger
{
    public write(message: string): void;
    {
        console.log(message);
    }
}

// Create a class which uses our implementation
class Widget
{
    @inject(ILogger)
    public log: ILogger

    public doThing(): void
    {
        this.log.write("Doing the thing!");
    }
}

// Create a new container and register our type
let container = new Container();

container.register(ILogger)
    .toClass(ConsoleLogger)
    .with(Lifetime.Singleton);

using (container.beginScope(), scope =>
{
    let myWidget = new Widget();
    scope.buildUp(myWidget); // We can use buildUp() so we dont have to register Widget
    myWidget.doThing();
});

Usage Guide

Declare your interfaces as you normally would in TypeScript

export interface ILogger
{
    write(message: string): void;
}

Because TypeScript interfaces do not generate any actual code, we need a symbol to bind to.

export const ILogger: unique symbol = Symbol("myproj:common:logger");

You can then register this symbol with a concrete class.

I prefer to keep the concrete classes only visible through a configuration function that I then export. This lets me keep as much of the module's concerns contained completely in that scope and not elsewhere.

class ConsoleLogger implements ILogger
{
    public write(message: string): void
    {
        console.log(message);
    }
}

export module log
{
    export function configure(container: IContainer)
    {
        container.register(ILogger) // Note that this is our exported symbol, not the interface.
            .toClass(ConsoleOutput)
            .with(Lifetime.Singleton);
    }
}

Dependent classes may declare their dependencies in their properties or via a constructor parameter:

class Widget
{
    @inject(ILogger)
    private readonly log: ILogger; // Property injected

    public doThing(): void
    {
        this.log.write("Hi");
    }
}

class Sprocket
{
    // Constructor injected.
    constructor(@inject(ILogger) private readonly log: ILogger)
    {
    }

    public doThing(): void
    {
        this.log.write("Greetings.");
    }
}

You have three choices on how to get parameters injected into these classes.

The first is to register them like you would with an interface.

export module things
{
    export function configure(container: IContainer)
    {
        container.register(IWidget).toClass(Widget); // The default lifetime is transient.
    }
}

Another other option is to use buildUp() to inject values in an already existing object.

scope.buildUp(myWidget);

The third option is to have the resolver get passed in as a parameter:

class Widget
{
    private sprocket: ISprocket;

    constructor(@inject(IResolver) resolver: IResolver)
    {
        this.sprocket = resolver.resolve(ISprocket);
    }
}

Factories

You can register a function or lambda as a factory method as well if you need to.

container.register(IWidget)
    .toFactory((sprocket) => new Widget(sprocket), ISprocket);

Life Cycles

Lepton has various utilites for managing object lifetimes. Objects can have one of three lifecycles:

  • Transient - This is the default lifetime of a registered class. Transient objects are not managed by the scope, they are simply created and returned.

  • Scoped - Scoped objects have their lifetimes tied to the life of the scope that created them. Once the scope is destroied, those objects will also be destroied.

  • Singleton - A singleton object will be created once per container, which are for the lifetime of the application.

Scopes

Scopes is the first and most prominant of these. A scope holds (or creates) objects until that scope is destroyed.

Scopes can be nested with subsequent calls to beginScope(). Child scopes will walk up their parent chain and create a new instance of an object if not found.

If the item cannot be found a new instance will be created for the caller and saved.

Disposable objects

If a class exposes a dispose() method, then Lepton will call this method when the owning scope of an object is destroied.

using()

The using method is a utility method that ensures that an object's dispose method gets called; regardless if an exception is thrown or not.

Resolving

The scope exposes an IResolver interface with the following functions:

buildUp()

This will inject properties into an already instantiated object. This is handy when you have a factory method for a class or other means of creating the object outside of the scope.

This method will not add the object to the scope's management.

wireUp()

This is similar to build up, but the object needs to be registered; it will also be added to the scope's management list, so it will be cleaned up when the scope goes out of context.

resolve()

This will resolve a new or existing object from the scope's management.