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

@aklinker1/zero-ioc

v1.3.4

Published

Zero dependency, type-safe, decorator-free IoC container

Readme

@aklinker1/zero-ioc

JSR NPM Version Docs API Reference License

Zero dependency, type-safe, decorator-free Inversion of Control (IoC) container.

Usage

Define your services. You can use classes or factory functions:

  • Class constructors can only accept a single argument, which is an object with the dependencies
  • Factory functions can only accept a single argument, which is an object with the dependencies
// database.ts
export function openDatabase(): Database {
  // ...
}

// user-repo.ts
export function createUserRepo(deps: { db: Database }): UserRepo {
  // ...
}

// user-service.ts
export class UserService {
  constructor(deps: { userRepo: UserRepo; db: Database }) {
    // ...
  }
}

Once your services are defined, you can register them on a container:

// main.ts
import { openDatabase } from "./database";
import { createUserRepo } from "./user-repo";
import { UserService } from "./user-service";
import { createIocContainer } from "@aklinker1/zero-ioc";

export const container = createIocContainer()
  .register({ db: openDatabase })
  .register({ userRepo: createUserRepo })
  .register({ userService: UserService });

And finally, to get an instance of a service from the container, use resolve:

const userService = container.resolve("userService");

Register Order

You can only call register with a service if you've already registered all of its dependencies. For example, if userRepo depends on db, you must register db in a separate call to register before registering userRepo.

The good news is TypeScript will tell you if you messed this up! If you haven't registered a dependency, you'll get a type error when you try to register the service that depends on it:

Additionally, thanks to this type-safety, TypeScript will also report an error for circular dependencies!

Access All Registered Services

To access an object containing all registered services, you have two options:

  1. container.registrations: Returns a proxy object that resolves services lazily when you access them.
    const { userRepo, userService } = container.registrations;
  2. container.resolveAll(): Immediately resolves all registered services and returns them as a plain object, no proxy magic. Useful when passing services to a third-party library that doesn't support proxies.
    const { userRepo, userService } = container.resolveAll();

Parameterization

Sometimes you need to pass additional parameters to a service, like config, that aren't previously registered services.

In this case, use parameterize. Any parameters passed in via the second argument don't need to be registered beforehand!

import { createIocContainer, parameterize } from "@aklinker1/zero-ioc";

const openDatabase = (deps: {
  username: string;
  password: string;
}): Database => {
  // ...
};

const container = createIocContainer().register({
  db: parameterize(openDatabase, {
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
  }),
});

Service Lifetime

Singleton

createIocContainer only manages singleton services. Once your service has been resolved, it will be cached and the same instance will be returned on subsequent calls.

interface UsersRepo {
  // ...
}
function createUsersRepo() {
  return {
    // ...
  };
}

const container = createIocContainer().register({
  usersRepo: createUsersRepo,
});

const usersRepo1 = container.resolve("usersRepo");
const usersRepo2 = container.resolve("usersRepo");
console.log(usersRepo1 === usersRepo2); // true

Transient

A "transient" service is a service that is created every time it is resolved... but that's just a function! More commonly referred to as a "factory" in this context.

So if you have a service that needs to be recreated every time it is resolved, register a factory instead:

interface UsersRepo {
  // ...
}
function createUsersRepo() {
  console.log("Creating new UsersRepo");

  return {
    // ...
  };
}

type UsersRepoFactory = () => UsersRepo;
function createUsersRepoFactory(): UsersRepoFactory {
  return createUsersRepo;
}

const container = createIocContainer().register({
  usersRepoFactory: createUsersRepoFactory,
});

const usersRepoFactory = container.resolve("usersRepoFactory");
const usersRepo1 = usersRepoFactory();
const usersRepo2 = usersRepoFactory();
console.log(usersRepo1 === usersRepo2); // false

This may not be as convenient as supporting transient services directly, but it's a simple and explicit way to achieve the same result.

If someone has a good proposal to add support for transient services that keeps the API simple, I'd be happy to consider it.

Scoped

"Scoped" services are services that are created once per "scope". Zero IoC does not currently support creating "scopes", and thus does not support scoped services.

If someone has a good proposal that keeps the API simple, I'd be happy to consider adding support.

Inspiration

This library was heavily inspired by Awilix, a powerful IoC container for JavaScript/TypeScript. While @aklinker1/zero-ioc is intentionally simpler and more opinionated (focusing on singletons, explicit dependencies, and type-safety through TypeScript), Awilix's approach to dependency registration and resolution without decorators was a major influence.