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

di-bag

v0.5.1

Published

DI container for agent-built codebases: the compiler checks the wiring, modules stay private, resources get cleaned up.

Readme

DI Bag

TypeScript dependency composition and resource ownership for modular codebases. Built for coding agents that ship one feature at a time.

Documentation · Quickstart · Modules as units of work · Comparison · Tutorial · API reference

Why DI Bag?

Compose ordinary TypeScript factories into reusable features. DI Bag checks declared dependencies, keeps module internals private, and manages resource creation and disposal. Build each feature against an explicit contract, test it with replaced dependencies, and let the compiler check the composition when independently developed features come together.

Install

Install di-bag from npm:

npm install di-bag

The API is pre-1.0 and includes breaking changes. Run the 0.4-to-0.5 codemod before upgrading, then review the changelog and the 0.5 migration guide.

The minimum supported TypeScript version is 6.0.3; enable strict in your tsconfig.json. The repository checks classic TypeScript 6.0.3 and native 7.0.2. For browsers and workers, see runtime support.

Quickstart

A service can be a configuration object, a database client, or a function. A factory creates a service. A container resolves its factories' declared dependencies. Services are created when needed. The container disposes owned resources when you close it.

Import from di-bag. Here, greeter needs config. Its parameter type describes that dependency, and its return value is the service it provides:

import { DiBag } from 'di-bag';

const app = DiBag.createBuilder()
  .withServices({
    config: () => ({ greeting: 'Hello' }),
    greeter: ({ config }: { config: { greeting: string } }) => ({
      greet(name: string) {
        return `${config.greeting}, ${name}!`;
      },
    }),
  })
  .buildContainer();

const greeter = app.resolve('greeter');
console.log(greeter.greet('Ada')); // Hello, Ada!
await app.close();

withServices({...}) adds factories to an immutable builder. buildContainer() checks the declared graph and creates a container. resolve('greeter') creates the greeter and its config on first use. The default scoped lifetime returns the same greeter on later resolutions in this container. Provider order does not matter.

TypeScript knows that greeter has a greet(name: string): string method. Removing the config factory makes buildContainer() a compile-time error. Changing greeting to a number also fails the type check because the greeter needs a string.

Swap a dependency for a test

Use createIndependentContainer() for a test with a replacement dependency. This example uses the graph from the quickstart:

import { DiBag } from 'di-bag';

const app = DiBag.createBuilder().withServices({
  config: () => ({ greeting: 'Hello' }),
  greeter: ({ config }: { config: { greeting: string } }) =>
    ({ greet: (name: string) => `${config.greeting}, ${name}!` }),
}).buildContainer();

const testApp = app.createIndependentContainer(['config'], {
  config: () => ({ greeting: 'Hi' }),
});

try {
  console.log(testApp.resolve('greeter').greet('Ada')); // Hi, Ada!
  console.log(app.resolve('greeter').greet('Ada')); // Hello, Ada!
} finally {
  await testApp.close();
  await app.close();
}

The replacement must satisfy the original service contract. Each independent container owns its own acquisitions and disposal. Close it separately. Factories can still return shared objects captured outside the container.

Work with async services

An async factory provides a promise. Declare that promise in any dependent factory and await it where you need the value:

import { DiBag } from 'di-bag';

const app = DiBag.createBuilder()
  .withServices({
    greeting: async () => 'Hello',
    message: async ({ greeting }: { greeting: Promise<string> }) =>
      `${await greeting}, Ada!`,
  })
  .buildContainer();

console.log(await app.resolve('message')); // Hello, Ada!
await app.close();

Within one container, repeated resolutions share the same pending promise. Synchronous factories keep returning ordinary values. See async behavior for details.

Give resources a clear owner

Use DiBag.providerWithDisposal to tell the container how to release a factory's result:

import { DiBag } from 'di-bag';

