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

vs-inject

v0.1.0

Published

Minimal typed dependency-injection container for TypeScript: tokens as parameter decorators, lazy singletons, circular-dependency detection. Zero dependencies.

Readme

vs-inject

Minimal typed dependency-injection container for TypeScript. Tokens double as parameter decorators, resolution is lazy-singleton, circular dependencies are detected and reported with the full chain. About 160 lines, zero runtime dependencies, ESM + CJS.

import { Container, Inject, token } from "vs-inject";

interface Logger { info(msg: string): void; }
const Logger = token<Logger>("logger");          // one name: type + token

class ConsoleLogger implements Logger {
  info(msg: string) { console.log(msg); }
}

class Cache { readonly size = 100; }

class Service {
  constructor(
    @Logger private readonly log: Logger,        // interface seam: token is the decorator
    @Inject(Cache) private readonly cache: Cache // concrete class: @Inject(Class)
  ) {}
  run() { this.log.info(`cache size ${this.cache.size}`); }
}

const c = new Container()
  .register(Logger, ConsoleLogger)   // bind a token to a class
  .register(Cache)                   // a concrete class as its own key
  .register(Service);

c.get(Service).run();                // "cache size 100"

Install

npm install vs-inject

Required tsconfig

The container relies on parameter decorators, which exist only under TypeScript's legacy decorator implementation:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

emitDecoratorMetadata is not required and not used. esbuild, tsup, tsc and swc all support experimentalDecorators.

Usage

Define a token next to its interface

export interface IConfig { readonly debug: boolean; }
export const IConfig = token<IConfig>("config");

TypeScript merges the interface and the const, so IConfig is both the type and the injection key. The string is a description used in error messages.

Three registration forms

c.register(IConfig, FileConfig);                                   // token -> class (lazy singleton)
c.register(StatusStore);                                           // class as its own key
c.register(IConfig, () => ({ debug: true }));                      // factory (values, pre-built objects)
c.register(CacheSize, (di) => (di.get(IConfig).debug ? 10 : 1000)); // factory using other deps

Registrations are order-independent; nothing is instantiated until the first get. Re-registering a key before it is instantiated replaces the factory (last wins). Re-registering after instantiation throws.

Declaring constructor dependencies

| Dependency kind | Decorator | Why | |---|---|---| | Interface / token | @IConfig cfg: IConfig (or @IConfig()) | a Token is itself a parameter decorator | | Concrete class | @Inject(StatusStore) store: StatusStore | a class can't be called without new, so it can't be its own decorator | | Token via Inject | @Inject(IConfig) cfg: IConfig | also works, for uniformity |

Every required constructor parameter of a class you register must carry a decorator; registration throws otherwise (see Errors). Classes never import the container: only your composition root does.

Resolving

const app = c.get(App);      // typed as App
const cfg = c.get(IConfig);  // typed as IConfig

Errors

All errors are plain Errors whose message starts with vs-inject:.

| Situation | When | Message contains | |---|---|---| | A registered class has an undecorated required parameter | register | class Foo has 2 required constructor parameter(s) but only 1 carry an injection decorator | | register(token) with no implementation | register | token "x" needs an implementation | | Key was never registered | get | no factory registered for "x" | | Circular dependency | get | circular dependency: a -> b -> a | | Re-register after instantiation | register | cannot re-register "x" after it was instantiated |

Design notes

  • No reflect-metadata, no emitDecoratorMetadata. Bundlers such as esbuild do not emit design:paramtypes, and interface seams have no runtime type anyway. Keys are always explicit, so the container works identically bundled or not.
  • Singletons only, lazy. One instance per key per container. No transient or scoped lifetimes; create a second Container if you need a second graph.
  • Factories, not a provider zoo. A value is () => value; a class is a class; anything else is a factory that receives the container.
  • Accepted trade-off. A parameter decorated with the wrong token compiles fine and fails at get time (parameter decorators carry no type link). Registration does validate that every parameter has some decorator.

License

MIT