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

dipstick

v0.5.0

Published

A dependency injection framework for TypeScript

Readme

Chat-GPT-Image-Jun-29-2025-04-26-18-PM.png

Dipstick is a dependency injection framework for TypeScript. Instead of using @Decorators, reflect-metadata, or unique strings to identify objects to the DI framework, Dipstick relies on the type system and code generation. The result is a DI framework that works with the strengths of typescript, instead of hacking around it's weaknesses.

Simple.

Unlike other DI frameworks, which can sometimes feel like learning an entirely new language, learning Dipstick only requires understanding two concepts: Containers and Bindings.

Obvious.

The principle of least surprise is fundamental to Dipstick. There are no magic decorators or obtuse indirections in code. All of your IDEs tools like "Find references" and "go to symbol" work just as well with Dipstick as without. If you know typescript, understanding the code generated by Dipstick is a piece of cake.

Type Safe.

Dipstick works with the type system, instead of around it. Use the same Container types you declared to generate code in your integration tests to provide mocks for specific Containers. Don't worry about a DI framework spreading any throughout your codebase -- the types that come out of Dipstick are exactly as strong as you author them to be.

Installation

npm install dipstick

Overview

Dipstick uses TypeScript's type system and code generation to create dependency injection containers. The framework supports both class instantiation and factory functions as implementations, making it flexible for various architectural patterns. The framework is designed to be type-safe and easy to use, with a focus on maintainability and developer experience.

Core Concepts

Containers

Containers are the core building blocks of Dipstick. They allow you to bind implementations to types that are used throughout your project. An instance of a container is akin to a "scope" in other DI frameworks -- the container instance will hold references to reusable bindings and static bindings. To create a container, export a type alias to Container, and define its bindings:

import { Container, Reusable, Transient } from 'dipstick';

interface IFoo {}
class Foo implements IFoo {}
interface IBaz {}
class Baz implements IBaz {}

function createBaz(foo: IFoo): IBaz {
  return new Baz(foo);
}

// Export a type thats assignable to `Container` for dipstick to pick it up during code generation
export type MyContainer = Container<{
  bindings: {
    // Class bindings
    foo: Reusable<Foo, IFoo>;
    bar: Transient<Bar, IBar>;

    // Function binding using typeof syntax
    baz: Reusable<typeof createDatabase>;
  };
}>;

Bindings

Bindings allow containers to associate an implementation with a type. All bindings take two type arguments. The first argument can be either a class (which will be instantiated) or a function (using the typeof syntax). The second, optional argument is a type to return the instance as, such as an interface. Within a single container, no two bindings may return the same type alias.

export type MyContainer = Container<{
  bindings: {
    userIface: Transient<User, IUser>;
    userImpl: Transient<User>;
  };
}>;
const container = new MyContainerImpl(); // MyContainerImpl is generated code

const userImpl = container.userImpl(); // User
const userIface = container.userIface(); // IUser

Using typeof with Factory Functions

When you use typeof functionName as the first type argument with only one argument, Dipstick automatically infers the bound type as ReturnType<typeof functionName>. This is particularly useful for factory functions:

// A factory function that returns a request handler
function createUserHandler(userService: IUserService) {
  return (req: Request, res: Response) => {
    const users = userService.getAll();
    res.json(users);
  };
}

export type HandlerContainer = Container<{
  bindings: {
    // Bound type is automatically ReturnType<typeof createUserHandler>
    userHandler: Reusable<typeof createUserHandler>;
  };
}>;

Other containers can then depend on HandlerContainer and receive the handler type:

class App {
  constructor(
    // Type is ReturnType<typeof createUserHandler>
    private readonly userHandler: ReturnType<typeof createUserHandler>
  ) {
    this.app.get('/users', this.userHandler);
  }
}

export type AppContainer = Container<{
  bindings: {
    app: Reusable<App>;
  };
  dependencies: [HandlerContainer];
}>;

Dipstick will automatically match the ReturnType<typeof createUserHandler> parameter to the userHandler binding from HandlerContainer.

Bindings come in three flavors, which are described below.

Reusable Bindings

Reusable bindings return the same instance every time they are called. This is useful for singletons or other objects that should only be created once per container:

export type MyContainer = Container<{
  bindings: {
    // Returns the same Foo instance every time
    foo: Reusable<Foo, IFoo>;
  };
}>;

Transient Bindings

Transient bindings return a new instance every time they are called. This is useful for objects that should be created fresh each time they are requested:

export type MyContainer = Container<{
  bindings: {
    // Returns a new Bar instance every time
    bar: Transient<Bar>;
  };
}>;

Static Bindings

Static bindings are used to provide objects to a container when the container is instantiated. Use static bindings when you want to incorporate an object created outside of dipstick into a container so that it can be used as a dependency of other objects.

class RequestHandler {
  constructor(req: Request, res: Response) {}

  execute() {
    res.send(200, `hello ${req.path}`);
  }
}

export type RequestContainer = Container<{
  bindings: {
    // Created outside of this container
    req: Static<Request>;
    res: Static<Request>;

    requestHandler: Transient<RequestHandler>;
  };
}>;

app.use((req, res) => {
  const container = new MyContainer({ req, res });
  const handler = container.requestHandler();
  handler.execute();
});

Modularity & Composition

Containers can depend on other containers. These dependencies are used to resolve types that the container cannot resolve itself:

class Foo {
  constructor(bar: Bar) {}
}

export type FooContainer = Container<{
  bindings: {
    foo: Reusable<Foo>;
  };
}>;

export type BarContainer = Container<{
  dependencies: [FooContainer];
  bindings: {
    bar: Transient<Bar>;
  };
}>;

const fooContainer = new FooContainer();
const barContainer = new BarContainer([fooContainer]);

// if a container has both dependencies and static bindings, pass both:
// const barContainer = new BarContainer({ baz: new Baz() }, [ fooContainer ])

Usage

  1. Define your containers using type aliases to Container
  2. Run the code generator:
    npm exec -- dipstick generate ./path/to/tsconfig.json --verbose
  3. Use the generated containers in your application:
    const myContainer = new MyContainerImpl();
    const service = myContainer.myService();
    ...

Code Generation

The code generator will:

  1. Scan your TypeScript files for exported container type aliases
  2. Generate implementation classes for each container
  3. Handle dependency injection and binding resolution
  4. Ensure type safety throughout the dependency graph

Contributing

Contributions are welcome! Please see our CONTRIBUTORS.md guide for detailed information about:

  • Setting up the development environment
  • Running tests and code quality checks
  • Code style guidelines
  • Submitting pull requests

For quick contributions:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run npm run build && npm run check && npm test
  5. Submit a Pull Request