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

@eslym/container

v1.1.1

Published

A lightweight dependency injection container for TypeScript and JavaScript.

Downloads

332

Readme

@eslym/container

A lightweight, type-safe dependency injection container for TypeScript and JavaScript.

@eslym/container uses typed context keys to describe values, factories, and dependencies. Values are resolved lazily, with optional singleton caching, parent-container lookup, batch resolution, and lifecycle hooks.

Installation

npm install @eslym/container

The package provides both ESM and CommonJS builds, plus bundled TypeScript declarations.

Quick Start

import { Container, context } from '@eslym/container';

const apiUrl = context<string>('apiUrl').key();
const apiClient = context<ApiClient>('apiClient')
	.singleton()
	.key(({ deps: [url] }) => {
		return new ApiClient(url);
	}, apiUrl);

class UserService {
	constructor(readonly client: ApiClient) {}
}

const userService = context<UserService>('userService')
	.singleton()
	.key(({ deps: [client] }) => new UserService(client), apiClient);

const container = new Container().set(apiUrl, 'https://example.com');

const service = container.make(userService);

The dependency types are inferred from the keys passed to key, so factories receive typed dependencies without a string-based lookup API.

Context Keys

Create a key with context<T>(name). A key can optionally define a default factory and can be marked as a singleton:

const transientId = context<string>('transientId').key(() => crypto.randomUUID());
const applicationName = context<string>('applicationName')
	.singleton()
	.key(() => 'my-app');

const container = new Container();

container.make(transientId); // A new value on each call
container.make(applicationName); // The same value on each call

Keys without a default factory must be supplied to a container with set or register.

Registering Values And Factories

Use set for an existing value:

const config = context<{ debug: boolean }>('config').key();

const container = new Container().set(config, { debug: true });

Use register to provide a factory after creating the key. Dependencies are resolved before the factory runs:

const host = context<string>('host').key();
const port = context<number>('port').key();
const address = context<string>('address').key();

const container = new Container()
	.set(host, 'localhost')
	.set(port, 8080)
	.register(
		address,
		({ deps: [currentHost, currentPort] }) => {
			return `${currentHost}:${currentPort}`;
		},
		host,
		port
	);

container.make(address); // "localhost:8080"

createFactory makes a reusable factory explicit, while factoryFromConstructor adapts a class constructor. Factory callbacks receive the resolving container, typed dependencies, and the factory instance:

import { Container, context, factoryFromConstructor } from '@eslym/container';

const name = context<string>('name').key();

class User {
	constructor(readonly name: string) {}
}

const user = context<User>('user').key(factoryFromConstructor(User, name));
const container = new Container().set(name, 'Ada');

container.make(user); // User { name: 'Ada' }

register(key) also accepts a key's default factory when one was provided at key creation time. Passing a Factory instance preserves that instance; passing a function creates a new factory from the function and the following dependency keys.

Function Keys

Function keys provide typed invocation through container.call:

const add = context<(left: number, right: number) => number>('add').key();
const loadUser = context<Promise<(id: string) => string>>('loadUser').key();

const container = new Container()
	.register(add, () => (left, right) => left + right)
	.register(loadUser, () => Promise.resolve((id) => `loaded: ${id}`));

container.call(add, 2, 3); // 5
await container.call(loadUser, 'user-1');

Batch Resolution

Use makeAll to resolve multiple keys while preserving their tuple types. Use makeAllAsync when the results should be awaited together:

const host = context<string>('host').key(() => 'localhost');
const port = context<number>('port').key(() => 8080);
const container = new Container();

const [currentHost, currentPort] = container.makeAll(host, port);
const [asyncHost, asyncPort] = await container.makeAllAsync(host, port);

Parent Containers

A container can resolve registrations from a parent container. Child registrations remain local to the child and take precedence over parent registrations for the same key:

const logger = context<Logger>('logger').key();
const parent = new Container().set(logger, new Logger());
const child = new Container(parent);
child.make(logger); // Resolves from parent

child.set(logger, new Logger());
child.make(logger); // Resolves the child's value

Use has to check whether a key is registered in the container or one of its parents:

child.has(logger); // true

Use resolved to check whether a key has a cached value in the container or an ancestor. A transient factory remains unresolved after make, while singleton values and values supplied with set are resolved:

const transient = context<number>('transient').key(() => 1);
const singleton = context<number>('singleton')
	.singleton()
	.key(() => 2);
const container = new Container();

container.resolved(transient); // false
container.make(transient);
container.resolved(transient); // false

container.resolved(singleton); // false
container.make(singleton);
container.resolved(singleton); // true

A child container's local registration takes precedence when checking resolved, even if its parent has already resolved the same key.

Hooks

Containers, context keys, and factories expose hooks for lifecycle events. Factory callbacks also receive the factory instance that is being resolved:

const value = context<number>('value').key(() => 1);
const container = new Container();

container.hooks.on('registered', ({ key }) => {
	console.log(`registered ${key.name}`);
});

value.hooks.on('resolving', ({ key }) => {
	console.log(`resolving ${key.name}`);
});

value.hooks.on('resolved', ({ value }) => {
	console.log(value);
});

value.defaultFactory.hooks.on('registered', ({ key }) => {
	console.log(`factory registered for ${key.name}`);
});

const factoryKey = context<number>('factoryKey').key(({ factory }) => {
	console.log(factory.dependencies);
	return 1;
});

registered fires for both register and set on the container, key, and registered factory. resolving and resolved fire when a value is actually resolved on the container and key. They do not fire when a cached singleton is returned. A value supplied with set emits these events while it is being set. The value on a resolved event is mutable, and the final value is returned or cached.

on returns a function that removes the listener and accepts an optional AbortSignal for automatic removal. Use off to remove a listener directly:

const controller = new AbortController();
const listener = () => {};
const remove = container.hooks.on('registered', listener, controller.signal);

remove();
container.hooks.off('registered', listener);

Resolution Rules

  • Factory-backed values are resolved lazily when make or call is used.
  • Singleton keys cache their resolved value in the container; keys are transient by default.
  • A key can only be registered once per container.
  • Circular dependencies throw CircularDependencyError.
  • Nested make and call operations are allowed while a dependency graph is resolving.
  • Missing factories throw an error identifying the key name.

Development

This project uses Bun for package scripts and tests.

bun install
bun test
bun run build
bun run lint
bun run format

License

MIT. See LICENSE.