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 🙏

© 2025 – Pkg Stats / Ryan Hefner

di-typed

v0.0.7

Published

DI library enables precise, minimal dependencies for testing

Readme

di-typed: Type-Safe, Minimal Dependency Injection for Testing

di-typed is a lightweight, type-safe Dependency Injection (DI) library focused on testability and minimal configuration. It enforces precise dependency declarations, ensuring that components only receive what they explicitly depend on—nothing more, nothing less.

  • Comprehensive TypeScript support
  • Automatically excludes registrations with unresolved dependencies
  • Promotes explicit and minimal dependency declarations
  • Designed for testing and modular design
  • Supports standard lifetimes: singleton, scoped, transient

API Overview

  • fromClass() – Create a DIRegistration from a class whose first constructor parameter receives dependencies
  • fromFunction() – Create DIRegistration by a factory function that receives dependencies as its first parameter
  • fromValue() – Create DIRegistration by a constant value
  • <DIRegistration>.singleton() – Create DIRegistration with singleton lifetime
  • <DIRegistration>.scoped() – Create DIRegistration with scoped lifetime
  • <DIRegistration>.transient() – Create DIRegistration with transient lifetime
  • register() – Create DIContainerBuilder with DIRegistration map
  • <DIContainerBuilder>.register() – Instance method variant of register() for incremental registration
  • <DIContainerBuilder>.build() – Finalize and create a DIContainer
  • <DIContainer>._scope() – Instantiate a new scoped container
  • CircularDependencyError – Error thrown when a cycle is detected in dependency graph
  • createFromAlias<Map>() - Create a fromAlias function with your own All type
  • UnresolvedKeys<Builder, Key> – Type-level utility to check which dependencies are missing for a given key
    • If there's a circular dependency, UnresolvedKeys<Builder, Key> may resolves to never.
  • InferContainerType<Container> – Infer the type of the container from the DIContainer

Basic Usage Example

interface All {
  myService: MyService;
  myRepository: MyRepository;
  someNonexistentKey: never;
  weirdDependent: WeirdService;
}

class MyRepositoryImpl implements MyRepository {
  saveSomething = noop;
  maybeMore = noop;
}

class MyServiceImpl implements MyService {
  constructor({ myRepository }: Pick<All, "myRepository">) {
    noop(myRepository);
  }
  doSomething = noop;
  maybeMore = noop;
}

class WeirdServiceImpl implements WeirdService {
  constructor({
    someNonexistentKey,
  }: Pick<All, "someNonexistentKey">) {
    noop(someNonexistentKey);
  }
  wtf = noop;
}

// Create and build the container
const builder = register({
  myService: fromClass(MyServiceImpl),
  myRepository: fromClass(MyRepositoryImpl),
  weirdDependent: fromClass(WeirdServiceImpl),
});

const container = builder.build();
type Container = InferContainerType<typeof container>;
/*
type Container = {
  readonly myRepository: MyRepositoryImpl;
  readonly myService: MyServiceImpl;
}
*/

type Missing = UnresolvedKeys<typeof builder, "weirdDependent">;
// "someNonexistentKey"

What is All type?

All type is a type that contains all the potential dependencies that are registered in the container.

You don't absolutely need it, but it's very inconvenient without it.

It enables better type safety and developer ergonomics.

Lifetime Semantics

Each registration must specify its lifetime explicitly via .singleton(), .scoped(), or .transient().

| Lifetime | Description | Instance Lifetime | |-------------|-----------------------------------------------------------------------|-------------------| | singleton | One shared instance across the entire container, including all scopes | Per container | | scoped | A new instance is created for each scope, shared within that scope | Per scope | | transient | A new instance is created every time it is accessed or injected | Per access |

When to use

  • singleton: Use for stateless services or long-lived shared instances (e.g., repositories, loggers)
  • scoped: Use for contextual or per-request state (e.g., user context, trace id, DI-managed DisposeScope)
  • transient: Use for disposable or short-lived instances, or when isolation between usages is needed

Scoped Dependency Example

declare const builder: DIContainerBuilder<SomeRegistrationSet>;

class ContextPrinter {
  private readonly context: ScopeContext;
  constructor(deps: Record<"context", ScopeContext>) {
    this.context = deps.context;
  }
  printSomething(): void {
    console.log(this.context.something);
  }
}

// Register scoped dependencies
const container = builder
  .register({
    context: fromFunction<ScopeContext>(() => ({ something: "hello" })).scoped(),
    contextPrinter: fromClass(ContextPrinter).scoped(),
  })
  .build();

// Use shared scope
container.contextPrinter.printSomething(); // "hello"

// Create a new scope
const scoped = container._scope();
scoped.context.something = "world";

container.contextPrinter.printSomething(); // "hello"
scoped.contextPrinter.printSomething();    // "world"

Need alias?

You can define your own fromAlias function using createFromAlias function with your own All type.

export const fromAlias = createFromAlias<All>();

// usage: builder.register({ db: fromAlias("real_db").scoped() });

Need dispose?

Maybe you don't need it, so the library doesn't include it.

If you really need dispose, you can define your own.

class DisposeAll {
  private readonly disposables: (() => void)[] = [];

  public add(disposable: () => void) {
    this.disposables.push(disposable);
  }

  public dispose() {
    this.disposables.forEach((disposable) => disposable());
  }
}

class DisposeScope extends DisposeAll {
  constructor({ disposeAll }: Pick<All, "disposeAll">) {
    super();
    disposeAll.add(() => {
      this.dispose();
    });
  }
}

interface All {
  disposeAll: DisposeAll;
  disposeScope: DisposeScope;
  // ...
}

// from singleton
class SingletonDisposable {
  public disposed: boolean;
  constructor({ disposeAll }: Pick<All, "disposeAll">) {
    this.disposed = false;
    disposeAll.add(() => {
      this.disposed = true;
    });
  }
}

// from scoped
class ScopedDisposable {
  public disposed: boolean;
  constructor({ disposeScope }: Pick<All, "disposeScope">) {
    this.disposed = false;
    disposeScope.add(() => {
      this.disposed = true;
    });
  }
}

License

MIT