const resources = DiBag.createBuilder()
  .withServices({
    cache: DiBag.providerWithDisposal({
      provider: () => new Map<string, string>(),
      disposeService: cache => cache.clear(),
    }),
  })
  .buildContainer();

try {
  resources.resolve('cache').set('answer', '42');
} finally {
  await resources.close();
}

The same pattern works for connections, clients, and subscriptions. Disposal can be asynchronous. Dependents close before their dependencies, and resources that were never created need no disposal. Ordinary factories return borrowed values. Having a close() method alone does not transfer ownership to the container.

Child and independent containers {#child-and-independent-containers}

| Operation | What it creates | Who closes it? | | --- | --- | --- | | container.createChildContainer() | A tracked child with its own scoped services | Close it when its work ends. The parent closes live children. | | container.createChildContainer(keys, providers) | A tracked child with selected scoped or transient services replaced | Close it when its work ends. The parent closes live children. | | container.createIndependentContainer() | A separate container with fresh instances | The caller closes it separately. | | container.createIndependentContainer(keys, providers) | A separate container with selected dependencies replaced | The caller closes it separately. |

An unmarked provider is scoped. Choose singleton explicitly when child containers should share it.

For request handling, checked test replacements, and loading dynamic features, see the server guide and integration recipes.

The tutorial also covers modules with private services, typed tokens, class and function adapters, optional and lazy dependencies, collections, service readiness, metadata, observers, and plugin validation.

Modules as units of work

A DI Bag module is the unit of work that one person or one coding agent can own: a directory with a small exported contract, private services, and its own tests. The composition is checked when the modules meet, so several modules can be developed in parallel and merged with confidence.

  • A boundary an owner can hold. buildModule({ exportedServiceKeys }) seals a feature and exports only the named services. Private services and their types stay inside, and two modules can use the same private names without collision.
  • Verification without the whole application. A module declares what its host must supply. A small check file installs the module with typed fixtures and calls verifyGraphAtCompileTime(). Tests can use createIndependentContainer(keys, providers) for checked replacements without live clients.
  • Checks at merge time. Installing every module into one builder is where independently developed work meets. A missing requirement, an incompatible replacement, or a contract that no longer matches its consumers fails at buildContainer() or verifyGraphAtCompileTime(). The di-bag-graph tool exports the declared edges and cycles for review.

Install modules with withInstalledModules([...]). If two modules require the same name for different contracts, rename each requirement before installation with withRenamedRequirement({ currentRequirementKey, newRequirementKey }). Use withRenamedExport({ currentExportKey, newExportKey }) for colliding exports.

For discovery, the directory layout is the map: one directory per module, the contract first. The modularity guide describes the recommended layout and shows separately owned features, isolated tests, and contributed tools in three runnable programs.

DI Bag's dependency graph describes how services are supplied, not what runs next. LLM harnesses and agent graphs are one application of the module pattern: model clients, tools, and context sources become modules, and the harness or graph framework owns routing, retries, persistence, and execution. The agent harness and graph guide is a complete example.

How it compares

DI Bag's appeal is the combination of object-parameter factories, checks across the declared graph, and explicit resource ownership. Decorator-free composition, async factories, and TypeScript support are also available in other libraries.

| Alternative | Reasons to choose it | DI Bag's different emphasis | | --- | --- | --- | | Manual dependency injection | Direct function calls may be all a small application needs. TypeScript checks their arguments. | Adds lazy caching, graph-wide composition checks, child containers, and coordinated disposal. | | Awilix | Function and class registration, inferred cradle types, lifetime options, and runtime strict checks. | Checks declared factory requirements against the registrations at compile time. | | InversifyJS / TSyringe | Token and class-oriented containers; Inversify also offers decorator-free factory bindings and awaited async resolution. | Starts with object-parameter factories and immutable builders; checks accumulated graph contracts. | | Typed Inject | A close alternative with compile-time dependency checks, explicit dependency tuples, child injectors, and disposal. | Adds object-parameter dependencies, forward references, private module exports, and selected service readiness with rollback. | | Effect Context / Layer | Typed requirements, scoped resources, and composition within Effect's broader async and error model. | Keeps ordinary T and Promise<T> service values and explicit container ownership. | | NestJS / Angular DI | Their native containers connect directly to framework components, testing tools, and lifecycles. | Provides standalone composition; applications supply the framework integration. |

See the comparison guide for primary sources, differences in async and disposal behavior, and the limits of these comparisons. There is no verified performance ranking against these libraries.

Runtime support

The package has zero runtime dependencies and one entry point: di-bag. The root import configures native Promise detection on Node, Bun, and Deno. It also bundles for browsers and workers because it has no node: imports.

In browsers and workers, buildContainer() names any factory that needs an explicit return kind. Register synchronous factories with DiBag.createProvider(factory, { factoryReturnKind: 'sync-value' }) and native Promise factories with 'native-promise'. See portable mode.

Tradeoffs and limits

  • Agent context is still your responsibility. DI Bag does not choose module boundaries, manage an agent's context window, or replace behavioral tests.
  • Async dependencies are explicit. A factory returning Promise<T> exposes that promise. Consumers declare and await it themselves.
  • Disposal waits for your work by default. Cancellation is cooperative; a factory or disposer that never settles keeps close() pending. Pass close({ waitTimeoutMs, abortSignal }) to stop waiting. The rejection names the disposers still running, and disposal continues in the background.
  • Type safety follows the declared graph. Casts, unchecked JavaScript, and unknown plugins need appropriate runtime checks. Dependency cycles are detected at runtime, or before running by di-bag-graph.
  • Graph types have a compiler cost. One fluent expression is bounded by the compiler's recursion budget: classic TypeScript 6.0.3 accepts about 1,000 chained calls and overflows beyond that (about 950 for a bulk map followed by individual replacements); native 7.0.2 has no such ceiling. Keep an expression to 500 calls or fewer and use bulk registration, groups, or named modules beyond that. See the compiler evidence.
  • Framework integration belongs to the application. DI Bag provides the composition and ownership primitives; the host connects request, job, or UI lifecycles. The server guide and the React guide are tested recipes for both.

Explore further

| Resource | What you'll find | | --- | --- | | Complete tutorial | Learn every public API through examples, from first composition to advanced ownership. | | API reference | Exact generated signatures, overloads, type parameters, and API inventories. | | Server guide | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request containers, service readiness, and shutdown. | | React guide | Browser applications: one app runtime at bootstrap, project runtimes owned from effects, Strict Mode, cancellation, bounded teardown, and useSyncExternalStore. | | Radical modularity | The recommended module layout, separately owned features, isolated tests, and contributed tools. | | Agent docs | Rules, module layout, and check commands for coding agents, with recipes and errors. Shipped in the package. | | Agent harnesses and graphs | One worked application: model and tool modules, metadata inspection, and node tests with typed fixtures. | | Static dependency graph | Export every builder chain, declared edge, and cycle to JSON with di-bag-graph for merge review and CI. | | Runnable examples | Modules, tokens, composition, collections, plugins, observers, child containers, and provider metadata. | | Integration guide | Tested recipes for request ownership, substitutions, and dynamic features. | | Comparison with alternatives | When DI Bag or another approach may be a better fit, with primary sources. | | Development and verification | Full checks, portable runtime testing, compiler scale, and performance evidence. | | Documentation map | Every guide, the generated reference, and the contributor documents. |

Working on DI Bag

CI Runtime dependencies: 0 License: MIT

After npm ci, run the main checks with Node and Bun installed:

npm run platform:pin
npm run check
npm run check:native

Run an example with bun run examples/composition.ts. The development guide covers the remaining compiler, platform, and packaging checks and their tool requirements.

License

MIT © Dany Fedorov