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

@pajecawav/di

v0.0.1

Published

Readme

@pajecawav/di

Dependency injection container for TypeScript with constructor injection via design:paramtypes metadata, an instance lifecycle (init / update / destroy), and React 19 bindings for MVVM.

  • Zero dependencies except the reflect-metadata polyfill.
  • SSR-safe: scope.global means "one instance per root container", not "one per process".
  • React 19 / StrictMode-safe: models resolve during render with pure constructors, lifecycle runs in effects, double-mount revives a fresh instance instead of re-initializing a dead one.

Setup

// tsconfig.json
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
    },
}

reflect-metadata is imported by the package entry — no extra setup.

Core (@pajecawav/di)

Lifetimes

import { scope } from "@pajecawav/di";

@scope.global // one instance per root container (SSR: per request)
class ApiService {}

@scope.container // one instance per first-resolving scope (page, model scope)
class PageModel {}

@scope.transient // new instance every resolution
class Validator {}

Decorators only record metadata — registration happens lazily at first resolution, so importing modules has no global side effects. Undecorated classes resolve as transient.

Containers

import { createRootContainer, rootContainer } from "@pajecawav/di";

const root = createRootContainer(); // fresh isolated root (SSR, tests)
const scope = root.createChild(); // child scope, shadows parent registrations
  • register(token, { useClass | useFactory | useValue | useToken }, lifetime?) — explicit registration (singleton by default). Duplicate registration throws.
  • replace(token, provider) — static override before first resolve; in a child container it shadows the parent. Live swapping is not supported.
  • resolve(token), isRegistered(token), createChild().
  • dispose() — async, cascades into children and destroys every instance the container created (after its init settled).
  • waitForInit() — awaits all pending inits of this container and its children; never rejects (failed inits are logged and exposed via initPromise(instance)).

Symbol tokens

import { createToken, inject } from "@pajecawav/di";

interface Logger {
    /* ... */
}
const LoggerToken = createToken<Logger>("Logger");

root.register(LoggerToken, { useValue: console });

class UserService {
    constructor(@inject(LoggerToken) private logger: Logger) {}
}

Class-typed constructor parameters (including Props<T>) need no @inject — metadata magic handles them.

Props token

Runtime props are injected as a read-only holder:

import { Props, init, update, destroy, type ViewModelLifecycle } from "@pajecawav/di";

interface PageProps {
    id: string;
}

@scope.container
class PageModel implements ViewModelLifecycle<PageProps> {
    constructor(readonly props: Props<PageProps>) {} // readable in the constructor

    [init]() {
        /* fetch page by this.props.current.id */
    }
    [update](props: PageProps) {
        /* called only when props changed (shallow) */
    }
    [destroy]() {
        /* cancel in-flight work */
    }
}

Lifecycle methods are optional symbol-keyed methods — they never collide with the model's own API. update is not called at init time; the initial props are already in the holder.

Tests

import { createTestContainer } from "@pajecawav/di";

const container = createTestContainer([
    [ApiToken, fakeApi], // plain value shorthand
    [Logger, { useValue: silentLogger }], // or full provider objects
]);

React layer (@pajecawav/di/react, peer: react ^19)

Shared subtree model

import { createViewModel } from "@pajecawav/di/react";

const [PageModelProvider, usePageModel] = createViewModel(PageModel);

function Page({ id }: { id: string }) {
    return (
        <PageModelProvider props={{ id }}>
            <Content />
        </PageModelProvider>
    );
}

function Content() {
    const model = usePageModel(); // same instance for the whole subtree
    // ...
}

The provider resolves the model once in its own scope and provides that scope as the DI container for the subtree, so nested models inject the same page model instance:

@scope.transient
class WidgetModel {
    constructor(readonly page: PageModel) {} // the provider's instance
}

Private component model

import { useModel } from "@pajecawav/di/react";

function Widget() {
    const model = useModel(WidgetModel, { filter: "open" }); // private to this component
}

Container scopes

import { DIContainerProvider, DIScopeProvider, rootContainer } from "@pajecawav/di/react";

<App>
    <DIContainerProvider container={rootContainer}>
        <DIScopeProvider>{/* route subtree sees a child container */}</DIScopeProvider>
    </DIContainerProvider>
</App>;

DIScopeProvider does not dispose itself — the scope lives until its owning container is disposed.

Lifecycle semantics

  • The model is resolved during render — the constructor must be pure (dependency wiring only). Wasted instances from discarded concurrent renders are garbage-collected with their scopes.
  • [init] runs on mount (in an effect, never during render); [destroy] runs on unmount, after init settled. Both may be async; destroy must cancel in-flight work.
  • [update] runs when props change (shallow compare), never at init.
  • StrictMode double-mount: cleanup destroys the instance and defers the scope disposal to a microtask; the immediate re-mount cancels the disposal and revives a fresh instance from the same scope. Net effect in dev: init runs twice on two instances, exactly the "write resilient cleanup" contract React asks for. In production everything happens once.

SSR pattern

import { createRootContainer } from "@pajecawav/di";

async function handleRequest() {
	const request = createRootContainer(); // fresh per request
	// optional: request.register(ConfigToken, { useValue: perRequestConfig });

	// Drive data-fetching inits before rendering if needed:
	const page = request.resolve(PageModel);
	request.initialize(page);
	await request.waitForInit();

	const html = renderToString(
		<DIContainerProvider container={request}>
			<Page />
		</DIContainerProvider>,
	);

	await request.dispose(); // destroys every instance created for the request
	return html;
}

rootContainer (the default export of the core) exists for SPA convenience; never use it on the server.

Non-goals

  • Property injection.
  • Live hot-swapping of resolved instances (use replace before resolve).
  • Async factories — construction is synchronous; async work belongs in [init].
  • Circular dependencies — refactor, or resolve lazily inside a factory method.