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

eldin

v1.1.0

Published

A lightweight, type-safe dependency injection container for TypeScript with typed tokens, lifetime management, and modular application orchestration.

Downloads

1,334

Readme

eldin 💉

npm version codecov Master Workflow Known Vulnerabilities Conventional Commits

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

No decorators. No metadata reflection. No magic. Just typed tokens, factory/value providers, and lifetime management.

Table of Contents

Installation

npm install eldin --save

Quick Start

import { Container, TypedToken } from 'eldin';

// 1. Define typed tokens
const DatabaseToken = new TypedToken<Database>('Database');
const UserServiceToken = new TypedToken<UserService>('UserService');

// 2. Create a container and register dependencies
const container = new Container();

container.register(DatabaseToken, {
    useFactory: () => new Database('postgres://localhost/mydb'),
});

container.register(UserServiceToken, {
    useFactory: (c) => new UserService(c.resolve(DatabaseToken)),
});

// 3. Resolve — fully typed, no generics needed
const userService = container.resolve(UserServiceToken);
//    ^ type is UserService, inferred from the token

Typed Tokens

TypedToken<T> carries a phantom type parameter that flows through register() and resolve(), giving you compile-time type safety without manual generic annotations.

import { TypedToken } from 'eldin';

// Each token encodes the type it resolves to
const ConfigToken = new TypedToken<Config>('Config');
const LoggerToken = new TypedToken<Logger>('Logger');

// resolve() infers the return type from the token
const config = container.resolve(ConfigToken);  // Config
const logger = container.resolve(LoggerToken);  // Logger

Each TypedToken instance has a unique id (a Symbol), so two tokens with the same name but different types will never collide:

const tokenA = new TypedToken<Foo>('Service');
const tokenB = new TypedToken<Bar>('Service');
// tokenA and tokenB are distinct registrations

Untyped Keys

You can also use plain symbol, string, or class constructor keys. These require a manual generic on resolve():

const MY_KEY = Symbol('MyKey');

container.register(MY_KEY, { useValue: 42 });
const value = container.resolve<number>(MY_KEY);

Providers

Two provider types keep the API unambiguous:

Value Provider

Registers a pre-constructed value. The container stores it as-is.

container.register(ConfigToken, {
    useValue: { port: 3000, host: 'localhost' },
});

Factory Provider

Registers a factory function. The container calls it when the dependency is first resolved (singleton) or every time (transient). The factory receives the container for recursive resolution.

container.register(UserServiceToken, {
    useFactory: (container) => {
        const db = container.resolve(DatabaseToken);
        const logger = container.resolve(LoggerToken);
        return new UserService(db, logger);
    },
});

Lifetime Management

Control how often a factory is called:

// Singleton (default) — created once, cached forever
container.register(DatabaseToken, {
    useFactory: () => new Database(),
});

// Transient — new instance on every resolve()
container.register(RequestIdToken, {
    useFactory: () => crypto.randomUUID(),
}, { lifetime: 'transient' });

Value providers are always singleton by nature (the value already exists).

Container API

The Container class implements the IContainer interface. Program against the interface to decouple your application from the concrete implementation — useful for dependency inversion and testing seams.

import { Container } from 'eldin';
import type { IContainer } from 'eldin';

const container: IContainer = new Container();

| Method | Description | |-------------------------------------|----------------------------------------------------------------------| | register(key, provider, options?) | Register a dependency with optional lifetime. Re-registering an existing key replaces the provider and clears any cached singleton. | | resolve(key) | Resolve a dependency. Throws ContainerError if not found. | | tryResolve(key) | Safe resolve returning Result<T> (no throw). | | has(key) | Check if a key is registered. | | unregister(key) | Remove a registration and its cached instance. |

Safe Resolution

const result = container.tryResolve(DatabaseToken);

if (result.success) {
    console.log(result.data); // Database
} else {
    console.error(result.error); // Error
}

Re-registration

Calling register() with an existing key replaces the provider and clears any cached singleton:

container.register(ConfigToken, { useValue: devConfig });
container.resolve(ConfigToken); // devConfig (cached)

container.register(ConfigToken, { useValue: prodConfig });
container.resolve(ConfigToken); // prodConfig (cache cleared)

Error Handling

When resolve() is called with an unregistered key, it throws a ContainerError:

import { ContainerError } from 'eldin';

try {
    container.resolve(UnknownToken);
} catch (error) {
    if (error instanceof ContainerError) {
        console.error(error.message); // "No registration found for: ..."
    }
}

To avoid exceptions, use tryResolve() which returns a discriminated union instead:

const result = container.tryResolve(MaybeToken);
if (!result.success) {
    console.warn('Not registered:', result.error.message);
}

Usage in Tests

eldin makes testing easy — just construct dependencies manually:

const mockDb = { query: vi.fn() };
const service = new UserService(mockDb);

Or create a test container with overrides:

const container = new Container();
container.register(DatabaseToken, { useValue: mockDb });
container.register(UserServiceToken, {
    useFactory: (c) => new UserService(c.resolve(DatabaseToken)),
});

const service = container.resolve(UserServiceToken);

Contributing

Before starting to work on a pull request, it is important to review the guidelines for contributing and the code of conduct. These guidelines will help to ensure that contributions are made effectively and are accepted.

License

Made with 💚

Published under MIT License.